插件配置合并
Day 04/08 说过路由可引用 service/consumer 复用配置。今天看这个"合并"具体怎么做:merge_service_route / merge_consumer_route 的规则、优先级、以及合并结果如何缓存以避免每请求重算。
为什么要合并
merge_service_route
plugin.lua:647-656:把 service 的配置合并进 route。Day 04 access 阶段调它。
function _M.merge_service_route(service_conf, route_conf)
local route_service_key = route_conf.value.id .. "#"
.. route_conf.modifiedIndex .. "#" .. service_conf.modifiedIndex
return merged_route(route_service_key, service_conf,
merge_service_route, service_conf, route_conf)
end
route_service_key 是缓存键(下一节)——由 route id + 两者的 modifiedIndex(etcd 版本)拼成。merged_route 用这个 key 缓存合并结果。真正的合并逻辑在内部的 merge_service_route 函数里。合并缓存
merged_route 用 lrucache 缓存合并结果——同一个 (route, service) 组合只合并一次,之后直接取缓存。
modifiedIndex(版本号)——route 或 service 一改,版本变、缓存键变、自动重新合并。"缓存 + 版本号失效"是 APISIX 里反复出现的模式(Day 05 变量缓存、Day 09 schema 编译缓存)——用版本号保证缓存和源数据一致。合并规则
看 merge_service_stream_route(plugin.lua:659-686,规则清晰)体会合并思路:
local new_conf = core.table.deepcopy(route_conf) -- 以 route 为基础
if service_conf.value.plugins then
for name, conf in pairs(service_conf.value.plugins) do
if not new_conf.value.plugins[name] then
new_conf.value.plugins[name] = conf -- ★ route 没配的插件,才用 service 的
end
end
end
if not new_conf.value.upstream and service_conf.value.upstream then
new_conf.value.upstream = service_conf.value.upstream -- route 没上游才用 service 的
end
limit-count(count=100) + prometheus;route 自己配 limit-count(count=20)。合并 →
limit-count 用 route 的 20(同名冲突 route 赢)、prometheus 用 service 的(route 没配、补进来)。最终这条路由跑 limit-count(20) + prometheus。👶 小白:consumer 优先级最高,那我在 route 上配的限流不就白配了?
👨🏫 老师:不会。是按插件名覆盖——只有 consumer 也配了同一个插件(比如两边都配 limit-count),才用 consumer 的;route 上那些 consumer 没配的插件照样生效。合并是"叠加 + 同名冲突时具体层赢",不是"整包替换"。
merge_consumer_route
plugin.lua:741:识别出 consumer 后,把 consumer(和 consumer group)的插件配置合并进 route。返回 route, changed——changed 表示合并后是否有变化(Day 04 据此决定要不要补跑 rewrite)。
changed),Day 04 会重新 filter + 补跑 rewrite_in_consumer(Day 12)。consumer group 是"一组 consumer 共享的配置",介于 consumer 和 route 之间。优先级链
最终配置 = 合并(从低到高优先级覆盖):
Plugin Config(可复用插件套餐)
< Service(插件+上游套餐)
< Route(本路由)
< Consumer Group(消费者组)
< Consumer(具体调用方,最高优先级)
conf_version 缓存键
Day 04 里合并 service 后设 api_ctx.conf_version = route.modifiedIndex .. "&" .. service.modifiedIndex。plugin.conf_version(conf)(plugin.lua:876)用它做各种缓存的键。
modifiedIndex——你一改配置,etcd 的 modifiedIndex 就变,缓存键跟着变,等于自动换了一把新钥匙,旧缓存直接作废、立刻重新合并。缓存快 + 版本号保证不脏,两全。今日小结 + 动手
🧠 今天你应该能回答
- 为什么要合并配置?解决什么矛盾?
- 合并的核心规则(route 优先、service 补充)?
- 优先级链从低到高是什么?consumer 为什么最高?
- 合并结果怎么缓存?conf_version 怎么保证一致性?
✋ 动手
cd /Users/bitmart/work/codes/github/apisix
sed -n '647,690p' apisix/plugin.lua
sed -n '741,800p' apisix/plugin.lua
grep -n "conf_version\|modifiedIndex" apisix/init.lua | head
find_consumer 怎么按 key 查找、attach_consumer 怎么把身份挂到 ctx 上供后续用。