《聊聊分布式》BASE理论 分布式系统可用性与一致性的工程平衡艺术

1. BASE理论概述:CAP理论的实践延伸

1.1 从ACID到BASE的演进之路

在分布式系统设计中,我们面临着传统ACID事务模型与分布式环境之间的根本矛盾:

// 传统ACID事务的局限性
public class ACIDLimitations {
    
    // 关系型数据库的ACID特性
    @Transactional
    public void traditionalTransaction() {
        // Atomicity(原子性):全部成功或全部失败
        // Consistency(一致性):数据始终处于一致状态
        // Isolation(隔离性):并发事务相互隔离
        // Durability(持久性):提交后数据永久保存
        
        // 问题:在分布式环境中,ACID会导致性能瓶颈和可用性问题
    }
    
    // 分布式环境下的挑战
    public void distributedChallenges() {
        // 网络延迟:跨节点通信需要时间
        // 节点故障:部分节点可能不可用
        // 分区容错:必须处理网络分区
        // 扩展性需求:需要支持水平扩展
        
        // ACID在这种环境下变得不切实际
    }
}

1.2 BASE理论的诞生背景

BASE理论由eBay的架构师Dan Pritchett提出,是对CAP定理中AP(可用性+分区容错)路线的具体实践指导:

image

BASE与ACID的哲学对比

  • ACID:悲观保守,强调数据强一致性(适合银行转账)
  • BASE:乐观灵活,强调系统可用性(适合互联网应用)

2. BASE理论三要素深度解析

2.1 基本可用(Basically Available)

基本可用不是"完全可用",而是在系统出现部分故障时,核心功能仍然可用:

// 基本可用的实现模式
@Service
public class BasicallyAvailableService {
    
    @Autowired
    private CircuitBreaker circuitBreaker;
    
    /**
     * 基本可用性的层次划分
     */
    public enum AvailabilityLevel {
        FULL_AVAILABILITY,     // 全功能可用
        CORE_FUNCTIONALITY,    // 核心功能可用
        READ_ONLY_MODE,        // 只读模式
        DEGRADED_SERVICE,      // 降级服务
        COMPLETE_UNAVAILABLE   // 完全不可用
    }
    
    /**
     * 电商系统的基本可用性设计
     */
    public ProductDetail getProductDetail(String productId) {
        try {
            // 1. 尝试获取完整商品信息
            return productService.getFullDetail(productId);
            
        } catch (ServiceUnavailableException e) {
            // 2. 核心服务不可用,返回降级数据
            AvailabilityLevel level = determineAvailabilityLevel();
            
            switch (level) {
                case CORE_FUNCTIONALITY:
                    // 核心功能:返回商品基本信息(从缓存)
                    return getBasicProductInfoFromCache(productId);
                    
                case READ_ONLY_MODE:
                    // 只读模式:返回静态页面数据
                    return getStaticProductInfo(productId);
                    
                case DEGRADED_SERVICE:
                    // 降级服务:返回默认商品信息
                    return getDefaultProductInfo();
                    
                default:
                    throw new ServiceDegradedException("服务暂时不可用");
            }
        }
    }
    
    /**
     * 熔断器模式实现基本可用
     */
    @HystrixCommand(
        fallbackMethod = "fallbackProductSearch",
        commandProperties = {
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000")
        }
    )
    public List<Product> searchProducts(String keyword) {
        // 正常搜索逻辑
        return searchService.fullTextSearch(keyword);
    }
    
    public List<Product> fallbackProductSearch(String keyword) {
        // 降级策略:返回缓存中的热门商品或默认结果
        return Arrays.asList(getDefaultProduct());
    }
}

2.2 软状态(Soft State)

软状态允许系统存在中间状态,不要求时刻保持数据的一致性:

// 软状态的实际应用
@Service
public class SoftStateExamples {
    
    /**
     * 示例1:分布式缓存中的软状态
     */
    public class DistributedCache {
        private Map<String, CacheEntry> cache = new ConcurrentHashMap<>();
        
