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-374load:从配置读出要启用的插件名列表,逐个 require 进来,存到 local_plugins

只加载"启用"的插件 104 个不是全加载——你在 config.yaml 的 plugins: 列表写要启用哪些,APISIX 只 require 这些。没启用的不占内存、不影响性能。启用列表变了会重新加载——插件也能"热更新"。还支持 stream 子系统插件(Day 18)和 wasm 插件(Day 19)。
L04

priority 排序:谁先跑

加载后,插件按 priority 排序(越大越先执行)。

按 priority 从高到低执行(左先跑) key-auth 鉴权priority 2500 limit-count 限流priority 1002 proxy-rewrite 改写priority 1008 prometheus 监控priority 500
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-520filter(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 全局插件。
← Day 10 Day 12 · 插件运行 →