在分布式系统中,事务注解(如 @Transactional
)与分布式锁的结合使用是保障数据一致性和高并发安全的核心手段。以下是两者的协同使用场景及技术实现要点:
一、事务注解的局限性及分布式锁的互补性
维度 | 事务注解(@Transactional) | 分布式锁 |
---|---|---|
作用范围 | 单数据库事务(ACID) | 跨服务、跨数据库的全局资源协调 |
适用场景 | 转账、库存扣减等单点数据操作 | 秒杀、集群任务调度、配置更新等分布式场景 |
典型问题 | 无法解决跨服务的事务隔离(如订单创建与物流系统协同) | 防止重复提交、资源超卖、多节点并发冲突 |
案例:用户支付时需同时更新订单状态(MySQL)和发送消息(Kafka),仅用 @Transactional
无法保证两者原子性,需配合分布式锁协调跨系统操作。
二、注解化分布式锁的实现方案
1. 核心组件
- 注解定义:自定义
@DistributedLock
注解,支持动态参数(如keys = {"#orderId}"
) - AOP切面:通过
@Around
拦截方法,在事务执行前后加锁/解锁 - 锁服务:基于 Redisson 或 RedisTemplate 实现原子操作
2. 代码示例(Spring Boot + Redisson)
@Aspect
@Component
public class DistributedLockAspect {
@Autowired
private RedissonClient redissonClient;
@Around("@annotation(distributedLock)")
public Object lock(ProceedingJoinPoint pjp, DistributedLock distributedLock) throws Throwable {
String lockKey = buildLockKey(pjp, distributedLock); // 动态生成Key(如:order:1001)
RLock lock = redissonClient.getLock(lockKey);
try {
if (lock.tryLock(3, 30, TimeUnit.SECONDS)) { // 等待3秒,锁持有30秒
return pjp.proceed(); // 执行事务方法
} else {
throw new RuntimeException("获取锁失败");
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
3. 动态Key生成策略
- 参数注入:使用 SpEL 表达式解析方法参数
@DistributedLock(prefix="order", keys={"#order.id", "#user.id"}) public void createOrder(Order order, User user) { ... }
- 组合Key:生成类似
order:1001:u2025
的唯一标识,避免锁粒度过粗或过细。
三、典型场景及最佳实践
1. 高并发下单场景
- 问题:用户多次点击导致重复订单
- 解决方案:
@DistributedLock(prefix = "order_lock", keys = {"#userId"}) @Transactional public Order createOrder(Long userId, Item item) { // 检查库存 → 扣减库存 → 生成订单(事务内操作) }
- 锁策略:以用户ID为粒度,限制同一用户5秒内仅允许一次下单
2. 定时任务防重复执行
- 问题:多节点同时触发报表生成导致数据错乱
- 解决方案:
/** * 0 0 12 * * ?:每天中午 12 点执行。 * 0 0/5 * * * ?:每 5 分钟执行一次。 * 0 0 8-18 ? * MON-FRI:周一至周五的 8 点到 18 点之间每小时执行一次。 */ @Scheduled(cron = "0 0 12 * * ?") @DistributedLock(prefix = "report_task", expire = 3600) public void generateDailyReport() { // 跨数据库汇总数据 → 写入ES(非事务操作仍需锁协调) }
- 锁超时:设置1小时过期时间,避免任务中断导致死锁
3. 分布式事务补偿
- 问题:跨服务事务部分失败后的数据回滚
- 方案组合:
-
使用
@DistributedLock
锁定主资源 -
@Transactional
执行本地事务 -
通过 TCC 或 Saga 模式实现跨服务补偿
对于某些分布式事务场景,流程多、流程长、还可能要调用其它公司的服务。特别是对于不可控的服务(其他公司的服务),这些服务无法遵循 TCC 开发模式,导致TCC模式的开发成本增高,且使用TCC模式会影响并发性能。
鉴于上述业务场景的分布式事务处理,提出了Saga分布式处理模式。Saga是一种“长事务的解决方案”,更适合于“业务流程长、业务流程多”的场景。特别是针对参与事务的服务是遗留系统服务,此类服务无法提供TCC模式下的三个接口,就可以采用Saga模式。
-
四、风险规避与性能优化
1. 死锁预防
- 自动续期:Redisson 看门狗机制(默认续期30秒)
- 超时兜底:Redis Key 设置 TTL(如
expire=30s
) - 熔断降级:锁获取失败时快速返回,避免线程阻塞
2. 性能优化
-
本地缓存:结合 Caffeine 实现二级锁(减少80% Redis请求)
-
锁分段:将热点资源拆分为多个锁(如商品库存按ID取模分10段)
-
无锁化改造:对非强一致性场景使用 版本号 或 CRDT 冲突-free 数据结构
- CRDT(conflict-freereplicateddatatype)无冲突复制数据类型,是一种可以在网络中的多台计算机上复制的数据结构,副本可以独立和并行地更新,不需要在副本之间进行协调,并保证不会有冲突发生。
- CRDT常被用在协作软件上,例如多个用户需要共同编辑/读取共享的文档、数据库或状态的场景。
- 在数据库软件,文本编辑软件,聊天软件等都可以用到它。
3. 监控指标
指标 | 阈值 | 工具 |
---|---|---|
锁等待时间(LockWaitTime) | < 100ms | Prometheus + Grafana |
锁持有时间(LockHoldTime) | < 1s | Redisson Metrics |
锁竞争失败率(LockFailRate) | < 0.1% | ELK 日志分析 |
五、选型对比
方案 | 适用场景 | TPS | 强一致性 | 开发成本 |
---|---|---|---|---|
Redisson + 注解 | 电商、金融等高并发 | 50万+ | ✅ | 低 |
ZooKeeper 临时节点 | 配置管理、选主 | 1万 | ✅ | 中 |
数据库乐观锁 + 事务注解 | 低频强事务(如ERP) | 5千 | ✅ | 低 |
ETCD 租约机制 | 云原生/K8s环境 | 10万 | ✅ | 高 |
总结
事务注解与分布式锁的协同实现了 本地事务原子性 与 全局资源协调 的双重保障。在云原生架构下,推荐采用 Redisson注解化锁 + Seata事务 的组合方案,既能通过声明式编程降低开发复杂度,又能支撑百万级 QPS 的分布式场景。关键是通过合理的锁粒度控制和监控体系,在一致性、性能、复杂度之间取得平衡。