第 4 章:扩展机制概述——插件、中间件与服务端扩展

API 网关装好之后,是不是就能直接用了?嗯,基本功能确实够了。但真实业务哪有那么规整?

限流要按用户等级打折、认证要从第三方接口拉数据、日志需要特殊格式……这些需求,平台工程团队迟早会遇到。靠网关内置功能硬凑,要么改内核代码,要么等着被压垮。

这时候就需要 扩展机制 上场了。说白了,就是给网关开几个“后门”,让你在不改核心代码的前提下,插进去自己的逻辑。

今天咱们就捋一捋主流的三种扩展路子:插件(Plugin)、中间件(Middleware)、服务端扩展(Lua / Go / WASM)。我会结合项目里踩过的坑,帮你搞清楚什么时候该用哪个。

API 网关扩展机制体系 API 网关运行时 插件 (Plugin) 中间件 (Middleware) 服务端扩展 认证插件 限流插件 预编译 / 热加载 请求日志 CORS 构成请求处理管道 Lua Go WASM 自定义 不同语言 / 沙箱 选型核心:性能 vs 灵活性 vs 运维成本

1. 插件——最成熟的“即插即用”

大部分商业网关和开源网关(比如 Kong、APISIX)都走插件路线。插件是编译期或启动时加载的模块,一般用网关宿主语言写成(比如 Kong 的 Lua、APISIX 的 Lua / Java / Python)。

优点很明显:

  • 隔离性好——每个插件独立运行,一个崩了不影响其他
  • 生态丰富——社区插件随便装,省了自己造轮子
  • 性能高——因为是原生语言编译,执行效率接近手写

缺点呢?灵活度有限。插件能 hook 的点是事先定死的,不是你想在哪插就在哪插。我早年在一个项目里,需要拦截响应体做动态改写,结果那款网关的插件 hook 只支持请求阶段,响应体压根儿拿不到……后来只能换路子,愣是用中间件曲线救国。

我的建议:如果需求在社区插件列表里能找到,优先用插件。别自己写,维护成本比你想象的高。

2. 中间件——请求处理管道里的“钩子”

中间件这个概念,后端同学应该不陌生。Express、Koa 里用过吧?在网关里,中间件就是挂在请求处理链路上的一个个函数,可以控制请求什么时候进入、什么时候返回。

典型的案例如下:

  • 请求日志记录(日志中间件)
  • 跨域处理(CORS 中间件)
  • 自定义认证(替换默认鉴权)
  • 请求体预处理(比如 JSON 校验)

中间件和插件的区别在于:

维度 插件 中间件
加载方式 编译期 / 部署时静态加载 运行时动态挂载(通常可热插拔)
影响范围 全局或路由级 按路由、按服务、按条件灵活配置
性能损耗 较低(预编译) 中等(每次请求都要进管道)
开发难度 中等(需了解插件 SDK) 低(纯函数,写个中间件类就行)

不过中间件用多了也有坑。我见过一个团队,在网关里挂了 17 个中间件,每个都干点小事情。结果呢?每次请求都要遍历一遍管道,延迟从 5ms 飙升到 80ms。其实有几个中间件完全可以用配置代替的。嗯,别拿中间件当万能胶。

避坑指南:我曾经因为中间件顺序写错,导致认证中间件在日志中间件之后执行,结果日志里记录了未认证的请求信息,排查安全问题费了好大劲。记住:中间件顺序就是执行顺序,谨慎调整。

3. 服务端扩展——用 Lua、Go、WASM 写自己的逻辑

当插件和中间件都满足不了你的需求时,就该上服务端扩展了。说白了,就是允许你在网关进程中运行一段你自己写的代码,能够拦截、修改、甚至替换请求和响应。

3.1 Lua 扩展

最经典的方案。OpenResty 生态让 Lua 在网关领域大放异彩。Kong、APISIX 底层都内置了 Lua 虚拟机。

  • 优点:性能非常高(JIT 编译),热更新,开发效率高
  • 适用场景:复杂请求路由、动态限流策略、请求/响应体处理
  • 我的经验:我做过一个动态灰度发布功能,用 Lua 在网关层解析请求头,根据用户 ID 哈希分流到不同版本的服务。写起来快,调试也不麻烦。
-- 一个简单的 Lua 限流示例
local limit = require("resty.limit.req")
local lim, err = limit.new("my_limit", 100, 200)
if not lim then
    ngx.log(ngx.ERR, "failed to instantiate limit: ", err)
    return
end

local delay, err = lim:incoming("user_123", true)
if not delay then
    ngx.exit(429)  -- 返回 Too Many Requests
end

3.2 Go 扩展

Go 的性能和并发能力让它成为不少新网关的选择(比如 APISIX 的 Go runner)。你需要写一个单独的 Go 服务,网关通过进程间通信(一般是 Unix socket)调用它。

  • 优点:强类型,社区庞大,goroutine 处理高并发是强项
  • 缺点:部署时需要额外启动一个进程,调用有网络开销
  • 适用场景:需要调用外部服务、处理复杂业务逻辑(比如调用内部的推荐引擎)
小窍门:Go 扩展的进程间通信(Unix socket)延迟很低,大约 0.1ms 级别,但如果你频繁写磁盘日志,这个开销会放大。建议把日志异步发送到集中式服务,别让扩展扛 I/O。

3.3 WASM 扩展

WASM(WebAssembly)是近年兴起的新方案。网关内嵌入一个 WASM 运行时,跑你编译好的 wasm 字节码。

  • 优点:语言无关(C/C++/Rust/Go 都能编译成 WASM),沙箱隔离,安全
  • 缺点:运行时还不成熟(GC 弱,内存有限),生态较小
  • 我的看法:这个方向有前景,但现阶段更适合偏安全的隔离场景,比如多租户插件市场。如果想试试,建议从 Rust 入手。
注意:WASM 扩展的启动体积很小,但如果你在里面做大量字符串操作(比如 JSON 序列化/反序列化),性能会显著下降。我曾经用它处理过流量镜像,CPU 占用直接翻倍。最后还是换回 Lua 了。

4. 如何选型?一张对比表送给你

特性 插件 中间件 Lua 扩展 Go 扩展 WASM 扩展
学习成本 低 低 中 中(需懂 Go) 高(需编译链)
性能 高 中 高 中(有 IPC 损耗) 中(沙箱开销)
灵活性 低(受限 hook 点) 中 高 高 高
热更新 部分支持 支持 支持 不支持(需重启进程) 支持(加载新字节码)
生态 丰富 中等 丰富(OpenResty) 一般 薄弱

最后说句大实话:没有万能扩展。平台工程的取舍,永远是业务复杂度与运维成本的平衡。别为了炫技选择冷门方案,也别为了省事把所有逻辑都堆在中间件里。多用表格对比给领导看,该上 Lua 就上 Lua,该装插件就装插件——稳定比什么都重要。