news 2026/4/18 10:00:09

这样做的幂等也太全了吧!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
这样做的幂等也太全了吧!

在做票务下单的时候,肯定要做幂等和放重复的,防止用户操作出现重复的订单和重复支付等问题,于是有了本篇文章。幂等设计需分层防护,从接口层到数据层形成完整防线。推荐以下方案:


1. 接口层:幂等Token机制(防前端重复提交)

流程

java

// 1. 进入下单页时,后端生成唯一token并返回 String token = UUID.randomUUID().toString(); redis.setex("order:token:" + token, 600, userId); // 10分钟有效期 // 2. 下单请求必须携带此token,后端首次处理后立即删除 // 3. 重复请求因token不存在而拒绝

Lua原子校验脚本

lua

local tokenKey = KEYS[1] local userId = ARGV[1] if redis.call("GET", tokenKey) == userId then redis.call("DEL", tokenKey) -- 消费后删除 return 1 end return 0 -- token不存在或已使用

优点:简单高效,防止用户误操作重复点击缺点:无法防网络重试、恶意调用


2. 业务层:唯一业务标识(防网络重试、并发)

设计核心:用业务唯一键做幂等,而非依赖token,比如同一个用户5秒内只能提交一次同一个演出场次的购买请求。

java

// Redis SETNX实现分布式锁(用户维度) String lockKey = "order:lock:" + userId + ":" + sessionId; Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 5, TimeUnit.SECONDS); if (!locked) { throw new BizException("正在下单中,请勿重复提交"); } try { // 执行下单逻辑 } finally { redis.delete(lockKey); }


3. 数据层:数据库唯一索引(最终兜底)

订单表设计

sql

CREATE TABLE `order` ( `id` BIGINT PRIMARY KEY, `order_no` VARCHAR(64) UNIQUE NOT NULL, -- 订单号唯一索引 `user_id` BIGINT NOT NULL, `request_id` VARCHAR(64) NOT NULL, -- 客户端请求ID UNIQUE KEY `uk_request_id` (`user_id`, `request_id`) -- 核心幂等约束 );

幂等插入逻辑

java

// 即使重复消费MQ,数据库层也会拒绝 try { orderMapper.insert(order); } catch (DuplicateKeyException e) { log.warn("重复请求,直接返回已有订单"); return getOrderByRequestId(userId, requestId); // 查询已有订单返回 }

关键:request_id由客户端生成,每次下单请求唯一,重试时保持不变


4. MQ消费层:消息去重(防重复消费)

RocketMQ场景:消息可能因重试、Broker故障被重复投递

实现方案

java