        public void updateProductPrice(String productId, BigDecimal price) {
            // 1. 更新数据库(最终一致性的源头)
            productDAO.updatePrice(productId, price);
            
            // 2. 异步更新缓存(允许短暂不一致)
            CompletableFuture.runAsync(() -> {
                try {
                    // 缓存更新可能延迟,这是可接受的软状态
                    cache.put(productId, new CacheEntry(price, System.currentTimeMillis()));
                } catch (Exception e) {
                    // 缓存更新失败不影响主流程
                    logger.warn("缓存更新失败,系统处于软状态", e);
                }
            });
        }
        
        public BigDecimal getProductPrice(String productId) {
            CacheEntry entry = cache.get(productId);
            if (entry != null && !isExpired(entry)) {
                return entry.getPrice(); // 可能不是最新价格
            }
            // 缓存失效时从数据库读取
            return productDAO.getPrice(productId);
        }
    }
    
    /**
     * 示例2:订单状态的软状态管理
     */
    public class OrderStateManagement {
        
        // 订单的中间状态
        public enum OrderIntermediateState {
            CREATED,           // 已创建
            PAYMENT_PROCESSING,// 支付处理中(软状态)
            PAYMENT_SUCCESS,   // 支付成功
            PAYMENT_FAILED,    // 支付失败
            SHIPPING,          // 发货中(软状态)
            DELIVERED          // 已送达
        }
        
        public void processOrderPayment(Long orderId) {
            // 1. 更新订单状态为"支付处理中"
            orderDAO.updateStatus(orderId, OrderIntermediateState.PAYMENT_PROCESSING);
            
            // 2. 调用支付网关(可能耗时)
            PaymentResult result = paymentGateway.processPayment(orderId);
            
            // 3. 根据支付结果更新最终状态
            if (result.isSuccess()) {
                orderDAO.updateStatus(orderId, OrderIntermediateState.PAYMENT_SUCCESS);
            } else {
                orderDAO.updateStatus(orderId, OrderIntermediateState.PAYMENT_FAILED);
            }
            
            // 在步骤2执行期间,订单处于"支付处理中"的软状态
            // 这是可接受的,因为系统需要时间完成支付操作
        }
    }
}

2.3 最终一致性(Eventual Consistency)

最终一致性是BASE理论的核心,保证数据在经过一段时间后达到一致状态:

// 最终一致性的实现模式
@Service
public class EventualConsistencyPatterns {
    
    /**
     * 模式1:基于消息队列的最终一致性
     */
    public class MessageQueueConsistency {
        
        @Autowired
        private RocketMQTemplate rocketMQTemplate;
        
        public void placeOrder(Order order) {
            // 1. 创建订单(本地事务)
            orderDAO.create(order);
            
            // 2. 发送事务消息(扣减库存)
            TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
                "order-topic", 
                MessageBuilder.withPayload(order).build(),
                order
            );
            
            // 3. 消息消费者异步处理库存扣减
            // 订单和库存可能短暂不一致,但最终会一致
        }
        
        // 消息监听器
        @RocketMQMessageListener(topic = "order-topic", consumerGroup = "inventory-group")
        public class InventoryConsumer implements RocketMQListener<Order> {
            @Override
            public void onMessage(Order order) {
                // 异步扣减库存
                inventoryService.deductStock(order.getProductId(), order.getQuantity());
                // 即使这里失败,也有重试机制保证最终成功
            }
        }
    }
    
    /**
     * 模式2:读写分离的最终一致性
     */
    public class ReadWriteSeparation {
        
        // 写操作:主数据库
        public void writeOperation(Product product) {
            // 写入主数据库(强一致性)
            masterDataSource.getProductDAO().update(product);
            
            // 异步同步到从数据库
            CompletableFuture.runAsync(() -> {
                slaveDataSource.getProductDAO().update(product);
            });
        }
        
        // 读操作:从数据库(可能数据延迟)
        public Product readOperation(String productId) {
            // 从从数据库读取(可能不是最新数据)
            return slaveDataSource.getProductDAO().getById(productId);
            // 可接受短暂的数据延迟,换取更好的读性能
        }
    }
    
    /**
     * 模式3:冲突解决的最终一致性
     */
    public class ConflictResolution {
        
        // 使用版本号解决并发冲突
        public class VersionedEntity {
            private String id;
            private String data;
            private long version; // 版本号
            private long timestamp;
        }
        
        public void concurrentUpdate(String entityId, String newData) {
            VersionedEntity current = entityDAO.get(entityId);
            
            // 乐观锁:基于版本号更新
            int updated = entityDAO.updateWithVersion(
                entityId, newData, current.getVersion(), current.getVersion() + 1);
            
            if (updated == 0) {
                // 更新冲突,需要解决冲突
                resolveConflict(entityId, newData);
            }
        }
        
        // 冲突解决策略:最后写入获胜(LWW)或自定义逻辑
        private void resolveConflict(String entityId, String newData) {
            // 可以基于时间戳、业务规则等解决冲突
            // 保证数据最终一致,即使中间有冲突
        }
    }
}

