news 2026/4/22 23:26:54

幂等性怎么写进PRD:重复提交/重复点击/弱网重试(附5个场景)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
幂等性怎么写进PRD:重复提交/重复点击/弱网重试(附5个场景)

前言

幂等性是防止重复操作的关键机制。很多线上问题都是因为没有做幂等:用户连点两次创建了两个订单、弱网重试导致重复扣款、重复发送短信。这篇给你5个常见场景的幂等性设计方法。

一、什么是幂等性

定义:同一个请求执行多次,结果和执行一次一样。

举例:

  • 查询操作:天然幂等(查10次结果一样)
  • 删除操作:幂等(删除已删除的记录,结果还是删除)
  • 创建操作:不幂等(创建10次会有10条记录)❌
  • 扣款操作:不幂等(扣10次会扣10次钱)❌

二、5个常见场景详解

场景1:创建订单(防止重复创建)

场景描述:用户点击"提交订单"按钮,因网络慢连点了3次,或者前端自动重试导致重复提交。

问题:创建了3个订单,用户投诉,客服工作量增加。

解决方案:

  • 前端防护:按钮防抖(500ms),提交后立即禁用按钮,显示"提交中..."
  • 后端幂等:幂等键 = 用户ID + 购物车ID + 时间戳(前端生成UUID)
  • 实现逻辑:
    1. 收到请求时,先查询幂等键是否存在(Redis或数据库)
    2. 如存在,返回原订单号和订单状态,HTTP 200
    3. 如不存在,创建订单,保存幂等键(有效期24小时)
    4. 返回新订单号

PRD写法:

接口名称:创建订单 幂等性要求:必须支持幂等 幂等键:userId_cartId_uuid(前端生成UUID) 幂等逻辑: 1. 收到请求时,先查询幂等键是否存在 2. 如存在,返回原订单号,HTTP 200,isDuplicate=true 3. 如不存在,创建订单,保存幂等键到Redis(有效期24小时) 4. 返回新订单号,isDuplicate=false 幂等键有效期:24小时 重复请求返回: { "code": 200, "message": "订单已创建", "data": { "orderId": "ORD20250101001", "isDuplicate": true, "createdAt": "2025-01-01 10:00:00" } }

技术实现示例:

// 伪代码 function createOrder(userId, cartId, idempotencyKey) { // 1. 检查幂等键 const existingOrder = redis.get(idempotencyKey); if (existingOrder) { return { orderId: existingOrder.orderId, isDuplicate: true }; } // 2. 创建订单 const order = db.createOrder({ userId, cartId }); // 3. 保存幂等键 redis.set(idempotencyKey, { orderId: order.id }, 24 * 3600); return { orderId: order.id, isDuplicate: false }; }

场景2:支付扣款(防止重复扣款)

场景描述:用户支付时网络超时,前端或支付网关自动重试3次,导致重复扣款。

问题:扣了3次钱,用户投诉,需要退款,影响用户体验和公司信誉。

解决方案:

  • 幂等键:订单号(唯一标识)
  • 实现逻辑:
    1. 支付前先查询订单支付状态
    2. 如已支付,直接返回成功,不调用支付接口
    3. 如未支付,调用支付接口,成功后更新订单状态
    4. 支付接口内部也要做幂等(支付网关通常支持)

PRD写法:

接口名称:支付扣款 幂等性要求:必须支持幂等 幂等键:订单号(orderId) 幂等逻辑: 1. 支付前先查询订单支付状态 2. 如已支付,直接返回成功,HTTP 200 3. 如未支付,调用支付接口,成功后更新订单状态为"已支付" 4. 支付接口内部也要做幂等(使用支付流水号) 幂等键有效期:永久(订单支付状态不可逆) 重复支付请求返回: { "code": 200, "message": "订单已支付", "data": { "orderId": "ORD20250101001", "payStatus": "paid", "paidAt": "2025-01-01 10:00:00" } }

注意事项:

  • 支付接口必须支持幂等,使用支付流水号作为幂等键
  • 支付成功后,订单状态必须立即更新,避免并发问题
  • 支付失败时,不要更新订单状态,允许用户重试

场景3:发送短信(防止重复发送)

场景描述:系统故障导致短信发送接口被调用多次,或者用户快速点击"发送验证码"按钮。

问题:用户收到多条相同短信,浪费短信费用,影响用户体验。

解决方案:

  • 幂等键:手机号 + 短信模板ID + 业务ID(如订单号)
  • 实现逻辑:
    1. 发送前先查询幂等键是否存在
    2. 如存在且在有效期内(如1分钟),直接返回成功,不发送
    3. 如不存在或已过期,发送短信,保存幂等键(有效期1分钟)
    4. 返回发送结果

