Day 07 / 共 20 天 · 第 2 周 路由/配置/数据面
配置中心 etcd(全动态的机密)
Day 01 说 APISIX"改配置秒级生效、零 reload"——机密就在 core/config_etcd.lua。今天看它怎么 watch etcd、把配置变更增量同步到每个 worker 的内存。
📍 你在整条链的位置
路由 D6→
etcd 配置中心(全动态)→
抽象对象 D8→
上游 D10
L01
为什么是 etcd
💡 etcd 天生适合当配置中心(三个特性)
etcd 是分布式键值存储(K8s 也用它):① 强一致(多节点数据一致);② 支持 watch(订阅 key 变化,一变主动推);③ 有 revision(全局递增版本号)。这三点正好是配置中心需要的。
📝 配置存成 etcd 的 key
APISIX watch 整个
/apisix/routes/1 → 路由1;/apisix/upstreams/5 → 上游5;/apisix/consumers/alice → 消费者。APISIX watch 整个
/apisix 前缀。L02
watch = 订阅推送,不是轮询
改配置写 etcd → etcd 主动推给所有 watch 的节点 → 各节点内存更新 → 秒级生效、零 reload。
💡 watch = "你变了主动推给我"
传统是轮询(每隔几秒去问变了没,有延迟又浪费)。watch 是订阅——etcd 一旦有 key 变化,立刻通过长连接推给 APISIX。所以配置变更秒级甚至毫秒级传到所有节点。
config_etcd.lua 的 do_run_watch(:135+)建长连接 watchdir(prefix) 订阅整个 /apisix 前缀。L03
单连接共享 watch
🤔 router、consumer、upstream… 每个都开一条 etcd 连接?
如果每个子系统各开一条 watch,一个 worker 就几十条、几十个 worker 上百条——etcd 扛不住。
💡 一条主 watch 连接监听整个前缀,按 key 分发
config_etcd.lua 用一个 watch_ctx(:78)在整个 worker 里共享一条 watch 连接——各子系统共用它。produce_res(:123)把事件按 key 前缀分发给对应子系统(用信号量唤醒)。大幅减少 etcd 连接数(早期每个资源类型一条,后来合并成一条)——大规模部署的关键优化。L04
revision 增量:只传变化的
config_etcd.lua:176:watch_ctx.rev = rev + 1。watch 带 start_revision(:192),etcd 只推"这个 revision 之后"的变化。
💡 revision = etcd 的"全局版本号"
etcd 每次写让全局 revision +1。APISIX 记住"我同步到 revision N",下次说"给我 N 之后的"——etcd 只推增量(改了哪几个 key),不用每次传全量。好比"我看到第 100 条消息了,只给我 101 之后的"。增量让配置变更传输量极小(只传变化那条路由)。
L05
读写分离:读走内存、写走 etcd
读写分离:请求处理读内存(极快,不碰 etcd);改配置写 etcd → watch → 更新内存。两全其美。
性能与动态兼得
各子系统通过
core.config.new(key) 创建配置对象,它 watch 自己那部分 key,把数据同步到内存 .values,维护 .conf_version。请求处理(Day 4-6)用的是内存里的数据(radixtree 用它建的树),完全不碰 etcd——所以匹配极快。etcd 只在配置变更时通过 watch 更新内存。conf_version 变化时触发重建(如 radixtree 重建树)。L06
全量兜底 + 断线重连
config_etcd.lua:165:主 watcher 首次启动先 cli:get(prefix) 拉一次全量,之后才 watch 增量。连接断了会重连并从记录的 revision 续上。
"先全量后增量 + 断线重连"的健壮性
刚启动内存是空的,先从 etcd 拉全量打底;之后靠 watch 增量维护。watch 断了(网络抖动)重连后从记录的 revision 续传;落后太多(revision 过期)就重新全量。三件套保证配置同步既快又不丢——生产级配置中心客户端的标准做法。
L07
standalone 模式:不用 etcd
APISIX 也支持 standalone 模式(core/config_yaml.lua):配置直接写在 conf/apisix.yaml 文件里,APISIX 监听文件变化热加载。
接口统一、实现可换(又见)
etcd 模式适合"多节点+频繁动态变更";standalone 适合单机、GitOps 声明式管理、不想维护 etcd 的场景(文件改了就重载,同样不 reload nginx)。两种模式(
config_etcd/config_yaml)实现同一套 core.config 接口,上层子系统(router 等)无感切换。还有 config_xds(对接 xDS 控制面,呼应 higress-group)。又是本系列反复出现的"接口统一、实现可换"。L08
今日小结 + 动手
🧠 今天你应该能回答
- etcd 的哪三个特性适合当配置中心?
- watch 和轮询的区别?为什么共享一条 watch 连接?
- revision 怎么实现增量?读/写路径怎么分离?
- standalone(config_yaml)模式适合什么场景?
✋ 动手
cd /Users/bitmart/work/codes/github/apisix
sed -n '135,210p' apisix/core/config_etcd.lua
ls apisix/core/config_*.lua
明天预告 · Day 08:配置从 etcd 同步进内存了——六大抽象对象(Route/Service/Upstream/Consumer/Plugin_config/Global_rule)的数据结构与引用关系,看它们怎么在 etcd 里组织。