聊聊常用的幂等性

幂等性是系统设计中确保操作多次执行与一次执行效果相同的关键概念。本文详细解释了幂等性的定义,以及在实际系统中如何通过Dedup机制、幂等令牌和确保唯一性的策略来实现。同时,讨论了在请求重试、竞争条件和系统间交互中实现幂等性的挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是幂等(Idempotency)?简单来说,一个操作如果具有任意多次执行所产生的影响均与一次执行的影响相同,我们就称之为幂等。

这样说来,似乎很容易理解。但要知道,这样的定义,其实是一个语义范畴对行为结果的定义。如何用语法和规则去确保行为能达到这个结果,往往需要很谨慎的设计和实现。实际系统中,幂等是一个极为重要的概念。无论是在大型互联网应用还是企业级架构中,我们都见到 REST API 被越来越多的采用。而正确实现幂等,往往是 API 中最难的技术点之一。

先说为什么重要。举一个简单易懂的例子。

比如你要处理一次电商网站收款或者付款的交易。当你给微信支付发送这个付款请求后,一个顺利的场景,是没有任何错误发生,微信支付收到你的付款请求,处理所有转账,然后返回一个 HTTP 200 消息表示交易完成。

那如果发出请求后,有个请求超时,你再也没有收到关于这个请求是成功还是失败的的回执,又该如何呢?

这里就有很多种可能情况:

这个请求在到达微信支付端前就已经发生超时,微信支付从来没有收到这样的请求。

这个请求到达微信支付端,但是支付交易失败,这时发生超时,微信支付收到这样的请求,但没有处理成功。

这个请求到达微信支付端,并且支付交易成功,这时发生超时,微信支付收到这样的请求,处理成功,但是没有回执。

这个请求到达微信支付端,并且支付交易成功,并且发回回执,然而因为网络原因回执丢失,客户端超时,微信支付收到这样的请求,处理成功,发出回执,但是客户没有收到。

很直观的一个想法,也是现实中用户最常见的做法,是重新提交一次支付请求。但是这样就有一个潜在的问题:请求超时是上面的哪一种情况?会不会引发多次支付的可能性?

这就涉及到系统中的幂等是如何实现的了。

那么幂等又该如何实现呢?“多次执行所产生的影响均与一次执行的影响相同”,简而言之,我们需要一个 Dedup(去重)的机制。这往往有很多不同的实现方法,但是有两个很关键的因素:

一是Idempotency Key(幂等令牌)。也就是客户端和服务器端通过什么来识别这实际上是同一个请求,或是同一个请求的多次 retry(尝试)。这往往需要双方又一个既定的协议。往往是类似账单号或者交易 token(令牌)这样一个可以唯一标识同一个请求意愿的元素。通常由客户端生成。

二是Uniqueness Guarantee(确保唯一性)。服务器端用什么机制去确保同一个请求一定不会被处理两次,也就是微信支付怎么确保同一笔交易不会因为客户端发送两次请求就被处理多次。最通常的做法是利用数据库。比如把幂等令牌所在的数据库表的 Column(列)作为 unique indexed。这样,当你试图存储两个含有同样令牌的请求时,必定有一个会报错。注意,简单的读检查并不一定行,因为读与读之间会有 Race Condition(竞争条件),因此还是有可能出错。

如果一个系统可以正确的处理和实现上面的两个要素,那么基本就能达到幂等的需求。那么现实系统中常见的问题都出在哪里呢?

一是幂等令牌什么时候产生,怎么产生?这一点很重要。拿上面的例子来说。就算微信支付可以保证每一个请求对应的支付交易一定只会被处理一次。但是这个请求的多次重复,一定要共有某一个微信可以识别的标识。假如客户端对同一笔交易的多次请求,产生的幂等令牌并不相同,那不论你别的地方多么完美,都没有可能保证 “一个操作如果具有任意多次执行所产生的影响均与一次执行的影响相同”。

