分布式接口幂等性设计:唯一索引、Token 与分布式锁
接口幂等性解决的是“同一个请求被执行多次会不会造成重复业务效果”的问题。用户重复点击、网络重试、MQ 重复消费都可能让同一业务被重复执行。一句话概括幂等就是多次调用和一次调用的业务结果一致查询和按唯一值删除天然幂等新增、支付、转账、发券这类写操作必须额外设计幂等。GET 查询DELETE 按唯一值删除POST 新增或支付PUT 增量更新请求进入业务接口是否天然幂等直接处理需要幂等设计数据库唯一索引Token Redis分布式锁什么是幂等幂等的定义是多次调用方法或接口不会改变业务状态重复调用结果和单次调用结果一致。常见需要幂等的场景用户重复点击提交按钮。网络波动导致前端或网关重试。MQ 消息重复投递。应用超时后触发重试机制。支付、转账、下单、发券等写操作。从 RESTful 看幂等性请求方式是否幂等说明GET幂等查询操作不改变状态POST通常不幂等新增请求执行多次会插入多条数据PUT看写法绝对值更新幂等增量更新不幂等DELETE通常幂等按唯一 ID 删除多次删除结果一致比如updatet_itemsetmoney500whereid1;这是幂等的因为执行多次结果都是 500。updatet_itemsetmoneymoney500whereid1;这不是幂等的因为执行多次会一直累加。数据库唯一索引新增类业务可以通过唯一索引防重复。比如订单表有业务订单号order_no给它加唯一索引createuniqueindexuk_order_noont_order(order_no);同一个订单号重复插入时数据库会拒绝第二次插入。适合场景创建订单。创建支付流水。MQ 消费记录。发券记录。唯一索引是最硬的兜底优先级很高。Token RedisToken 方案适合提交订单、转账、支付这类“前端先申请令牌再带令牌提交”的场景。存在不存在客户端请求 token服务端生成唯一 tokentoken 存入 Redis返回 token 给客户端客户端携带 token请求业务接口Redis 中 token 是否存在删除 token执行业务重复请求直接返回关键点是验证 token 和删除 token 必须是原子操作。否则两个重复请求同时进来都可能判断 token 存在。实际项目里可以用 Redis Lua 脚本完成“判断 删除”。示例脚本localtokenKeyKEYS[1]localexistsredis.call(exists,tokenKey)ifexists1thenredis.call(del,tokenKey)return1elsereturn0endJava 侧伪代码可以这样理解LongresultredisTemplate.execute(script,List.of(submit:token:token));if(result1){// token 存在且已删除可以执行业务createOrder();}else{// token 不存在说明是重复提交或非法请求return;}为什么要用 Lua因为 Redis 执行 Lua 脚本是原子的exists和del不会被其他请求插队。这样两个重复请求同时到达时只有一个请求能删除成功并继续执行业务。分布式锁分布式锁也能控制重复执行。典型写法是用业务唯一值作为锁 keyRLocklockredissonClient.getLock(order:orderNo);booleanlockedlock.tryLock(10,TimeUnit.SECONDS);try{if(!locked){thrownewRuntimeException(重复提交);}// 执行业务}finally{if(locked){lock.unlock();}}使用分布式锁要注意锁粒度要小最好按业务单号加锁。抢不到锁要快速失败。要保证 finally 释放锁。高并发场景下性能比 Token 或唯一索引更重。方案怎么选方案适合场景优点注意点唯一索引新增、流水、消费记录简单可靠数据库兜底需要设计业务唯一键Token Redis表单提交、支付、转账性能好体验清晰校验和删除要原子分布式锁新增或修改都要串行控制范围灵活性能较低锁粒度要小面试回答模板可以这样答幂等是指多次调用接口不会改变业务状态重复调用和单次调用的结果一致。查询接口天然幂等POST 新增、支付、转账、MQ 消费这类场景需要额外设计。新增数据可以用数据库唯一索引比如订单号或消息 ID 做唯一约束。提交订单、支付等场景可以用 Token Redis第一次请求生成 token 存入 Redis第二次提交时携带 token服务端验证 token 存在后删除并执行业务不存在就认为是重复请求。新增或修改也可以用分布式锁但性能相对低要控制锁粒度。小结幂等设计的核心不是“防重复提交”一句话而是识别业务唯一性。能用唯一索引兜底就先兜底需要前置防重复就用 Token需要串行化处理再考虑分布式锁。