[{"data":1,"prerenderedAt":554},["ShallowReactive",2],{"learn-concept-/zh/learn/idempotency":3},{"id":4,"title":5,"body":6,"cardImage":491,"cardImageAlt":492,"date":493,"description":494,"domain":495,"domainKey":496,"extension":497,"featured":498,"fullName":499,"interaction":500,"maturity":501,"mentalModel":502,"meta":503,"navigation":504,"neighbors":505,"ogImage":536,"path":537,"published":504,"robots":536,"seo":538,"shortName":5,"sitemap":539,"socialImage":536,"socialImageAlt":536,"sources":540,"stem":547,"tags":548,"translationKey":500,"updated":493,"__hash__":553},"learnZh/zh/learn/idempotency.md","幂等性",{"type":7,"value":8,"toc":468},"minimal",[9,12,21,24,30,34,42,64,67,70,73,83,90,105,108,112,115,121,124,130,133,137,140,146,149,176,179,182,188,191,194,199,202,205,208,212,215,218,225,229,232,236,239,324,327,330,368,372,392,395,412,415,432,436],[10,11,5],"h1",{"id":5},[13,14,15,16,20],"p",{},"Idempotency（幂等性）解决的是分布式系统里最棘手的一类失败：不是明确成功或失败，而是 ",[17,18,19],"strong",{},"unknown outcome（结果未知）","。",[13,22,23],{},"客户端发起支付；服务端已经扣款，但响应在网络中消失。客户端只看到超时。如果放弃重试，本该完成的支付可能被误认为失败；如果直接重试，用户又可能被扣两次。",[13,25,26,27],{},"幂等契约把恢复与重复分开：",[17,28,29],{},"请求可以再次尝试，但同一个业务意图不能再次产生业务效果。",[31,32,33],"h2",{"id":33},"餐厅取餐号",[13,35,36,37,41],{},"想象你把编号 ",[38,39,40],"code",{},"A-842"," 的点菜单递给厨房，却没有听见确认，于是又递了一次。",[43,44,45,49,55,58],"ul",{},[46,47,48],"li",{},"没有订单号，厨房可能做两顿饭。",[46,50,51,52,54],{},"有稳定订单号，厨房查到 ",[38,53,40],{},"，只返回原订单状态。",[46,56,57],{},"真正的新订单必须使用新编号。",[46,59,60,61,63],{},"仍用 ",[38,62,40],{}," 却换了菜品，厨房应该拒绝，而不是猜测。",[13,65,66],{},"取餐号只是身份。厨房仍然需要可靠账本，而且登记号码与接单之间不能有不安全的缝隙。",[31,68,69],{"id":69},"数学定义与系统定义",[13,71,72],{},"数学里的幂等函数满足：",[74,75,81],"pre",{"className":76,"code":78,"language":79,"meta":80},[77],"language-text","f(f(x)) = f(x)\n","text","",[38,82,78],{"__ignoreMap":80},[13,84,85,86,89],{},"软件系统更关心语义：同一请求执行多次，其 ",[17,87,88],{},"intended effect（预期效果）"," 与执行一次相同。它不要求响应字节完全相同，也不禁止额外的日志、指标和时间戳。",[13,91,92,93,96,97,100,101,104],{},"例如，第一次 ",[38,94,95],{},"DELETE /documents/42"," 返回 ",[38,98,99],{},"204","，第二次返回 ",[38,102,103],{},"404","。响应不同，但资源最终都处于“不存在”的状态。",[31,106,107],{"id":107},"两种获得幂等性的方式",[109,110,111],"h3",{"id":111},"让操作自然幂等",[13,113,114],{},"状态设定通常会收敛：",[74,116,119],{"className":117,"code":118,"language":79,"meta":80},[77],"SET order.status = \"PAID\"     repeated → still PAID\nDELETE document 42            repeated → still absent\nPUT /profile { name: \"Li\" }   repeated → same representation\n",[38,120,118],{"__ignoreMap":80},[13,122,123],{},"相对变化通常不会：",[74,125,128],{"className":126,"code":127,"language":79,"meta":80},[77],"balance = balance + 10\ntoggle subscription\nsend welcome email\ncreate a new charge\n",[38,129,127],{"__ignoreMap":80},[13,131,132],{},"一个很实用的判断是：能否把“再变化一次”改写成“让它等于这个状态”？",[109,134,136],{"id":135},"增加-idempotency-key幂等键","增加 Idempotency Key（幂等键）",[13,138,139],{},"创建支付、预约或云资源不能总被改写成简单赋值。这时客户端为一个业务意图生成稳定 key，并在每次重试中复用。",[74,141,144],{"className":142,"code":143,"language":79,"meta":80},[77],"Idempotency-Key: order-842-payment\nPOST /payments { amount: 42, currency: \"CAD\" }\n",[38,145,143],{"__ignoreMap":80},[13,147,148],{},"完整协议通常要做到：",[150,151,152,155,158,161,164,167,170,173],"ol",{},[46,153,154],{},"一个意图一个 key；新意图使用新 key；",[46,156,157],{},"按调用者、账户和操作限定 key 的作用域；",[46,159,160],{},"用唯一约束或事务原子占位；",[46,162,163],{},"绑定请求指纹，参数变化时拒绝；",[46,165,166],{},"记录处理中、完成或失败状态；",[46,168,169],{},"向已完成的重复请求重放原结果或语义等价结果；",[46,171,172],{},"定义并发重复请求看到什么；",[46,174,175],{},"声明 TTL（Time to Live，存活时间），说明 key 何时可能被当作新请求。",[31,177,178],{"id":178},"原子缝隙才是真正危险的地方",[13,180,181],{},"下面的实现存在竞态：",[74,183,186],{"className":184,"code":185,"language":79,"meta":80},[77],"if key does not exist:\n  charge_card()\n  save(key, result)\n",[38,187,185],{"__ignoreMap":80},[13,189,190],{},"两个并发请求可能同时看到 key 不存在，于是都扣款；服务也可能在扣款后、保存记录前崩溃，让重试无法与新请求区分。",[13,192,193],{},"只要可能，key 占位、业务状态变化与结果记录就应该共享一个原子边界。如果效果跨过数据库、队列、邮件或第三方支付系统，每段边界都要有自己的幂等策略，通常需要状态机、Transactional Outbox（事务发件箱）或消费者 Inbox（收件箱）。",[13,195,196],{},[17,197,198],{},"只有一个 header，没有原子状态机，只是装饰，不是保证。",[31,200,201],{"id":201},"契约必须说明的四种情况",[109,203,204],{"id":204},"已完成的重复请求",[13,206,207],{},"返回第一次记录的结果，或语义等价的当前结果；不要再次执行业务动作。",[109,209,211],{"id":210},"相同-key不同参数","相同 key，不同参数",[13,213,214],{},"必须拒绝，否则服务无法判断这是一次重试，还是一次错误的 key 复用。Stripe 与 Amazon Elastic Compute Cloud（Amazon EC2）都把参数不匹配视为错误。",[109,216,217],{"id":217},"同时到达的重复请求",[13,219,220,221,224],{},"第二个请求不能也开始产生效果。它可以等待、收到 ",[38,222,223],{},"in progress","，或收到冲突响应；API（Application Programming Interface，应用程序编程接口）契约必须做出选择。",[109,226,228],{"id":227},"已过期的-key","已过期的 key",[13,230,231],{},"幂等记录通常不会永久保存。记录清理后，同一个 key 可能再次执行。服务端承诺的保证窗口必须覆盖客户端最长的重试周期。",[31,233,235],{"id":234},"http安全不等于幂等","HTTP：安全不等于幂等",[13,237,238],{},"HTTP（Hypertext Transfer Protocol，超文本传输协议）把 safe method（安全方法）与 idempotent method（幂等方法）分开。",[240,241,242,261],"table",{},[243,244,245],"thead",{},[246,247,248,252,255,258],"tr",{},[249,250,251],"th",{},"Method",[249,253,254],{},"Safe?",[249,256,257],{},"语义上幂等？",[249,259,260],{},"含义",[262,263,264,280,295,309],"tbody",{},[246,265,266,272,275,277],{},[267,268,269],"td",{},[38,270,271],{},"GET",[267,273,274],{},"yes",[267,276,274],{},[267,278,279],{},"请求读取，不应要求状态变化。",[246,281,282,287,290,292],{},[267,283,284],{},[38,285,286],{},"PUT",[267,288,289],{},"no",[267,291,274],{},[267,293,294],{},"用给定表示替换目标，重复提交仍然收敛。",[246,296,297,302,304,306],{},[267,298,299],{},[38,300,301],{},"DELETE",[267,303,289],{},[267,305,274],{},[267,307,308],{},"会改变一次状态，但重复删除的预期效果相同。",[246,310,311,316,318,321],{},[267,312,313],{},[38,314,315],{},"POST",[267,317,289],{},[267,319,320],{},"默认 no",[267,322,323],{},"常表示“再创建一个”，需要业务层协议赋予幂等性。",[13,325,326],{},"所以，一个操作可以改变状态，同时仍然幂等。幂等不等于无害或只读。",[31,328,329],{"id":329},"看清相邻概念",[43,331,332,338,344,350,356,362],{},[46,333,334,337],{},[17,335,336],{},"Retry（重试）是恢复策略。"," 它控制 timeout、尝试上限、exponential backoff（指数退避）与 jitter（抖动）；幂等性让这些尝试不会重复产生业务效果。",[46,339,340,343],{},[17,341,342],{},"Deduplication（去重）是检测机制。"," 它可以帮助实现幂等性，但语义契约不只是丢弃重复。",[46,345,346,349],{},[17,347,348],{},"At-least-once Delivery（至少一次交付）是交付保证。"," 它可能重复投递，所以消费者需要幂等处理。",[46,351,352,355],{},[17,353,354],{},"Exactly-once（恰好一次）是更强、也常被误用的保证。"," 幂等性不阻止重复投递或执行，只让限定范围内的效果收敛得像发生一次。",[46,357,358,361],{},[17,359,360],{},"Optimistic Concurrency Control（OCC，乐观并发控制）保护陈旧写入。"," OCC 区分互相竞争的不同意图；幂等性识别同一意图的再次送达。",[46,363,364,367],{},[17,365,366],{},"Transactional Outbox（事务发件箱）关闭跨系统一致性缝隙。"," 它仍然允许重复发布，所以消费者仍需幂等。",[31,369,371],{"id":370},"它不能解决什么","它不能解决什么？",[43,373,374,377,380,383,389],{},[46,375,376],{},"不能阻止重试风暴；仍需次数上限、backoff、jitter、限流与熔断。",[46,378,379],{},"不能自动跨越数据库、队列、邮件和第三方 API。",[46,381,382],{},"不能阻止两个不同 key 同时争抢同一份库存。",[46,384,385,386,20],{},"不能替 API 决定是否缓存并重放第一次 ",[38,387,388],{},"500",[46,390,391],{},"不能创造全局 exactly-once processing（恰好一次处理）。",[31,393,394],{"id":394},"最后记住五件事",[150,396,397,400,403,406,409],{},[46,398,399],{},"幂等性回应的是结果未知：可以重试这次尝试，不能重复这个意图的效果。",[46,401,402],{},"领域允许时，优先把动作改成自然幂等的状态设定。",[46,404,405],{},"一个意图一个 key；重试复用它；新意图换新 key。",[46,407,408],{},"相同 key + 不同参数必须拒绝，并明确并发与过期行为。",[46,410,411],{},"最危险的 bug 位于副作用与幂等记录之间的非原子缝隙。",[31,413,414],{"id":414},"自测",[150,416,417,420,423,426,429],{},[46,418,419],{},"支付已经提交但响应消失时，什么证据能让重试安全？",[46,421,422],{},"你的 key 识别业务意图，还是只对 payload 做 hash？",[46,424,425],{},"相同 key 携带不同金额时会发生什么？",[46,427,428],{},"两个重复请求同时到达时，谁获得执行权？",[46,430,431],{},"key 的寿命是否覆盖客户端最长的重试周期？",[31,433,435],{"id":434},"further-reading","Further reading",[43,437,438,447,454,461],{},[46,439,440],{},[441,442,446],"a",{"href":443,"rel":444},"https://www.rfc-editor.org/rfc/rfc9110.html#section-9.2.2",[445],"nofollow","RFC 9110 · HTTP Semantics §9.2.2 Idempotent Methods",[46,448,449],{},[441,450,453],{"href":451,"rel":452},"https://docs.stripe.com/api/idempotent_requests",[445],"Stripe API · Idempotent requests",[46,455,456],{},[441,457,460],{"href":458,"rel":459},"https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/",[445],"AWS Builders' Library · Making retries safe with idempotent APIs",[46,462,463],{},[441,464,467],{"href":465,"rel":466},"https://docs.aws.amazon.com/ec2/latest/devguide/ec2-api-idempotency.html",[445],"Amazon EC2 · Ensuring idempotency in API requests",{"title":80,"searchDepth":469,"depth":469,"links":470},2,[471,472,473,478,479,485,486,487,488,489,490],{"id":33,"depth":469,"text":33},{"id":69,"depth":469,"text":69},{"id":107,"depth":469,"text":107,"children":474},[475,477],{"id":111,"depth":476,"text":111},3,{"id":135,"depth":476,"text":136},{"id":178,"depth":469,"text":178},{"id":201,"depth":469,"text":201,"children":480},[481,482,483,484],{"id":204,"depth":476,"text":204},{"id":210,"depth":476,"text":211},{"id":217,"depth":476,"text":217},{"id":227,"depth":476,"text":228},{"id":234,"depth":469,"text":235},{"id":329,"depth":469,"text":329},{"id":370,"depth":469,"text":371},{"id":394,"depth":469,"text":394},{"id":414,"depth":469,"text":414},{"id":434,"depth":469,"text":435},"/learn-img/idempotency/card-4x5.jpg","三张具有相同订单编号的纸质收据汇入一张完成收据，表示多次尝试只产生一次业务效果。","2026-07-16","让结果未知的操作可以安全重试：同一个意图可以到达多次，但只产生一次业务效果。","软件系统","software-systems","md",false,"Idempotency","idempotency","growing","同一个意图可以被重复送达；系统只让它产生一次业务效果，后续重试复用第一次的结果。",{},true,[506,511,516,521,526,531],{"name":507,"fullName":508,"category":509,"summary":510},"Retry","Retry（重试）","恢复策略","决定何时、怎样再次尝试；幂等性决定再次尝试会不会重复产生业务效果。",{"name":512,"fullName":513,"category":514,"summary":515},"Idempotency Key","Idempotency Key（幂等键）","请求身份机制","为一个业务意图命名，让系统把每次重试识别为同一次操作。",{"name":517,"fullName":518,"category":519,"summary":520},"Deduplication","Deduplication（去重）","重复检测机制","检测或压制重复，是实现幂等行为的一种技术机制。",{"name":522,"fullName":523,"category":524,"summary":525},"At-least-once Delivery","At-least-once Delivery（至少一次交付）","交付保证","消息可能被重复投递，因此消费者需要用幂等处理安全吸收重复。",{"name":527,"fullName":528,"category":529,"summary":530},"Transactional Outbox","Transactional Outbox（事务发件箱）","一致性模式","消除数据库变更与消息发布之间的不安全缝隙，同时仍要求消费者处理重复。",{"name":532,"fullName":533,"category":534,"summary":535},"Exactly-once","Exactly-once Processing（恰好一次处理）","更强保证","常被过度宣称的端到端保证；幂等性通常只让效果收敛，并不阻止重复投递或执行。",null,"/zh/learn/idempotency",{"title":5,"description":494},{"loc":537},[541,543,545,546],{"title":542,"url":443},"RFC 9110 · Idempotent Methods",{"title":544,"url":451},"Stripe · Idempotent requests",{"title":460,"url":458},{"title":467,"url":465},"zh/learn/idempotency",[549,550,551,552],"可靠性","重试","分布式系统","API 设计","X_AZ1zHbj0kITA9Q_8EGOW_XgAgV2CEPpEhWU_q5TQM",1785418435121]