PRD写法:

接口名称:发送短信 幂等性要求:必须支持幂等 幂等键:手机号_模板ID_业务ID(如:13800138000_VERIFY_ORD001) 幂等逻辑: 1. 发送前先查询幂等键是否存在 2. 如存在且在有效期内(1分钟),直接返回成功,不发送 3. 如不存在或已过期,发送短信,保存幂等键到Redis(有效期1分钟) 4. 返回发送结果 幂等键有效期:1分钟(防止短时间内重复发送) 重复请求返回: { "code": 200, "message": "短信已发送", "data": { "isDuplicate": true, "sentAt": "2025-01-01 10:00:00" } }

扩展场景:

  • 验证码短信:1分钟内相同手机号+模板只发送一次
  • 通知短信:相同业务ID(如订单号)只发送一次
  • 营销短信:相同手机号+活动ID每天只发送一次

场景4:库存扣减(防止超卖)

场景描述:两个用户同时购买最后1件商品,或者秒杀活动时大量并发请求。

问题:库存变成-1,超卖问题,用户下单后无法发货,影响用户体验。

解决方案:

  • 使用乐观锁:版本号机制
  • 实现逻辑:
    1. 查询库存时,同时获取版本号(version)
    2. 扣减库存时,使用版本号作为条件:UPDATE stock SET qty = qty - 1, version = version + 1 WHERE id = ? AND version = ?
    3. 如果更新影响行数=0,说明版本号不匹配(已被其他请求修改),返回"库存不足"
    4. 如果更新影响行数>0,说明扣减成功

PRD写法:

接口名称:库存扣减 幂等性要求:必须支持幂等(使用乐观锁) 幂等键:订单号(防止重复扣减) 并发控制:乐观锁(版本号) 实现方式: 1. 查询库存时,返回版本号(version) 2. 扣减库存时,使用版本号作为条件: UPDATE stock SET qty = qty - 1, version = version + 1 WHERE id = ? AND version = ? AND qty > 0 3. 如果更新影响行数=0,说明版本号不匹配或库存不足,返回"库存不足" 4. 如果更新影响行数>0,说明扣减成功 用户提示:库存不足,请选择其他商品 恢复路径:无(库存已售罄)

技术实现示例:

// 伪代码 function deductStock(productId, orderId, quantity) { // 1. 检查幂等键(防止重复扣减) const existingDeduction = redis.get(`deduct:${orderId}`); if (existingDeduction) { return { success: true, isDuplicate: true }; } // 2. 查询库存和版本号 const stock = db.query("SELECT qty, version FROM stock WHERE id = ?", productId); if (stock.qty < quantity) { return { success: false, message: "库存不足" }; } // 3. 使用乐观锁扣减库存 const affectedRows = db.execute( "UPDATE stock SET qty = qty - ?, version = version + 1 WHERE id = ? AND version = ? AND qty >= ?", [quantity, productId, stock.version, quantity] ); if (affectedRows === 0) { return { success: false, message: "库存不足,请重试" }; } // 4. 保存幂等键 redis.set(`deduct:${orderId}`, { productId, quantity }, 3600); return { success: true, isDuplicate: false }; }

注意事项:

  • 乐观锁适合读多写少的场景,性能高
  • 如果冲突频繁,可以考虑悲观锁(SELECT FOR UPDATE)
  • 秒杀场景建议使用Redis分布式锁或消息队列削峰

场景5:数据导入(防止重复导入)

场景描述:用户上传Excel导入数据,因网络问题重复上传,或者用户误操作重复点击"导入"按钮。

问题:数据重复,需要人工清理,影响数据准确性。

解决方案:

  • 幂等键:文件MD5(文件内容唯一标识)
  • 实现逻辑:
    1. 上传文件后,计算文件MD5
    2. 导入前先查询文件MD5是否已导入
    3. 如已导入,返回"该文件已导入"和导入记录
    4. 如未导入,执行导入,保存文件MD5和导入记录

PRD写法:

接口名称:数据导入 幂等性要求:必须支持幂等 幂等键:文件MD5(文件内容唯一标识) 幂等逻辑: 1. 上传文件后,计算文件MD5 2. 导入前先查询文件MD5是否已导入 3. 如已导入,返回"该文件已导入"和导入记录(导入时间、导入条数等) 4. 如未导入,执行导入,保存文件MD5和导入记录到数据库 幂等键有效期:永久(文件内容不变,MD5不变) 重复导入返回: { "code": 200, "message": "该文件已导入", "data": { "isDuplicate": true, "importedAt": "2025-01-01 10:00:00", "importedCount": 100 } }

