接口幂等性概念
定义:用户对同一操作发起一次或多次请求的结果一致,多次调用不会改变业务状态,确保重复调用与单次调用结果相同。
使用场景
-
前端重复提交:用户多次点击提交按钮
-
接口超时重试:网络波动导致客户端自动重试
-
消息重复消费:消息队列因ACK机制触发消息重复投递
实现方法
1. Token 机制
流程:
-
客户端首次请求获取 Token
-
服务端生成全局唯一 ID 作为 Token(如百度的
uid-generator
、美团的Leaf
) -
Token 存储至 Redis,并返回给客户端
-
-
客户端二次请求携带此 Token
-
服务端校验 Token:
-
存在:执行业务逻辑,删除 Redis 中的 Token
-
不存在:判定为重复请求,直接返回结果
-
注意事项:
-
原子性操作:Token 校验与删除建议通过 Lua 脚本实现
-
Key 设计:
业务前缀 + 唯一标识
(如fruit_milk_device:jwt_token
)
2. Redis 的 SETNX 命令(分布式锁)
流程:
-
客户端发起请求,生成唯一业务标识(如用户ID+时间戳)
-
执行
SETNX key value
:-
成功(返回1):执行业务,设置超时时间(如
expire key 30
) -
失败(返回0):判定为重复请求,直接返回
-
项目实践示例:
-
场景:学生一卡通支付(食堂刷卡)
-
实现:
-
请求参数加密(RSA 防篡改)
-
校验请求时间有效性(如时间差超过1分钟则拒绝)
-
执行原子操作:
Boolean flag = redisTemplate.opsForValue().setIfAbsent( "nx:" + key, // Key 示例:一卡通卡号 key, Long.valueOf(repeatSwipeInterval), TimeUnit.SECONDS );
-
项目实践:Token 机制案例
需求背景:水果奶下单支付(学生选择方案后支付)
实现步骤:
-
生成校验码:
-
客户端调用校验接口,服务端通过
IdUtils.fastUUID()
生成随机校验码 -
Redis 存储 Key:
fruit_milk_device:jwt_token
,Value:校验码
-
-
下单请求校验:
-
客户端携带校验码(Header 中)
-
服务端从 Redis 查询校验码:
-
不存在:抛出「非法请求」异常
-
存在:执行支付流程
-
-
-
支付并发控制:
-
方案1(推荐):数据库原子操作
UPDATE fruit_milk_account SET balance = balance - #{money} WHERE account_id = #{accountId} AND balance >= #{money}
-
方案2:Redis 分布式锁 + 乐观锁
// 1. 获取锁 boolean lock = redisService.tryLock(lockKey); // 2. 查询并扣减余额 FruitMilkAccount account = queryAccount(accountId); account.setBalance(account.getBalance() - money); // 3. 乐观锁更新 boolean updated = updateById(account); if (!updated) throw new RuntimeException("余额更新失败"); // 4. 释放锁 redisService.releaseLock(lockKey);
-
关键总结
-
Token 机制:适用于需严格一次性验证的场景(如支付)
-
SETNX 分布式锁:适合高频短时操作(如秒杀)
-
原子性保障:优先使用数据库原子操作,高并发场景结合 Redis 锁与乐观锁