接口幂等性设计:用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 指令就够了。"

相关推荐
还是大剑师兰特1 小时前
Redis面试题及详细答案100道(01-15) --- 基础认知篇
redis·大剑师·redis面试
布朗克1682 小时前
MySQL UNION 操作符详细说明
数据库·mysql·union
IT小辉同学3 小时前
Spring Boot Redis 缓存完全指南
spring boot·redis·缓存
喵桑..5 小时前
视图是什么?有什么用?什么时候用?MySQL中的视图
数据库·mysql
奋进小子6 小时前
达梦DISQL执行SQL和SQL脚本
数据库·sql
EasyCVR7 小时前
视频汇聚系统EasyCVR调用设备录像保活时视频流不连贯问题解决方案
数据库·ubuntu·音视频·云存储·云端录像
YueiL7 小时前
Linux文件系统基石:透彻理解inode及其核心作用
linux·网络·数据库
cyhysr9 小时前
redis8.0.3部署于mac
redis·macos
老华带你飞9 小时前
数码论坛|基于SprinBoot+vue的数码论坛系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·毕设·数码论坛系统
独泪了无痕10 小时前
Hutool-RedisDS:简化Redis操作的Java工具类
数据库·redis