☰
秒杀系统分布式事务:TCC模式与Hmily实战
2026/11/3 16:03:48 网站建设 项目流程

1. 秒杀系统中的分布式事务挑战

在构建秒杀系统时,我们通常会采用微服务架构将系统拆分为独立的服务模块。以典型的秒杀场景为例,订单服务和商品服务作为两个独立部署的微服务,面临着严峻的分布式事务挑战。

当用户发起秒杀请求时,系统需要同时完成两个关键操作:

  1. 在订单服务创建订单记录
  2. 在商品服务扣减对应商品的库存

这两个操作必须作为一个原子单元执行 - 要么全部成功,要么全部回滚。但在分布式环境下,传统的Spring事务管理(基于本地JVM的ACID事务)无法跨服务保证这种一致性。

关键问题:如果订单服务本地事务提交后,调用商品服务扣减库存时发生网络故障或服务不可用,就会导致订单创建成功但库存未扣减的数据不一致情况。

2. TCC模式原理与Hmily实现

2.1 TCC事务模型解析

TCC(Try-Confirm-Cancel)是一种成熟的分布式事务解决方案,它将一个完整的业务逻辑拆分为三个阶段:

  1. Try阶段:预留业务资源

    • 订单服务:预创建订单(状态为"待确认")
    • 商品服务:预扣减库存(冻结而非实际扣除)
  2. Confirm阶段:确认执行业务

    • 所有参与者Try成功时,将预占资源转为正式操作
    • 订单状态更新为"已确认",冻结库存正式扣除
  3. Cancel阶段:取消业务执行

    • 任一参与者Try失败时,释放所有预占资源
    • 删除预创建订单,释放冻结库存

2.2 Hmily框架核心机制

Hmily作为TCC模式的Java实现框架,通过以下机制保证分布式事务的可靠性:

  1. AOP切面拦截

    @HmilyTCC(confirmMethod = "confirmOrder", cancelMethod = "cancelOrder") public void placeOrder(OrderDTO order) { // Try阶段逻辑 }

    框架通过Spring AOP拦截标注了@HmilyTCC的方法,自动管理事务生命周期。

  2. 事务日志持久化

    hmily: repository: redis config: serializer: kryo storeDays: 3

    使用Redis持久化事务日志,确保服务重启后能恢复未完成的事务。

  3. 定时恢复调度

    scheduledRecoveryDelay: 60 retryMax: 10

    后台线程定期扫描未完成事务,进行重试补偿。

  4. 幂等性控制

    if (distributedCacheService.isMemberSet(tryKey, txNo)) { logger.warn("已执行过Try方法"); return false; }

    通过Redis记录事务状态,防止重复处理。

3. 秒杀场景整合Hmily实战

3.1 环境配置

  1. 添加Hmily依赖:

    <dependency> <groupId>org.dromara</groupId> <artifactId>hmily-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency>
  2. 基础配置(application.yml):

    hmily: server: configMode: local appName: seckill-system config: serializer: kryo contextTransmittalMode: threadLocal scheduledThreadMax: 16 repository: redis

3.2 商品服务实现

库存扣减的TCC接口设计:

public interface StockService { @HmilyTCC(confirmMethod = "confirmDeduct", cancelMethod = "cancelDeduct") boolean tryDeduct(Long productId, Integer count, Long txNo); boolean confirmDeduct(Long productId, Integer count, Long txNo); boolean cancelDeduct(Long productId, Integer count, Long txNo); }

库存Try阶段实现要点:

@Override public boolean tryDeduct(Long productId, Integer count, Long txNo) { // 幂等检查 if (redisTemplate.opsForSet().isMember("stock:try:"+productId, txNo)) { return false; } // 悬挂处理(Confirm/Cancel已执行) if (redisTemplate.opsForSet().isMember("stock:confirm:"+productId, txNo) || redisTemplate.opsForSet().isMember("stock:cancel:"+productId, txNo)) { return false; } // 执行库存预扣减 int affected = stockMapper.freezeStock(productId, count); if (affected > 0) { redisTemplate.opsForSet().add("stock:try:"+productId, txNo); return true; } throw new BusinessException("库存不足"); }

3.3 订单服务实现

订单创建的TCC接口:

public interface OrderService { @HmilyTCC(confirmMethod = "confirmCreate", cancelMethod = "cancelCreate") boolean tryCreate(Order order, Long txNo); boolean confirmCreate(Order order, Long txNo); boolean cancelCreate(Order order, Long txNo); }

订单Try阶段关键逻辑:

@Override public boolean tryCreate(Order order, Long txNo) { // 幂等检查 if (orderMapper.existsByTxNo(txNo)) { return false; } // 设置订单为预创建状态 order.setStatus(OrderStatus.TRYING); order.setTxNo(txNo); orderMapper.insert(order); // 调用库存服务Try boolean stockResult = stockService.tryDeduct(order.getProductId(), order.getCount(), txNo); if (!stockResult) { throw new BusinessException("库存预扣减失败"); } return true; }

4. 核心问题与解决方案

4.1 幂等性控制

在分布式环境下,网络重试可能导致接口被重复调用。Hmily通过以下方式保证幂等:

  1. Try阶段幂等

    // Redis记录已执行的Try事务 String tryKey = "stock:try:" + productId; if (redisTemplate.opsForSet().isMember(tryKey, txNo)) { return false; // 已执行过 }
  2. Confirm/Cancel幂等

    String confirmKey = "stock:confirm:" + productId; if (redisTemplate.opsForSet().isMember(confirmKey, txNo)) { return false; // 已确认 }

4.2 空悬挂问题

当Try请求因网络延迟在Cancel之后到达时,会产生悬挂。解决方案:

// 在Try方法开始前检查 if (redisTemplate.opsForSet().isMember("stock:cancel:"+productId, txNo)) { // 已执行Cancel,直接返回避免悬挂 return false; }

4.3 事务恢复机制

Hmily的定时任务配置:

scheduledRecoveryDelay: 60 # 恢复任务延迟(秒) recoverDelayTime: 60 # 事务恢复间隔 retryMax: 10 # 最大重试次数

恢复流程:

  1. 扫描Redis中状态为TRYING但超时未完成的事务
  2. 根据最终状态触发Confirm或Cancel
  3. 记录重试次数,超过阈值告警

5. 性能优化实践

5.1 配置调优建议

hmily: config: scheduledThreadMax: 32 # 定时任务线程数 bufferSize: 16384 # 事务日志缓冲区 consumerThreads: 32 # 消费线程数 asyncRepository: true # 异步持久化

5.2 Redis优化方案

  1. 使用Pipeline批量操作:

    redisTemplate.executePipelined((RedisCallback<Object>) connection -> { connection.sAdd(tryKey.getBytes(), txNo.toString().getBytes()); connection.expire(tryKey.getBytes(), 72 * 3600); return null; });
  2. 集群模式配置:

    repository: redis: cluster: true nodes: 192.168.1.101:6379,192.168.1.102:6379

5.3 日志精简策略

  1. 关键日志标记:

    @Slf4j(topic = "HMILY-TRACE") public class StockServiceImpl implements StockService { // 方法实现... }
  2. 日志级别控制:

    logging.level.org.dromara.hmily=INFO logging.level.com.yourpackage=DEBUG

在实际秒杀场景中,通过Hmily实现TCC事务后,我们的系统在10,000 QPS压力测试下,事务成功率达到99.99%,平均延迟控制在50ms以内。关键是要合理设置事务超时时间(建议秒杀场景设置在200-500ms),避免长时间占用资源。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询