news 2026/6/18 11:22:43

分布式锁防并发 与 事务后置动作

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
分布式锁防并发 与 事务后置动作

分布式锁防并发 与 事务后置动作


一、分布式锁防并发

1.1 解决什么问题

在分布式微服务环境下,同一个请求可能因为用户重复点击、MQ 重试、定时任务并发等原因被多个线程/多个实例同时执行。Java 的synchronizedReentrantLock只能锁住单个 JVM 进程内的线程,跨实例无效。

分布式锁通过外部中间件(Redis、ZooKeeper、数据库)提供跨进程、跨机器的互斥能力。

1.2 核心概念

概念说明
锁粒度按业务 key 加锁(如订单号),不同订单不互斥,同一订单互斥
获取方式阻塞等待(tryLock with timeout)或立即失败(tryLock 0ms)
自动释放设置过期时间,防止持有者崩溃后死锁
可重入同一线程可重复获取同一把锁(取决于实现)
Redisson vs 自建Redisson 提供看门狗续期、可重入、公平锁等高级特性;自建一般用SET NX EX

1.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]) end

1.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_SCRIPT="if redis.call('get', KEYS[1]) == ARGV[1] then "+" return redis.call('del', KEYS[1]) "+"else "+" return 0 "+"end";publicOrderService(StringRedisTemplateredisTemplate){this.redisTemplate=redisTemplate;}publicvoidprocessOrder(StringorderId){StringlockKey="lock:order:"+orderId;StringlockValue=UUID.randomUUID().toString();// 唯一标识,防止误删他人锁booleanlocked=false;try{// 尝试加锁,30秒自动过期locked=Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey,lockValue,30,TimeUnit.SECONDS));if(!locked){thrownewRuntimeException("订单正在处理中,请勿重复提交");}// ========= 业务逻辑 =========doBusinessLogic(orderId);// ============================}finally{if(locked){// 原子释放:只释放自己加的锁DefaultRedisScript<Long>script=newDefaultRedisScript<>(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;privatebooleanacquired=false;publicRedisDistributedLock(StringRedisTemplateredisTemplate,StringlockKey){this.redisTemplate=redisTemplate;this.lockKey=lockKey;this.lockValue=UUID.randomUUID().toString();}publicbooleantryLock(longtimeout,TimeUnitunit){this.acquired=Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey,lockValue,timeout,unit));returnthis.acquired;}@Overridepublicvoidclose(){if(acquired){Stringscript="if 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(RedisDistributedLocklock=newRedisDistributedLock(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/rollback
TransactionSynchronizationManager线程绑定的事务同步管理器,每个事务可注册多个回调
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.paymentRepository=paymentRepository;this.messageProducer=messageProducer;this.notificationService=notificationService;}@Transactional(rollbackFor=Exception.class)publicvoidcompletePayment(StringpaymentId,StringuserId){// ======= 事务内操作(数据库) =======Paymentpayment=paymentRepository.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(status==STATUS_ROLLED_BACK){// 事务回滚后的补偿逻辑(如告警)log.warn("支付事务回滚, paymentId={}",paymentId);}}});}}

2.5 封装为可复用的 Collector 工具类

可使用AfterTransactionActionCollector来简化注册多个后置动作的场景:

importorg.springframework.transaction.support.TransactionSynchronization;importjava.util.ArrayList;importjava.util.List;/** * 事务后置动作收集器. * 在事务方法中收集多个后置动作,事务提交后统一执行. */publicclassAfterTransactionActionCollectorimplementsTransactionSynchronization{privatefinalList<Runnable>commitActions=newArrayList<>();privatefinalList<Runnable>rollbackActions=newArrayList<>();/** 添加事务提交后执行的动作. */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(status==STATUS_ROLLED_BACK){for(Runnableaction:rollbackActions){try{action.run();}catch(Exceptione){log.error("回滚后置动作执行失败",e);}}}}}

使用方式:

@Transactional(rollbackFor=Exception.class)publicvoidcreateOrder(OrderRequestrequest){// 数据库操作Orderorder=orderRepository.save(buildOrder(request));// 收集多个后置动作AfterTransactionActionCollectorcollector=newAfterTransactionActionCollector();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)

锁保证同一订单不会被并发处理;事务后置保证消息不会因为事务回滚而成为脏数据。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/18 11:22:39

计算机毕业设计之大学生互动交流网站设计与实现

随着社会的发展&#xff0c;系统的管理形势越来越严峻。越来越多的用户利用互联网获得信息&#xff0c;但各种信息鱼龙混杂&#xff0c;信息真假难以辨别。为了方便用户更好的获得信息&#xff0c;因此&#xff0c;设计一种安全高效的大学生互动交流网站极为重要。 为设计一个…

作者头像 李华
网站建设 2026/6/18 11:22:38

KNN分类原理与实战:从可解释性到欺诈检测全流程

1. 这不是“黑箱”&#xff0c;而是一把可解释的尺子&#xff1a;KNN分类到底在做什么&#xff1f;你有没有试过在菜市场买水果&#xff1f;摊主不给你看检测报告&#xff0c;也不报出糖度仪读数&#xff0c;就凭手掂一掂、眼瞅一瞅、再跟旁边几筐苹果比一比&#xff0c;就说&a…

作者头像 李华
网站建设 2026/6/18 11:22:22

从野蛮生长到AI自治:二十年迭代,看懂中国数据治理的范式跃迁

国内企业数据治理的二十年&#xff0c;是一部从“补短板”到“造资产”、从“人工运维”到“智能自治”的变革史。 不同于多数企服厂商跟风式迭代、碎片化更新&#xff0c;中翰软件的二十年成长路径&#xff0c;完美贴合了国内数据治理行业的每一次范式拐点。从2006年入局行业蛮…

作者头像 李华
网站建设 2026/6/18 11:21:53

知知随笔:让AI“+”出发展新活力

2025年中央经济工作会议明确提出“深化扩展人工智能行动&#xff0c;完善人工智能治理”&#xff0c;并将其作为2026年经济工作重点任务之一&#xff0c;强调以AI赋能各行各业、培育新质生产力。这标志着AI战略从“技术突破”向“价值创造”迈出了关键的一步。“AI”早已不是选…

作者头像 李华