当前位置: 首页 > news >正文

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

【实战博客】
Redis + 请求幂等号:5 分钟给接口加上“防抖+幂等”双保险


一、为什么要做幂等?

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

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


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

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

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

1. 通用幂等工具类
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 300return "OK".equals(jedis.set(key, value, "NX", "EX", TTL_SECONDS));}
}
2. 接口 Controller
@PostMapping("/add")
public ApiResp<Void> add(@RequestBody @Valid UserOrderDto dto) {// 1. 生成或补全 requestIdif (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 控制

七、前端也要配合

// 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 脚本(如需脚本模式)

-- KEYS[1] = key
-- ARGV[1] = value
-- ARGV[2] = ttl
if redis.call("exists", KEYS[1]) == 1 thenreturn 0
elseredis.call("setex", KEYS[1], ARGV[2], ARGV[1])return 1
end

调用方式:

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

“接口防抖只是第一层,真正的幂等是把业务语义也考虑进去。
但 90 % 的场景,一条 Redis 指令就够了。”

http://www.lryc.cn/news/590347.html

相关文章:

  • 前端技术之---应用国际化(vue-i18n)
  • 中医文化学习软件,传承国粹精华
  • Java全栈面试实录:从电商支付到AIGC的深度技术考察
  • 什么是数据仓库?数据库与数据仓库有什么关系?
  • 基于WebRTC构建应用的可复用模块
  • Ansible 查看PostgreSQL的版本
  • Rocky9安装Ansible
  • Android CameraX使用
  • PyCharm高效入门指南
  • 深度解析:如何在 Windows 系统中高效配置 Android MCP 服务
  • 【Unity】IL2CPP相关理论知识学习
  • CSS:transition语法
  • 网络安全初级(XSS-labs 1-8)
  • 【黑客与安全】windows平台的BurpSuite的安装
  • Opencv---cv::minMaxLoc函数
  • API Gateway HTTP API 控制客户端访问 IP 源
  • [硬件电路-28]:从简单到复杂:宇宙、芯片与虚拟世界的共通逻辑
  • Linux 716 数据库迁移
  • 汽车电子功能安全标准ISO26262解析(二)——需求部分
  • 网络编程(数据库)
  • ST表及数学归纳法
  • LLM OCR vs 传统 OCR:解锁文档处理的未来
  • 统一日志格式规范与 Filebeat+Logstash 实践落地
  • LeetCode 3201.找出有效子序列的最大长度 I:分类统计+贪心(一次遍历)
  • 跟着Carl学算法--回溯【2】
  • Python高级编程技巧探讨:装饰器、Patch与语法糖详解
  • Android动态获取当前应用占用的内存PSS,Java
  • x86版Ubuntu的容器中运行ARM版Ubuntu
  • 消息中间件(Kafka VS RocketMQ)
  • AQS(AbstractQueuedSynchronizer)抽象队列同步器