Day 06 / 共 20 天 · 第 2 周 过滤器与请求
L4 网络过滤器链
第 2 周深入"请求怎么被处理"。先看最底层的 L4(TCP 字节流)过滤器:FilterStatus、ReadFilter/WriteFilter 接口、FilterManager 的读写链表,以及迭代/暂停/恢复机制。
📍 第 2 周(过滤器与请求)· 你在这里(从最底层的 L4 字节流开始)
D6 L4 过滤器→
D7 HCM→
D8 L7 过滤器→
D9 Router→
D10 Codec
L01
过滤器链思想
🤔 痛点:网关要做限速、加密、日志、转发……全塞一个大函数里吗?
如果把所有处理写进一个几千行的
handleConnection(),想加一个"限流"就得改这个巨函数,改错一处全崩。而且不同场景要的功能组合还不一样(有的要限流不要日志)。怎么让功能像积木一样自由增删、互不影响?💡 本质:把处理拆成一节节"工位",串成流水线(今天的类比世界观:机场安检)
答案是过滤器链:数据像行李一样,依次经过一个个独立工位(过滤器),每个工位只干一件事,处理完决定"放行给下一关(Continue)"还是"扣下先别过(StopIteration)"。就像机场安检流水线:一道验登机牌、一道过 X 光、一道人工复查,任何一关都能把你拦下。想加一道安检,插一个工位即可,别的关卡不用动——这就是"可插拔"。
过滤器链 = "流水线"
Envoy 处理请求像工厂流水线:数据依次经过一个个"工位"(过滤器),每个工位做一件事(限速、加密、日志、转发…)。每个过滤器处理完,决定"传给下一个工位"(Continue)还是"先停一下"(StopIteration)。好处:功能可组合、可插拔——想加个限流,插一个限流过滤器进链子即可,不用改别的。L4 过滤器处理原始 TCP 字节流,L7 过滤器处理解析后的 HTTP(Day 08)。今天讲 L4。
L02
FilterStatus(迭代控制)
// envoy/network/filter.h:41
enum class FilterStatus {
Continue, // 继续调用后续过滤器
StopIteration // 停止迭代(等异步操作完成后再 continue)
};
读法:L4 层的迭代控制极简——只有两个值。每个过滤器处理完返回其一:Continue = "我处理好了,交给下一个";StopIteration = "我还没处理完(比如在等一个异步鉴权),先别往下走,我完事了会主动喊继续"。L7 的状态码丰富得多(Day 08)。
👶 小白:返回 StopIteration 之后,被停住的后续过滤器不就永远饿死了吗?
👨🏫 老师:不会。StopIteration 是"先暂停"不是"永久取消"。返回它的那个过滤器,一定是在等某件异步的事(比如问外部鉴权服务)。事情办完,它会主动调 continueReading()(L07 会讲),链子就从暂停点接着往下跑。就像安检员对你说"你这箱子要开箱复查,先靠边等一下"——查完了照样放你走,不是把你扔那不管了。真正"拦死"的场景是过滤器直接关连接或返回错误,那是另一回事。
L03
ReadFilter(读路径)
// envoy/network/filter.h:241
class ReadFilter {
virtual FilterStatus onData(Buffer::Instance& data, bool end_stream) PURE; // :252 读到数据时调用
virtual FilterStatus onNewConnection() PURE; // :260 连接首次建立
virtual void initializeReadFilterCallbacks(ReadFilterCallbacks& callbacks) PURE; // :273
};
读法:ReadFilter 处理"从下游连接读进来"的数据。
onData 是核心(拿到字节缓冲 data,可修改)。onNewConnection 做一次性初始化。回调 ReadFilterCallbacks 提供 continueReading()(恢复被停的链)、injectReadDataToFilterChain()(注入数据)等。HCM(Day 07)就是一个 ReadFilter。L04
WriteFilter(写路径)
// envoy/network/filter.h:123
class WriteFilter {
virtual FilterStatus onWrite(Buffer::Instance& data, bool end_stream) PURE; // :133 数据写往连接时
};
// Filter(:289)同时继承 ReadFilter + WriteFilter,一个实例覆盖读写两路
读法:WriteFilter 处理"往下游连接写出去"的数据(响应方向)。
Filter = 读写都管的过滤器。读=下游进来的、写=返回下游的,方向相反。L05
FIFO vs LIFO
// FilterManager 接口 envoy/network/filter.h:295
addReadFilter() // :315 FIFO 顺序(先加先调)
addWriteFilter() // :303 LIFO 顺序(后加先调)
为什么读是 FIFO、写是 LIFO?
想象过滤器 A、B 按顺序配置(A 在前)。读方向(进来):A→B(先配的先处理进入的数据,FIFO)。写方向(出去):B→A(后配的先处理出去的数据,LIFO)。为什么?因为要对称——数据"进来时 A 先看、出去时 A 后看",就像剥洋葱:进去一层层往里剥(A、B),出来一层层往外包(B、A)。比如 A 是加密过滤器:进来时 A 先解密(最外层),出去时 A 最后加密(最外层)。FIFO/LIFO 保证了这种对称的"洋葱模型"。
图注:A 是最外层。进来 A 先剥(解密),出去 A 最后包(加密)——像剥/包洋葱,方向对称,所以读 FIFO、写 LIFO。
📝 举个例子:A=加解密、B=统计字节数
配置顺序 A、B。请求进来(读 FIFO):A 先把密文解成明文 → B 数明文有多少字节。响应出去(写 LIFO):B 先数一遍出去的明文字节 → A 最后把明文加密成密文发走。
如果写方向也用 FIFO(A 先),A 会先加密,B 再去数——B 数到的是密文乱码,统计就错了。正是 LIFO 保证了 B 永远处理明文、A 永远在最外层管密文。
如果写方向也用 FIFO(A 先),A 会先加密,B 再去数——B 数到的是密文乱码,统计就错了。正是 LIFO 保证了 B 永远处理明文、A 永远在最外层管密文。
L06
onContinueReading(读迭代核心)
// source/common/network/filter_manager_impl.cc:62 —— L4 读的心脏
for (; entry != upstream_filters_.end(); entry++) {
if (!(*entry)->initialized_) { // 首次先调 onNewConnection()
FilterStatus status = (*entry)->filter_->onNewConnection();
if (status == FilterStatus::StopIteration) return;
}
StreamBuffer read_buffer = buffer_source.getReadBuffer();
if (read_buffer.buffer.length() > 0 || read_buffer.end_stream) {
FilterStatus status = (*entry)->filter_->onData(read_buffer.buffer, read_buffer.end_stream);
if (status == FilterStatus::StopIteration) return; // 停止即返回
}
}
读法:就是一个 for 循环遍历读过滤器链,逐个调
onData。任一过滤器返回 StopIteration 就 return(中断迭代)。onRead()(:100)是入口。这个朴素的"遍历 + 可中断"就是过滤器链的执行引擎。L07
暂停与恢复
某过滤器返回 StopIteration 后,链子暂停。等它异步操作完成,调 continueReading()(ActiveReadFilter 回调 parent_.onContinueReading(this, ...))以 this 为起点从 std::next(filter->entry()) 恢复迭代。
为什么要能"暂停"?
有些过滤器要做异步操作——比如调外部鉴权服务问"这个连接允许吗?",这需要时间。它不能傻等(会阻塞整个 worker 线程、拖累其他连接,违背 Day 04 的事件驱动)。所以它返回 StopIteration("我先挂起,别人先处理"),等鉴权结果回来(通过事件循环),再调
continueReading() 从暂停点继续。这就是事件驱动世界里过滤器做异步操作的标准姿势——挂起而非阻塞。连接关闭也有协调机制(disableClose 延迟关闭直到过滤器处理完)。L08
今日小结 + 动手
🧠 今天你应该能回答
- 过滤器链的"流水线"思想?可插拔的好处?
- FilterStatus 两个值各是什么意思?
- ReadFilter/WriteFilter/Filter 各处理什么方向?
- 读 FIFO、写 LIFO 的"洋葱模型"?
- 过滤器为什么要能暂停?怎么恢复?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/envoy
sed -n '41,46p' envoy/network/filter.h # FilterStatus
sed -n '241,281p' envoy/network/filter.h # ReadFilter
sed -n '62,104p' source/common/network/filter_manager_impl.cc # onContinueReading
明天预告 · Day 07:HTTP 连接管理器 HCM——L7 的心脏。它本身是个 L4 ReadFilter,
onData 把字节喂给 codec 解析 HTTP,newStream 为每个请求建 ActiveStream,启动 L7 过滤器链。