Day 15 / 共 20 天 · 第 3 周 插件系统

典型插件精读

第 3 周收官。把前面学的插件机制落到实处——精读三个代表性插件:limit-count(限流,最经典)、proxy-rewrite(改写请求)、ai-proxy(AI 代理,呼应上一站)。看真实插件怎么写。

📍 你在整条链的位置(第 3 周 插件系统 · 收官)
鉴权/consumer D14 典型插件精读(机制落地) 服务发现 D16 Admin API D17
L01

三个代表

🤔 痛点:机制学了一堆,真插件到底长啥样? priority、filter、run_plugin、阶段方法、配置合并……前 4 天全是"骨架"。可一个真能限流的插件到底怎么写?光看机制不看肉,还是虚。
💡 本质:三个插件 = 插件的三种典型动作 104 个插件万变不离其宗,动作就三类:拦下(limit-count 限流)、改写(proxy-rewrite 改请求)、代理(ai-proxy 转发 LLM)。就像机场的三种岗位:安检拦人值机改登机牌摆渡车送你上飞机。读透这三个,剩下 100 个都是同款套路换皮。
  • limit-count:限流——"access 阶段拦截超限请求"的典型,还展示了"本地/Redis 多策略"。
  • proxy-rewrite:改写——"rewrite 阶段修改请求(uri/header/host)后转发"的典型。
  • ai-proxy:AI 代理——把请求转成各家 LLM 格式,呼应上一站 Higress ai-proxy。
读法:选这三个是因为它们分别代表插件的三类典型行为:拦截(limit-count)、改写(proxy-rewrite)、代理转发(ai-proxy)。读懂它们,其他 100 个插件都是同样的套路。
L02

limit-count 结构

plugins/limit-count.lua 是薄封装(priority = 1002),真正逻辑在 plugins/limit-count/ 目录:

plugins/limit-count.lua          # 入口(priority/schema/access)
plugins/limit-count/
  init.lua                       # 核心逻辑,按 policy 选实现
  limit-count-local.lua          # 本地计数(单机,共享内存)
  limit-count-redis.lua          # Redis 计数(分布式)
  limit-count-redis-cluster.lua  # Redis 集群
读法:init.lua:26-34policy 配置 require 对应实现(local / redis / redis-cluster)。又是"策略模式"——限流算法固定(滑动窗口计数),存计数的地方可换(本地内存 / Redis)。
L03

三种限流策略

同一个服务 3 个节点,限"每分钟 100 次" local:各节点各算各的 → 实际放行 300 节点1 计100 节点2 计100 节点3 计100 redis:3 节点共享一个计数器 → 全局刚好 100 节点1 节点2 节点3 Redis 计数器
local 快但不准(每节点独立计数);redis 准但多一跳(所有节点共享计数器)。
本地 vs Redis:单机快 vs 全局准 本地(local):计数存在本 worker 的共享内存里——极快,但每个节点各算各的(3 个节点各限 100 = 实际 300)。Redis:计数存 Redis——所有节点共享一个计数器,全局精确限流,但每次要访问 Redis(略慢)。你按需选:单机部署或能接受不精确用 local;多节点要精确全局限流用 redis。这正好呼应上一站 wasm-go Day 15 说的"全局限流必须用 Redis 因为 VM 各自独立"——APISIX 的 worker 之间也是同理。
L04

limit-count access

limit-count.lua:36access(简化逻辑):

function _M.access(conf, ctx)
    -- 按 key(如客户端 IP / consumer 名)取限流器
    -- 对这个 key 的计数 +1,检查是否超过 conf.count(在 conf.time_window 窗口内)
    local delay, remaining = lim:incoming(key, ...)
    if not delay then
        -- 超限了
        return conf.rejected_code or 503, conf.rejected_msg   -- Day 12 的"返回 code 即拦截"
    end
    -- 没超限:设响应头 X-RateLimit-Remaining 等,放行
    core.response.set_header("X-RateLimit-Remaining", remaining)
end
限流的本质:计数 + 阈值 "每分钟最多 100 次" = 对某个 key(IP/consumer/自定义)在时间窗口内计数,到 100 就拒。超限时 return 429/503(Day 12 的拦截机制)。没超限就放行,还贴心地在响应头告诉客户端"你还剩几次"。key 可以是 IP(每 IP 限)、consumer 名(每用户限,配合 Day 14 的鉴权)、或路由——非常灵活。这是最经典、最能体现"access 阶段拦截"的插件。
📝 举个例子(限流 = 排队分流窗口)count=3, time_window=60, key=remote_addr(每 IP 每分钟 3 次)。同一 IP 一分钟内:
第 1、2、3 次 → 放行,响应头 X-RateLimit-Remaining: 2 / 1 / 0
第 4 次 → lim:incoming 返回 nil → return 503 拦下(就像窗口挂出"今日号已放完")。下一分钟窗口重置,又能来 3 次。
L05