3. BASE理论在大型互联网公司的应用案例

3.1 淘宝/天猫的分布式事务实践

// 淘宝的最终一致性实践:TCC模式
@Service
public class TaobaoDistributedTransaction {
    
    /**
     * TCC(Try-Confirm-Cancel)模式实现最终一致性
     * 适用于订单、库存、积分等业务场景
     */
    public class TCCPattern {
        
        public void placeOrder(OrderRequest request) {
            // 第一阶段:Try(尝试)
            boolean tryResult = tryPhase(request);
            
            if (tryResult) {
                // 第二阶段:Confirm(确认)
                confirmPhase(request);
            } else {
                // 第二阶段:Cancel(取消)
                cancelPhase(request);
            }
        }
        
        private boolean tryPhase(OrderRequest request) {
            // 1. 尝试冻结库存
            boolean inventoryFrozen = inventoryService.tryFreeze(
                request.getProductId(), request.getQuantity());
            
            // 2. 尝试预扣积分
            boolean pointsReserved = pointsService.tryReserve(
                request.getUserId(), request.getPointsNeeded());
            
            // 3. 尝试创建订单(待确认状态)
            boolean orderCreated = orderService.tryCreate(request);
            
            return inventoryFrozen && pointsReserved && orderCreated;
        }
        
        private void confirmPhase(OrderRequest request) {
            // 确认阶段:实际执行业务操作
            try {
                inventoryService.confirmDeduct(request.getProductId());
                pointsService.confirmDeduct(request.getUserId());
                orderService.confirmCreate(request.getOrderId());
            } catch (Exception e) {
                // 确认失败,需要人工干预或重试
                logger.error("TCC确认阶段失败", e);
                // 系统处于中间状态,但最终会通过补偿达到一致
            }
        }
        
        private void cancelPhase(OrderRequest request) {
            // 取消阶段:释放预留资源
            inventoryService.cancelFreeze(request.getProductId());
            pointsService.cancelReserve(request.getUserId());
            orderService.cancelCreate(request.getOrderId());
        }
    }
}

3.2 微信朋友圈的读写优化

// 微信朋友圈的BASE理论应用
@Service
public class WechatMomentsService {
    
    /**
     * 朋友圈发布:写操作优化
     */
    public void publishMoment(Moment moment) {
        // 1. 异步写入主存储(允许延迟)
        CompletableFuture<Void> writeFuture = CompletableFuture.runAsync(() -> {
            momentDAO.insert(moment);
        });
        
        // 2. 立即写入缓存(基本可用)
        redisTemplate.opsForValue().set(
            "moment:" + moment.getId(), 
            moment, 
            30, TimeUnit.MINUTES);
        
        // 3. 异步更新索引(最终一致性)
        CompletableFuture.runAsync(() -> {
            searchService.indexMoment(moment);
        });
        
        // 4. 异步推送通知(软状态)
        CompletableFuture.runAsync(() -> {
            pushService.notifyFriends(moment.getUserId(), moment.getId());
        });
        
        // 用户立即看到发布结果,后台异步处理各种任务
        // 系统保证最终所有数据一致
    }
    
