Day 05 / 共 20 天 · 第 1 周收官

请求入口全景

把前 4 天串起来:一个连接从被 accept 到进入过滤器链,完整走一遍。这是第 2 周深入过滤器前的"地图预览"。收官第 1 周。

📍 第 1 周收官 · 把前 4 天串成一条"请求进门"的链路
D1 全景 D2 启动路径 D3 线程模型 D4 事件循环 D5 请求入口(收官)
L01

完整链路(都在 worker 线程)

🤔 前 4 天学了一堆零件,它们怎么拼成一条流水线? Day 03 的 worker 线程、Day 04 的事件循环、Day 01 的 Listener/Filter……单独看都懂了,但"一条真实连接进来后,这些零件按什么顺序接力"还是模糊的。今天不学新零件,专门把它们串成一条完整链路——这是进第 2 周(钻进过滤器)前的"地图预览"。
💡 本质:请求入口 = 小区门口的"接客三道岗" 延续 Day 01 的门卫世界观:一条连接进门要过三道岗——①门卫先偷看你几眼判断你是谁(监听器过滤器,peek 字节探测 TLS/HTTP)→ ②过 TCP 层安检(L4 网络过滤器)→ ③如果是 HTTP,交给"会说 HTTP 的专职前台"(HCM)做精细接待(L7 过滤器)。信息越往后越清晰:从"一堆字节"到"一条 TCP 连接"再到"一个结构化的 HTTP 请求"。
监听 socket 就绪 → ActiveTcpListener::onAccept(active_tcp_listener.cc:80)
连接均衡,可能改派给别的 worker(onAcceptWorker :108, post :166)
监听器过滤器ActiveTcpSocket::continueFilterChain active_tcp_socket.cc:112)
newConnection选过滤器链findFilterChain filter_chain_manager_impl.cc:556)
网络过滤器createNetworkFilterChain
连接可读 → 跑网络读过滤器FilterManagerImpl::onContinueReading :62)
HCM::onData(会说 HTTP 的网络过滤器,conn_manager_impl.cc:539)→ 解析 HTTP → L7 过滤器链
Router::decodeHeaders(选路由+集群,转发上游,router.cc:440)
读法:这条链把 Day 01-04 的知识串起来了:worker 线程(Day 03)的事件循环(Day 04)监听到连接就绪,一路经三种过滤器处理,直到 Router 转发。后面每周深入其中一段。
接客三道岗:信息由粗到细(字节 → 连接 → HTTP) 连接进门accept ①监听器过滤器偷看字节·探测协议 ②L4 网络过滤器TCP 字节流 HCM(桥)字节→HTTP ③L7 过滤器→Router结构化 HTTP 这是什么流量? TCP 代理/限速 认证/限流/改 header
图注:三道岗对应"三种过滤器"(L04 详列)。HCM 是把 L4 字节翻译成 L7 HTTP 的"桥"(L06)。
🚶 第一人称:现在你是一条刚 accept 的连接 "我刚被 worker 的监听 socket 接进来(Day 03 说内核用 SO_REUSEPORT 把我分给了 worker 2)。门卫先偷看我头几个字节——'哦,是 TLS 握手,客户端要访问 shop.com'(监听器过滤器 peek 出 SNI)。门卫按 SNI 从多条过滤器链里挑了一条接待我(L05 选链)。接着我这堆字节被喂进 L4 网络过滤器链,最后一站是个特殊过滤器 HCM,它把我解密、解析成一个 HTTP 请求,再交给 L7 过滤器做认证限流。全程我一直待在 worker 2 这一个线程里,没跳过线程。"
L02

accept 连接

每个 worker 有一个 ConnectionHandlerImplconnection_handler_impl.cc:40),管理它的 listener。监听 socket 就绪(Day 04 的 IO 事件)触发 ActiveTcpListener::onAcceptactive_tcp_listener.cc:80)——每来一条连接触发一次。

读法:借助 SO_REUSEPORT,每个 worker 有自己的监听 socket 分片(getListenSocket(worker_index)),内核帮忙把新连接分到各 worker——天然负载均衡、无锁。这是 Day 03 "每 worker 独立"在接入层的体现。
L03

连接均衡

onAcceptWorker:108)做连接均衡::117-124 若目标 worker 不是自己则 target_handler.post(std::move(socket)) 把连接改派给别的 worker。

为什么还要"再均衡"? 虽然内核(SO_REUSEPORT)已经分连接了,但可能不够均匀(比如某 worker 攒了一堆长连接)。Envoy 可选地做一次"连接均衡":如果发现某 worker 负载明显偏高,就把新连接 post 给更闲的 worker 处理。注意用的还是 post(Day 04)——把连接的所有权转移到目标 worker 的线程里,之后这条连接就归它了。这体现了"连接一旦定了 worker 就不跨线程"的原则(Day 03)。
L04

三种过滤器

