Day 10 / 共 20 天 · 第 2 周 路由/配置/数据面

upstream 与 balancer(转发给谁)

第 2 周收官。请求处理完决策,最后要转发给某个后端实例——从上游的一组 nodes 里挑一个。今天看 balancer.lua:五种负载均衡、健康检查剔坏节点、失败重试。

📍 你在整条链的位置(第 2 周最后一块,请求即将出网关)
匹配路由 选插件+跑插件 balancer 选上游实例 转发
L01

转发给谁的难题

🤔 一个上游有 3 个实例,请求该发给哪个? 后端服务通常多实例(负载 + 高可用)。请求来了发给哪个?平均分?如果有实例挂了呢?主备切换呢?
💡 balancer 阶段负责"挑一个 + 失败换一个" Day 02 说 balancer 阶段特殊(可被调多次)。balancer.lua:343run 里核心是 pick_server:361)——选一个上游实例;连不上就重试选下一个。负载均衡 = "挑哪个" + "挑失败了换一个"的容错。
L02

五种负载均衡算法

apisix/balancer/,upstream 的 type 选用哪个:

算法怎么选适合
roundrobin轮流(按 weight 加权),默认最公平通用
chash一致性哈希(按 key 固定映射)同用户总打同实例(本地缓存)
least_conn选连接数最少的长连接不均
ewma选响应最快的(动态感知)自动避开慢节点
priority先用高优先级组,挂了降级主备/多机房
请求 balancer 按算法选roundrobin/chash/… 实例1 (健康✅) 实例2 (健康✅) 实例3 (挂❌不选)
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-97fetch_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:343run 配合 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 鉴权;精读典型插件。
← Day 09 Day 11 · 插件机制 →