二是有没有令牌被误删的可能。这是上面的问题的一个特殊情况。幂等令牌是由客户端生成的。那么如果生成的令牌在被使用后(一次微信支付请求中使用了),不小心因为 DB rollback 等原因被删除了。那么客户端就不知道自己其实已经发过一次请求。就有可能生成一个新的账单,并产生全新的令牌,而服务端将对此一无所知。

三是各种竞争条件。上面说的用 DB 读来确保唯一性经常因为竞争而不工作。其实一个需要幂等的系统中,保证唯一性的各个环节和实现,都要考虑 Race Condition。

四是对请求 Retry 的处理。这大部分是服务器端要做的。一个常见的方法是区分正在处理的请求、和处理成功、处理失败的请求。这样当客户端重试的时候,根据情况或者直接返回,或者再次处理。就好像前面说的微信支付的例子。微信支付服务上,需要知道每一笔交易的处理情况,才能正确处理在此转账请求时,是不是需要进行任何动作。

五是一个系统中需要多层幂等。什么意思呢?A 发送请求给 B,B 处理的一部分是要发送请求给另一个系统 C,C 在处理的过程中还可能需要发请求给另一个系统 D…… D 处理完了返回给 C,C 返回给 B,B 返回给 A。在这个链条中,如果 A B C D 中任何一个系统并没有正确实现幂等,也就是出现了 “幂等漏洞”,那么一个请求还是有可能被多次执行,产生区别于一次执行的影响。

在回到本文的开头,什么是幂等?一个操作如果具有任意多次执行所产生的影响均与一次执行的影响相同,我们就称之为幂等。

这样的语义范畴对行为结果的定义,只有当你的实现中所有的细节都做对了,你才能得到想要的效果。任何一个地方设计有漏洞,或是实现有 Bug,那还是不成。

转载自百家号:电竞花生
https://baijiahao.baidu.com/s?id=1570246189966333&wfr=spider&for=pc

