Kong作为基于Nginx和OpenResty构建的API网关,其插件体系是核心能力之一。但在实际项目中,不少团队会遇到这样的困惑:明明配置了认证插件,为什么限流插件却先执行了?自定义插件里读取的上下文数据为什么是空的?这些问题的答案都藏在Kong插件的Phase优先级与链式调用机制里。理解这套机制,是写好、用好Kong插件的前提。

Kong请求处理流程中的Phase划分
Kong的插件并不是随意挂载的,而是严格映射到OpenResty的生命周期阶段上。每个插件可以实现若干个handler方法,例如init_worker、rewrite、access、header_filter、body_filter和log。这些方法分别在请求的不同阶段被触发,而阶段本身的先后顺序是固定的,任何插件的priority值都无法改变。
以一个典型请求为例,当客户端请求到达Kong时,首先进入rewrite阶段,此时还没有匹配到具体的Service和Route,所以只有配置为global的插件才会在此阶段生效。接着是access阶段,这是认证、限流、ACL类插件的主战场,此时路由匹配已完成,插件可以拿到完整的消费者和服务信息。上游响应返回后进入header_filter和body_filter阶段,响应转换类插件(如correlation-id、response-transformer)在这里工作。最后log阶段负责收尾,记录日志或上报指标。
需要特别注意的是body_filter可能被多次调用。当响应体以流式方式分块传输时,每个分块都会触发一次该阶段,插件内部必须正确处理is_body_truncated或累积分块的逻辑,否则会出现响应体被截断或重复处理的怪异现象。
priority字段如何决定同一Phase内的插件顺序
既然Phase顺序是固定的,那么插件之间排序就完全由插件声明中的PRIORITY数值决定。Kong在启动时会收集所有已加载插件的优先级,在每个Phase内按照priority从大到小依次调用。数值越大越先执行,这一点非常关键。例如key-auth的priority是1250,rate-limiting是910,所以在access阶段,认证一定发生在限流之前。这个设计是合理的:先确认身份,再基于身份做限流统计。
-- 自定义插件的schema.lua或handler.lua中声明优先级
local MyPlugin = {
PRIORITY = 750, -- 数值越大越先执行
VERSION = "1.0.0",
}
function MyPlugin:access(conf)
-- 此时key-auth(1250)已执行完毕,可以安全读取消费者信息
local consumer_id = kong.client.get_consumer() and kong.client.get_consumer().id
kong.log.info("当前消费者: ", consumer_id or "匿名")
end
return MyPlugin官方对priority的取值有隐性约定:认证类插件通常在1200以上,安全类插件在900到1200之间,转换类插件在700到900,日志类一般在100以下。自定义插件时建议参考这份约定,把自己插件的priority插到合适区间,而不是随意设一个大数。如果自定义认证插件priority设为2000,它会先于key-auth执行,此时消费者尚未认证,依赖消费者身份的逻辑就会拿到空值,产生难以排查的空指针问题。
查看某个插件的实际priority,可以直接阅读其handler.lua源码,或者在Kong的安装目录下搜索PRIORITY字段。也可以在kong.conf所在环境执行kong config相关命令导出生效配置进行确认。排查顺序问题时,第一步永远是确认各插件在目标Phase上的priority排序。
链式调用中的上下文共享与常见陷阱
多个插件在同一个请求中被依次调用,这就是所谓的链式调用。Kong为每个请求维护一个kong.ctx.shared表,它是插件间传递数据的标准通道。先执行的插件写入数据,后执行的插件读取数据,这个模式在组合插件时非常常见。
-- 插件A:priority 1000,先执行
function PluginA:access(conf)
kong.ctx.shared.trace_id = ngx.md5(ngx.req.get_headers()["x-req-id"] or "")
end
-- 插件B:priority 500,后执行
function PluginB:access(conf)
local trace_id = kong.ctx.shared.trace_id
if not trace_id then
return 500, { message = "缺少追踪ID,上游插件未执行" }
end
kong.service.request.set_header("X-Trace-Id", trace_id)
end链式调用有几个高频陷阱。第一,读取顺序依赖priority,如果插件B的priority被调高到插件A之前,shared表里就是空值,因此插件间的数据契约必须与priority配置保持一致,最好在插件文档中明确声明依赖关系。第二,header_filter阶段不能使用会阻塞的API,且此时若想向客户端输出自定义头,应通过kong.response.set_header完成,而不是继续操作请求头。第三,当某个插件在access阶段直接返回响应(比如认证失败返回401),后续插件的access方法将不会执行,这是链式调用的短路特性,设计拦截类插件时要意识到这一点。
实操:验证与排查插件执行顺序
理论清楚之后,最可靠的手段是实际观测。最简单的办法是在每个插件的每个Phase打日志。可以在kong.conf中设置log_level = debug,然后通过docker logs或journalctl观察输出。如果使用DB-less模式或Kong 3.x,还可以借助Admin API查看路由上挂载的插件列表:
# 查看某个路由上启用的插件 curl -s http://127.0.0.1:8001/routes/my-route/plugins | python3 -m json.tool # 列出所有已加载插件及其版本信息 curl -s http://127.0.0.1:8001 | python3 -c "import sys,json;print(list(json.load(sys.stdin)['plugins'].keys()))"
排查时建议按照三步走:先确认插件生效范围(global、Service级还是Route级),同一请求中多层配置会合并,Service级插件与Route级插件会同时生效,这一点常被忽视;再核对各插件在目标Phase上的priority排序;最后用debug日志确认实际的调用序列。多数顺序问题在前两步就能定位。
另外提醒一点,Kong 3.x对插件API做了不少调整,例如PRIORITY的声明位置统一到handler返回表中,部分旧的kong.命名空间函数被拆分。如果从2.x迁移过来,顺序异常也可能是API行为变化导致的,务必对照官方迁移文档逐项核对。掌握Phase固定顺序加priority动态排序这一模型,再配合shared上下文的规范使用,Kong插件的调度行为就完全可预测了。