Java微服务防重核心是Token“一次一验、验完即废”,依赖Redis原子校验删除、拦截器统一接入及数据库唯一索引兜底。

Java 微服务中用 Token 令牌机制防重,核心是“一次一验、验完即废”,靠服务端主动识别并拦截重复请求,不依赖前端控制。关键不在生成 Token,而在于校验与删除的原子性、时效性与分布式一致性。
Token 的生成与下发时机
用户进入提交页(如订单确认页)时,前端主动调用 /idempotent/token 接口获取 Token。后端生成方式推荐:
- 用 Snowflake ID 或 UUID(避免时间可预测性),不拼接用户信息或时间戳
- 存入 Redis,key 为
idempotent:token:{uuid},value 可设为空或用户 ID,TTL 设为 15–30 分钟 - 响应头中可附加
X-Idempotency-Token,便于前端统一携带;也可走请求体或 URL 参数,保持前后端约定一致
提交时的校验与原子操作
真正防重发生在业务接口被调用时。Token 必须在业务执行前完成「存在性判断 + 删除」两个动作,且必须原子执行:
- 禁止先查再删(存在竞态:两次请求同时查到存在,都进入业务逻辑)
- 推荐使用 Redis Lua 脚本:
if redis.call("exists", KEYS[1]) == 1 then return redis.call("del", KEYS[1]) else return 0 end - Spring Boot 中可用
RedisTemplate.execute()执行该脚本,返回非零值表示校验通过、Token 已清除 - 若返回 0,直接返回
400 Bad Request和提示“请求已处理,请勿重复提交”
拦截器统一接入,避免每个 Controller 重复写
定义一个 @Aspect 切面或 Spring MVC HandlerInterceptor,按注解自动生效:
立即学习“Java免费学习笔记(深入)”;
- 自定义注解
@Idempotent,标注在需防重的 POST/PUT 方法上 - 拦截器提取请求中的 Token 字段(支持 Header、Form、JSON Body 多种位置)
- 调用统一的
IdempotentService.checkAndDelete(token)方法,失败则中断流程、抛出IdempotentException - 全局异常处理器捕获该异常,统一转为 400 响应,不透出堆栈
兜底与容错设计
Token 机制不是银弹,需配合其他策略应对边界情况:
- 网络超时导致 Token 删除失败?允许客户端带重试 ID(如
X-Retry-ID),服务端记录已成功处理的 ID,在幂等日志表中做二次校验 - Redis 故障?降级为本地缓存(如 Caffeine)+ 短期 TTL,仅限单机场景应急,不可长期依赖
- 业务执行成功但响应丢失?前端需配合「提交后禁用按钮 + 轮询结果」,而非盲目重发;服务端对成功请求返回确定性结果(如订单号),供前端查询状态
- 数据库层加唯一索引(如
order_no或req_id字段)作为最终兜底,防止极端情况下 Token 漏检


