    /**
     * 朋友圈读取:读操作优化
     */
    public List<Moment> getFriendMoments(String userId, int page, int size) {
        // 1. 先尝试从缓存读取(基本可用)
        String cacheKey = "friend_moments:" + userId + ":" + page;
        List<Moment> moments = redisTemplate.opsForList().range(cacheKey, 0, -1);
        
        if (moments != null && !moments.isEmpty()) {
            return moments; // 缓存命中,快速返回
        }
        
        // 2. 缓存未命中,查询数据库(可能数据不是最新)
        moments = momentDAO.getFriendMoments(userId, page, size);
        
        // 3. 异步更新缓存(最终一致性)
        CompletableFuture.runAsync(() -> {
            redisTemplate.opsForList().rightPushAll(cacheKey, moments);
            redisTemplate.expire(cacheKey, 5, TimeUnit.MINUTES);
        });
        
        return moments;
    }
    
    /**
     * 点赞功能:计数器的最终一致性
     */
    public void likeMoment(String momentId, String userId) {
        // 1. 先更新缓存计数器(高性能)
        redisTemplate.opsForHash().increment("moment_likes", momentId, 1);
        
        // 2. 异步持久化到数据库(最终一致)
        CompletableFuture.runAsync(() -> {
            try {
                likeDAO.insert(new Like(momentId, userId, new Date()));
            } catch (DuplicateKeyException e) {
                // 重复点赞,回滚缓存计数器
                redisTemplate.opsForHash().increment("moment_likes", momentId, -1);
            }
        });
        
        // 用户立即看到点赞效果,数据最终持久化
    }
}

3.3 美团外卖的订单系统

// 美团外卖的BASE理论实践
@Service
public class MeituanOrderService {
    
    /**
     * 下单流程:保证基本可用性
     */
    public OrderResult createOrder(OrderRequest request) {
        // 1. 前置校验(必须同步,保证业务正确性)
        ValidationResult validation = preCheck(request);
        if (!validation.isValid()) {
            return OrderResult.fail(validation.getErrorMsg());
        }
        
        // 2. 创建订单(同步,但允许降级)
        try {
            Order order = orderManager.createOrder(request);
            
            // 3. 异步处理后续流程(保证基本可用)
            asyncPostOrderProcess(order);
            
            return OrderResult.success(order);
            
        } catch (SystemBusyException e) {
            // 系统繁忙时降级处理
            return degradeOrderCreation(request);
        }
    }
    
    /**
     * 异步订单后续处理
     */
    @Async
    public void asyncPostOrderProcess(Order order) {
        try {
            // 1. 通知商家系统(允许重试)
            merchantService.notifyNewOrder(order);
            
            // 2. 分配骑手(最终一致性)
            dispatchService.assignRider(order);
            
            // 3. 更新搜索索引(软状态)
            searchService.updateOrderIndex(order);
            
            // 4. 发送营销消息(可降级)
            marketingService.sendOrderCreatedMsg(order);
            
        } catch (Exception e) {
            // 单个步骤失败不影响整体流程
            logger.warn("订单后续处理异常,订单ID: {}", order.getId(), e);
            // 通过监控告警和重试机制保证最终处理成功
        }
    }
    
    /**
     * 降级下单策略
     */
    private OrderResult degradeOrderCreation(OrderRequest request) {
        // 1. 记录降级日志
        degradeLogger.warn("订单创建降级,用户: {}", request.getUserId());
        
        // 2. 返回友好提示,而不是系统错误
        return OrderResult.degrade("系统繁忙,请稍后查看订单状态");
        
        // 3. 异步尝试创建订单
        CompletableFuture.runAsync(() -> {
            try {
                orderManager.createOrder(request);
            } catch (Exception e) {
                // 记录失败,人工介入处理
                manualReviewService.addReviewTask(request);
            }
        });
    }
    
    /**
     * 订单状态最终一致性保证
     */
    @Scheduled(fixedDelay = 30000) // 每30秒执行一次
    public void reconcileOrderStatus() {
        // 对账任务:修复订单中间状态
        List<Order> inconsistentOrders = orderDAO.findInconsistentOrders();
        
        for (Order order : inconsistentOrders) {
            try {
                // 根据各子系统状态修复主订单状态
                repairOrderStatus(order);
            } catch (Exception e) {
                // 修复失败,告警人工处理
                alertService.sendAlert("订单状态修复失败: " + order.getId());
            }
        }
    }
}

4. BASE理论的实现技术与框架

4.1 消息队列实现最终一致性

