Envoy 移除 trace_refresh_after_route_refresh 运行时开关:路由刷新后 Trace 决策与 Decorator 永久刷新机制解析
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
本文聚焦 Envoy 在最新版本(changelogs/current对应版本)中对 HTTP 连接管理器(HTTP Connection Manager,HCM)的一项变更:正式移除运行时开关envoy.reloadable_features.trace_refresh_after_route_refresh及其守护的旧代码路径。路由刷新后,HCM 将始终重新计算 trace 采样决策并应用新路由的 decorator,同时统计信息统一由chargeStats路径上报。读完本文,你将理解该开关引入的背景(v1.36.0 行为变更)、新行为的源码级实现细节、pack_trace_reason的边界注意事项,以及升级时的迁移与验证要点。
变更总览:一个运行时开关的完整生命周期
该变更记录在 tracing__trace-refresh-after-route-refresh.rst 中,核心内容如下:
Removed the runtime guard
envoy.reloadable_features.trace_refresh_after_route_refreshand the legacy code path it guarded. The HTTP connection manager now always refreshes the trace decision and decorator when the route is refreshed, and charges the tracing statistics fromchargeStatsrather than from the old un-refreshed code path.
即本次变更包含三件事:
- 删除运行时开关:
envoy.reloadable_features.trace_refresh_after_route_refresh从源码中彻底移除,无法再通过设置其为false恢复旧行为; - 固化新行为:HCM 在路由被刷新(route refresh)时,总是刷新 trace 决策(trace decision)与 decorator;
- 统一统计路径:tracing 统计信息统一从
chargeStats路径上报,旧的"未刷新代码路径"被删除。
背景:这个开关为何存在
要理解此次移除,需要先回溯该开关的引入。在 changelogs/1.36.0.yaml 的behavior_changes一节中,可以找到这条历史变更记录:
A route refresh will now result in a tracing refresh. The trace sampling decision and decoration of the new route will be applied to the active span. This change can be reverted by setting the runtime guard
envoy.reloadable_features.trace_refresh_after_route_refreshtofalse.
也就是说,在 Envoy 1.36.0(发布于 2025 年 10 月 14 日)中,"路由刷新后同步刷新 tracing"作为一个默认开启但可回退的行为变更被引入:默认启用新逻辑,运维人员可通过运行时开关临时回退到旧逻辑。经过若干版本验证后,在changelogs/current对应的版本中,该开关被移除,新行为成为唯一行为。
什么是"路由刷新"?在 Envoy 中,路由刷新指已建立请求的活跃流(ActiveStream)在其生命周期内再次触发路由解析(re-route)并更新缓存路由的过程。典型场景包括:HTTP 过滤器在请求处理中途通过recalculateRoute(源码中对应ActiveStream::recalculateRoute,见 conn_manager_impl.cc)重新选择路由——例如基于 scoped RDS 的 scope 键头变化、或过滤器修改了路由相关头信息。刷新完成后,源码会依次调用:
refreshTracing()—— 刷新 trace 决策与 decorator;refreshDurationTimeout()—— 刷新基于路由的超时;refreshIdleAndFlushTimeouts()—— 刷新空闲/冲刷超时;refreshBufferLimit()—— 刷新缓冲上限。
由此可见,tracing 刷新只是路由刷新后一系列"按新路由对齐流状态"动作中的一环,与超时、缓冲等逻辑处于同等地位。
源码级解析:refreshTracing() 到底做了什么
本次移除开关后固化的核心逻辑位于ConnectionManagerImpl::ActiveStream::refreshTracing(),实现在 conn_manager_impl.cc。该方法在执行前先做三项前置判断,任一不满足则直接返回:
if (!connection_manager_tracing_config_.has_value() || active_span_ == nullptr || request_headers_ == nullptr) { return; } ASSERT(cached_route_.has_value());即:未配置 HCM 级 tracing 配置、尚无活跃 span、或请求头不存在时无需刷新。随后按以下步骤完成刷新:
1. 重新计算 trace reason(追踪原因)
const auto trace_reason = ConnectionManagerUtility::mutateTracingRequestHeader( *request_headers_, connection_manager_.runtime_, *connection_manager_.config_, cached_route_.value().get()); filter_manager_.streamInfo().setTraceReason(trace_reason);mutateTracingRequestHeader(定义于 conn_manager_utility.cc)负责根据请求头(如x-envoy-force-trace)、运行时配置与路由配置,判定该请求应被追踪的原因(reason),并将其写入StreamInfo。这保证路由刷新后 trace reason 反映的是新路由的配置。
2. 重新做采样决策并应用到活跃 span
const Tracing::Decision tracing_decision = Tracing::TracerUtility::shouldTraceRequest(filter_manager_.streamInfo()); if (active_span_->useLocalDecision()) { active_span_->setSampled(tracing_decision.traced); }TracerUtility::shouldTraceRequest综合StreamInfo中的 trace reason、采样率配置等得出最终的Tracing::Decision(是否追踪、以及追踪原因)。若当前活跃 span 使用的是"本地决策"(useLocalDecision(),即未被外部注入决策强制覆盖),则调用setSampled(traced)将新决策应用到 span 上。
3. 刷新路由级 decorator 与 tracing 配置
if (hasCachedRoute()) { route_decorator_ = cached_route_.value()->decorator(); route_tracing_ = cached_route_.value()->tracingConfig(); } if (!operationNameFormatter(*connection_manager_tracing_config_, route_tracing_)) { // Only set decorator when there is no operation name formatter configured at either // the HCM level or the route level. setRequestDecorator(*request_headers_); }这里将缓存路由的decorator()(路由装饰器,可为 span 设置 operation name)与tracingConfig()重新赋值。需要注意一个细节:仅当 HCM 级与路由级都未配置 operation name formatter 时,才会调用setRequestDecorator将 decorator 应用到请求头与 span——否则以 formatter 生成的 operation name 为准。这避免了 decorator 与自定义操作名格式化器之间的冲突。
setRequestDecorator(conn_manager_impl.cc 起)会根据当前请求方向(ingress/egress)、是否覆盖装饰操作名(decorator_overriden_)、是否传播(decorated_propagate_)等状态,决定是否把 decorator 的 operation name 写入x-envoy-decorator-operation响应头,从而保证外部可观测系统(如追踪后端)看到的操作名与最新路由保持一致。
统计路径统一:为什么从 chargeStats 上报
变更说明中特别强调:tracing 统计信息"charges the tracing statistics fromchargeStatsrather than from the old un-refreshed code path"。
ActiveStream::chargeStats(const ResponseHeaderMap& headers)(conn_manager_impl.cc)是每个请求完成/产生响应头时统一执行统计上报的入口,其 tracing 统计部分如下:
if (connection_manager_tracing_config_.has_value()) { const Tracing::Decision tracing_decision = Tracing::TracerUtility::shouldTraceRequest(filter_manager_.streamInfo()); ConnectionManagerImpl::chargeTracingStats(tracing_decision.reason, connection_manager_.config_->tracingStats()); }它基于最终(刷新后)的 StreamInfo重新计算shouldTraceRequest决策,再通过chargeTracingStats累加如tracing.random_sampling、tracing.service_required、tracing.client_enabled、tracing.not_traceable等统计。由于此时StreamInfo中的 trace reason 已在refreshTracing阶段被更新为新路由的判定结果,因此上报的统计天然反映"路由刷新后的最终决策",不再需要旧的未刷新代码路径单独维护一份统计逻辑。这也是本次变更删除旧路径的动机:消除双路径统计的不一致,确保观测数据与真实追踪行为严格对齐。
关键边界:pack_trace_reason 与"不可撤销"的已追踪请求
升级到新行为后,有一个必须理解的边界,它在 v1.36.0 的变更记录中已被明确标注,并且在refreshTracing()的源码注释中也有对应说明:
NOTE: if the trace reason have been encoded into the request id then the trace reason may not be updated. That means we may cannot force a traced request to be untraced by the refreshing.
(见 conn_manager_impl.cc 的注释)
这与 UUID 请求 ID 扩展中的pack_trace_reason配置相关,定义于 uuid.proto:
google.protobuf.BoolValue pack_trace_reason = 1;当pack_trace_reason为true(默认值)时,trace reason 会被编码进请求 ID(x-request-id)中,并随请求传播到上游。由于 reason 已固化在请求 ID 里,即使路由刷新后新路由判定"不应采样",已被标记为 traced 的请求也无法在刷新后被取消标记(unmarked)。反之,如果pack_trace_reason设置为false,则 trace reason 不写入请求 ID,刷新后存在将请求从 traced 调整为 untraced 的可能。
因此,在规划依赖该行为的使用场景时,需要明确:
- 若
pack_trace_reason保持默认true,路由刷新只能"从 untraced 变为 traced",或保持 traced 不变,不能反向撤销已追踪请求; - 若你的场景依赖"刷新后撤销追踪",需要显式设置
pack_trace_reason: false,但需自行评估请求 ID 不再携带 trace reason 对端到端传播的影响。
升级影响与迁移检查清单
由于运行时开关已被移除,任何依赖旧行为的配置都将失效。建议按以下清单核查:
- 搜索运行时开关引用:在配置与代码中检索
envoy.reloadable_features.trace_refresh_after_route_refresh。当前仓库源码中该字符串仅存在于变更记录文件(tracing__trace-refresh-after-route-refresh.rst 与历史版本 1.36.0.yaml)中,source/目录已无任何引用,说明该开关从代码层面已彻底下线。若你的运行配置中显式设置过该开关,升级后它会被忽略(建议删除以免产生误导)。 - 确认依赖的追踪语义:若此前依赖"路由刷新后不刷新 trace 决策"的旧行为(例如路由中途切换后仍按原路由采样),升级后采样行为将变化,需在测试环境先行验证。
- 检查统计口径变化:tracing 统计改由
chargeStats统一上报后,旧路径对应的统计计数将不再产生,监控告警若按旧口径设定阈值需同步调整。 - 评估 pack_trace_reason 影响:若在
UuidRequestIdConfig中启用了默认的pack_trace_reason=true,注意刷新后无法撤销已 traced 请求,相关 A/B 或抽样策略需据此设计。
如何验证新行为
可以通过以下方式验证路由刷新后的 trace 行为:
- 配置基于 scoped RDS 或可动态变更的路由配置,配合会在请求中途修改路由相关头(如 scope key)的 HTTP 过滤器,观察请求在路由刷新前后的采样结果变化;
- 开启 tracing 统计(
tracing: random_sampling等)后,对比chargeStats路径上报的统计与最终 span 的采样状态是否一致,验证统计与追踪行为的对齐; - 针对
pack_trace_reason两种取值分别验证:true时确认已 traced 请求刷新后保持 traced;false时确认刷新后决策可被更新,甚至可以反向撤销。
小结
envoy.reloadable_features.trace_refresh_after_route_refresh的移除,标志着"路由刷新后同步刷新 trace 决策与 decorator"从可回退的行为变更正式演变为Envoy 的固有语义。从源码看,refreshTracing()以"重新计算 trace reason → 重新决策采样 → 应用新路由 decorator"三步完成刷新,并直接作用于活跃 span 与请求头;统计则统一收敛到chargeStats,消除了双路径统计的不一致。对于使用者而言,升级后只需重点关注pack_trace_reason带来的"已追踪请求不可撤销"边界,以及统计口径的切换即可平滑迁移。
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考