想象你站在写字楼下的自动售货机前扫码买水。手机弹出“支付成功”,但货道卡住了,水没掉出来,网络也超时了。你不确定刚才有没有扣款成功,于是又扫了一次码。这时后台系统收到了两笔“订单号完全相同”的请求——真正的麻烦来了:如果系统傻乎乎地按订单执行两次扣款和出货,你就会被扣两次钱,还可能拿到两瓶水(或者一瓶水都没有,却扣了两次钱)。 为了避免这种惨剧,系统需要一套“防重复执行”的机制。这套机制在分布式系统里有个专门的名字——幂等性设计(Idempotency Design)。所谓幂等,就是“同一操作重复执行一次和一万次,最终结果都一样”。它像一只尽职的小管家:收到请求后先翻查手里的账本,如果这单已经办过了,就直接把上次的结果告诉你,绝不再办第二次。 接下来,我们会用小管家、出货记录册、订单号和防抖齿轮这些比喻,把幂等性设计的四大方案——唯一请求 ID、去重表、乐观锁、状态机——和实战选型一次讲透。
掌握幂等性设计,是分布式系统容错的必修课。它直接决定你的接口在“网络重试、消息重投、用户狂点”三重摧残下,能否守住“不重复、不超扣、不乱账”的生命线。架构师和高并发后端工程师都要用它来做设计决策与故障排查。
可视化演示
动态展示幂等原理、唯一ID去重流程、四种实现策略与状态机约束机制。
本节配有交互动效(进入视口后自动加载,约 3KB)。
幂等基础
自动售货机的小管家是个非常较真的人。他每天收钱出货,但有一条铁律:同一张订单号,只出一次货。你可能会问:如果客人明明被扣了两次款,凭什么只出一次货?小管家的回答是:因为这是同一笔订单的重复通知,不是新生意。他要做的,是把多出来的那次扣款退回去,而不是再搬一瓶水。这就是幂等性的生活原型——同一件事,做一次和做多次,世界不该因此改变。
在技术世界里,我们把“做一次和做多次结果都一样”的操作称为幂等操作。数学上可以写成 f(f(x)) = f(x):不管对同一输入执行多少次操作函数 f,最终的结果都等于只执行一次。注意,幂等不等于“只执行一次”,而是“重复执行的副作用为零”。系统无法阻止请求重复到达,但它可以保证重复到达后不再重复生效。
为什么分布式系统特别需要幂等?因为分布式环境里没有可靠的“一次且仅一次”投递。网络会超时,客户端会重试,消息队列会重投,用户会手抖连点。任何一环都不是百分之百可靠,而幂等性是让整个链路在不确定性中保持业务一致性的“安全气囊”。它和“防重”也有区别:防重是并发场景下的去抖(比如 1 秒内只让一个请求通过),而幂等要对付的,是已经成功执行过后再次到达的“幽灵请求”。
回到小管家的账本,他判断“这单是不是重复”靠的不是记忆,而是规则:订单号相同,就是同一件事。第一笔真正出货,账本记下;后续相同的订单号进来,直接调出上次的结果回复客人——“您的水已经出过了,在取货口呢”。数学上等价于 f(f(x)) = f(x):无论同样的指令来多少次,货只出一瓶,结果一直是那瓶水。
- 操作函数 f代表任意业务动作,例如“扣款十元”或“发货一件”。输入 x 是操作的参数,输出是执行结果。
- 第一次执行的结果把第一次执行记作 y,表示业务操作产生了一笔真实变化。
- 对结果再执行一次把 y 当作输入再次调用同一操作,于是得到 f(f(x))。
- 幂等成立条件第二次操作没有产生新的副作用,最终结果仍等于首次结果。重复无数次,等于一次。
唯一请求 ID
小管家最怕两单生意长得一模一样。假如两个客人同时买了一款同样的水,付的钱也一样,光看内容根本分不清谁是谁。还好每张支付回执单上都有一个“订单号”——这是这笔交易从生到死都不变的身份证。凭借这串数字,小管家才能知道:哦,这单号我见过,这是刚才那笔的重复通知。
在系统里,这个身份证就是唯一请求 ID(Request ID / Idempotency Key)。它的核心作用是让所有下游节点都能识别“这是哪一笔操作”。如果请求里没有这个 ID,服务端就只能靠“参数是否完全一致”来猜测,面对参数相同的两笔合法请求就会误伤——所以唯一请求 ID 是幂等设计的基石。
唯一请求 ID 的生成要满足两个要求:全局唯一、尽量有序。业界常用方案有 UUID、数据库自增 ID,以及高并发下常见的雪花算法(Snowflake)。雪花算法把 ID 拆成三部分:时间戳 + 机器编号 + 序号,既能保证唯一,又能让 ID 大体递增,有利于数据库索引。生成后,ID 由客户端在发起请求时创建,放在请求头或消息体中一路透传到服务端;服务端拿到后,把它作为幂等键存入数据库或 Redis,配合后续的去重表、状态机一起使用。
有了订单号这枚“身份证”,小管家的账本才有意义。他不需要去比对两位客人的长相、买的水是不是同一个牌子——只看订单号,就能认出谁是新客户、谁又是同一笔订单换了身衣服又来敲门。这就是唯一请求 ID 在整个幂等体系里的位置:它是所有后续方案的前置基础。
- 时间戳 timestamp记录 ID 生成时刻,让 ID 随时间递增,避免时间维度上的碰撞。
- 机器编号 machine_id标识生成 ID 的是哪台机器,防止不同服务实例在同一时刻生成相同 ID。
- 序列号 sequence同一毫秒内在单台机器上从 0 递增的序号,保证超高并发下也不重复。
- 拼成完整 ID三段按位或按字符串拼接,形成一个紧凑的全局唯一标识。
去重表方案
小管家管自己叫“记账型选手”。他每天上岗第一件事,就是把出货记录册摊开:每出一瓶水,就在册子上工工整整写一行“订单号 + 已出货 + 时间”。下一次再有订单号过来,他先翻册子——查到了就直接说“这单出过了”,查不到才动手出货。这本册子,就是去重表。
去重表方案是最直观的幂等实现:单独建一张“幂等记录表”,主键或唯一键就是请求 ID。每次请求到达时,先查这张表;如果已存在记录,说明是重复请求,直接返回首次处理的结果;如果不存在,则执行业务逻辑,同时向去重表插入一条记录。图省事的话也可以把幂等键直接挂在业务表上作为唯一索引,比如“订单号唯一”,让数据库来强制去重。
去重表最关键的机制是“插入时的唯一约束兜底”。为什么不能只做“先查再插”?因为在高并发下两个相同的请求可能同时通过查询,都发现“查无此单”,然后都去执行业务——重复就发生了。解决办法是给请求 ID 建唯一索引:两个请求同时插入时,数据库保证只有一个能成功,另一个插入直接报唯一键冲突,冲突方把这次操作视为“已被别人处理”,不再执行业务。这样即使业务层竞争失败了,数据层也绝不放水。去重表的缺点是记录会不断增长,所以需要定期清理(例如只保留最近 30 天),或者按日期分表归档。
小管家的出货记录册有一个死规矩:册子上每一行订单号都只能用一次,谁也别想涂改。正因为册子背后有“一条订单号只能写一行”的铁律,他才敢在客人催单时说:“这单早出过了,您去取货口看看。”——账本不乱,生意就不乱。
- 集合 S表示去重表中已处理成功的请求 ID 集合。
- id 属于 S请求已在册,说明是重复请求,直接拒绝重复执行并返回旧结果。
- id 不属于 S首次到达,执行业务逻辑并向 S 插入该 ID。
- 唯一索引兜底即使判断出现竞争,数据库的唯一索引也会强制插入只成功一个。
乐观锁机制
后来售货机生意太好,一台机器配了两位小管家。麻烦来了:两位管家可能同时翻开记录册,同时看到“订单号 10086 还没有出货记录”,然后同时搬起饮料往出货口塞——两瓶水卡在一起,货道直接崩了。小管家的对策是在记录册上盖“版本号”印章:谁要改这一行,先记下当前版本号,落笔时如果版本号没变才允许写;如果发现别人已经改过了,这一笔就算白抢。
这就是乐观锁(Optimistic Lock)的思想。它不锁数据,而是靠“版本号比对”保证并发改写的安全性。业务表里加一个 version 字段,每次更新时带着当初读到的 version 作为条件:UPDATE … SET version = version + 1 WHERE version = 旧版本。如果更新的影响行数是 1,说明这期间没有人改过,操作成功;如果影响行数是 0,说明 version 已经变了,操作失败,需要重读后重试或放弃。
乐观锁非常适合“并发竞争少、冲突频率低”的场景,例如防止订单金额被重复扣减、库存被超卖。它与去重表的区别在于:去重表解决的是“同一请求来了两次”,乐观锁解决的是“不同请求同时改同一行数据”。二者经常组合使用:去重表挡住重复请求,乐观锁挡住并发竞争。需要注意,乐观锁不适合写冲突极高的热点数据——如果同一把库存每秒被抢 10 万次,乐观锁会大量失败重试,反而拖垮系统,这时应该换用分布式锁或队列化。
两位管家后来养成了好习惯:动手改记录册之前,先看一眼右上角的“版本号”。如果还是自己刚才记下的那个数字,就放心下笔;如果已经被人加过一横,就乖乖合上册子退一步。这份“核对再落笔”的默契,让两个管家能同时在店里忙碌,而账本永远不会乱。
- 表 t 与主键 k指定要更新的业务记录,k = id 精确定位到这一行。
- 条件 v = v_0v_0 是读取时拿到的旧版本号,只有版本没变才允许更新。
- 更新版本号 v+1每次成功修改,版本号递增一步,让后来者发现版本已变。
- 影响行数判定等于 1 表示抢写成功;等于 0 表示版本已过期,禁止覆盖他人结果。
状态机方案
小管家的出货记录册越写越厚,他发现光记“已出货”不够。比如客人付款后网络断了几秒,他这边已经扣了款、正在出货,这时订单来了,他该怎么说?总不能翻遍册子说“查无此单”然后再扣一次款。于是他把册子升级成了“状态条”:每笔订单从生到死,必须依次经历「待出货 → 出货中 → 已出货」三个阶段。
这个思路在系统设计中叫状态机(State Machine):把业务对象建模成一组有限状态,只有在规定的顺序里才能从一个状态跳到另一个状态。比如订单的状态依次是“待支付 → 已支付 → 已发货 → 已完成”,任何一步都不能跳级或倒流。重复请求到达时,状态机只需检查当前状态:如果订单已经在“已完成”这种终态,任何后续事件都不会改变它。
状态机天然具备幂等性,因为它的核心是“状态决定行为,而不是次数决定行为”。比如支付回调进来时先查订单状态:如果订单是“待支付”,就执行入账并把状态推进到“已支付”;如果订单已经是“已支付”或“已完成”,说明这笔回调是重复消息,直接返回成功,不再动账。终态尤其重要——终态一旦达到,必须拒绝一切流转,这样哪怕同一个事件被重投一万次,业务也纹丝不动。状态机的实现可以是数据库里的状态字段加校验逻辑,也可以引入专门的状态机框架。
有了状态条,小管家面对客户的连环追问再也不慌了。“出货中?稍等,正在掉货。”“已出货?去取货口拿吧。”最妙的是一旦订单盖上了“已出货”的终态章,哪怕客户把订单号报上一百遍,他也只会指着取货口说同一句话。状态机的魔法就在这:答案由状态决定,与询问次数无关。
- 状态转移函数 δ给定当前状态 s 与到达事件 e,得到新状态 s'。
- 非法事件被拒绝如果事件 e 不是从状态 s 出发的合法动作,转移可以被拦截,确保状态不乱跳。
- 终态自循环终态 s_term 收到任何事件后,结果仍是它自己,这就是重复请求落空的原因。
- 幂等性的保证判断依据是“当前状态是什么”,而不是“事件来了多少次”。
策略选型
同样是售货机,摆在早晚高峰的地铁站和摆在景区门口,脾气完全不同。地铁站的机器每分钟被扫几百次,顾客排队,网络经常抖动,重复回调像下雨一样密——小管家必须用最轻快的手段挡下大部分重复;景区门口的机器一天开不了几单,偶尔来一单重试,小管家有充分时间慢慢翻册子。
这就是幂等方案的选型问题:没有银弹,只有取舍。核心判断维度有三个:重复请求量有多大、写冲突有多激烈、能不能接受额外的存储成本。网络重试场景下,因为系统层面无法阻止重试,通常用“唯一请求 ID + 去重表”来兜底;消息队列场景下,很多 MQ 只能保证 at-least-once(至少一次)投递,消费端必须靠状态机或去重表自行去重;高并发场景下,要把压力最重的那条路用乐观锁和状态机顶住,把去重表的查询尽量前置到缓存。
组合运用是生产环境的常态。一个典型的订单接口会这样分层:第一层,前端和网关携带唯一请求 ID;第二层,Redis 缓存快速判断“这个 ID 最近是否见过”,挡住 99% 的重复请求;第三层,数据库去重表用唯一索引做最终裁决;第四层,订单表用版本号乐观锁防止两个不同请求同时改同一订单;第五层,订单状态机保证终态后再来的任何消息都进不了业务逻辑。层层设防,每一层都能独立兜底。另外,在引入“失败重试 n 次”策略前,一定要确认操作是幂等的——否则重试越多,账错得越离谱。
地铁站的小管家把防重复的活儿分给了三道工序:门口快速看一眼订单号(缓存)、柜台核销记录册(去重表)、出货口核对状态条(状态机)。景区的小管家则只需要一本册子慢慢翻。选型不是找最贵的兵器,而是摸清自己站台的客流强度,再决定把防线铺在第几层。
- 单次失败概率 p一次请求因超时、宕机等原因失败的概率。
- n 次全部失败的概率假设每次失败相互独立,n 次都失败的概率是各次失败概率的连乘。
- 至少一次成功的概率用 1 减去“全部失败”的概率,就得到重试 n 次后至少成功一次的概率。
- 幂等让重试放飞正因为操作幂等,重试不会带来副作用,这个成功率才真正属于你。
实践复盘
月底,售货机厂商来巡查。老板撂下一摞投诉单:“这个月有 3 个客人说被扣了两次款,还有 5 个说水出了但没扣款。小管家,你的册子是怎么记的?”面对事故,小管家不慌,他有固定的排查三步:先找出这笔订单的订单号,再查记录册上有没有登记,最后看订单当前处于哪个状态。证据链一旦串起来,问题出在哪个环节一目了然。
生产环境的幂等故障排查,遵循同样的三步走:第一步确认请求是否携带了唯一请求 ID,以及这个 ID 在整条链路中有没有被透传;第二步查幂等记录(去重表、Redis 缓存)里有没有对应条目,检查插入是否命中唯一索引;第三步查业务对象的状态,看它停在了哪一步。顺序很重要——先确认“是不是同一笔”,再判断“有没有被执行过”,最后看“执行到什么程度”。
幂等性设计上线时就要配套监控告警:去重表命中率(重复请求占比)、唯一索引冲突次数、状态机非法流转次数、幂等拦截率的变化曲线。用幂等率 R 衡量有效性。一个典型的生产案例复盘:某支付系统在网关升级后,部分回调丢失了原始请求 ID,服务端用“子订单号 + 时间戳”重新拼了一个不够唯一的幂等键,导致同一笔订单产生了多条记录,重复入账——最后通过固定幂等键生成规则、增加状态机终态校验才彻底修复。
小管家把三个月的记录册摊在会议室桌上,用订单号做索引,一条一条核对状态,最后定位到问题:上周更换新记录册时,有一页纸没印版本号栏。他连夜补上这一栏,并给册子加了“终态红印”。厂商老板听完点了点头:“有订单号可查、有记录可对、有状态可验——这套账本,才是售货机真正值钱的东西。”
- N_重复 理论重复请求数根据日志关联、MQ 重投统计等手段识别出的重复请求总量。
- N_拦截 被拦截的请求数去重表、状态机等真正挡下并返回旧结果的请求数。
- 拦截率 R两者相除再乘 100%,量化幂等机制的有效性。
- 目标值健康系统的 R 应长期维持在 100%;一旦跌落,立即触发告警启动排查。
AI 应用场景
支付回调幂等
阶段:分布式事务与接口容错设计
解决的问题:微信、支付宝等第三方支付平台会因网络原因重复推送同一笔支付结果。传统做法是“收到回调直接改订单状态”,这会导致一笔订单被重复入账。引入幂等性设计后,以商户订单号为幂等键,先去重表查询,再结合订单状态机判断,保证回调处理只生效一次。
具体案例:某电商平台每天处理百万级支付回调,通过“订单号唯一索引 + 状态机终态校验”,将重复回调拦截率提升到接近 100%,每年挽回重复入账损失数百万元。
消息队列重复消费
阶段:消息中间件与流处理容错
解决的问题:Kafka 在故障重启、消费者重平衡时可能重复投递消息,而 at-least-once 语义保证消息不丢但不保证不重。消费端直接处理会导致库存多扣、积分多发。解决方式:消费前根据消息内的业务 ID 进行幂等校验,使用状态机判断消息对应业务是否已处于终态。
具体案例:某电商订单创建后发送“扣库存”消息,消费端用订单状态机 + 乐观锁版本号双重校验,确保同一消息重复消费 100 次也不会重复扣减库存,支撑大促峰值每秒数万消息的稳定处理。
RPC 重试下的下单接口
阶段:微服务与高可用设计
解决的问题:Dubbo、Feign 等 RPC 框架在调用超时后会触发重试,非幂等接口在重试下会重复建单。方案是客户端生成全局唯一请求 ID 放入 RPC 请求头,服务端用分布式缓存防重 + 数据库唯一索引兜底,从源头阻断重复下单。
具体案例:某 O2O 平台在下单服务中接入幂等键与去重表,配合状态机,将重复订单率从 1.2% 降至接近 0,同时接口可用性提升到 99.99%。
Python 演示
import threadingimport timeimport uuidfrom datetime import datetime class IdempotentProcessor: """幂等处理器:相同 request_id 只执行一次业务""" def __init__(self): self._store = {} self._lock = threading.Lock() def process(self, request_id, business_func): with self._lock: if request_id in self._store: return self._store[request_id], False # 命中幂等 result = business_func() # 真正执行一次 self._store[request_id] = result return result, True def main(): processor = IdempotentProcessor() call_count = [] def deduct_balance(): """模拟扣款业务:真实环境里这里是改库操作""" time.sleep(0.05) call_count.append(1) return "扣款成功" request_id = str(uuid.uuid4()) # 两个线程同时用同一个 request_id 发起请求,模拟网络重试 threads = [ threading.Thread(target=lambda: processor.process(request_id, deduct_balance)), threading.Thread(target=lambda: processor.process(request_id, deduct_balance)), ] for t in threads: t.start() for t in threads: t.join() print(f"实际执行业务次数: {len(call_count)} (期望 1)") print(f"幂等存储记录数: {len(processor._store)} (期望 1)") print(f"当前时间: {datetime.now().strftime('%H:%M:%S')}") if __name__ == "__main__": main()
self._store = {}用字典模拟去重表,键是请求 ID,值是首次处理结果。if request_id in self._store:查重逻辑:命中说明请求已处理过,直接返回旧结果,不再执行业务。result = business_func()只有首次到达的请求才会真正调用业务逻辑,这份“第一次”是被幂等机制保护起来的。self._store[request_id] = result处理完后把请求 ID 记入“去重表”,后面的重复请求就能查到了。with self._lock:加锁避免两个线程同时查重都判断为“未处理”,从而造成重复执行。
常见误区
误区我觉得幂等就是防止用户重复点击,只要前端把按钮在提交后置灰、加个 loading,就不会有重复请求了。
正解防重只是“并发瞬间的去抖”,它挡不住网络层超时重试、MQ 重投这类来源完全不受前端控制的重复请求。幂等是服务端的底线能力:无论重复请求来自哪里、间隔多久,系统都能识别并保证只生效一次。前端置灰只是礼貌,服务端幂等才是契约。
为何常见:日常开发里最先遇到的重复请求就是“用户手抖连点”,前端很容易解决;而网络重试、消息重投属于基础设施行为,看不到摸不着,容易忽略。
误区两个请求参数一模一样,那肯定是重复请求,直接拦截第二个就行。
正解参数相同不代表业务相同。比如“给同一个用户充值 10 元”,用户完全可能先后发起两次合法请求,每次都该生效。幂等的依据必须是业务语义上的唯一键,比如订单号、支付流水号,而不是参数的完全相等。用参数做幂等键会把合法请求误杀。
为何常见:在简单接口测试里,重复请求确实都长一个样;但真实业务里“参数相同”和“事件重复”是两回事,初学者很容易把表象当成本质。
误区去重表插入遇到唯一键冲突抛异常,我就直接把这个异常返回给前端,让他们重试。
正解唯一索引冲突恰恰是“已经有人处理过了”的信号,应该把这次冲突翻译为“幂等命中”,然后返回首次处理的结果,而不是当成错误抛给上游。处理冲突的标准姿势:先查去重表拿结果再决定返回什么;如果担心并发窗口,可以用“有则查、无则插”的原子操作。
为何常见:唯一索引冲突是个数据库异常,直觉上会当成故障处理;但在这个场景里它实际上是业务逻辑的一部分,需要换个角度看。
误区既然幂等这么重要,我把唯一请求 ID、去重表、乐观锁、状态机全给所有接口安排上,肯定万无一失。
正解过度设计本身就是一种风险:为纯查询接口加去重表会白白增加每次请求的写放大和高延迟;为低频写接口上乐观锁,冲突率接近零,收益甚微。正确做法是按接口分级:核心交易链路才需要层层设防,普通读接口只需保证天然幂等即可。
为何常见:学过一门技术后容易“手里拿着锤子看什么都像钉子”,希望把所有方案堆上去求安全感,却忽略了成本和复杂度。
误区订单只要到了“已完成”状态不继续变,状态机就完工了,中间的跳转不用管。
正解状态机必须约束每一跳。比如“已支付”订单就不能再回到“待支付”,“已发货”不能跳到“已支付”。如果只锁终态不锁中间态,重复回调可能把“已支付”重新置回“支付中”,或者把“已发货”又触发一次发货。终态是最后一道闸,但每一道闸门都要有名字。
为何常见:重复请求最常撞上的确实是终态,所以大家观察到的现象集中在“终态没拦”,一叶障目忽略了中间态也可能被重复事件打乱。
面试题
Q1. 对幂等性最准确的理解是?
- A.接口只允许调用一次
- B.重复执行结果与一次一致← 正确答案
- C.所有请求都必须加锁
- D.接口必须瞬间返回结果
答案:B。幂等的关键是“重复执行与单次执行结果一致”,而不是限制调用次数。A 把幂等误解为防重;C 和 D 与幂等无关。就像售货机重复收款但只出一瓶水,结果始终是那瓶水。
Q2. 唯一请求 ID 的作用是?
- A.让接口调用更快
- B.标识同一笔业务操作← 正确答案
- C.代替密码完成鉴权
- D.压缩请求数据体积
答案:B。唯一请求 ID 是业务的“身份证”,让服务端能认出同一笔操作,这是幂等设计的地基。A、D 是性能优化无关项;C 是安全范畴,与幂等无关。
Q3. 去重表方案实现幂等的关键保障是?
- A.定期清理历史数据
- B.数据库唯一索引约束← 正确答案
- C.Redis 缓存预热
- D.查询结果分页返回
答案:B。唯一索引是最终兜底:即使并发下两个请求都查不到记录,数据库也只会让一个插入成功。清理数据只是运维手段,缓存只是加速,分页与幂等无关。
Q4. 乐观锁更新时影响行数为 0,说明?
- A.更新操作成功执行
- B.版本号已被他人更新← 正确答案
- C.数据库连接断开了
- D.表结构发生变化
答案:B。影响行数为 0 意味着 WHERE 条件里 version = 旧版本没有命中,即这行数据已被别人改过。这就像两位管家抢着盖章,后到的人发现版本号已经变了,只能放弃。
Q5. 状态机保证幂等的根本是?
- A.状态字段使用枚举
- B.终态拒绝后续流转← 正确答案
- C.状态表建立唯一索引
- D.每次请求都记日志
答案:B。终态一旦到达,任何事件都无法改变它,所以重复请求即使再来一万次也落空。枚举类型只是实现细节,唯一索引是去重表的事,记日志不能防重复。
Q6. 第三方支付重复回调应优先以什么为幂等键?
- A.回调服务器 IP 地址
- B.回调数据的时间戳
- C.原始商户订单号← 正确答案
- D.回调数据包字节数
答案:C。商户订单号在业务语义上唯一,是天然的幂等键。IP 可能是负载均衡地址,时间戳可能重复,字节数更与业务无关。就像小管家认订单号,不认“谁送来的信”。
Q7. 消息队列 at-least-once 场景下,消费端幂等可用的手段有?
- A.去重表拦截重复消息← 正确答案
- B.状态机校验终态← 正确答案
- C.乐观锁更新版本号← 正确答案
- D.抛弃所有异常消息
答案:ABC。去重表、状态机、乐观锁都是成熟的幂等手段。D 会丢消息,违反可靠性原则。就像小管家面对重复通知是查册子、验状态,而不是把信扔进垃圾桶。
Q8. 高并发重复下单场景,合理的防线组合是?
- A.请求头携带唯一 ID← 正确答案
- B.服务端缓存快速查重← 正确答案
- C.数据库唯一索引兜底← 正确答案
- D.屏蔽所有重试请求
答案:ABC。唯一 ID + 缓存查重 + 唯一索引兜底是经典三层防线。D 是掩耳盗铃——重试是网络环境的客观存在,系统层面无法完全屏蔽。就像售货机不能怪客人多投币,只能提高自己防重复的本事。
Q9. 去重表记录膨胀的治理方式包括?
- A.定期清理过期记录← 正确答案
- B.按日期分表归档← 正确答案
- C.缓存留存活跃键← 正确答案
- D.永不清理保留全量
答案:ABC。定期清理、分表归档、把活跃键放缓存都合理。D 会让表无限膨胀,拖垮查询性能。就像记录册写满了要换新册子,旧册子归档到仓库,但不能永远堆在柜台上。
Q10. 生产重复入账事故,排查顺序正确做法是?
- A.查请求 ID 与幂等记录状态← 正确答案
- B.直接修改数据库回滚金额
- C.重启服务清空全部缓存
- D.通知前端修改置灰逻辑
答案:A。正确顺序是先确认幂等链路:请求 ID 是否唯一、幂等记录是否被写入、订单停在哪个状态。直接改库是破坏数据完整性的危险操作,重启服务会清掉现场,改前端置灰治标不治本。