ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

brpc 熔断机制全解析:从默认策略到可配置的 EMA 自适应熔断

brpc 熔断机制全解析:从默认策略到可配置的 EMA 自适应熔断 brpc 熔断机制全解析从默认策略到可配置的 EMA 自适应熔断【免费下载链接】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/brpcbrpc 是一款工业级 C RPC 框架。在微服务调用场景中下游某个节点发生故障时如果不能及时将其从可用节点列表中剔除整个调用链路都可能被拖垮。本文围绕 docs/cn/circuit_breaker.md 讲解 brpc 的熔断Circuit Breaker功能包括始终开启的默认连接级熔断策略、可选的基于错误率的自适应熔断策略以及节点隔离、健康检查恢复、监控数据展示等完整闭环。读完本文你将掌握如何正确开启熔断、如何按 QPS 与错误容忍度调优长短窗口参数、如何理解熔断的隔离与恢复过程并能结合 src/brpc/circuit_breaker.cpp 源码深入理解其底层实现。熔断在 RPC 调用链路中的位置一次 RPC 发起后brpc 的处理流程大致是从命名服务Naming Service拿到一个可用节点列表根据负载均衡策略如 rr、random、weighted round robin 等从列表中挑选出一个节点作为实际访问节点向该节点发起请求并等待响应。熔断机制介入的位置就是节点列表这一层当某个节点出现故障时brpc 会将它自动从可用节点列表中剔除并周期性对故障节点进行健康检查健康检查通过后再将其放回列表。这样负载均衡就不会再选中故障节点避免了无效请求对故障节点的反复冲击。brpc 的熔断分为两层默认熔断策略面向无法建立连接这一最明确的故障信号无需任何配置、始终开启、无法关闭可选熔断策略面向连接正常但请求持续失败/超时的隐性故障需要用户在ChannelOptions中显式开启基于错误率与延迟的 EMA 统计实现自适应判定。默认熔断策略连接失败即熔断触发条件特定的错误码在默认策略下brpc 一旦检测到某个节点无法建立连接就会将该节点熔断。以下错误码会被判定为目标节点无法建立连接ECONNREFUSED连接被拒绝ENETUNREACH网络不可达EHOSTUNREACH主机不可达EINVAL参数无效连接阶段三次连接超时的特殊处理需要特别指出的是假如 brpc 发现某个节点出现连续三次连接超时而不是 RPC 超时那么第三次超时也会被当作ENETUNREACH来处理从而触发熔断。这里的连接超时指ChannelOptions.connect_timeout_ms控制的建连阶段超时而非整个 RPC 的超时。由此引出一个非常容易踩坑的配置约束如果 RPC 的超时时间设置得比连接超时更短那么当节点无法建立连接时RPC 超时会比连接超时更早触发导致节点永远不会被判定为连续三次连接超时熔断也就永远无法被触发。因此在自定义超时时间时必须保证ChannelOptions.timeout_ms ChannelOptions.connect_timeout_ms默认策略的定位是最底线的保护它只对最明确、最无可争议的故障连不上生效不依赖任何额外配置也无法被关闭。可选熔断策略基于错误率的自适应熔断为什么需要更激进的策略默认策略有一个明显的盲区假如某个下游节点的业务逻辑线程全部卡死但 IO 线程仍能正常工作那么所有请求都会超时而 TCP 连接却能够正常建立。此时默认策略完全失效——连接能建立错误码也未必是上述四个之一但服务实际上已经不可用。对于这类连接正常、服务已死的场景brpc 在默认策略的基础上提供了可选熔断策略根据出错率判断节点是否处于故障状态。开启方法可选熔断策略默认关闭需要在ChannelOptions中显式开启brpc::ChannelOptions option; option.enable_circuit_breaker true;该字段在 src/brpc/channel.h 中定义注释明确说明当某个节点的错误率过高时将节点隔离且这种隔离是进程内全局的——隔离期间该节点对进程中所有 channel 均不可用。ChannelOptions构造函数中该字段的默认值是false见 src/brpc/channel.cpp。开启后channel 发起 RPC 时会给Controller打上FLAGS_ENABLED_CIRCUIT_BREAKER标记见 src/brpc/channel.cpp只有带此标记的请求其结果才会被提交给熔断器采样。工作原理EMA 错误成本模型可选的熔断由CircuitBreaker类实现src/brpc/circuit_breaker.h。开启熔断后CircuitBreaker会记录每一个请求的处理结果并维护一个累计出错时长记为acc_error_cost。判定规则很简单当acc_error_cost max_error_cost时熔断该节点。每次请求成功之后的更新更新 max_error_cost更新 latency 的 EMA 值记为ema_latencyema_latency ema_latency * alpha (1 - alpha) * latencyEMAExponential Moving Average指数移动平均让统计对近期数据更敏感。这里latency指该次请求的耗时alpha是平滑系数略小于 1由window_size和circuit_breaker_epsilon_value共同决定。根据ema_latency更新max_error_costmax_error_cost window_size * max_error_rate * ema_latency其中window_size和max_error_rate均来自 gflags 常量对应长短窗口各自的配置alpha由窗口大小和circuit_breaker_epsilon_value推导。每次请求返回之后的更新更新 acc_error_cost请求处理成功acc_error_cost alpha * acc_error_cost错误成本按比例衰减用于遗忘历史错误请求处理失败acc_error_cost acc_error_cost min(latency, ema_latency * 2)累加错误成本。这里的alpha与计算max_error_cost用的是同一个值。之所以对失败请求的latency做min(latency, ema_latency * 2)修正是因为出现超时等错误时latency 往往会远远大于 ema_latency——如果不加限制一次超时就会把错误成本顶到天上熔断变得过于敏感。这个倍率默认 2同样可以通过 gflag 配置circuit_breaker_max_failed_latency_mutiple。对照 src/brpc/circuit_breaker.cpp 的UpdateErrorCost实现可以看到失败分支通过原子操作累加min(ema_latency * max_mutiple, error_cost)并与max_error_cost实现中为ema_latency * _window_size * (_max_error_percent / 100.0) * (1.0 EPSILON)比较成功分支则将ema_error_cost乘以_smooth衰减且当错误成本低于circuit_breaker_min_error_cost_us默认 500 微秒时直接清零。初始阶段的错误计数判定EMA 需要积累一定量的数据才能反映真实水平因此在熔断的初始阶段已收集到的请求数 窗口大小会退化为简单计数判定若acc_error_count window_size * max_error_rate为真则进行熔断。对应 src/brpc/circuit_breaker.cpp 中EmaErrorRecorder::OnCallEnd的初始化分支窗口未填满时直接比较累计错误数与window_size * max_error_percent / 100。长短双窗口容忍抖动与剔除长期高错率节点为了允许某个节点在短时间内抖动同时又能够剔除长期错误率较高的节点CircuitBreaker同时维护了长、短两个窗口且两个窗口必须同时判定健康OnCallEnd均返回 true才认为节点健康if (_long_window.OnCallEnd(error_code, latency) _short_window.OnCallEnd(error_code, latency)) { return true; } MarkAsBroken(); return false;见 src/brpc/circuit_breaker.cpp长窗口阈值较低默认允许 5% 错误率主要作用是剔除那些长期错误率较高的服务。可以根据实际的 QPS 及对错误的容忍程度调整circuit_breaker_long_window_size及circuit_breaker_long_window_error_percent。短窗口允许更精细地控制熔断灵敏度。在对抖动很敏感的场景可以通过调小circuit_breaker_short_window_size、调低circuit_breaker_short_window_error_percent来缩短短窗口长度、降低对错误的容忍程度使出现抖动时能快速熔断故障节点。epsilon_value连续抖动的容忍度调节circuit_breaker_epsilon_value用于调整窗口对连续抖动的容忍程度。其与alpha的关系在源码中有明确注释src/brpc/circuit_breaker.cppsmooth pow(EPSILON, 1 / window_size) // smooth 即公式中的 alphacircuit_breaker_epsilon_value的值越低计算出的alpha越小acc_error_cost下降的速度就越快当circuit_breaker_epsilon_value达到0.001时若一整个窗口的请求都没有出错正好可以把acc_error_cost降低到 0默认值为0.02。以窗口大小 100 为例EPSILON 0.1时smooth ≈ 0.9772EPSILON 0.3时smooth ≈ 0.9880窗口越大相同 epsilon 对应的 smooth 越接近 1即历史数据权重越大。半开窗口half-open平滑恢复的可选特性除了文档正文描述的内容源码中还提供了半开窗口特性src/brpc/circuit_breaker.cppcircuit_breaker_half_open_window_size默认 0即关闭熔断恢复后允许放行有限数量的试探请求只有这些试探请求全部成功熔断器才真正进入关闭closed状态否则回到打开open状态继续熔断。对应测试 test/brpc_circuit_breaker_unittest.cpp 中的should_isolate_with_half_open用例验证了半开窗口下试探请求的放行与再次熔断行为。熔断的范围连接粒度而非请求粒度brpc 在决定熔断某个节点时熔断掉的是整个连接Socket而不是某一次请求。具体表现为pooled 连接模式下会熔断掉与该节点之间的所有连接连接被 channel 共享brpc 的 TCP 连接会被多个 channel 共享某个连接被熔断后所有 channel 都不能再使用这个故障的连接若想避免第 2 点中的殃及池鱼可以通过设置ChannelOptions.connection_group将 channel 放进不同的ConnectionGroup不同 ConnectionGroup 的 channel 并不会共享连接。connection_group在 src/brpc/channel.h 中定义相同connection_group的 channel 共享连接设置为不同值即停止共享大小写敏感前后空格会被忽略默认为空字符串。在源码层面熔断的实际动作由Socket::FeedbackCircuitBreaker触发src/brpc/socket.cpp当CircuitBreaker::OnCallEnd判定节点需要隔离时调用SetFailed(main_socket_id())将 Socket 置为失败状态并打印日志Socket[...] isolated by circuit breaker。SetFailed内部src/brpc/socket.cpp会调用circuit_breaker.MarkAsBroken()并立即以当前隔离时长启动健康检查。由于 Socket 是共享资源这一失败会立刻传导到所有引用该连接的 channel。熔断数据的收集只统计开启了熔断的 channel只有通过开启了enable_circuit_breaker的 channel 发送的请求才会将请求的处理结果提交到CircuitBreaker。这一点在 channel 侧由FLAGS_ENABLED_CIRCUIT_BREAKER标记保证src/brpc/channel.cpp在 Socket 侧由Controller检查该标记后调用FeedbackCircuitBreaker完成采样。由此得到一个重要的运维建议假如决定对下游某个服务开启可选的熔断策略最好在所有的连接到该服务的 channel 里都开启enable_circuit_breaker。否则部分流量不走熔断统计采样样本不完整错误率判定会出现偏差。熔断的恢复隔离 健康检查隔离期避免假恢复反复熔断目前 brpc 使用通用的健康检查来判定某个节点是否恢复只要能够建立 TCP 连接就认为该节点已经恢复。但这带来一个问题某些故障节点如逻辑线程卡死TCP 连接是正常的健康检查会立刻误判恢复导致刚恢复又被熔断形成抖动循环。为此brpc 在每次熔断之后会先对故障节点进行一段时间的隔离隔离期间故障节点不会被负载均衡选中也不会进行健康检查。若节点在短时间内被连续熔断隔离时间翻倍指数退避初始隔离时间为circuit_breaker_min_isolation_duration_ms默认100ms最大隔离时间和判断两次熔断是否为连续熔断的时间间隔都由circuit_breaker_max_isolation_duration_ms控制默认30 秒。对应 src/brpc/circuit_breaker.cpp 的UpdateIsolationDuration若距上次恢复时间小于max_isolation_duration_ms则isolation_duration_ms min(isolation_duration_ms * 2, max_isolation_duration_ms)否则重置为最小值。恢复流程隔离期结束后健康检查线程开始探测节点src/brpc/details/health_check.cpp 中的HealthCheckTask先尝试建立 TCP 连接CheckHealth成功后调用Socket::Revive将 Socket 复活同时清除circuit_breaker的历史数据sp-circuit_breaker.Reset()见 src/brpc/socket.cpp节点重新回到可用节点列表参与负载均衡。Reset()会清空两个窗口的历史采样、置_broken为 false并在开启半开窗口特性时进入 half-open 状态src/brpc/circuit_breaker.cpp。数据体现在监控页面观测熔断节点的熔断状态可以在内置监控页面的/connections中查看。即便没有开启可选的熔断策略brpc 也会统计这些数据nBreak进程启动之后该节点的总熔断次数RecentErr该节点最近一次从熔断中恢复之后累计的出错请求数。字段在 src/brpc/builtin/connections_service.cpp 的表格头与输出行中定义分别对应Socket::isolated_times()来自circuit_breaker.isolated_times()与Socket::recent_error_count()由每次出错请求累加见 src/brpc/socket.cpp。由于默认熔断策略一直开启即便没有开启可选的熔断策略nBreak 也可能会大于 0这时 nBreak 通常是因为 TCP 连接建立失败如ECONNREFUSED、连续三次连接超时而产生的属于默认策略的正常工作痕迹而非可选熔断被误触发。更完整的/connections页面说明可参考 docs/cn/connections.md。熔断参数速查表以下 gflag 均在 src/brpc/circuit_breaker.cpp 中定义可通过-flagvalue或配置文件在进程启动时修改gflag默认值含义circuit_breaker_short_window_size1500短窗口采样样本数circuit_breaker_long_window_size3000长窗口采样样本数circuit_breaker_short_window_error_percent10短窗口允许的最大错误率0-99百分比circuit_breaker_long_window_error_percent5长窗口允许的最大错误率0-99百分比circuit_breaker_min_error_cost_us500error_cost 的 EMA 低于该值微秒时清零circuit_breaker_max_failed_latency_mutiple2失败请求 latency 相对平均延迟的最大倍率用于修正 acc_error_cost 中的 latencycircuit_breaker_min_isolation_duration_ms100最小隔离时长毫秒circuit_breaker_max_isolation_duration_ms30000最大隔离时长以及判定连续熔断的时间间隔毫秒circuit_breaker_epsilon_value0.02用于推导 EMA 平滑系数alpha 1 - pow(epsilon, 1/window_size)circuit_breaker_half_open_window_size0半开窗口允许放行的试探请求数全部成功才关闭熔断0 表示关闭该特性实战调优建议先保证超时约束任何自定义超时场景下始终保证ChannelOptions.timeout_ms ChannelOptions.connect_timeout_ms否则默认熔断策略可能永远无法触发。对关键下游全面开启可选熔断对同一下游服务的所有 channel 统一设置enable_circuit_breaker true保证采样数据完整。用长短窗口分别控制灵敏与稳定高 QPS、对抖动敏感的服务可以调小circuit_breaker_short_window_size并降低circuit_breaker_short_window_error_percent如从 10 调到 5让瞬时抖动被快速熔断长窗口保持较高容忍度默认 5%用于剔除长期不健康节点。用 epsilon 调节连续抖动的容忍度若业务允许短时连续失败适当调高circuit_breaker_epsilon_valuealpha 增大、错误成本衰减变慢会让熔断更恋旧反之调低可让节点快速洗白。隔离时长配合指数退避circuit_breaker_max_isolation_duration_ms默认 30 秒连续熔断时隔离时间从 100ms 翻倍直至上限避免健康检查与熔断在假恢复节点上反复拉锯。善用监控即使不开启可选熔断也建议观察/connections页面的 nBreak 与 RecentErr用于发现网络层或连接层异常开启后则可进一步评估错误率触发的隔离是否频繁、参数是否需要调整。通过以上机制brpc 在连接不可达与服务逻辑故障两个层面都提供了自动的节点剔除与恢复能力配合负载均衡与健康检查相关背景可参考 docs/cn/load_balancing.md 与 docs/cn/health_check.md构成了一套完整的下游故障保护闭环。【免费下载链接】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),仅供参考
返回列表