扩展场景:

  • 批量导入:相同文件MD5只导入一次
  • 增量导入:相同文件MD5+导入时间只导入一次
  • 数据更新:相同文件MD5+业务ID只更新一次

三、幂等键设计原则

幂等键的设计直接影响幂等性的有效性,需要遵循以下原则:

设计原则

  1. 唯一性:幂等键必须能唯一标识一次业务操作
  2. 稳定性:相同业务操作,幂等键必须相同
  3. 可读性:幂等键最好包含业务信息,便于排查问题
  4. 长度限制:幂等键长度要适中,避免过长影响性能
场景幂等键设计生成方式有效期存储位置
创建订单userId_cartId_uuid前端生成UUID24小时Redis
支付扣款订单号系统生成永久数据库
发送短信手机号_模板ID_业务ID系统生成1分钟Redis
库存扣减订单号(防重复)+ 版本号(防并发)系统生成订单生命周期Redis + 数据库
数据导入文件MD5系统计算永久数据库

常见错误

  • 错误1:使用时间戳作为幂等键(时间戳会变化,无法保证唯一性)
    ❌ 错误:幂等键 = userId + timestamp ✅ 正确:幂等键 = userId + cartId + uuid
  • 错误2:幂等键包含随机数(随机数每次不同,无法保证幂等)
    ❌ 错误:幂等键 = userId + random() ✅ 正确:幂等键 = userId + cartId + uuid(前端生成,重试时保持不变)
  • 错误3:幂等键有效期设置过长(占用存储空间)
    ❌ 错误:发送短信幂等键有效期24小时 ✅ 正确:发送短信幂等键有效期1分钟(业务需求决定)
  • 错误4:幂等键只存Redis,不存数据库(Redis故障时丢失)
    ❌ 错误:支付幂等键只存Redis ✅ 正确:支付幂等键存数据库(永久有效,不能丢失)

四、PRD模板与最佳实践

标准PRD模板

接口名称:创建订单 接口路径:POST /api/orders 幂等性要求:必须支持幂等 【幂等键设计】 幂等键:userId_cartId_uuid 生成方式:前端生成UUID,重试时保持不变 组成规则:{userId}_{cartId}_{uuid} 示例:12345_67890_a1b2c3d4-e5f6-7890-abcd-ef1234567890 【幂等逻辑】 1. 收到请求时,先查询幂等键是否存在(Redis) 2. 如存在,返回原订单号和订单状态,HTTP 200,isDuplicate=true 3. 如不存在,执行业务逻辑(创建订单) 4. 业务逻辑成功后,保存幂等键到Redis(有效期24小时) 5. 返回新订单号,isDuplicate=false 【幂等键存储】 存储位置:Redis 有效期:24小时 Key格式:idempotency:order:{userId}_{cartId}_{uuid} Value格式:{"orderId": "ORD001", "status": "created", "createdAt": "2025-01-01 10:00:00"} 【重复请求返回】 HTTP状态码:200 响应体: { "code": 200, "message": "订单已创建", "data": { "orderId": "ORD20250101001", "isDuplicate": true, "status": "created", "createdAt": "2025-01-01 10:00:00" } } 【异常处理】 1. Redis故障:降级到数据库查询(性能较低,但保证可用性) 2. 幂等键冲突:返回"请求处理中,请稍候"(防止并发问题) 3. 业务逻辑失败:不保存幂等键,允许重试

最佳实践

  1. 前端生成幂等键:前端生成UUID,重试时保持不变,这样前端重试时可以带上相同的幂等键
  2. 幂等键包含业务信息:幂等键最好包含业务信息(如userId、cartId),便于排查问题
  3. 幂等键有效期合理:根据业务需求设置有效期,不要过长或过短
  4. 幂等键存储选择:
    • 临时性幂等键(如创建订单):存Redis,设置过期时间
    • 永久性幂等键(如支付):存数据库,永久有效
  5. 幂等键查询优化:使用Redis的SETNX命令,保证原子性
  6. 幂等键降级方案:Redis故障时,降级到数据库查询,保证可用性
  7. 幂等键监控:监控幂等键命中率,评估幂等性效果

