APISIX
收录于 APISIX 多租户网关治理
用 APISIX 做企业级限流,别先把套餐写死在 Route 上
多租户 API 一旦开始分免费版、付费版和入口防护,限流就不只是某个 Route 上的插件参数问题。真正要先拆开的,是入口层、套餐层和路由职责。
这组文章的起点,其实来自开放组件团队——他们先把这套治理思路做了出来,我在其中作为接口人,把它落到我们组这边的业务和交付链路里。实际往下推时越来越觉得,这套拆法不是纸面架构,落地性很强。对象职责清楚,协作边界清楚,后面的接入、升级和治理动作都能顺着它往下收。也正因为这样,我后来才把这几篇文章单独整理出来,想把这套方案为什么行得通讲清楚。
很多团队第一次用 APISIX 做限流,动作都差不多:先挑一个 Route,把 limit-count 挂上去,阈值一填,接口能挡住一部分高峰,请求超限也会直接回 429。早期这么做没什么问题,省事,而且立刻见效。
麻烦是业务不会一直停在“一个接口一个阈值”这个阶段。
只要系统开始往 ToB 或多租户方向走,流量很快就会分成几层。入口层要先挡掉明显异常的访问,套餐层要区分免费版和付费版,同一个 API 还会被不同租户以不同频率调用。到了这一步,如果你还把限流理解成“在某个 Route 上再补一个插件”,配置马上就会越写越拧巴。
我后来把这件事拆成了两步。第一步先承认:入口防护和套餐控制不是一回事。第二步再承认:套餐也不是路由自己的字段。
一开始好用,往前走就会拧巴
Route 上挂限流,最适合的是那种很直接的场景:接口不多,调用方也没分层,大家先共用一套规则。
但企业场景里,先冒出来的通常不是“某个参数该调到多少”,而是下面这些现实动作:
- 同一个
/api/v1/data,免费版一小时只能调 1000 次,付费版能调 10 万次。 - 某个业务路由忘了单独加限流,但入口层还是得先拦一层异常 IP。
- 一个客户升级套餐时,你希望改的是客户归属,不是把所有 Route 再翻出来改一遍。
这几个诉求混在一起时,Route 就很容易失真。它本来应该负责的是接口匹配、认证开启、上游转发,结果最后变成了套餐、身份、入口防护全往里塞的总开关。
短期当然也能跑,不过后面一旦 API 数量和租户数量一起涨,配置会越来越像一坨胶水。
入口层先放一道粗防线
我更愿意先把最粗的一层拿出来,交给 Global Rule。
原因很简单。这一层根本不关心调用方是谁,也不关心他买的是哪档套餐。它只干一件事:所有请求先过一遍默认防护,别让系统入口裸奔。
这里有个很容易写偏的细节,我后来会直接在示例里说透:如果前面已经是多节点 APISIX,limit-count 默认的 policy=local 只会在当前节点内存里计数,不会天然形成跨节点共享额度。所以入口层如果要表达“整个网关集群共用的一道粗挡板”,我会直接把示例写成 Redis 共享计数;不然它更像每个节点各挡一层。
像这种场景,我一般会直接按来源 IP 做一层宽松限流:
curl -X PUT "http://127.0.0.1:9180/apisix/admin/global_rules/1" \
-H "X-API-KEY: ${admin_key}" \
-H "Content-Type: application/json" \
-d '{
"plugins": {
"limit-count": {
"count": 2000,
"time_window": 60,
"key": "remote_addr",
"policy": "redis",
"redis_host": "127.0.0.1",
"redis_port": 6379,
"redis_password": "password",
"redis_database": 0,
"rejected_code": 429
}
}
}'
这层我不会追求特别精细。它的价值在于不管后面哪个业务路由漏配了什么,入口都还有一道统一生效的挡板。
先有这一层就行。
这个思路定下来以后,套餐该放哪就比较清楚了——不该放在这里,因为这里面对的是所有请求,而不是某个租户。
套餐层别落在 Route,上到 Consumer Group
开始明显省事的一步,是把套餐从 Route 上拿下来。
我现在更认可的拆法是:
- Route 只负责接口、认证、转发。
- Consumer 负责“谁在调用”。
- Consumer Group 负责“这一类调用方共享什么额度和策略”。
这样一来,同一个 API 就不用再把免费版、付费版、企业版的区别硬编码在路由配置里。Route 先保持纯粹,只把识别链路打通就够了,比如开一个 key-auth:
curl -X PUT "http://127.0.0.1:9180/apisix/admin/routes/data_route" \
-H "X-API-KEY: ${admin_key}" \
-H "Content-Type: application/json" \
-d '{
"uri": "/api/v1/data",
"plugins": {
"key-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
然后把套餐额度挂到 Consumer Group:
curl -X PUT "http://127.0.0.1:9180/apisix/admin/consumer_groups/free_plan" \
-H "X-API-KEY: ${admin_key}" \
-H "Content-Type: application/json" \
-d '{
"plugins": {
"limit-count": {
"count": 1000,
"time_window": 3600,
"rejected_code": 429,
"policy": "redis",
"redis_host": "127.0.0.1",
"redis_port": 6379,
"redis_password": "password",
"redis_database": 0,
"group": "free_plan_quota"
}
}
}'
curl -X PUT "http://127.0.0.1:9180/apisix/admin/consumer_groups/paid_plan" \
-H "X-API-KEY: ${admin_key}" \
-H "Content-Type: application/json" \
-d '{
"plugins": {
"limit-count": {
"count": 100000,
"time_window": 3600,
"rejected_code": 429,
"policy": "redis",
"redis_host": "127.0.0.1",
"redis_port": 6379,
"redis_password": "password",
"redis_database": 0,
"group": "paid_plan_quota"
}
}
}'
最后,具体租户只需要作为 Consumer 绑定到对应 group:
curl -X PUT "http://127.0.0.1:9180/apisix/admin/consumers" \
-H "X-API-KEY: ${admin_key}" \
-H "Content-Type: application/json" \
-d '{
"username": "free_user_01",
"plugins": {
"key-auth": {
"key": "free-key-01"
}
},
"group_id": "free_plan"
}'
这时候,同一个 /api/v1/data 可以继续只保留一条 Route,但免费用户和付费用户已经开始走两套不同的额度。配置关系一下子顺了很多,因为额度终于跟“调用方类型”绑在了一起,而不是跟“接口地址”绑在一起。
同样要补一句边界:如果你想表达的是多节点 APISIX 集群上的统一套餐额度,这里也不能偷懒用默认 local 计数。套餐层的共享额度要么走 Redis / Redis Cluster,要么就老老实实承认它只是单节点局部额度。
拆到这里,整体的层次和对象关系差不多是这样:
flowchart TD
REQ(["所有请求"]) -->|"不分身份,先过防护"| GR["Global Rule · 入口层<br/>按 remote_addr 粗拦<br/>Redis 共享计数"]
GR -->|"通过后进入业务路由"| RT["Route · 路由层<br/>接口匹配 + key-auth + 转发<br/>不挂限流插件"]
RT -->|"key-auth 识别身份"| C["Consumer · 身份层<br/>绑定具体租户"]
C -->|"group_id 绑定套餐"| CG["Consumer Group · 套餐层<br/>free_plan: 1000次/小时<br/>paid_plan: 10万次/小时"]
这套拆法真正省的,不是几行配置
我更在意的是后面的维护动作。
如果套餐写在 Route 上,很多日常动作都会变重:
- 新租户接入,要不要改多条业务路由。
- 用户升级套餐,要不要把原来的限流插件整个搬一遍。
- 新 API 上线,是不是又得重新复制免费版和付费版两套规则。
但如果套餐在 Consumer Group,上面这几件事就会收敛成另外一组动作:
- 新租户接入,只需要新建 Consumer 并绑定
group_id。 - 套餐升级,改的是归属,不是改路由。
- 新 API 上线,Route 只管功能,套餐层不用跟着复制。
这时候你会发现,APISIX 里有价值的不是“插件足够多”,而是它给了几层不同职责的对象。
先停在这里
这一篇我只想把最容易被写乱的第一步收住:企业级限流别从 Route 开始想,更别把套餐直接写死在 Route 上。
先把入口防护单独放出来,再把套餐挂到 Consumer Group,原本会越写越重的配置,到了这里就已经能收住一大半。
至于套餐内再按不同 API 做更细的矩阵,那已经是下一层题了。等对象关系先摆正,再往下抠才不会一路都在还配置债。