Envoy 有三个层次的过滤器,请求入口会依次遇到:

  • 监听器过滤器(Listener Filter):连接建立前、在裸 socket 上运行,可 peek 字节探测 SNI/ALPN/协议(如 tls_inspectorhttp_inspector)。接口 envoy/network/filter.h:423
  • 网络过滤器(L4,Network Filter):作用于连接字节流(读/写)。接口 filter.h:241/123。(Day 06 详讲)
  • HTTP 过滤器(L7):在 HCM 内部、对已解析的 HTTP 运行。接口 envoy/http/filter.h。(Day 08 详讲)
三层过滤器 = 三个处理时机 监听器过滤器最早——连接刚建立、还没解析任何协议,用来"偷看"头几个字节判断这是什么流量(是 TLS 吗?是 HTTP 吗?)。网络过滤器(L4)处理原始字节流(TCP 层面,比如 TCP 代理、限速)。HTTP 过滤器(L7)处理解析后的 HTTP(认证、限流、改 header)。三层由浅入深:字节 → 连接 → HTTP。越往后信息越结构化,能做的处理越精细。这三层是第 2 周的主角。
L05

选过滤器链

findFilterChainfilter_chain_manager_impl.cc:556)按"最具体匹配"选一条过滤器链:

① 目的端口 destination_ports_map_(:568,回退端口 0)
② 目的 IP findFilterChainForDestinationIP(:605,用 LC-Trie 高效匹配)
③ SNI findFilterChainForServerName(:622,读 socket.requestedServerName(),先精确再通配)
读法:一个 Listener 可以有多条过滤器链,按"目的端口/IP/SNI"选一条。比如同一个 443 端口,按 SNI(客户端要访问的域名)选不同的 TLS 证书和后续处理。选中后建传输 socket、建 ServerConnection、装网络过滤器链(含 HCM)。
📝 举个例子:同一个 443 端口,按域名选不同的链 Listener 监听 0.0.0.0:443,配了两条过滤器链:链 A 匹配 SNI=shop.com(用商城证书),链 B 匹配 SNI=admin.com(用后台证书 + 额外 IP 白名单过滤器)。
客户端 TLS 握手带 SNI=admin.com → 监听器过滤器 peek 出这个 SNI → findFilterChainForServerName 命中链 B → 用后台证书解密 + 走白名单校验。换成 shop.com 就走链 A。一个端口,多套接待方案,按"你要找谁"来分流。
L06

HCM 桥接 L4→L7

HTTP 连接管理器(HCM)本身是一个网络(L4)读过滤器——这是 L4 到 L7 的桥。连接可读 → 网络过滤器链跑 → 最后一个是 HCM → HCM::onDataconn_manager_impl.cc:539)→ codec_->dispatch(data) 解析 HTTP → 触发 L7 过滤器链。

HCM:一个"会说 HTTP 的网络过滤器" 这是 Envoy 一个精妙的设计:L4(字节流)和 L7(HTTP)不是两套独立系统,而是通过"HCM 是一个特殊的 L4 网络过滤器"连接起来。网络过滤器链处理原始字节,走到 HCM 这个过滤器时,它把字节喂给 HTTP 解析器(codec),解析出 HTTP 请求后,再启动 L7 HTTP 过滤器链。所以从 L4 到 L7 是无缝衔接的——HCM 是那个"翻译官 + 分发器"。Day 07 整天讲 HCM。
L07

路由在哪算(易错点)

一个常见误解:很多人以为路由是在 Router 过滤器里算的。其实不是!HTTP 路由匹配发生在 HCM 的 refreshCachedRoute()conn_manager_impl.cc:1822),在请求头解码时就算好并缓存。Router 过滤器(router.cc:440)只是读取已缓存的 route() 结果去转发。
为什么这样设计?因为路由结果可能被多个 L7 过滤器用到(不只是 Router),提前算好缓存,大家共享。记住这个易错点,读源码不会找错地方。
L08

第 1 周收官 🎓

🧠 第 1 周(架构与线程)你已掌握

  • Day 01 Envoy 全景、代理概念、目录、六大术语、在 higress 的角色
  • Day 02 启动路径 main → MainCommon → InstanceImpl → 主线程事件循环
  • Day 03 线程模型:1 主 + N worker + ThreadLocal 无锁(招牌设计)
  • Day 04 Dispatcher 事件循环:libevent、非阻塞 IO、post、延迟删除
  • Day 05 请求入口:accept → 连接均衡 → 三种过滤器 → 选链 → HCM 桥接

你现在理解了 Envoy 的"骨架和引擎":怎么启动、为何快(无锁线程模型 + 事件驱动)、请求怎么进来。第 2 周深入"血肉"——过滤器链怎么逐层处理请求。

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/envoy
sed -n '80,170p' source/common/listener_manager/active_tcp_listener.cc | head -40
sed -n '556,630p' source/common/listener_manager/filter_chain_manager_impl.cc | head -40
grep -n 'refreshCachedRoute' source/common/http/conn_manager_impl.cc | head
明天预告 · Day 06(第2周开始)L4 网络过滤器链——FilterStatus(Continue/StopIteration)、ReadFilter/WriteFilter 接口、FilterManager 的读写链表(FIFO/LIFO)、onContinueReading 迭代。
← Day 04 事件循环 Day 06 · L4 网络过滤器链 →