Day 10 / 共 20 天 · 第 2 周 路由/配置/数据面
upstream 与 balancer(转发给谁)
第 2 周收官。请求处理完决策,最后要转发给某个后端实例——从上游的一组 nodes 里挑一个。今天看 balancer.lua:五种负载均衡、健康检查剔坏节点、失败重试。
📍 你在整条链的位置(第 2 周最后一块,请求即将出网关)
匹配路由→
选插件+跑插件→
balancer 选上游实例→
转发
L01
转发给谁的难题
🤔 一个上游有 3 个实例,请求该发给哪个?
后端服务通常多实例(负载 + 高可用)。请求来了发给哪个?平均分?如果有实例挂了呢?主备切换呢?
💡 balancer 阶段负责"挑一个 + 失败换一个"
Day 02 说 balancer 阶段特殊(可被调多次)。
balancer.lua:343 的 run 里核心是 pick_server(:361)——选一个上游实例;连不上就重试选下一个。负载均衡 = "挑哪个" + "挑失败了换一个"的容错。L02
五种负载均衡算法
apisix/balancer/,upstream 的 type 选用哪个:
| 算法 | 怎么选 | 适合 |
|---|---|---|
| roundrobin | 轮流(按 weight 加权),默认 | 最公平通用 |
| chash | 一致性哈希(按 key 固定映射) | 同用户总打同实例(本地缓存) |
| least_conn | 选连接数最少的 | 长连接不均 |
| ewma | 选响应最快的(动态感知) | 自动避开慢节点 |
| priority | 先用高优先级组,挂了降级 | 主备/多机房 |
balancer 按算法从健康节点里挑一个(挂了的实例 3 被健康检查剔除,不参与)。
又是"策略模式"
每种算法一个模块(
balancer/roundrobin.lua 等),upstream 配 type 选用。加新算法 = 加个文件。和路由、配置中心一脉相承。L03
create_server_picker
balancer.lua:99-134:按 type 加载算法模块,用健康节点建一个"picker":
local function create_server_picker(upstream, checker)
local picker = pickers[upstream.type]
if not picker then
pickers[upstream.type] = require("apisix.balancer." .. upstream.type) -- 按名加载
picker = pickers[upstream.type]
end
local up_nodes = fetch_health_nodes(upstream, checker) -- 只用健康节点(L05)
if #up_nodes._priority_index > 1 then
return priority_balancer.new(up_nodes, upstream, picker) -- 多优先级组(L06)
end
return picker.new(up_nodes[...], upstream) -- 单组直接建
end
读法:
require("apisix.balancer." .. type) 按配置算法名动态加载。picker.new(nodes, upstream) 建选择器对象,之后每次请求调它的 get() 选一个。picker 会缓存(pickers[type]),upstream 不变就复用。L04
pick_server:单节点快路径
balancer.lua:195-280:
local function pick_server(route, ctx)
local up_conf = ctx.upstream_conf
if #up_conf.nodes == 1 then -- 只有一个节点,直接返回(快路径)
local node = up_conf.nodes[1]
ctx.balancer_ip = node.host; ctx.balancer_port = node.port
return node
end
ctx.balancer_try_count = (ctx.balancer_try_count or 0) + 1 -- 多节点:用 picker 选
-- ... server_picker:get(ctx) 选一个 → 设 balancer_ip/port
end
单节点快路径
只有一个实例就不跑负载均衡算法——直接返回(常见情况的优化)。多节点才走 picker。选中的实例 IP/端口存进
ctx.balancer_ip/port,Nginx 据此转发。L05
健康检查:剔除坏节点
balancer.lua:64-97 的 fetch_health_nodes:结合健康检查器,只把"健康的"节点交给 picker——挂了的不参与。
💡 主动 + 被动两种健康检查
主动(active):后台定时探测每个节点(发心跳),不通就标记不健康。被动(passive):据真实请求成败判断——回想 Day 02 log 阶段的
healthcheck_passive,这次请求 5xx 就给这节点记一笔。两者结合,坏节点自动剔除,请求只发健康节点。这就是"上游一台挂了,网关自动不给它发流量"的实现。配置在 upstream 的 checks(Day 08)。L06
priority 主备降级
balancer.lua:117-122:节点可分优先级组。priority_balancer 先用最高优先级组的健康节点;全挂了才降到下一级。
主备 / 多机房容灾
给主机房节点 priority=10、备机房 priority=0。正常只用主机房;主机房全挂才切备机房。实现"主备切换""多机房容灾"。两层选择:先选组(按优先级+健康),组内再按算法(roundrobin 等)选实例。
L07
失败重试
balancer.lua:343 的 run 配合 balancer_try_count:某实例连不上,Nginx 重新进 balancer,pick_server 排除已试过的、选下一个,直到成功或超过 retries 次。
"选一个→失败→排除它→选下一个"
重试次数来自 upstream 的
retries(Day 08)。这让单实例的临时故障不影响用户(自动切到其他实例)。run 里还调 plugin_funcs("before_proxy")(:378)——转发前给插件最后改请求的机会。L08
今日小结 + 动手(第 2 周收官)
🧠 第 2 周你应该能回答
- 五种负载均衡算法各适合什么场景?
- picker 怎么按 type 动态加载?单节点为什么走快路径?
- 主动 vs 被动健康检查?坏节点怎么被剔除?
- priority 怎么做主备降级?失败重试怎么工作?
✋ 动手
cd /Users/bitmart/work/codes/github/apisix
ls apisix/balancer/
sed -n '99,135p' apisix/balancer.lua
sed -n '195,230p' apisix/balancer.lua
下周预告 · 第 3 周:APISIX 的灵魂——插件系统。plugin.lua 怎么加载 104 个插件、按 priority 排序、在各阶段运行;filter 怎么选插件;配置合并;consumer 鉴权;精读典型插件。