生产级实战:基于Redis Lua的分布式幂等框架设计与实现1
一、痛点分析:为什么你的接口不安全?
在支付、下单、账务核心链路中,我们常遇到:
-
用户重复提交:前端防抖失效,用户连续点击“支付”按钮。
-
超时重试:HTTP/RPC 客户端设置了超时重试机制(如 Feign Retry)。
-
消息队列重复消费:Kafka 的 Rebalance 或 RocketMQ 的 ACK 超时导致消息重新投递。
-
分布式事务回滚重试:Seata 或 TCC 模式的 Cancel 阶段重试。
核心诉求:
-
唯一性:同一个请求只执行一次。
-
原子性:判断幂等Key是否存在、记录状态必须原子操作。
-
高性能:不能因为幂等校验成为系统瓶颈。
-
高可用:支持过期清理,防止Redis无限膨胀。
二、方案选型与设计
1. 幂等Key的设计
我们采用 业务唯一标识 + 令牌(Token) 的方式:
-
Token模式(推荐):前端在调用接口前先获取Token,提交时携带,Token用后即焚。
-
业务Key模式:
order:create:{userId}:{productId}:{timestamp},适用于MQ消费。
2. 存储选型:Redis
利用 Redis 的 SETNX (Set if Not Exists) 特性。但单纯的 SETNX 无法解决 状态流转 问题(例如:请求正在处理中,还是已处理完成)。
3. 核心逻辑
我们将幂等记录分为三个状态:
-
0: 处理中 (Processing) -
1: 处理成功 (Success) -
2: 处理失败 (Fail)
三、生产级代码实战
1. 定义幂等注解(AOP的核心)
通过注解实现无侵入式的幂等校验。
2. Lua脚本:保证原子性(核心)
这是整个方案的灵魂。我们使用 Lua 脚本来处理复杂的逻辑判断,避免并发下的竞态条件。
3. Redis配置与Lua脚本加载
4. AOP切面:拦截请求
5. 业务接口应用
6. 全局异常处理器
四、源码级深度剖析
Lua脚本为何能保证原子性?
Redis 是单线程执行命令的。当 Lua 脚本被调用时,Redis 会将其作为一个 整体 执行,期间不会被其他命令打断。这就解决了以下并发问题:
-
线程A判断Key不存在。
-
线程B同时判断Key不存在。
-
线程A设置Key,线程B设置Key(导致重复执行)。
在Lua脚本中,SET NX 和后续的 GET 操作是连续的,中间不会插入其他Redis指令,因此保证了判断和设置的原子性。
状态机设计
我们的Lua脚本实现了一个简单的 状态机:
-
初始状态:Key不存在。
-
迁移1:
SET NX成功 ->0(Processing)。 -
迁移2:业务成功 ->
1(Success)。 -
迁移3:业务失败 ->
2(Fail)。 -
迁移4:Fail状态下再次请求 -> 重置为
0(允许重试)。
这种设计比单纯的 SETNX 更强大,因为它区分了“处理中”和“处理完成”,有效防止了 “悬挂请求”(即第一个请求很慢,第二个请求进来时第一个还没写完结果)导致的数据不一致。
五、生产环境避坑指南
-
Redis Key 爆炸:务必设置合理的
expire时间。对于Token模式,建议在业务完成后主动删除Key(设置delKey=true),减少Redis内存占用。 -
SpEL 表达式性能:虽然 SpEL 很方便,但在超高并发下(QPS > 10万),解析表达式会有微小开销。如果对性能极度敏感,可以改为在注解中直接指定参数名,通过反射获取,或者使用
ThreadLocal传递幂等Key。 -
Redis 集群模式:Lua 脚本要求所有 Key 必须落在同一个 Slot 上。我们的 Key 设计是
prefix:id,只要prefix相同,就会路由到同一个 Slot,因此该方案天然支持 Redis Cluster。 -
事务一致性:如果业务方法包含数据库事务,Lua脚本的执行是在事务之外的。这意味着:Redis中标记为成功,但数据库事务回滚了。解决方案:使用
TransactionSynchronizationManager在事务提交后再更新Redis状态,或者采用 最终一致性 方案(定时任务核对Redis与DB状态)。 -
Token生成策略:如果是前端获取Token,Token必须是 一次性 的。生成Token时也要利用 Redis 的原子操作。
六、总结
本文设计了一套基于 Redis + Lua + AOP 的通用幂等框架。
核心优势:
-
通用性:通过注解和SpEL表达式,适用于任何接口。
-
安全性:Lua脚本保证了原子性,状态机设计防止了并发问题。
-
高性能:Redis内存操作,微秒级响应。
-
可维护:AOP实现无侵入,业务代码零耦合。
生产建议:
-
对于核心链路(如支付),建议配合 分布式锁 使用(先拿锁,再校验幂等)。
-
定期监控 Redis 中幂等Key的数量,防止内存泄漏。
-
在网关层(如Spring Cloud Gateway)也可以集成类似的幂等逻辑,做第一道拦截。
本文由

浙公网安备 33010602011771号