ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

brpc 熔断功能(Circuit Breaker)完全指南:默认策略、错误率熔断与节点隔离恢复

brpc 熔断功能(Circuit Breaker)完全指南:默认策略、错误率熔断与节点隔离恢复 brpc 熔断功能Circuit Breaker完全指南默认策略、错误率熔断与节点隔离恢复【免费下载链接】brpcbrpc is an Industrial-grade RPC framework using C Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. brpc means better RPC.项目地址: https://gitcode.com/GitHub_Trending/brpc/brpc熔断Circuit Breaker是 brpc 在客户端侧保障高可用的一项核心能力当某个下游节点出现故障时brpc 会自动将其从可用节点列表中剔除避免请求持续打到故障节点上拖垮整体时延。本文基于 docs/cn/circuit_breaker.md 展开结合 src/brpc/circuit_breaker.cpp、src/brpc/socket.cpp 等源码实现系统讲解 brpc 默认熔断策略的触发条件、可选熔断策略的开启方法与 EMA 错误成本算法、全部可调 gflag 参数、熔断的隔离与恢复机制以及如何在监控页面查看熔断数据。读完本文你将能够在自己的 brpc 客户端上正确配置并调优熔断策略让搜索、存储、推荐等高性能在线服务在下游抖动时依然稳定。brpc 熔断的整体工作流程在讲解具体策略之前先明确熔断在整个 RPC 调用链路中的位置。当发起一次 RPC 时brpc 首先从命名服务Naming Service获取可用节点列表然后根据负载均衡策略从中挑选一个节点作为实际访问目标。当某个节点出现故障时brpc 需要能够自动把它从可用节点列表中剔除并且周期性对故障节点进行健康检查待其恢复后再重新纳入。这一发现故障 → 剔除 → 隔离 → 健康检查 → 恢复的闭环就是熔断功能要解决的完整问题。从源码结构看熔断逻辑主要由三个组件协作完成src/brpc/circuit_breaker.h 与 src/brpc/circuit_breaker.cpp定义CircuitBreaker类及内部EmaErrorRecorder实现错误率统计与熔断判定src/brpc/socket.cpp每个连接Socket的SharedPart中持有一个circuit_breaker实例src/brpc/socket.cpp#L199RPC 结束后通过Socket::FeedbackCircuitBreaker将结果反馈给熔断器src/brpc/socket.cpp#L1083-L1089src/brpc/channel.cpp 与 src/brpc/channel.h通过ChannelOptions.enable_circuit_breaker控制是否开启可选熔断策略。默认的熔断策略brpc 默认内置一套简单熔断策略只要检测到某个节点无法建立 TCP 连接就对该节点实施熔断。具体而言当一次 RPC 返回以下错误码之一时brpc 会认为目标节点无法建立连接从而触发熔断ECONNREFUSED连接被拒绝通常意味着端口上没有服务在监听ENETUNREACH网络不可达EHOSTUNREACH主机不可达EINVAL参数非法。连接超时的特殊处理连续三次视为 ENETUNREACH一个容易踩坑的细节是如果 brpc 发现某个节点出现连续三次连接超时注意是连接超时 connect timeout而不是 RPC 超时那么第三次超时会被当作ENETUNREACH处理同样触发熔断。这里衍生出一个重要的配置约束RPC 的超时时间必须大于连接超时时间即ChannelOptions.timeout_ms ChannelOptions.connect_timeout_ms原因在于如果 RPC 超时设置得比连接超时更短那么当节点无法建立连接时RPC 超时会比连接超时更早触发请求在还没等到连接失败这个结论之前就以 RPC 超时结束了最终导致永远触发不了熔断。在 src/brpc/channel.h#L48-L58 中可以看到两者的默认值connect_timeout_ms默认为 200 毫秒-1 表示无限等待timeout_ms默认为 500 毫秒默认配置本身就满足上述约束但如果你自定义了超时务必保持这个大小关系。默认策略的特性默认熔断策略具有以下特点一直开启不需要做任何配置默认生效无法关闭它不依赖enable_circuit_breaker开关无需维护由 brpc 框架内部自动完成节点的剔除与恢复。可选的熔断策略基于错误率仅依赖默认策略有时并不能完全满足需求。举一个极端例子假设某个下游节点的业务逻辑线程全部卡死但 IO 线程仍能正常工作。此时所有请求都会超时但 TCP 连接却能够正常建立——默认熔断策略完全检测不到这种半死不活的节点。为此brpc 在默认策略的基础上提供了一种更激进的熔断策略开启之后brpc 会根据节点的出错率来判断其是否处于故障状态即使 TCP 连接正常只要错误率超标同样会熔断。开启方法可选的熔断策略默认是关闭的ChannelOptions构造函数中enable_circuit_breaker初始为false见 src/brpc/channel.cpp#L61。用户按需在ChannelOptions中开启brpc::ChannelOptions option; option.enable_circuit_breaker true;在 src/brpc/channel.h#L77-L81 中该字段的注释明确说明当某个服务节点的错误率过高时节点会被隔离且这种隔离是全局的——在隔离期间该节点对本进程内所有 channel 都不可用。在 src/brpc/channel.cpp#L511-L513 可以看到channel 在发起 RPC 时会把该开关翻译为 Controller 的FLAGS_ENABLED_CIRCUIT_BREAKER标志位用于标记本次调用需要参与熔断统计。工作原理EMA 错误成本模型可选的熔断由CircuitBreaker实现。开启熔断后CircuitBreaker会记录每一个请求的处理结果并维护一个累计出错时长acc_error_cost。当acc_error_cost max_error_cost时对该节点实施熔断。整个判定过程的核心实现在EmaErrorRecorder::OnCallEndsrc/brpc/circuit_breaker.cpp#L83-L110。每次请求返回成功之后更新max_error_cost首先更新 latency 的 EMA指数移动平均 值记为ema_latencyema_latency ema_latency * alpha (1 - alpha) * latency从源码看alpha实际由_smooth pow(EPSILON, 1.0/window_size)计算而来src/brpc/circuit_breaker.cpp#L76其中EPSILON即 gflagcircuit_breaker_epsilon_value首次请求时ema_latency直接取本次 latencysrc/brpc/circuit_breaker.cpp#L125-L129。之后根据ema_latency更新max_error_costmax_error_cost window_size * max_error_rate * ema_latency源码中的实际实现为ema_latency * _window_size * (_max_error_percent / 100.0) * (1.0 EPSILON)src/brpc/circuit_breaker.cpp#L147-L148可见阈值在窗口大小 × 错误率 × 平均延迟的基础上还叠加了(1.0 EPSILON)的松弛系数避免瞬时波动误伤节点。每次请求返回之后更新acc_error_cost如果请求处理成功acc_error_cost alpha * acc_error_cost即错误成本按平滑系数指数衰减src/brpc/circuit_breaker.cpp#L152-L170成功请求越多累计错误成本下降越快如果请求处理失败acc_error_cost acc_error_cost min(latency, ema_latency * 2)。其中计算acc_error_cost所用的alpha与计算max_error_cost所用的为同一个值。考虑到出现超时等错误时latency 往往会远远大于ema_latency所以在累加acc_error_cost时对失败请求的 latency 做了修正使其不超过ema_latency的两倍。这个倍率同样可以通过 gflag 配置circuit_breaker_max_failed_latency_mutiple默认 2见 src/brpc/circuit_breaker.cpp#L40-L42。此外源码中还有一个值得注意的边界处理如果请求返回ELIMIT服务端达到最大并发CircuitBreaker::OnCallEnd会直接返回 healthysrc/brpc/circuit_breaker.cpp#L194-L196。因为ELIMIT通常意味着整个下游集群过载此时熔断单个节点反而会加重下游压力且ELIMIT对应的 latency 往往很小既不能按成功处理也不宜按失败累加成本因此直接忽略。长短双窗口机制为了允许某个节点在短时间内抖动同时又能剔除长期错误率较高的节点CircuitBreaker同时维护了长窗口和短窗口两个统计窗口src/brpc/circuit_breaker.cpp#L173-L184长窗口_long_window阈值较低主要作用是剔除那些长期错误率较高的服务。可通过circuit_breaker_long_window_size与circuit_breaker_long_window_error_percent调整短窗口_short_window阈值较高允许我们更精细地控制熔断灵敏度。在一些对抖动很敏感的场景可以通过circuit_breaker_short_window_size和circuit_breaker_short_window_error_percent缩短短窗口长度、降低短窗口对错误的容忍程度使得抖动出现时能够快速对故障节点实施熔断。两个窗口的判定是与关系_long_window.OnCallEnd(...) _short_window.OnCallEnd(...)只要任一窗口判定不健康节点即被标记为熔断src/brpc/circuit_breaker.cpp#L213-L218。可以根据实际 QPS 及对错误的容忍程度来调整长窗口参数。初始化阶段的兜底判定由于计算 EMA 需要积累一定量的数据在熔断的初始阶段即目前已收集到的请求数 窗口大小EmaErrorRecorder会直接使用错误数量来判定是否熔断src/brpc/circuit_breaker.cpp#L95-L107若 acc_error_count window_size * max_error_rate 为真则进行熔断。这一兜底逻辑确保熔断器在数据积累不足时依然具备防护能力而不是在窗口空转期完全失效。熔断参数速查表gflags所有可选的熔断参数均为 brpc 的 gflag 配置项定义于 src/brpc/circuit_breaker.cpp#L29-L54支持运行时通过bvar/gflags 动态调整。完整参数如下参数名默认值说明circuit_breaker_short_window_size1500短窗口的样本数量circuit_breaker_long_window_size3000长窗口的样本数量circuit_breaker_short_window_error_percent10短窗口允许的最大错误率百分比取值范围 0-99circuit_breaker_long_window_error_percent5长窗口允许的最大错误率百分比取值范围 0-99circuit_breaker_min_error_cost_us500error_cost 的最小值微秒当 error cost 的 EMA 小于该值时直接归零避免长期累积的微小误差导致误判circuit_breaker_max_failed_latency_mutiple2失败请求 latency 相对成功请求平均 latency 的最大倍数即上文ema_latency * 2中的 2circuit_breaker_min_isolation_duration_ms100最小隔离时长毫秒circuit_breaker_max_isolation_duration_ms30000最大隔离时长毫秒同时作为判断两次熔断是否为连续熔断的时间间隔circuit_breaker_epsilon_value0.02用于计算 EMA 平滑系数ema_alpha 1 - std::pow(epsilon, 1.0 / window_size)circuit_breaker_half_open_window_size0半开窗口允许放行的请求数量0 表示关闭半开功能关键参数的调优语义circuit_breaker_epsilon_value控制对连续抖动的容忍程度。该值越低计算公式中的alpha越小acc_error_cost下降的速度越快。当该值达到 0.001 时若一整个窗口的请求都没有出错正好可以把acc_error_cost降低到 0。源码注释src/brpc/circuit_breaker.cpp#L57-L66给出了直观示例当window_size 100时EPSILON 0.1对应smooth 0.9772EPSILON 0.3对应smooth 0.9880EPSILON越大平滑系数越大早期数据占比越高错误成本衰减越慢。circuit_breaker_min_error_cost_us兜底归零阈值。在成功请求的衰减路径上如果当前ema_error_cost小于该值默认 500 微秒会直接原子地归零而不是继续乘系数src/brpc/circuit_breaker.cpp#L156-L161避免数值长期残留在半死不活的小量级上。circuit_breaker_half_open_window_size半开窗口默认关闭。开启后节点从熔断状态恢复时先进入半开状态只允许有限数量的请求放行探活只有这些请求全部成功才转为闭合状态否则立刻回到熔断状态src/brpc/circuit_breaker.cpp#L200-L211 与 src/brpc/circuit_breaker.cpp#L226-L229。该值需为非负整数由BRPC_VALIDATE_GFLAG校验。熔断的范围连接级熔断与 connection_groupbrpc 在决定熔断某个节点时熔断的是整个连接而非单个请求或单个 channelpooled 模式熔断所有连接如果使用pooled连接模式同一节点复用连接池熔断会作用于该节点上的所有连接连接被 channel 共享brpc 的 TCP 连接会被多个 channel 共享相同connection_group的 channel 共享连接当某个连接被熔断之后所有共享该连接的 channel 都不能再使用这个故障连接用connection_group隔离共享范围如果希望避免第 2 点中一个故障连接拖累所有 channel的情况可以通过设置ChannelOptions.connection_group将 channel 放进不同的 ConnectionGroup。不同 ConnectionGroup 的 channel 不会共享连接src/brpc/channel.h#L143-L147。connection_group区分大小写、忽略首尾空格默认值为空字符串。从 src/brpc/channel.h#L77-L81 的注释还可以确认一个重要特性熔断隔离是进程级全局的在隔离期间该节点对所有 channel 都不可用——这是设计上防止故障节点被不同客户端重复击穿的主动选择。熔断数据的收集范围只有通过开启了enable_circuit_breaker的 channel发送的请求才会把请求的处理结果提交给CircuitBreaker。也就是说熔断统计是按 channel 粒度收集的。因此如果你决定对下游某个服务开启可选熔断策略最好在所有连接到该服务的 channel 上都开启enable_circuit_breaker否则未开启的 channel 发起的请求不会参与错误率统计可能造成熔断判定失真。熔断的恢复与健康检查目前 brpc 使用通用的健康检查来判定某个节点是否已经恢复只要能够建立 TCP 连接就认为该节点已经恢复。这里存在一个矛盾如果故障节点只是逻辑卡死TCP 能连上健康检查会误判其已恢复。为了正确摘除这类能建立 TCP 连接但实际故障的节点brpc 在每次熔断之后会先对故障节点进行一段时间的隔离隔离期间该节点既不会被负载均衡选中也不会进行健康检查。隔离时长遵循指数退避策略CircuitBreaker::UpdateIsolationDuration见 src/brpc/circuit_breaker.cpp#L239-L253初始隔离时间为 100mscircuit_breaker_min_isolation_duration_ms若节点在短时间内被连续熔断则隔离时间翻倍isolation_duration_ms * 2直至达到上限最大隔离时间和判断两次熔断是否为连续熔断的时间间隔都由circuit_breaker_max_isolation_duration_ms控制默认 30 秒如果两次熔断的时间间隔超过了该上限则视为非连续熔断隔离时间重置回最小值。隔离结束后的恢复路径同样有源码可循连接恢复时Socket::OnConnectable会调用circuit_breaker.Reset()清空窗口统计并解除熔断标记src/brpc/socket.cpp#L1052-L1056而连接失败时Socket::OnFailed会调用MarkAsBroken()并启动健康检查src/brpc/socket.cpp#L908-L911。数据体现在监控页面观察熔断状态节点的熔断次数、最近一次从熔断中恢复之后的累积错误数都可以在监控页面的/connections中找到。即使没有开启可选的熔断策略brpc 也会对这些数据进行统计。两个关键指标nBreak表示进程启动之后该节点的总熔断次数。对应源码中的isolated_times()src/brpc/socket.cpp#L1075-L1081RecentErr表示该节点最近一次从熔断中恢复之后累计的出错请求数。对应recent_error_count()src/brpc/socket.cpp#L1067-L1073。需要特别说明的是由于 brpc 默认熔断策略一直开启即便没有开启可选熔断策略nBreak 也可能大于 0——此时的熔断通常是 TCP 连接建立失败如ECONNREFUSED、ENETUNREACH等导致的。因此排查 nBreak 指标时应先判断熔断原因属于默认策略连接失败还是可选策略错误率超标再决定调优方向。实践建议小结先保证超时配置正确维持timeout_ms connect_timeout_ms否则默认熔断策略可能因 RPC 超时抢先触发而永远无法生效按业务容忍度开可选熔断对逻辑线程卡死但 TCP 正常的下游服务务必开启enable_circuit_breaker true并尽量在所有指向该服务的 channel 上统一开启用长短窗口配合调灵敏度长窗口默认 3000 样本 / 5% 错误率负责剔除长期高错误率节点短窗口默认 1500 样本 / 10% 错误率负责快速响应抖动对抖动敏感的场景可调低短窗口错误容忍度用connection_group控制熔断爆炸半径如果担心共享连接导致故障放大可为不同业务线设置不同的connection_group结合 /connections 监控验证通过 nBreak 与 RecentErr 观察熔断是否按预期触发并据此微调circuit_breaker_epsilon_value等参数。【免费下载链接】brpcbrpc is an Industrial-grade RPC framework using C Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. brpc means better RPC.项目地址: https://gitcode.com/GitHub_Trending/brpc/brpc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表