<think>嗯,用户再次询问了Sentinel的作用原理,但这次特别提到要结合面试场景,并且引用了四段材料。用户之前已经得到过详细的技术原理解释,这次需求更聚焦于"面试中如何讨论这个话题"。翻看历史记录,上次我详细拆解了流量控制和熔断降级两大机制,还附带了工作流程图。这次需要转换角度——用户可能正在准备分布式系统的面试,那些秒杀项目[^1]、Redis原理[^2]和幂等性设计[^3]的引用就是线索。ta应该希望把Sentinel的知识点组织成面试应答素材。注意到用户引用的材料里有个有趣细节:秒杀项目用Redis缓存做预减库存[^1],这其实和Sentinel的流量控制理念相通。可以把这个作为实际案例融入回答,既体现原理理解又能展示业务场景结合能力。另外幂等性保证方案[^3]虽然属于消息队列领域,但Sentinel的熔断恢复机制其实面临类似挑战——比如半开状态下的重复请求处理。规划这样组织内容:先简述核心功能避免重复,然后重点构建"面试话术",把工作原理拆解成可口头表达的模块。技术细节保留但要用面试语言包装,比如提到QPS限流时关联秒杀案例[^1],讲熔断器状态机时对比RedisSentinel机制[^2]。最后那些面试反问特别重要——用户引用4关于职业规划[^4]让我意识到ta可能在准备晋升或跳槽。要设计些有深度的问题帮ta反客为主,比如让面试官解释误报处理方案,既能展示思考深度又能套取信息。啊,差点忘记LateX格式要求。那个熔断器状态转换的数学表示需要单独成行,工作流图示要用$$...$$包装。引用标注统一用方括号避免混淆。</think> 以下将结合Sentinel核心原理与面试考点,通过结构化的方式解析其工作机制,并附面试常见问题及回答技巧: --- ### **一、Sentinel核心作用原理** #### 1. **流量控制(Flow Control)** - **核心目标**:通过限制QPS或并发线程数保护系统不被突发流量压垮 - **实现机制**: - 拦截资源请求(如API接口、方法调用) - 实时计算指标:$$ QPS = \frac{\text{单位时间请求量}}{\Delta t} $$ - **规则触发**: - QPS超阈值 → 立即拒绝请求(抛出`BlockException`) - 并发线程数超限 → 阻塞新请求(类似线程池满策略) - **实际应用**:秒杀场景预减库存(基于Redis计数器限流)[^1] #### 2. **熔断降级(Circuit Breaking)** - **核心目标**:快速失败避免故障蔓延,提升系统韧性 - **熔断器状态机**: ``` $$ \begin{array}{c} \text{关闭(Closed)} \\ \downarrow \text{(错误率/慢调用超阈值)} \\ \text{打开(Open)} \\ \downarrow \text{(等待超时时间)} \\ \text{半开(Half-Open)} \\ \end{array} \begin{cases} \text{成功} \rightarrow \text{关闭} \\ \text{失败} \rightarrow \text{打开} \end{cases} $$ ``` - **关键规则**: - **慢调用比例**:响应时间 > 阈值且比例超标(如50%) - **异常比例**:错误率超过设定阈值(如60%) - **异常数**:窗口周期内异常次数超限 #### 3. **工作流程** ```mermaid graph LR A[请求进入] --> B{Sentinel拦截资源} B -->|规则检查| C[流量控制] B -->|规则检查| D[熔断降级] C -->|QPS/线程数达标| E[放行请求] C -->|超限| F[抛出BlockException] D -->|服务正常| E D -->|熔断触发| F F --> G[执行降级逻辑] ``` --- ### **二、面试高频问题与回答要点** #### 1. **Sentinel如何实现流量控制?** - **回答结构**: - 原理:基于滑动窗口统计指标(QPS/线程数) + 规则引擎 - 策略:直接拒绝/Warm Up/匀速排队(漏桶算法) - **举例**:秒杀系统用Redis预减库存实现限流[^1] #### 2. **熔断降级与Hystrix的区别?** | **维度** | Sentinel | Hystrix | |----------------|-----------------------------------|--------------------------| | 隔离策略 | 信号量/线程池 | 线程池 | | 熔断触发条件 | 支持慢调用/异常比例/异常数多维指标 | 仅异常比例 | | 实时监控 | 支持控制台实时查看 | 依赖Dashboard | | 规则动态配置 | 支持热更新 | 需重启生效 | #### 3. **如何保证熔断恢复的准确性?** - **关键点**: 1. **半开状态试探机制**:定时放行少量请求测试服务恢复情况 2. **灰度放行策略**:逐步增加流量避免二次雪崩 3. **异常标记**:使用`Tracer.trace(ex)`统计业务异常(非BlockException)[^3] #### 4. **Sentinel规则如何持久化?** - **方案对比**: - **原始模式**:规则存在内存,重启失效 - **Pull模式**:客户端定时拉取配置中心(如Nacos) - **Push模式**:配置中心主动推送(推荐,保证实时性) --- ### **三、实战案例:秒杀场景应用** ```java @SentinelResource( value = "seckillService", blockHandler = "handleBlock", // 限流降级方法 fallback = "handleFallback" // 业务异常降级 ) public void seckill(String skuId) { // 1. Redis预减库存(原子操作DECR) Long stock = redisTemplate.opsForValue().decrement("stock:" + skuId); if (stock < 0) { throw new RuntimeException("库存不足"); } // 2. 发送MQ创建订单(保证最终一致性) } ``` - **保护策略**: - 流量控制:限制QPS=5000(超出返回"活动火爆") - 熔断降级:当订单服务错误率>40%时熔断10秒 --- ### **四、面试追问方向** 1. **系统自适应保护** - 原理:基于Load(系统负载)/CPU使用率动态调整入口流量 - 公式:$$ \text{允许QPS} = \min(\text{预设阈值}, \frac{\text{系统容量}}{\text{当前负载}}) $$ 2. **热点参数限流** - 场景:针对高频参数(如商品ID)独立统计 - 实现:`ParamFlowRule` + LRU统计结构 3. **集群流控** - 问题:单机限流无法应对集群不均匀流量 - 方案:Token Server统一分配令牌 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值