proxy-rewrite

plugins/proxy-rewrite.lua:在 rewrite 阶段修改"发给上游的请求"。schema(:53+)支持改 uriregex_uri(正则替换)、hostheadersmethod 等。

-- rewrite 阶段(简化):
if conf.uri then req_set_uri(conf.uri) end              -- 改路径
if conf.regex_uri then ... end                          -- 正则替换路径
if conf.host then ctx.var.upstream_host = conf.host end  -- 改发给上游的 Host
-- 改 headers ...
"改写"是网关常用能力 后端 API 路径和对外暴露的不一样(对外 /api/v1/users、后端 /users)——proxy-rewrite 把 uri 改了再转发。或者给上游加个内部 header、改 Host 做多租户。它在 rewrite 阶段(转发前)改 ctx.var 里的请求属性。注意改 uri/host 可能影响路由——但这已经匹配完路由了(rewrite 在 access 里、路由匹配之后),改的是"发给上游"的,不影响已匹配的路由。
L06

register_var 复习

proxy-rewrite.lua:46-48 用了 Day 05 的 register_var

core.ctx.register_var("proxy_rewrite_regex_uri_captures", function(ctx)
    return ctx.proxy_rewrite_regex_uri_captures
end)
读法:回收 Day 05 伏笔——插件注册自定义变量 $proxy_rewrite_regex_uri_captures(正则改写捕获的分组),之后能在其他配置/日志里用。这展示了插件怎么优雅地"暴露自己算出的值"给 APISIX 变量系统。真实插件里 register_var 用得不少。
L07

ai-proxy

plugins/ai-proxy.lua + plugins/ai-proxy/ + plugins/ai-drivers/:把请求代理到各家 LLM(OpenAI/通义/Claude…),统一协议。还有一大批 AI 插件:ai-prompt-guardai-prompt-templateai-ragai-proxy-multiai-aliyun-content-moderation 等。

又见 AI 网关 这和上一站 Higress 的 ai-proxy 是同一类东西——用 ai-drivers/ 里的各家"驱动"把统一请求转成各厂商格式、代理调用、统一响应。APISIX 也全面拥抱了 AI 网关(README 标题就有"AI Gateway")。它可能在 access 阶段直接调 LLM 并返回(不走普通上游转发)——回想上一站 wasm-go 里 "ai-proxy 直接用 http client 调上游、bypass nginx upstream",APISIX 的 bypass_nginx_upstream(init.lua:490)就是干这个。两大网关在 AI 能力上殊途同归。

👶 小白:ai-proxy 也是插件,那它转发给 LLM,走的还是 Day 08-10 那套 upstream/负载均衡吗?

👨‍🏫 老师:不一定。它常在 access 阶段用自己的 http client 直接把请求打给 LLM 厂商、拿到响应就返回,绕过 nginx 的 upstream 转发——源码里 bypass_nginx_upstream(init.lua:490)就是这个开关。因为"调 OpenAI/通义"要做协议转换、流式改写,不是普通的反代,插件自己接管更灵活。这也呼应了上一站 wasm-go 里 ai-proxy 的同款做法。

L08

今日小结 + 动手(第 3 周收官)

🧠 第 3 周你应该能回答

  • limit-count 怎么用 access + 返回 code 实现拦截?本地 vs Redis 策略?
  • proxy-rewrite 在哪个阶段、改什么?会影响路由吗?
  • ai-proxy 和上一站 Higress ai-proxy 的共性?bypass_nginx_upstream 是什么?
  • 三类插件行为(拦截/改写/代理)各自的典型阶段?

✋ 动手

cd /Users/bitmart/work/codes/github/apisix
ls apisix/plugins/limit-count/
sed -n '36,60p' apisix/plugins/limit-count.lua
sed -n '44,80p' apisix/plugins/proxy-rewrite.lua
ls apisix/plugins/ | grep -i ai
下周预告 · 第 4 周高级子系统与运维——服务发现(动态获取上游)、Admin/control API(配置管理入口)、stream 子系统(L4 TCP/UDP)、SSL/secret/wasm,最后部署 + 三种网关大对比收官。
← Day 14 Day 16 · 服务发现 →