接口幂等性设计:用Redis避免接口重复请求

【实战博客】

Redis + 请求幂等号:5 分钟给接口加上"防抖+幂等"双保险


一、为什么要做幂等?

场景 结果(无幂等)
用户双击按钮 创建两条数据
网关 504 重试 接口被调用 N 次
脚本并发调用 数据出现脏记录

一句话:"网络会骗人,用户会手滑,幂等就是最后的兜底。"


二、方案选型:为什么选了 Redis?

工具 优点 缺点
数据库唯一索引 100% 准确 必须落库后才能判断,延迟高
Guava Cache 本地 0 RTT 集群部署会丢一致性
Redis 原子指令、TTL、高并发、低延迟 需考虑宕机(可接受)

三、最终代码(可直接复制)

1. 通用幂等工具类
java 复制代码
public final class IdempotentUtil {
    private static final String PREFIX = "order:operator:";
    private static final int TTL_SECONDS = 5 * 60;

    public static boolean tryAcquire(JedisCluster jedis, String biz, String requestId) {
        String key   = PREFIX + biz + ":" + requestId;
        String value = "1";
        // 原子:SET key value NX EX 300
        return "OK".equals(jedis.set(key, value, "NX", "EX", TTL_SECONDS));
    }
}
2. 接口 Controller
java 复制代码
@PostMapping("/add")
public ApiResp<Void> add(@RequestBody @Valid UserOrderDto dto) {
    // 1. 生成或补全 requestId
    if (StrUtil.isBlank(dto.getRequestId())) {
        dto.setRequestId(IdUtil.simpleUUID());
    }
    // 2. 幂等锁
    if (!IdempotentUtil.tryAcquire(jedisCluster, "create", dto.getRequestId())) {
        throw new IllegalArgumentException("重复请求");
    }
    // 3. 真正业务
   
    return ApiResp.success();
}

四、原子性验证:SET vs SETNX+EXPIRE

指令 是否原子 并发测试
SET key val NX EX 300 ✅ 原子 1000 线程 0 误闯
SETNX + EXPIRE ❌ 非原子 1000 线程 6 次误闯

结论:必须一条命令完成"不存在才写"+"设 TTL"


五、Key 设计最佳实践

复制代码
order:operator:{动作}:{requestId}
  • 动作:add / del / update
  • requestId:前端生成 UUID,或后端兜底生成
    好处:同一个 requestId 换动作也不会串。

TTL 5 分钟是业务可接受的最大重试窗口,可按场景调整。


六、异常 & 降级策略

故障 处理
Redis 不可用 捕获 JedisConnectionException,可放行(打日志 + 告警)
极端并发 仍可保证幂等,因 Redis 单线程执行 SET NX
客户端时钟漂移 无影响,TTL 由 Redis 控制

七、前端也要配合

js 复制代码
// axios 拦截器:统一加 requestId
axios.interceptors.request.use(config => {
  if (!config.headers['X-Request-Id']) {
    config.headers['X-Request-Id'] = uuidv4();
  }
  return config;
});

八、性能压测数据

  • 单机 4C8G,Redis 3 主 3 从
  • 10 万并发请求,99.9 % 延迟 < 2 ms
  • 0 例重复入库

九、小结

维度 结果
原子性 SET NX EX
复杂度 1 个工具类 + 3 行代码
侵入性 零侵入业务
可扩展 任意写接口直接复用

把这套模板沉淀到公共包,团队其他接口只需加一行 tryAcquire 即可。
"写接口,先拿锁,再办事,已经成为团队铁律。"


十、附录:完整 Lua 脚本(如需脚本模式)

lua 复制代码
-- KEYS[1] = key
-- ARGV[1] = value
-- ARGV[2] = ttl
if redis.call("exists", KEYS[1]) == 1 then
    return 0
else
    redis.call("setex", KEYS[1], ARGV[2], ARGV[1])
    return 1
end

调用方式:

java 复制代码
Long ret = (Long) jedisCluster.eval(lua, 1, key, value, String.valueOf(TTL_SECONDS));
if (ret == 0) throw new IllegalArgumentException("重复请求");

"接口防抖只是第一层,真正的幂等是把业务语义 也考虑进去。

但 90 % 的场景,一条 Redis 指令就够了。"

相关推荐
ao_lang10 分钟前
MySQL的存储过程和触发器
android·数据库·mysql
en-route25 分钟前
Spring 框架下 Redis 会话存储应用实践
java·redis·spring
JIngJaneIL27 分钟前
基于Java酒店管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot
颜颜yan_35 分钟前
DevUI自定义开发实践:从零开始构建自定义组件和插件
android·java·数据库
编织幻境的妖36 分钟前
数据库隔离级别详解与选择
数据库
wljt37 分钟前
达梦导入大数据
数据库
马克学长40 分钟前
SSM物流系统h7fel(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm框架·物流管理系统
一颗宁檬不酸41 分钟前
Oracle序列从2开始而不是从1开始的常见原因及解决方法
数据库·oracle
VX:Fegn089542 分钟前
计算机毕业设计|基于springboot + vue健身房管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
Leon-Ning Liu1 小时前
Oracle 19c RAC ASM 密码文件恢复方案三:将补丁升级至 19.8 版本后,利用 asmcmd --nocp credfix 进行修复
数据库·oracle