Day 11 / 共 20 天 · 第 3 周 插件系统
插件机制全景(APISIX 的灵魂)
第 3 周进入 APISIX 的灵魂——插件系统。104 个插件(鉴权/限流/可观测/AI)都是同一套机制。今天看 plugin.lua 怎么加载、按 priority 排序、以及 filter 怎么为一个请求选出该跑的插件。
📍 你在整条链的位置
数据面 W2 ✅→
插件机制(加载/排序/选)→
插件运行 D12→
配置合并/鉴权/典型插件
L01
插件是 APISIX 的灵魂
🤔 网关的功能这么多,都写死在内核里吗?
鉴权、限流、监控、改写、AI 代理…几十上百个功能。全塞内核会臃肿、难维护。
💡 内核小、插件多
APISIX 内核只做"路由 + 把请求交给插件链 + 转发上游",具体功能全靠插件(
apisix/plugins/ 有 104 个)。内核稳定精简,功能靠插件无限扩展。你只启用需要的,不用的零开销。这和 Envoy 的 filter 链、Higress 的 Wasm 插件是同一思想——网关都走"插件化"路线。区别:APISIX 插件是纯 Lua(改起来快、热加载)。L02
一个插件的结构
每个插件是一个 Lua 模块,标准结构:
local _M = {
version = 0.1,
priority = 1002, -- 优先级(越大越先跑,L04)
name = "limit-count",
schema = schema, -- 配置校验 schema(Day 09)
}
function _M.check_schema(conf) ... end -- 校验配置
function _M.rewrite(conf, ctx) ... end -- rewrite 阶段逻辑
function _M.access(conf, ctx) ... end -- access 阶段逻辑
function _M.header_filter(conf, ctx) ... end -- 响应头阶段
function _M.log(conf, ctx) ... end -- 日志阶段
return _M
💡 插件 = priority + name + schema + 若干阶段方法
它想在哪个 Nginx 阶段(Day 02)做事,就实现对应方法(access/header_filter…)。和上一站 wasm-go 的"填阶段钩子"如出一辙——只是这里是 Lua 函数、那里是 Go 回调。
L03
plugin.load 加载
plugin.lua:342-374 的 load:从配置读出要启用的插件名列表,逐个 require 进来,存到 local_plugins。
只加载"启用"的插件
104 个不是全加载——你在 config.yaml 的
plugins: 列表写要启用哪些,APISIX 只 require 这些。没启用的不占内存、不影响性能。启用列表变了会重新加载——插件也能"热更新"。还支持 stream 子系统插件(Day 18)和 wasm 插件(Day 19)。L04
priority 排序:谁先跑
加载后,插件按 priority 排序(越大越先执行)。
priority 决定执行顺序:先鉴权(高)、再限流/改写、最后监控(低)。顺序错了会出问题。
priority 为什么关键
插件有执行顺序——"鉴权"必须在"限流"前(先确认身份再按身份限流)、"改写"要在"转发"前。priority 定这个顺序。鉴权类 priority 高、日志类低。本博客还有专门的
apisix-plugin-order.html 讲优先级表。理解顺序才能理解插件如何协作。L05
阶段方法:只实现需要的
插件通过实现不同阶段方法,在请求生命周期不同点介入(对应 Day 02 Nginx 阶段):
rewrite/access:请求处理(鉴权、限流、改写——决策类)before_proxy:转发上游前最后一改header_filter/body_filter:改响应log:记录/上报
读法:一个插件不必实现所有阶段——只实现它需要的。limit-count 主要在 access(拦截超限);prometheus 主要在 log(上报指标);proxy-rewrite 在 rewrite(改请求)。Day 12 看这些方法怎么被 run_plugin 调起。
L06
filter 选插件(成对存)
plugin.lua:469-520 的 filter(Day 04 access 阶段调它):从"全局加载的插件"里,挑出"这个路由启用了的":
function _M.filter(ctx, conf, plugins, route_conf, phase)
local user_plugin_conf = conf.value.plugins -- 路由/服务配置里的 plugins
for _, plugin_obj in ipairs(local_plugins) do -- 遍历所有已加载插件(按 priority 序)
local plugin_conf = user_plugin_conf[plugin_obj.name]
if type(plugin_conf) ~= "table" then goto continue end -- 这条路由没配这个插件
if check_disable(plugin_conf) then goto continue end -- 显式禁用
core.table.insert(plugins, plugin_obj) -- 选中:插件对象
core.table.insert(plugins, plugin_conf) -- + 它的配置(成对存!)
::continue::
end
return plugins
end
"成对存"的巧思
结果
plugins 是 {对象1, 配置1, 对象2, 配置2, ...}——插件和它的配置成对相邻。这样 run_plugin 遍历时 plugins[i] 是对象、plugins[i+1] 是配置,一步到位(Day 12 的 for i=1,#plugins,2)。因为按 local_plugins(已按 priority 排序)遍历,选出的天然也是 priority 序。L07
启用/禁用/自定义排序
filter 里还有几个细节:
check_disable:插件配置里可写_meta.disable=true临时禁用(保留配置但不跑)。run_policy == "prefer_route":某些插件路由级配置优先于 consumer 级(:494-500)。_meta.priority(:513):单个插件实例可覆盖默认 priority,此时custom_sort重排。
读法:这些让插件编排更灵活:能临时禁用、能针对某路由调整顺序。Day 13 讲配置合并时会再见到这些优先级规则。
L08
今日小结 + 动手
🧠 今天你应该能回答
- 为什么说插件是 APISIX 的灵魂?"内核小插件多"的好处?
- 一个插件的标准结构(priority/name/schema/阶段方法)?
- priority 决定什么?为什么鉴权 priority 要高?
- filter 怎么选插件?"成对存"有什么好处?
✋ 动手
cd /Users/bitmart/work/codes/github/apisix
sed -n '342,374p' apisix/plugin.lua
sed -n '469,520p' apisix/plugin.lua
grep -n "priority" apisix/plugins/key-auth.lua apisix/plugins/limit-count.lua
明天预告 · Day 12:插件运行——
run_plugin 怎么在每个阶段遍历选中的插件、调阶段方法、处理插件返回的"提前结束"(拦截);以及 global_rules 全局插件。