分布式锁防并发 与 事务后置动作一、分布式锁防并发1.1 解决什么问题在分布式微服务环境下同一个请求可能因为用户重复点击、MQ 重试、定时任务并发等原因被多个线程/多个实例同时执行。Java 的synchronized或ReentrantLock只能锁住单个 JVM 进程内的线程跨实例无效。分布式锁通过外部中间件Redis、ZooKeeper、数据库提供跨进程、跨机器的互斥能力。1.2 核心概念概念说明锁粒度按业务 key 加锁如订单号不同订单不互斥同一订单互斥获取方式阻塞等待tryLock with timeout或立即失败tryLock 0ms自动释放设置过期时间防止持有者崩溃后死锁可重入同一线程可重复获取同一把锁取决于实现Redisson vs 自建Redisson 提供看门狗续期、可重入、公平锁等高级特性自建一般用SET NX EX1.3 Redis 分布式锁原理加锁: SET lock_key unique_value NX PX 30000 → NX: 只有 key 不存在时才设置互斥 → PX: 30 秒后自动过期防死锁 释放: 用 Lua 脚本保证原子性 if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) end1.4 代码示例通用importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.data.redis.core.script.DefaultRedisScript;importorg.springframework.stereotype.Service;importjava.util.Collections;importjava.util.UUID;importjava.util.concurrent.TimeUnit;ServicepublicclassOrderService{privatefinalStringRedisTemplateredisTemplate;// 释放锁的 Lua 脚本确保只有持有者能释放privatestaticfinalStringRELEASE_SCRIPTif redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end;publicOrderService(StringRedisTemplateredisTemplate){this.redisTemplateredisTemplate;}publicvoidprocessOrder(StringorderId){StringlockKeylock:order:orderId;StringlockValueUUID.randomUUID().toString();// 唯一标识防止误删他人锁booleanlockedfalse;try{// 尝试加锁30秒自动过期lockedBoolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey,lockValue,30,TimeUnit.SECONDS));if(!locked){thrownewRuntimeException(订单正在处理中请勿重复提交);}// 业务逻辑 doBusinessLogic(orderId);// }finally{if(locked){// 原子释放只释放自己加的锁DefaultRedisScriptLongscriptnewDefaultRedisScript(RELEASE_SCRIPT,Long.class);redisTemplate.execute(script,Collections.singletonList(lockKey),lockValue);}}}privatevoiddoBusinessLogic(StringorderId){// 扣库存、生成发货单等...}}1.5 使用 try-with-resources 封装/** * 分布式锁封装实现 AutoCloseable 支持 try-with-resources. */publicclassRedisDistributedLockimplementsAutoCloseable{privatefinalStringRedisTemplateredisTemplate;privatefinalStringlockKey;privatefinalStringlockValue;privatebooleanacquiredfalse;publicRedisDistributedLock(StringRedisTemplateredisTemplate,StringlockKey){this.redisTemplateredisTemplate;this.lockKeylockKey;this.lockValueUUID.randomUUID().toString();}publicbooleantryLock(longtimeout,TimeUnitunit){this.acquiredBoolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey,lockValue,timeout,unit));returnthis.acquired;}Overridepublicvoidclose(){if(acquired){Stringscriptif redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end;redisTemplate.execute(newDefaultRedisScript(script,Long.class),Collections.singletonList(lockKey),lockValue);}}}使用方式try(RedisDistributedLocklocknewRedisDistributedLock(redisTemplate,lock:order:orderId)){if(lock.tryLock(30,TimeUnit.SECONDS)){doBusinessLogic(orderId);}else{thrownewRuntimeException(获取锁失败);}}// close() 自动释放锁1.6 注意事项问题解决锁过期但业务未执行完Redisson 看门狗机制自动续期或评估好超时时间Redis 主从切换丢锁RedLock 算法多数派加锁但复杂度高非强一致场景一般不用锁粒度太粗用lock:order:{orderId}而非lock:order避免全局串行释放他人的锁用 UUID 标记持有者Lua 脚本原子校验注博客https://blog.csdn.net/badao_liumang_qizhi二、事务编排 事务后置动作2.1 解决什么问题一个典型场景数据库写入成功后需要发 MQ 消息通知下游。如果在同一个事务方法中直接发消息消息已发但事务回滚 → 下游收到脏消息事务已提交但消息发送失败 → 下游丢消息事务后置动作保证只有事务成功提交后才执行消息发送等副作用操作。2.2 Spring 事务同步机制Spring 提供了TransactionSynchronization接口可以注册回调在事务的不同阶段执行publicinterfaceTransactionSynchronization{voidbeforeCommit(booleanreadOnly);// 提交前voidbeforeCompletion();// 完成前无论成功失败voidafterCommit();// 提交成功后 ★voidafterCompletion(intstatus);// 完成后带状态码}通过TransactionSynchronizationManager.registerSynchronization()注册。2.3 核心知识点知识点说明Transactional方法级事务Spring AOP 代理管理 begin/commit/rollbackTransactionSynchronizationManager线程绑定的事务同步管理器每个事务可注册多个回调afterCommit()事务提交成功后触发此时数据已持久化适合发 MQ/调外部接口afterCompletion(STATUS_ROLLED_BACK)事务回滚后触发适合做补偿/告警Propagation嵌套事务REQUIRES_NEW场景下同步回调跟随各自事务独立触发2.4 代码示例通用importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;importorg.springframework.transaction.support.TransactionSynchronization;importorg.springframework.transaction.support.TransactionSynchronizationManager;ServicepublicclassPaymentService{privatefinalPaymentRepositorypaymentRepository;privatefinalMessageProducermessageProducer;privatefinalNotificationServicenotificationService;publicPaymentService(PaymentRepositorypaymentRepository,MessageProducermessageProducer,NotificationServicenotificationService){this.paymentRepositorypaymentRepository;this.messageProducermessageProducer;this.notificationServicenotificationService;}Transactional(rollbackForException.class)publicvoidcompletePayment(StringpaymentId,StringuserId){// 事务内操作数据库 PaymentpaymentpaymentRepository.findById(paymentId).orElseThrow(()-newRuntimeException(支付单不存在));payment.setStatus(COMPLETED);paymentRepository.save(payment);// 扣减库存inventoryRepository.deductStock(payment.getProductId(),payment.getQuantity());// 注册事务后置动作 // 只有上面的 save deductStock 都成功提交后才会执行以下逻辑TransactionSynchronizationManager.registerSynchronization(newTransactionSynchronization(){OverridepublicvoidafterCommit(){// 发送 MQ 消息通知物流系统messageProducer.send(payment.completed,paymentId);// 发送用户通知notificationService.notifyUser(userId,您的支付已完成);}OverridepublicvoidafterCompletion(intstatus){if(statusSTATUS_ROLLED_BACK){// 事务回滚后的补偿逻辑如告警log.warn(支付事务回滚, paymentId{},paymentId);}}});}}2.5 封装为可复用的 Collector 工具类可使用AfterTransactionActionCollector来简化注册多个后置动作的场景importorg.springframework.transaction.support.TransactionSynchronization;importjava.util.ArrayList;importjava.util.List;/** * 事务后置动作收集器. * 在事务方法中收集多个后置动作事务提交后统一执行. */publicclassAfterTransactionActionCollectorimplementsTransactionSynchronization{privatefinalListRunnablecommitActionsnewArrayList();privatefinalListRunnablerollbackActionsnewArrayList();/** 添加事务提交后执行的动作. */publicvoidaddCommitSyncAction(Runnableaction){commitActions.add(action);}/** 添加事务回滚后执行的动作. */publicvoidaddRollbackAction(Runnableaction){rollbackActions.add(action);}OverridepublicvoidafterCommit(){for(Runnableaction:commitActions){try{action.run();}catch(Exceptione){// 后置动作失败不影响已提交的事务仅记录日志log.error(事务后置动作执行失败,e);}}}OverridepublicvoidafterCompletion(intstatus){if(statusSTATUS_ROLLED_BACK){for(Runnableaction:rollbackActions){try{action.run();}catch(Exceptione){log.error(回滚后置动作执行失败,e);}}}}}使用方式Transactional(rollbackForException.class)publicvoidcreateOrder(OrderRequestrequest){// 数据库操作OrderorderorderRepository.save(buildOrder(request));// 收集多个后置动作AfterTransactionActionCollectorcollectornewAfterTransactionActionCollector();collector.addCommitSyncAction(()-mqProducer.send(order.created,order.getId()));collector.addCommitSyncAction(()-pushService.pushToUser(order.getUserId(),下单成功));collector.addRollbackAction(()-alertService.alert(订单创建事务回滚: request.getOrderNo()));// 注册到当前事务TransactionSynchronizationManager.registerSynchronization(collector);}2.6 与其他方案对比方案优点缺点事务后置动作本方案简单直接无额外中间件应用崩溃时消息可能丢失本地消息表可靠性最高可重试需要额外表 定时补偿任务RocketMQ 事务消息中间件级保障依赖特定 MQ 实现编码复杂TransactionalEventListenerSpring 原生注解解耦优雅事件驱动模式需额外定义事件类2.7 注意事项问题说明afterCommit 中抛异常不会导致事务回滚已提交但会中断后续 action需要 try-catch没有活跃事务调用registerSynchronization会抛异常需确保在Transactional方法内异步 vs 同步afterCommit 默认同步执行长耗时操作建议投递到线程池REQUIRES_NEW嵌套事务各自独立内层事务提交时触发内层的 afterCommit不等外层三、两者如何配合在发货场景中两者组合使用的完整时序1. 获取分布式锁Redis ↓ 成功 2. 开启数据库事务Transactional ↓ 3. 业务逻辑校验 → 扣库存 → 生成发货单 → 保存明细 ↓ 4. 注册事务后置动作发 MQ 给物流 ↓ 5. 事务提交 ↓ 6. afterCommit 触发 → MQ 消息发出 ↓ 7. 释放分布式锁finally / try-with-resources锁保证同一订单不会被并发处理事务后置保证消息不会因为事务回滚而成为脏数据。