2026/1/11 21:15:39
网站建设
项目流程
iframe网站后台模板,广州室内装修设计,闵行网站建设哪家好,互联网行业 英文一、什么是幂等性#xff1f;定义#xff1a;在分布式系统中#xff0c;同一个操作#xff08;如创建订单、支付扣款#xff09;被执行一次或多次#xff0c;对系统状态的影响是完全相同的。典型场景#xff1a;用户重复点击提交按钮网络超时后客户端重试消息队列重复消…一、什么是幂等性定义在分布式系统中同一个操作如创建订单、支付扣款被执行一次或多次对系统状态的影响是完全相同的。典型场景用户重复点击提交按钮网络超时后客户端重试消息队列重复消费第三方回调重复通知二、8 种幂等性设计方案系统组件交互图方案1前端防抖第一道防线适用场景所有Web/移动端应用// 前端按钮防抖如使用lodash submitOrder _.debounce(async () { await api.createOrder(data); }, 1000, { leading: true, trailing: false }); // 或提交后禁用按钮 button.disabled true;优点简单有效减少无效请求缺点无法防止绕过前端的请求如API直接调用方案2Token 令牌机制最常用流程进入提交页时后端生成唯一Token存入Redis设置合理过期时间Token返回前端提交时随请求带上后端验证Token是否存在存在删除Token继续业务不存在返回“重复提交”错误// 伪代码示例 public boolean checkToken(String token) { String key order:token: token; // 使用Redis的del命令删除成功表示第一次请求 Long result redisTemplate.delete(key); return result ! null result 1; }优点实现简单可靠性高缺点需要额外存储增加一次Redis操作方案3数据库唯一约束适用场景创建具有唯一标识的资源-- 订单表添加唯一索引 ALTER TABLE orders ADD UNIQUE INDEX uk_order_no (order_no); -- 或业务唯一组合约束 ALTER TABLE orders ADD UNIQUE INDEX uk_user_product (user_id, product_id, date);try { // 插入订单 orderDao.insert(order); } catch (DuplicateKeyException e) { // 已存在直接返回原订单 return getExistingOrder(orderNo); }优点绝对可靠数据库层保证缺点只能用于插入操作索引影响写入性能难以处理“部分字段相同”的复杂场景方案4乐观锁版本号控制适用场景更新操作如扣减库存、更新状态-- 添加version字段 UPDATE product_stock SET stock stock - 1, version version 1 WHERE product_id 1001 AND version 1;// MyBatis Plus示例 boolean success update() .setSql(stock stock - 1) .eq(product_id, productId) .eq(stock, currentStock) // 或使用版本号 .update();优点避免数据覆盖保证更新安全缺点需要设计版本字段高并发时可能更新失败方案5状态机幂等适用场景有明确状态流转的业务订单、支付等// 检查订单状态是否允许变更 public boolean processOrder(String orderId, String targetStatus) { Order order orderDao.selectById(orderId); // 状态机验证比如已支付订单不允许再次支付 if (OrderStatus.PAID.equals(order.getStatus())) { log.warn(订单已支付重复请求); return true; // 幂等返回成功 } // 只有待支付才能变更为已支付 if (!OrderStatus.UNPAID.equals(order.getStatus())) { throw new IllegalStateException(订单状态异常); } // 正常处理... orderDao.updateStatus(orderId, targetStatus); }优点符合业务逻辑自然幂等缺点需要清晰的状态设计方案6分布式锁适用场景分布式系统防止多台机器同时处理public String createOrder(OrderRequest request) { String lockKey order:lock: request.getUserId(); // 使用Redis分布式锁 String lockValue UUID.randomUUID().toString(); try { // 尝试加锁设置过期时间防止死锁 Boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS); if (!locked) { throw new BusinessException(请勿重复提交); } // 执行业务逻辑 return doCreateOrder(request); } finally { // 使用Lua脚本保证原子性解锁 String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; redisTemplate.execute(script, Collections.singletonList(lockKey), lockValue); } }优点分布式环境下强一致性缺点性能损耗实现复杂方案7去重表防重表设计思路单独建立一张防重表记录已处理的请求CREATE TABLE request_unique ( id BIGINT PRIMARY KEY AUTO_INCREMENT, biz_type VARCHAR(32) COMMENT 业务类型, unique_key VARCHAR(128) COMMENT 唯一标识, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_biz_type_key (biz_type, unique_key) );public boolean checkDuplicate(String bizType, String uniqueKey) { try { // 插入成功说明是第一次请求 duplicateDao.insert(bizType, uniqueKey); return false; } catch (DuplicateKeyException e) { // 插入失败说明已存在 return true; } }优点通用性强可追溯缺点表数据量会持续增长需定期清理方案8消息队列幂等消费适用场景异步消息处理// RocketMQ示例使用MessageId或业务Key去重 public void handleOrderMessage(Message message) { String msgId message.getMsgId(); String orderNo message.getUserProperty(orderNo); String dedupKey mq:dedup: orderNo; if (redisTemplate.opsForValue().setIfAbsent(dedupKey, 1, 24, TimeUnit.HOURS)) { // 第一次处理 processOrder(orderNo); } else { log.info(消息已处理直接确认: {}, msgId); } }优点适合异步场景保证消息只消费一次缺点需要消息中间件支持三、方案选型建议方案适用场景性能影响实现复杂度可靠性前端防抖所有Web场景无简单低Token令牌表单提交类低简单高唯一约束数据创建类中简单非常高乐观锁数据更新类低中等高状态机有状态流转业务低中等高分布式锁分布式并发控制高复杂非常高去重表通用防重中中等高消息幂等异步消息处理中中等高四、最佳实践组合对于订单提交这种核心场景建议采用多层次防护第一层前端防抖立即反馈用户第二层Token机制拦截重复请求第三层数据库唯一索引最终保障-- 订单号全局唯一 ALTER TABLE orders ADD UNIQUE INDEX uk_order_no (order_no); -- 或用户商品时间组合唯一防同一商品重复下单第四层状态机校验业务逻辑幂等// 伪代码完整的订单创建 public OrderResponse createOrder(OrderRequest request) { // 1. Token校验 if (!tokenService.checkToken(request.getToken())) { throw new DuplicateRequestException(); } // 2. 分布式锁防并发 Lock lock lockFactory.getLock(order: request.getUserId()); try { if (!lock.tryLock(3, TimeUnit.SECONDS)) { throw new ConcurrentRequestException(); } // 3. 业务唯一性校验如同一活动只能参与一次 if (activityService.hasParticipated(request.getUserId(), request.getActivityId())) { return getPreviousOrder(request); } // 4. 创建订单数据库唯一约束兜底 return orderDao.insert(request); } finally { lock.unlock(); } }五、重要注意事项幂等与防重的区别防重防止重复提交但可能返回错误幂等重复提交返回相同结果用户无感知HTTP方法幂等性GET、PUT、DELETE天然幂等POST非幂等需要特殊处理全局ID生成确保订单号等业务ID全局唯一雪花算法SnowflakeRedis原子递增数据库序列响应设计重复请求应返回什么// 首次成功 { code: 200, data: { orderNo: 202311210001 } } // 重复请求幂等返回 { code: 200, data: { orderNo: 202311210001 }, message: 订单已创建 } // 或使用特定code { code: 409, message: 订单已存在请勿重复提交 }清理策略Token、分布式锁、去重记录需要设置合理的过期时间避免存储膨胀。总结解决接口幂等性问题没有银弹需要根据业务场景选择合适方案。对于电商订单这种高价值操作建议采用Token分布式锁数据库唯一约束的组合方案既保证用户体验快速响应又确保数据一致性绝对防重。黄金法则越是核心的业务越要在靠近数据层的地方做幂等保障因为前端和网关的拦截都可能被绕过但数据库的唯一约束是最后的坚实防线。