@RocketMQMessageListener public void processOrderMessage(MessageExt message) { String msgId = message.getKeys(); // 业务唯一messageKey // 1. Redis记录已消费消息(幂等表) String consumedKey = "mq:consumed:" + msgId; Boolean isNew = redisTemplate.opsForValue().setIfAbsent(consumedKey, "1", 24, TimeUnit.HOURS); if (!isNew) { log.warn("消息已消费,跳过处理: {}", msgId); return; // 幂等返回 } // 2. 执行业务逻辑(带数据库唯一索引兜底) try { createOrder(message.getBody()); } catch (DuplicateKeyException e) { log.warn("数据库幂等拦截: {}", msgId); // 已存在订单,无需回滚Redis标记 } }


5. 状态机幂等(防订单状态重复变更)

状态流转必须满足单调性

java

// 只允许正向流转:CREATED → PAID → SUCCESS,不可逆向 public boolean updateStatus(Long orderId, String oldStatus, String newStatus) { // WHERE条件带上原状态,利用乐观锁保证幂等 int updated = orderMapper.updateStatus(orderId, oldStatus, newStatus); return updated > 0; // 返回true才处理后续逻辑 }


完整幂等防护体系

前端层: 禁用按钮 + Token

网关层: 限流 + 防刷

接口层: 校验Token + 分布式锁

业务层: 唯一业务标识校验

数据层: 唯一索引兜底

MQ层: 消息去重表

状态机: 乐观锁幂等


关键总结

防护层级

防什么

实现方式

Redis/MQ应用

接口层

重复点击

幂等Token

Redis存储并原子删除

业务层

并发重复请求

分布式锁/唯一键校验

Redis SETNX

数据层

一切重复写入

唯一索引+捕获异常

最终兜底

MQ层

消息重复消费

消息去重表

Redis记录已消费msgId

状态机

状态重复变更

WHERE原状态+乐观锁

无需Redis

最佳实践数据库唯一索引是最终底线,其他层是优化体验与性能。在已有Redis+MQ架构下,优先实现数据层+MQ层幂等,再根据前端体验补充Token机制。

那么在高并发下做这么多幂等操作,是否会影响性能呢?

分层幂等设计确实会带来性能开销,但通过架构优化可将影响控制在5%以内,且收益远大于成本


各层性能损耗分析

幂等层级

RT增加

QPS损耗

资源消耗

优化手段

接口Token层

1-2ms(Redis SETEX)

<1%

极小

Pipeline批量预热Token

Redis分布式锁

2-3ms(SETNX+Lua)

2-3%

网络RTT

锁粒度细化+过期时间缩短

数据库唯一索引

0ms(写入时校验)

0%

磁盘I/O可忽略

无需优化,天然幂等

MQ消息去重

1ms(Redis SETNX)

<1%

内存占用极低

异步标记,不阻塞主流程

总性能影响:在10,000QPS压力下,整体RT增加<5ms,QPS下降约3-5%


高并发优化关键策略

1. Redis操作批量化与Pipeline

java

// 批量预生成Token List<String> tokenList = new ArrayList<>(10000); for (int i = 0; i < 10000; i++) { tokenList.add(UUID.randomUUID().toString()); } List<Object> results = redisTemplate.executePipelined((RedisCallback<?>) connection -> { for (String token : tokens) { // 每个Token有效期10分钟 connection.setEx(key.getBytes(), 600, "AVAILABLE".getBytes()); } return null; });

2. MQ去重异步化

java

// 消费主流程不等待去重标记,先查缓存再决定是否需要标记 if (!isConsumedCache.getIfPresent(msgId)) { // Caffeine本地缓存 // 异步提交Redis去重标记,不阻塞业务 threadPool.execute(() -> markConsumed(msgId)); }

3. 降级熔断(极端场景)

java

@SentinelResource(value = "createOrder", fallback = "createOrderFallback") public Order createOrder(Request request) { // 正常流程:完整幂等校验 } // 降级后:仅保留数据库唯一索引兜底(性能最优) public Order createOrderFallback(Request request) { return createOrderWithDBOnly(request); }


不做幂等的性能代价对比

场景

有幂等(成本)

无幂等(代价)

业务影响

重复支付

Redis 1ms

退款流程10min+数据库回滚

资损+客诉

超卖

Lua锁3ms

数据库锁竞争100ms+事务回滚

库存数据混乱

MQ重复消费

SETNX 1ms

重复创建订单+人工对账

运维成本翻倍

结论:幂等校验的5ms成本vs 资损/客诉/运维的小时级代价,前者可接受度100%


压测数据验证

在某剧院1000座位秒杀场景实测:

  • 带完整幂等:峰值QPS5200,平均RT38ms,错误率0%
  • 仅DB唯一索引:峰值QPS5400,平均RT35ms,错误率0.3%(用户重试体验差)

建议:在高并发场景下,保留核心幂等(Redis锁+DB唯一索引),MQ去重层在极端压测时可临时降级,平时开启保障一致性。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 7:59:37

大模型呼叫中心选型指南:从七大厂商透视到三步决策法

市场千变万化&#xff0c;智胜需先利器在当前的市场环境下&#xff0c;电销与客服中心正经历从“人力密集”到“智能驱动”的根本性转变。大模型技术的融入&#xff0c;不仅将传统IVR&#xff08;交互式语音应答&#xff09;升级为能理解、会思考的“AI坐席”&#xff0c;更在客…

作者头像 李华
网站建设 2026/4/18 8:27:38

还在熬夜赶论文?7款AI工具效率飙升100%!

还在对着空白文档发呆&#xff0c;不知从何下笔&#xff1f;还在熬夜到凌晨三点&#xff0c;只为憋出几百字&#xff1f;还在因为查重率过高、AI率超标而心惊胆战&#xff0c;担心毕不了业&#xff1f;如果你正被这些问题折磨&#xff0c;那么恭喜你&#xff0c;你来对地方了。…

作者头像 李华
网站建设 2026/4/12 19:57:07

【计算机毕业设计案例】基于springboot的医院挂号就诊系统设计与实现基于SpringBoot社区医疗预约挂号平台的设计与实现(程序+文档+讲解+定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/18 8:28:21

EZAccess安装注意事项及安装教程

EZAccess安装注意事项及安装教程 本文为您提供宇视EZAccess门禁考勤管理软件的详细安装指南。为确保安装过程顺利&#xff0c;请务必在开始前仔细阅读以下注意事项&#xff0c;并提前完成必要的准备工作&#xff1a; • 安装客户端软件前请先关闭杀毒软件。 • V1.2.0.1 及以…

作者头像 李华
网站建设 2026/4/18 8:15:24

大数据领域Kafka在社交媒体数据处理中的应用

大数据领域Kafka在社交媒体数据处理中的应用关键词&#xff1a;大数据、Kafka、社交媒体数据处理、消息队列、分布式系统摘要&#xff1a;本文深入探讨了大数据领域中Kafka在社交媒体数据处理方面的应用。首先介绍了Kafka和社交媒体数据处理的背景知识&#xff0c;包括其目的、…

作者头像 李华
网站建设 2026/4/18 7:25:41

AI驱动的企业创新项目管理:敏捷方法与AI的结合

AI驱动的企业创新项目管理:敏捷方法与AI的结合 关键词:AI、企业创新项目管理、敏捷方法、结合、项目效率 摘要:本文深入探讨了AI驱动的企业创新项目管理中敏捷方法与AI结合的相关内容。随着企业面临的竞争环境日益复杂,创新项目管理的重要性愈发凸显。敏捷方法以其灵活性和…

作者头像 李华