分布式系统下的Token幂等防重机制:原理、实现与最佳实践
目录导读
- 什么是Token幂等防重?
- 为什么需要分布式幂等防重?
- Token生成与分发策略
- 核心实现架构(含流程图)
- 常见问题与FAQ
- 生产环境最佳实践
什么是Token幂等防重?
在分布式系统中,Token幂等防重是一种通过唯一令牌(Token)确保同一请求仅被处理一次的技术,其核心逻辑是:客户端发起请求前,先向服务端申请一个唯一Token;服务端将Token存入缓存(如Redis),客户端在后续请求中携带此Token;服务端收到请求后,检查Token是否存在,若存在则执行逻辑并立即删除Token,若不存在或已过期则拒绝处理。

关键特性:
- 唯一性:每个Token全局唯一(通常基于UUID或雪花算法)。
- 时效性:Token设置过期时间(如60秒),避免堆积。
- 原子性:检查与删除操作需原子执行(Redis Lua脚本或SET NX EX命令)。
为什么需要分布式幂等防重?
在微服务、高并发场景下,以下问题迫使我们需要Token防重:
- 网络重复请求:客户端超时重试、用户快速连击导致同一请求多次到达。
- 分布式事务不一致:跨服务调用时,重复扣款、重复创建订单会破坏数据一致性。
- 消息队列重复消费:Kafka/RabbitMQ的at-least-once语义导致同一消息被多次处理。
无防重机制的风险:
当用户点击支付按钮两次,若未防重,系统可能扣款两次但只生成一笔订单,造成资损。
Token生成与分发策略
Token生成算法对比
| 算法 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| UUID | 简单、无中心化依赖 | 字符串长、无序 | 低并发内部系统 |
| 雪花算法 | 时间有序、ID短 | 依赖机器时钟 | 高并发分布式系统 |
| Redis原子递增 | 可控、可预测 | 依赖Redis | 短生命周期Token |
推荐方案:
采用「全局唯一ID + 时间戳 + 随机数」组合,
Token = snowflakeId("{业务代码}") + "_" + System.currentTimeMillis() + "_" + random.nextInt(99999)
分发接口设计
GET /api/token/generate?bizType=order
Response: { "token": "order_1698000001_123456", "expireTime": 60 }
要求:
- Token需绑定业务类型(bizType)。
- 客户端必须在有效期内使用Token,过期作废。
核心实现架构
1 基于Redis的原子性操作
核心代码(Go语言示例):
func CheckAndConsumeToken(redisClient *redis.Client, token string) bool {
// Lua脚本:检查Token是否存在 -> 存在则删除 -> 返回1/0
script := `
local exist = redis.call("EXISTS", KEYS[1])
if exist == 1 then
redis.call("DEL", KEYS[1])
return 1
else
return 0
end
`
result, _ := redisClient.Eval(script, []string{token}).Int()
return result == 1
}
2 请求处理流程图
客户端 服务端 Redis
| | |
|-- 1.请求Token ------------> | |
| |-- 2.生成Token -------------> |
| | (SET key token EX 60 NX) |
| <-- 3.返回Token ----------- | |
| | |
|-- 4.携带Token执行业务 ----> | |
| |-- 5.检查Token -------------> |
| | (Lua脚本原子操作) |
| | <-- 存在/不存在 ------------ |
| | |
| <-- 6.返回业务结果 ---------| |
3 异常处理策略
- Token过期:返回
400错误,引导客户端重新获取Token。 - Redis宕机:降级为「本地内存缓存+限流」方案,或直接返回错误。
- 重复请求:返回
200为“请求已处理”,避免前端误判。
常见问题与FAQ
Q1:Token防重与数据库唯一索引有什么区别?
答:
- Token防重:在业务逻辑执行前拦截,适用于高频、短时重复请求。
- 唯一索引:依赖数据库约束,适用于低频、数据层去重。
两者常结合使用:Token防重挡住大部分重复,唯一索引兜底防止并发穿透。
Q2:如果Redis集群发生主从切换,Token丢失怎么办?
答:
- 设置Token过期时间(如60秒),主从切换期间的旧Token自动失效。
- 对于关键业务(如支付),可引入数据库持久化Token表作为最终保障。
Q3:如何防止恶意用户大量申请Token耗尽系统资源?
答:
- 对Token生成接口做限流(如每秒100次/用户)。
- 设置Token最小过期时间(如10秒),减少无效占用。
- 使用布隆过滤器记录已发放Token,拒绝无效Token。
Q4:Token防重能完全保证幂等吗?
答:不能,因为Token检查与业务执行并非原子操作(分布式环境中)。
解决方案:
- 采用「业务方执行 + 状态机」:业务执行后修改订单状态为「已支付」,下次Token即使通过也返回已支付。
- 引入最终一致性校验:通过MQ补偿或对账系统兜底。
生产环境最佳实践
-
Token绑定业务上下文
每个Token需关联userId、bizType、timestamp,避免跨业务伪造。
-
差异化过期时间
- 低频操作(如退款):Token过期时间设为10分钟。
- 高频操作(如点赞):过期时间设为3秒。
-
降级与熔断
当Redis响应超过200ms时,进入半降级状态,从100%请求中抽样10%依赖本地缓存。
-
监控指标
- Token生成QPS、Token重复率、Redis缓存命中率。
- 注意:Token重复率超过5%时,需排查前端重复请求或分布式环境问题。
-
测试要点
- 压测场景:同时发送10个相同Token的请求,验证仅执行一次。
- 极端场景:Redis突然断开,验证降级逻辑是否符合预期。
Token幂等防重是分布式系统的基础设施,但需警惕“过度设计”——在非关键业务中,直接使用数据库唯一索引可能更简单可靠,始终记住:防重不是目的,数据一致性才是。