Day 06 / 共 20 天 · 第 2 周 路由/配置/数据面

路由 router(请求的第一个大决策)

第 2 周拆数据面。路由是"请求进来后第一个大决策":这个 uri/host/method 该走哪条配置?今天看 router.lua 怎么用 radixtree(前缀树)在几千条路由里微秒级匹配。

📍 你在整条链的位置
请求进入 匹配路由 radixtree 合并配置/选插件 上游
L01

路由要解决什么

🤔 几千条路由,怎么快速找到匹配那条? 你可能配了几千条路由(/api/user/*→服务A、/api/order/*→服务B、host=a.com→C…)。请求进来,要在几千条里快速找到匹配那条。逐条比对(O(n))路由多了就慢。
💡 用 radixtree(前缀树),匹配代价与路由数无关 APISIX 用 radixtree——把路径组织成树,匹配是 O(路径长度) 而非 O(路由数),几千条也能微秒级。这就是路由子系统的核心任务:又快又准地"选路"。
L02

radixtree 为什么快

/api /user /order → 服务A(路由1) → 服务B(路由2) 请求 /api/user/1 顺着树走: /api → /user → 命中路由1 只走 2 步,跟"有几千条 路由"无关!
radixtree(前缀树):路由按公共前缀组织成树,匹配时顺树走——代价只跟路径长度有关,跟路由总数无关。
像查字典 找 "apple" 不翻遍所有词,而是先 a、再 ap、再 app…按前缀逐层缩小。radixtree 就是这个思想。路由再多,匹配只跟"路径多长"有关。而且它是 C 实现(lua-resty-radixtree,FFI 调用),比纯 Lua 快得多。除 uri,还能按 host/method/header/参数/优先级组合匹配。
L03

可插拔路由

router.lua:74-93http_init_worker 按配置选路由实现:

local router_http_name = "radixtree_uri"     -- 默认
if conf.apisix.router then router_http_name = conf.apisix.router.http or router_http_name end
local router_http = require("apisix.http.router." .. router_http_name)
💡 路由算法可换(策略模式) 支持多种:radixtree_uri(按 uri)、radixtree_host_uri(按 host+uri)、radixtree_uri_with_parameter 等,在 apisix/http/router/。你在 config.yaml 选用哪个。SSL 路由(按 SNI 选证书)也可插拔(radixtree_sni)。又是"策略模式"——和 wasm-go/前面项目一脉相承。
L04

http_init_worker:三个路由器

router.lua:74-96:装 router_http(uri 业务路由)、router_ssl(SNI 证书路由)、api(内部管理 API 路由)。http_route.init_worker(filter) 建立对 etcd /routes 的 watch(Day 07)——路由配置变了,重新喂给 radixtree 建树。

读法:filter 是每条路由存入前的预处理函数(下一节)。三个路由器:http(业务)、ssl(证书)、api(内部管理)。
L05

filter 预处理:把重复计算前移

router.lua:29-50filter(route)——每条路由存入前预处理:host 转小写、上游预解析、标记 has_domain。

local function filter(route)
    if route.value.host then route.value.host = str_lower(route.value.host)  -- host 转小写
    elseif route.value.hosts then for i,v in ipairs(route.value.hosts) do route.value.hosts[i] = str_lower(v) end end
    apisix_upstream.filter_upstream(route.value.upstream, route)  -- 预处理上游
end
"存入时预处理"是优化 host 转小写、上游预解析这些活,如果每次请求都做就浪费。放在"路由存入内存时"做一次,之后每次匹配直接用处理好的结果。配置不常变、请求很频繁——在配置变更时多算点、请求时省点,划算(回想 Day 05 的惰性求值思路:都是"把计算放到最省的时机")。
L06

match 匹配

回到 Day 04:init.lua:654router.router_http.match(api_ctx)。match 把当前请求的 uri/host/method/args 喂给 radixtree,返回匹配路由,放进 api_ctx.matched_route

读法:匹配还考虑优先级(多条都匹配时选 priority 高的)和各种条件(vars 表达式、自定义匹配函数)——radixtree 都支持。匹配到的路由对象里就有插件配置、上游引用,供后续(Day 04 的合并、选插件)使用。
L07

router_ssl / api:路由能力的复用

  • router_sslradixtree_sni):TLS 握手时按 SNI(客户端请求的域名)选证书——在 ssl_phase(Day 19)用。
  • apiapi_router):APISIX 内部 API 的路由(插件暴露的 /apisix/* 接口),和业务路由分开。
"路由匹配"能力被复用到证书选择 你可能有几百张证书,握手时客户端说"我要 a.com"(SNI),网关要快速找到 a.com 的证书——这也是"从一堆规则里按名字快速找",正好用 radixtree(按 SNI)。APISIX 把"路由匹配"这个能力复用到了证书选择上。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 路由要解决什么?为什么不能逐条比对?
  • radixtree 为什么快(跟路由数无关,像查字典)?
  • 路由为什么可插拔?有哪三个路由器?
  • filter 预处理为什么放"存入时"而非"匹配时"?

✋ 动手

cd /Users/bitmart/work/codes/github/apisix
sed -n '29,96p' apisix/router.lua
ls apisix/http/router/
明天预告 · Day 07:APISIX"全动态"的核心机密——config_etcd 怎么 watch etcd、把配置变更增量同步到内存,让"改配置秒级生效、零 reload"成为可能。
← Day 05 Day 07 · etcd →