// 基于RocketMQ的最终一致性实现
@Configuration
public class RocketMQBaseConfiguration {
    
    /**
     * 事务消息生产者
     */
    @Service
    public class TransactionalMessageProducer {
        
        @Autowired
        private RocketMQTemplate rocketMQTemplate;
        
        public void sendTransactionalMessage(String topic, Object payload, 
                                           LocalTransactionExecutor executor) {
            // 发送事务消息
            TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
                topic, 
                MessageBuilder.withPayload(payload).build(),
                executor
            );
            
            // 事务消息有半消息和提交/回滚两阶段
            // 保证本地事务和消息发送的最终一致性
        }
    }
    
    /**
     * 本地事务执行器
     */
    @Component
    public class OrderTransactionExecutor implements TransactionExecutor {
        
        @Override
        public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
            try {
                // 执行本地事务
                Order order = (Order) arg;
                orderDAO.create(order);
                
                return LocalTransactionState.COMMIT_MESSAGE;
            } catch (Exception e) {
                return LocalTransactionState.ROLLBACK_MESSAGE;
            }
        }
        
        @Override
        public LocalTransactionState checkLocalTransaction(MessageExt msg) {
            // 检查本地事务状态
            String orderId = msg.getKeys();
            Order order = orderDAO.getById(orderId);
            
            return order != null ? LocalTransactionState.COMMIT_MESSAGE 
                                : LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }
}

4.2 分布式任务调度保证最终一致性

// 基于Elastic-Job的最终一致性保障
@Service
public class EventuallyConsistentJob {
    
    /**
     * 数据对账作业:修复最终一致性
     */
    @ElasticJobScheduler(
        name = "dataReconciliationJob",
        cron = "0 0 2 * * ?", // 每天凌晨2点执行
        shardingTotalCount = 3
    )
    public class DataReconciliationJob implements SimpleJob {
        
        @Override
        public void execute(ShardingContext context) {
            int shardIndex = context.getShardingItem();
            
            // 分片处理数据对账
            switch (shardIndex) {
                case 0:
                    reconcileOrderData(); // 对账订单数据
                    break;
                case 1:
                    reconcileInventoryData(); // 对账库存数据
                    break;
                case 2:
                    reconcilePaymentData(); // 对账支付数据
                    break;
            }
        }
        
        private void reconcileOrderData() {
            // 查找状态不一致的订单
            List<Order> inconsistentOrders = findInconsistentOrders();
            
            for (Order order : inconsistentOrders) {
                try {
                    // 基于业务规则修复数据
                    repairOrderConsistency(order);
                } catch (Exception e) {
                    // 记录修复失败,人工介入
                    logRepairFailure(order, e);
                }
            }
        }
    }
}

5. BASE理论的挑战与应对策略

5.1 数据一致性延迟问题

// 处理最终一致性的延迟问题
@Service
public class ConsistencyDelayHandler {
    
    /**
     * 读己之所写(Read-Your-Writes)一致性
     */
    public class ReadYourWritesConsistency {
        
        public Product getProductWithRYW(String productId, String userId) {
            // 1. 先检查用户是否有最近的写操作
            Long lastWriteTime = userWriteCache.get(userId + ":" + productId);
            
            if (lastWriteTime != null && 
                System.currentTimeMillis() - lastWriteTime < 5000) {
                // 5秒内有写操作,从主库读取
                return masterProductDAO.getById(productId);
            } else {
                // 从从库读取
                return slaveProductDAO.getById(productId);
            }
        }
        
        public void updateProduct(String productId, String userId, Product product) {
            // 更新时记录用户写操作时间
            masterProductDAO.update(product);
            userWriteCache.put(userId + ":" + productId, System.currentTimeMillis());
        }
    }
    
    /**
     * 单调读一致性(Monotonic Reads)
     */
    public class MonotonicReadConsistency {
        
        private Map<String, String> userReadSource = new ConcurrentHashMap<>();
        
