Day 09 / 共 20 天 · 第 2 周 过滤器与请求
Router 路由过滤器
Router(router.cc,2600+ 行)是 decode 链的终端过滤器——它选上游、发请求、收响应,把响应灌回 encode 链。它是"请求进"和"响应出"的交汇点。
📍 第 2 周(过滤器与请求)· 你在这里(前 3 周知识的交汇点)
D6 L4 过滤器→
D7 HCM→
D8 L7 过滤器→
D9 Router→
D10 Codec
L01
终端过滤器
🤔 痛点:前面的过滤器都"处理完往下传",那"最后往哪传"?
Day 08 的认证、限流过滤器处理完都调 Continue 交给下一个。可 decode 链总得有个尽头——总得有人真正把请求发到后端,并把后端的响应拿回来。这个"最后一站"就是 Router。它一个人扛下了"选后端、发请求、等响应、失败重试"这一大摊子和上游打交道的活。
💡 本质:Router = 流水线尽头的"发货 + 收货"工位(今天的类比世界观)
延续 Day 06/08 的流水线比喻:前面的工位都在"加工"这个请求,Router 是最后一个工位,负责"打包发货"——查地址单(读路由)、从一堆分店里挑一家(选集群 + 负载均衡选主机)、走物流专线(连接池)把货发出去。等对方签收回执(上游响应),它再启动"返程流水线"(encode 链)把回执逆序送回客户。发货失败(后端 503)?它会换一家分店重发(重试)。所以 Router 是"请求方向的终点、响应方向的起点"。
Filter(router.h:298)继承 Http::StreamDecoderFilter——它是 decode 链的最后一个过滤器。
"终端过滤器"是什么?
前面的 L7 过滤器(认证、限流…)处理完都调 Continue 往下传。Router 是最后一站——它不再往下传,而是把请求转发到上游后端,然后等响应。它是"请求方向的终点、响应方向的起点"。好比工厂流水线的最后一个工位负责"打包发货(转发上游)",收到回执(响应)后再启动"返程流水线(encode 链)"。所有请求最终都汇到 Router 转发出去(除非被前面的过滤器 sendLocalReply 截胡)。
L02
拿路由
// router.cc:440 Filter::decodeHeaders
route_ = callbacks_->route(); // :471 读 HCM 已缓存的路由(Day 05/07 强调:路由在 HCM 算好)
if (!route_) { sendLocalReply(404); return StopIteration; } // :472 没路由 → 404
// 直接响应(direct response)处理 :483
route_entry_ = route_->routeEntry(); // :512
读法:Router 不算路由,只读 HCM 缓存好的
route() 结果(Day 05 易错点)。没匹配到路由就直接返回 404。routeEntry() 拿到路由条目(含目标集群名、重试策略、超时等)。L03
选集群主机
// router.cc:517 从线程本地拿目标集群(Day 13 的 getThreadLocalCluster)
cluster = config_->cm_.getThreadLocalCluster(route_entry_->clusterName());
cluster_ = cluster->info(); // :529
// :677 cluster->chooseHost(this) 负载均衡选一个上游主机(Day 14 的 chooseHost)
// 同步选到主机 → continueDecodeHeaders(:686)→ return StopIteration(:688 等上游)
读法:把第 3 周的知识用起来了:
getThreadLocalCluster(Day 13 无锁拿集群)→ chooseHost(Day 14 负载均衡选后端)。选到主机后返回 StopIteration——因为要等上游响应,decode 链暂停。L04
发上游请求
continueDecodeHeaders(router.cc:725):createConnPool(:732)拿连接池 → 选主机 → 创建 UpstreamRequest(:831)向上游编码请求。请求体经 decodeData(:975)转发给上游。
连接池:复用上游连接
Router 不为每个请求都新建一条到上游的连接(太慢)。它用"连接池"——把到某个上游主机的连接缓存复用。要发请求时从池里取一条空闲连接(或新建),用完放回。这对性能至关重要:建立 TCP + TLS 连接开销大,复用能省掉大量握手。
UpstreamRequest 代表"这次转发到上游的请求",管理它的生命周期。L05
响应回程(启动 encode 链)
// router.cc:1711 Filter::onUpstreamHeaders(上游响应头到达)
// 处理 gRPC 状态映射、outlier detection(Day 15 被动健康检查上报)
callbacks_->encodeHeaders(std::move(headers), end_stream, ...); // :1927 ★启动 encode 过滤器链!
// onUpstreamData(:1931)→ callbacks_->encodeData(:1945)
读法:上游响应回来,Router 调
callbacks_->encodeHeaders——这就是启动 encode 过滤器链的触发点(Day 08 的 encode 路径从这里开始)。它还顺便向 outlier detection 上报请求结果(Day 15 被动健康检查靠这个累积统计)。L06
重试与超时
Router 还负责重试(上游失败/超时时按路由配置的重试策略换个主机再试)和超时(请求太久没响应就中断)。这些配置来自 route_entry_(路由条目)。
重试为什么在 Router 做?
重试需要"重新选一个上游主机再发一次"——这正是 Router 的职责范围。比如后端 A 返回 503,Router 按重试策略(重试 2 次、只对 5xx 重试)换后端 B 再发。这对用户透明(用户只看到最终结果)。超时同理——Router 起个定时器(Day 04),到点还没响应就放弃/重试。把"选主机 + 发请求 + 重试 + 超时"都放在 Router,因为它掌握着"和上游打交道"的全部上下文。这是 Envoy 提供"弹性"(resilience)的核心。
📝 举个例子:后端 A 抽风返回 503,用户却无感
路由配了重试策略:
请求发到后端 A → A 返回
用户从头到尾只看到一次成功的
retry_on: 5xx,num_retries: 2。请求发到后端 A → A 返回
503 → Router 一看是 5xx、符合重试条件、还没到 2 次 → 重新负载均衡选中后端 B → 发给 B → B 返回 200 → Router 把这个 200 启动 encode 链送回客户端。用户从头到尾只看到一次成功的
200,完全不知道中间 A 挂过。这就是"弹性":单台后端抖动不影响用户体验。L07
交汇点
Router 是理解 Envoy 请求流的枢纽:
• decode 链在这里终结(请求方向的终点)
• 在这里选集群(Day 13)+ 负载均衡选主机(Day 14)+ 用连接池发上游
• 上游响应回来,在这里启动 encode 链(响应方向的起点)
所以 Router 是"decode 和 encode 的交汇点",也是"L7 过滤器世界和上游世界的边界"。搞懂 Router,就把前 3 周(过滤器 + 集群 + 负载均衡)全串起来了。想看更简单的过滤器例子,可读
• decode 链在这里终结(请求方向的终点)
• 在这里选集群(Day 13)+ 负载均衡选主机(Day 14)+ 用连接池发上游
• 上游响应回来,在这里启动 encode 链(响应方向的起点)
所以 Router 是"decode 和 encode 的交汇点",也是"L7 过滤器世界和上游世界的边界"。搞懂 Router,就把前 3 周(过滤器 + 集群 + 负载均衡)全串起来了。想看更简单的过滤器例子,可读
source/extensions/filters/http/ 下的 cors、buffer 等。图注:decode 链在 Router 终结→发上游;上游响应回来在 Router 触发 encode 链。选集群(D13)+选主机(D14)也在这里发生。
👶 小白:既然路由是 HCM 算的、集群是集群管理器管的,Router 好像啥都"借"别人的,那它到底价值在哪?
👨🏫 老师:Router 的价值正是"把这些能力编排成一次完整的上游调用"。它像一个总装师傅:路由结果(HCM 给)、集群(集群管理器给)、主机(负载均衡给)、连接(连接池给)——单独每样都不能完成转发,是 Router 把它们按正确顺序串起来,还叠加了重试、超时、上报健康统计这些"和上游打交道才需要"的逻辑。理解 Router,等于把前 3 周(过滤器+集群+负载均衡)在一个点上全串通了。
L08
今日小结 + 动手
🧠 今天你应该能回答
- "终端过滤器"是什么?Router 为什么是终端?
- Router 拿路由是"算"还是"读"?
- 选集群和选主机分别用哪个前面学过的机制?
- 连接池为什么重要?响应回程怎么启动 encode 链?
- 重试/超时为什么在 Router 做?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/envoy
sed -n '440,530p' source/common/router/router.cc | head -50
grep -n 'getThreadLocalCluster\|chooseHost\|encodeHeaders' source/common/router/router.cc | head
ls source/extensions/filters/http/ | head -30
明天预告 · Day 10(第2周收官):Codec(H1/H2 抽象)——HTTP/1、HTTP/2 怎么被统一抽象成同一接口,让 HCM/过滤器代码零改动支持多协议。端到端 11 步串讲收官。