实现检查清单

  • [ ] 幂等键设计合理(唯一性、稳定性、可读性)
  • [ ] 幂等键生成方式正确(前端生成UUID,重试时保持不变)
  • [ ] 幂等键存储位置正确(临时性存Redis,永久性存数据库)
  • [ ] 幂等键有效期合理(根据业务需求设置)
  • [ ] 幂等逻辑实现正确(先查询,存在则返回,不存在则执行)
  • [ ] 重复请求返回正确(HTTP 200,isDuplicate=true)
  • [ ] 异常处理完善(Redis故障降级、幂等键冲突处理)
  • [ ] 前端防抖实现(按钮防抖,提交后禁用)
  • [ ] 日志记录完善(记录幂等键、请求参数、处理结果)
  • [ ] 监控告警配置(监控幂等键命中率、异常情况)

五、常见错误与陷阱

错误1:幂等键设计不合理

问题:使用时间戳作为幂等键,导致每次请求幂等键都不同,无法保证幂等性。

❌ 错误示例: 幂等键 = userId + timestamp 问题:时间戳每次不同,无法保证幂等性 ✅ 正确示例: 幂等键 = userId + cartId + uuid(前端生成,重试时保持不变)

错误2:幂等键只存Redis,不存数据库

问题:Redis故障时,幂等键丢失,导致重复操作。

❌ 错误示例: 支付幂等键只存Redis 问题:Redis故障时,幂等键丢失,可能导致重复扣款 ✅ 正确示例: 支付幂等键存数据库(永久有效,不能丢失) 临时性幂等键(如创建订单)可以只存Redis

错误3:幂等键有效期设置不合理

问题:幂等键有效期过长,占用存储空间;有效期过短,导致正常重试失败。

❌ 错误示例: 发送短信幂等键有效期24小时(过长,占用空间) 创建订单幂等键有效期1分钟(过短,正常重试可能失败) ✅ 正确示例: 发送短信幂等键有效期1分钟(业务需求决定) 创建订单幂等键有效期24小时(业务需求决定)

错误4:幂等逻辑实现不正确

问题:先执行业务逻辑,再保存幂等键,导致并发时重复执行。

❌ 错误示例: 1. 执行业务逻辑(创建订单) 2. 保存幂等键 问题:并发时,两个请求都执行了业务逻辑 ✅ 正确示例: 1. 查询幂等键是否存在 2. 如存在,返回原结果 3. 如不存在,执行业务逻辑,保存幂等键(使用SETNX保证原子性)

错误5:重复请求返回错误状态码

问题:重复请求返回4xx错误,导致前端认为请求失败,继续重试。

❌ 错误示例: 重复请求返回 HTTP 400 Bad Request 问题:前端认为请求失败,继续重试,导致无限循环 ✅ 正确示例: 重复请求返回 HTTP 200 OK,isDuplicate=true 前端根据isDuplicate判断是否重复,不再重试

错误6:前端没有防抖处理

问题:用户快速点击按钮,发送多个请求,虽然后端做了幂等,但浪费资源。

❌ 错误示例: 前端没有防抖,用户快速点击发送多个请求 问题:虽然后端做了幂等,但浪费资源,增加服务器压力 ✅ 正确示例: 前端按钮防抖(500ms),提交后立即禁用按钮,显示"提交中..." 减少不必要的请求,提升用户体验

六、FAQ

Q1:幂等键由前端生成还是后端生成?

答:建议前端生成(如UUID),这样前端重试时可以带上相同的幂等键。如果后端生成,前端重试时无法获取相同的幂等键,导致幂等性失效。

前端生成的优势:

  • 前端重试时可以带上相同的幂等键
  • 减少后端压力(不需要生成幂等键)
  • 更好的用户体验(前端可以控制重试逻辑)

Q2:幂等键存在哪里?

答:根据业务需求选择存储位置:

  • 临时性幂等键(如创建订单):存Redis,设置过期时间,性能高
  • 永久性幂等键(如支付):存数据库,永久有效,不能丢失
  • 混合方案:Redis + 数据库,Redis作为缓存,数据库作为持久化

Q3:所有接口都要做幂等吗?

答:不是。需要做幂等的接口:

  • 创建操作:创建订单、创建用户、创建商品等
  • 修改操作:支付扣款、库存扣减、积分扣减等
  • 发送操作:发送短信、发送邮件、发送推送等

不需要做幂等的接口:

  • 查询操作:查询订单、查询用户、查询商品等(天然幂等)
  • 删除操作:删除订单、删除用户等(天然幂等,删除已删除的记录结果还是删除)

Q4:幂等键和分布式锁有什么区别?

答:幂等键和分布式锁是两种不同的机制:

  • 幂等键:防止重复操作,允许并发请求,但只执行一次
  • 分布式锁:防止并发操作,同一时间只允许一个请求执行