        public Product getProductWithMonotonicRead(String productId, String userId) {
            String lastReadSource = userReadSource.get(userId);
            
            if ("master".equals(lastReadSource)) {
                // 之前从主库读过,继续从主库读(避免看到旧数据)
                return masterProductDAO.getById(productId);
            } else {
                // 从从库读,但记录数据版本
                Product product = slaveProductDAO.getById(productId);
                long currentVersion = getDataVersion(productId);
                
                if (product.getVersion() < currentVersion) {
                    // 从库数据过时,切换到主库
                    product = masterProductDAO.getById(productId);
                    userReadSource.put(userId, "master");
                }
                
                return product;
            }
        }
    }
}

5.2 补偿事务机制

// BASE理论下的补偿事务实现
@Service
public class CompensationTransaction {
    
    /**
     * 补偿事务管理器
     */
    @Component
    public class CompensationManager {
        
        @Autowired
        private CompensationLogDAO compensationLogDAO;
        
        /**
         * 记录补偿点
         */
        public void recordCompensationPoint(String businessId, 
                                          String operation, 
                                          Object data) {
            CompensationLog log = new CompensationLog();
            log.setBusinessId(businessId);
            log.setOperation(operation);
            log.setCompensationData(JSON.toJSONString(data));
            log.setCreateTime(new Date());
            
            compensationLogDAO.insert(log);
        }
        
        /**
         * 执行补偿操作
         */
        @Scheduled(fixedDelay = 60000) // 每分钟执行一次
        public void executeCompensation() {
            List<CompensationLog> pendingLogs = compensationLogDAO.findPendingLogs();
            
            for (CompensationLog log : pendingLogs) {
                try {
                    compensate(log);
                    compensationLogDAO.markAsCompleted(log.getId());
                } catch (Exception e) {
                    compensationLogDAO.markAsFailed(log.getId(), e.getMessage());
                    // 告警人工处理
                }
            }
        }
        
        private void compensate(CompensationLog log) {
            switch (log.getOperation()) {
                case "REFUND_ORDER":
                    refundOrder(JSON.parseObject(log.getCompensationData(), Order.class));
                    break;
                case "RESTORE_INVENTORY":
                    restoreInventory(JSON.parseObject(log.getCompensationData(), Inventory.class));
                    break;
                // 其他补偿操作...
            }
        }
    }
}

6. BASE理论的最佳实践总结

6.1 适用场景判断

// BASE理论适用性评估框架
@Service
public class BaseSuitabilityAssessment {
    
    public boolean isBaseSuitable(BusinessScenario scenario) {
        // 评估维度
        int score = 0;
        
        // 1. 一致性要求
        if (scenario.getConsistencyRequirement() == ConsistencyRequirement.STRONG) {
            score -= 2; // 强一致性场景不适合BASE
        } else {
            score += 2;
        }
        
        // 2. 可用性要求
        if (scenario.getAvailabilityRequirement() == AvailabilityRequirement.HIGH) {
            score += 2;
        }
        
        // 3. 数据更新频率
        if (scenario.getUpdateFrequency() == UpdateFrequency.LOW) {
            score += 1; // 低频更新更适合BASE
        }
        
        // 4. 业务容忍度
        if (scenario.getBusinessTolerance().isTolerantToInconsistency()) {
            score += 2;
        }
        
        return score >= 3; // 分数达到阈值认为适合BASE
    }
    
    public enum ConsistencyRequirement {
        STRONG,    // 必须强一致:金融交易
        WEAK       // 可接受最终一致:社交动态
    }
    
    public enum AvailabilityRequirement {
        HIGH,      // 高可用要求:电商下单
        MEDIUM,    // 中等可用:后台管理
        LOW        // 低可用:批量处理
    }
}

6.2 实施路线图

image

总结

BASE理论不是对一致性的放弃,而是对分布式系统现实约束的理性应对。它通过巧妙的架构设计,在保证系统可用性的前提下,合理地控制数据一致性的粒度。

核心价值

  1. 业务导向:根据业务特性选择合适的一致性级别
  2. 弹性设计:系统具备应对故障和压力的能力
  3. 渐进优化:通过技术手段逐步减小一致性延迟
  4. 运维友好:建立完善的监控和补偿机制

BASE理论的成功实践需要技术、业务、运维的紧密配合。在微服务、云原生架构成为主流的今天,深入理解和熟练应用BASE理论,是构建高可用、可扩展分布式系统的关键能力。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一枚后端工程狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值