stream 子系统(L4 代理)
前面都是 HTTP(L7)代理。但 APISIX 还能代理 L4(TCP/UDP)流量——比如 MySQL、Redis、MQTT。今天看 stream 子系统:它和 HTTP 子系统共享架构,但工作在更底层。
L4 vs L7
stream 阶段
init.lua 里有一套平行的 stream 阶段函数(对应 Nginx stream 模块的阶段):
stream_init(:1066)/stream_init_worker(:1082):初始化(类比 http_init)stream_preread_phase(:1120):主处理(类比 http_access_phase)stream_balancer_phase(:1239):选上游stream_log_phase(:1251):日志
stream_preread_phase
init.lua:1120-1160(和 http_access_phase 神似):
function _M.stream_preread_phase()
local api_ctx = core.tablepool.fetch("api_ctx", 0, 32) -- 一样用 tablepool
ngx_ctx.api_ctx = api_ctx
if not verify_tls_client(api_ctx) then return ngx_exit(1) end
core.ctx.set_vars_meta(api_ctx) -- 一样的 ctx 机制
router.router_stream.match(api_ctx) -- stream 路由匹配
local matched_route = api_ctx.matched_route
if not matched_route then return ngx_exit(1) end
-- 取上游(upstream_id / service_id)...
end
router_stream(按 L4 条件匹配,如源 IP、SNI、端口)。这就是为什么 core/ 里的 ctx/config/router/plugin/balancer 设计成"子系统无关"——http 和 stream 都能用。省了大量重复代码。preread 的含义
preread 阶段就是"在把流转发给上游前,先预读一小段"。APISIX 在这个阶段做路由匹配(可能基于预读的内容,如 SNI)。这是 L4 代理能做"基于内容路由"的关键——虽然是 L4,但偷看一眼开头就能智能路由。比如 MQTT 网关按 client-id 路由、TLS 按 SNI 分流。db.example.com:443。TLS 第一个包 ClientHello 里带 SNI=db.example.com。stream_preread_phase 偷看这个 SNI →
router_stream.match 匹配到一条 stream_route(sni=db.example.com)→ 转发到对应数据库集群。全程没解密内容,只瞄了一眼握手信封。👶 小白:L4 都看不懂内容,为什么还能"基于内容路由"?不矛盾吗?
👨🏫 老师:关键在"偷看开头"。它不解析整个流,只在 preread 阶段读最前面几个字节——TLS 的 SNI、MQTT 的 client-id 都在协议开头。够做路由决策就行,剩下的字节照样原样转发、不拆包。这就是 preread(预读)这个名字的由来。
stream 路由与上游
stream 有自己的抽象对象 stream_route(schema_def.lua:960),匹配条件是 L4 的(remote_addr、server_addr、server_port、sni 等),上游还是复用 upstream(Day 08/10 的负载均衡、健康检查全能用)。preread 匹配后,stream_balancer_phase(:1239)选上游实例,转发。
/apisix/stream_routes/{id})。上游(upstream)两边共用——所以一个数据库集群的负载均衡、健康检查配置,L4 代理直接享用 Day 10 学的那套。Day 06 的 merge_service_stream_route(Day 13 见过)就是 stream 版的 service 合并。stream 插件
stream 也有插件(Day 11 plugin.load 里的 load_stream、stream_filter :564)——但因为 L4 没有 HTTP 语义,stream 插件较少(如 mqtt-proxy、ip-restriction、限流类)。
local_stream_plugins),但共用同一套运行引擎。xrpc 协议扩展
Day 03 的 http_init 末尾有 xrpc.init()。xRPC 是 APISIX 的"L4 之上的协议框架"——让你为特定协议(如 Redis、Dubbo)写"协议感知"的代理逻辑(超越纯字节转发)。
今日小结 + 动手
🧠 今天你应该能回答
- L4 和 L7 代理的区别?APISIX 为什么要支持 L4?
- stream 阶段和 HTTP 阶段怎么对称?复用了哪些 core 组件?
- "preread"是什么意思?为什么 L4 也能基于内容路由?
- stream 插件相比 HTTP 插件受什么限制?xRPC 补充了什么?
✋ 动手
cd /Users/bitmart/work/codes/github/apisix
sed -n '1120,1160p' apisix/init.lua
sed -n '960,999p' apisix/schema_def.lua # stream_route
ls apisix/stream/