使用场景:

  • 幂等键:适合创建订单、支付扣款等场景(允许并发,但只执行一次)
  • 分布式锁:适合库存扣减、秒杀等场景(不允许并发,必须串行执行)

Q5:幂等键冲突了怎么办?

答:幂等键冲突是正常情况,表示重复请求,应该返回原结果,而不是报错。如果使用Redis的SETNX命令,可以保证原子性,避免并发问题。

Q6:Redis故障时,幂等性怎么保证?

答:需要降级方案:

  • 临时性幂等键:降级到数据库查询(性能较低,但保证可用性)
  • 永久性幂等键:直接存数据库,不依赖Redis
  • 混合方案:Redis + 数据库,Redis作为缓存,数据库作为持久化

Q7:幂等键有效期怎么设置?

答:根据业务需求设置:

  • 创建订单:24小时(订单创建后24小时内不会重复创建)
  • 发送短信:1分钟(1分钟内不会重复发送)
  • 支付扣款:永久(支付状态不可逆)
  • 数据导入:永久(文件内容不变,MD5不变)

Q8:如何监控幂等性效果?

答:监控以下指标:

  • 幂等键命中率:重复请求占比,评估幂等性效果
  • 幂等键存储量:Redis/数据库中的幂等键数量,评估存储压力
  • 幂等键查询耗时:查询幂等键的耗时,评估性能影响
  • 重复操作次数:没有幂等键的重复操作次数,评估业务影响

工具入口

生成幂等性设计思维导图

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

并发冲突怎么处理:乐观锁/悲观锁/最终一致性(附PRD写法)

前言 并发冲突是多用户系统的常见问题&#xff1a;两人同时编辑同一条数据&#xff0c;后提交的覆盖了先提交的。这篇给你3种并发控制策略的完整对比和PRD写法。 一、3种并发控制策略对比 策略原理适用场景优点缺点乐观锁提交时检查版本号读多写少性能高&#xff0c;无锁等待…

作者头像 李华
网站建设 2026/4/23 13:14:32

HTML动态图表生成:Miniconda-Python3.10集成Plotly可视化库

HTML动态图表生成&#xff1a;Miniconda-Python3.10集成Plotly可视化库 在数据密集型项目中&#xff0c;最令人头疼的往往不是算法本身&#xff0c;而是环境配置和成果展示——你是否也经历过“代码跑通了&#xff0c;但同事打不开图表”“换台机器就报错”“静态图表达不清趋势…

作者头像 李华
网站建设 2026/4/23 13:17:32

锂电池均衡之主动均衡Simulink仿真探索

锂电池均衡 主动均衡 simulink仿真 耦合电感类 耦合电感四节电池 耦合电感加开关电容的六节电池在锂电池应用系统中&#xff0c;电池均衡技术对于提升电池组性能、延长使用寿命至关重要。主动均衡作为一种更为高效的均衡方式&#xff0c;近年来备受关注。今天咱就聊聊基于耦合电…

作者头像 李华
网站建设 2026/4/16 0:04:42

Linux下PyTorch安装教程GPU支持:基于Miniconda-Python3.10镜像快速部署

Linux下PyTorch安装教程GPU支持&#xff1a;基于Miniconda-Python3.10镜像快速部署 在人工智能项目开发中&#xff0c;最让人头疼的往往不是模型设计本身&#xff0c;而是环境搭建——明明代码没问题&#xff0c;却因为PyTorch版本和CUDA不匹配、Python依赖冲突导致“在我机器…

作者头像 李华
网站建设 2026/4/21 7:39:49

CodeSys——TCP客户端通讯

本示例工程以汇川AC712控制器为例&#xff0c;打开“InoProShop”&#xff0c;新建工程。PLC_PRG代码&#xff1a;PROGRAM POU VARTCP_Client_0: TCP_Client;m1: STRING : 200.200.200.90;m0: BOOL;port: UINT : 9004;hClint: __XWORD;TCP_Send_0: TCP_Send;m2: BOOL;uiSize: U…

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

Pyenv install python3.10失败?切换Miniconda-Python3.10绕过编译难题

Pyenv install python3.10失败&#xff1f;切换Miniconda-Python3.10绕过编译难题 你有没有在深夜调试环境时&#xff0c;被 pyenv install 3.10 卡住几个小时&#xff1f;明明只是想跑个简单的机器学习脚本&#xff0c;却因为 OpenSSL 缺失、zlib 编译失败或 GCC 版本不兼容而…

作者头像 李华