跳转到内容

Kong Least-Connections 负载均衡算法分析

本报告基于 kong/runloop/balancer/least_connections.lua 源码,深入分析 least-connections 负载均衡算法的工作机制。

-- 第188行:使用最小唯一堆
binaryHeap = binaryHeap.minUnique()

关键特性

  • 最小堆:优先级值越小的元素在堆顶
  • 唯一堆:每个 payload(address对象)在堆中唯一存在
  • peek() 特性:总是返回位置1的元素(堆顶)
-- 第28行和第44行:优先级计算
priority = (addr.connectionCount + 1) / addr.weight

公式解析

  • 连接数越少,优先级越高(值越小)
  • 权重越大,优先级越高(值越小)
  • +1 确保零连接时权重仍然有效
-- 第96行:核心选择逻辑
address = self.binaryHeap:peek()

选择规则

  1. peek() 总是返回堆顶元素(位置1)
  2. 堆顶元素是当前优先级最高的地址
  3. 选中后连接数+1,优先级重新计算
-- 第158-166行:afterHostUpdate方法
function lc:afterHostUpdate()
clearHeap(self.binaryHeap)
for _, target in ipairs(self.balancer.targets) do
for _, address in ipairs(target.addresses) do
insertAddr(self.binaryHeap, address)
end
end
end

重要发现:地址按照 targets 数组的顺序插入堆中。

  • 8个target:T1, T2, T3, T4, T5, T6, T7, T8
  • 并发1-2:同时只有1-2个活跃连接
  • 相同权重:所有target权重相同

初始状态(所有target连接数为0)

Section titled “初始状态(所有target连接数为0)”
Target | 连接数 | 权重 | 优先级 | 堆位置
--------|--------|------|-----------|--------
T1 | 0 | 100 | 1/100=0.01| 1 (堆顶)
T2 | 0 | 100 | 1/100=0.01| 2
T3 | 0 | 100 | 1/100=0.01| 3
... | ... | ... | ... | ...
T8 | 0 | 100 | 1/100=0.01| 8
请求序号 | 选择Target | T1连接数 | T2连接数 | ... | T8连接数 | 堆顶
---------|------------|----------|----------|-----|----------|------
1 | T1 | 1 | 0 | ... | 0 | T2
2 | T2 | 0(释放) | 1 | ... | 0 | T1
3 | T1 | 1 | 0(释放) | ... | 0 | T2
4 | T2 | 0(释放) | 1 | ... | 0 | T1

结果:只有T1和T2获得流量,T3-T8没有流量!

-- binaryheap 的比较函数(默认)
lt = function(a,b) return (a < b) end

关键特性

  • 当两个元素优先级相同时,a < b 返回 false
  • 相同优先级的元素不会交换位置
  • 堆的相对位置保持稳定
-- 按targets数组顺序插入
for _, target in ipairs(self.balancer.targets) do
for _, address in ipairs(target.addresses) do
insertAddr(self.binaryHeap, address) -- 先插入的更可能在前面
end
end

结果:配置中靠前的target更容易占据堆的前部位置。

-- peek()总是返回位置1的元素
address = self.binaryHeap:peek()

问题:在低并发场景下,只有堆顶附近的少数元素会被选中。

在低并发场景下,least-connections算法会导致流量集中在少数几个target上,后加入的target可能长期没有流量。

  1. 二叉堆的位置稳定性:相同优先级元素位置不变
  2. peek()的固定性:总是选择位置1的元素
  3. 插入顺序:先插入的target更容易获得流量
  4. 低并发特性:活跃连接少,优先级差异小

在并发为N的场景下,理论上最多只有前N+1个target会获得流量:

  • N个target各有1个连接(优先级:2/weight)
  • 1个target有0个连接(优先级:1/weight,位于堆顶)

在多网关节点部署的环境中,least-connections算法面临更严重的流量分配不均问题:

场景设定

  • 多个Kong网关节点:Gateway1, Gateway2, Gateway3…
  • 多个后端Target:Target1, Target2, Target3…
  • 轮询请求分发:客户端或负载均衡器将请求轮询分发到各个网关节点
  • 独立堆管理:每个网关节点维护自己的二叉堆,不共享连接状态

多网关节点场景下,每个网关节点独立维护自己的二叉堆,不共享连接状态。这导致所有节点可能同时选择相同的Target。

时间线分析

时间事件Gateway1状态Gateway2状态Target1实际负载
T1请求1→GW1T1:1连接,堆顶:T2T1:0连接,堆顶:T11个连接
T2请求2→GW2T1:1连接,堆顶:T2T1:1连接,堆顶:T22个连接(过载!)
T3请求3→GW1T1:0连接,堆顶:T1T1:1连接,堆顶:T21个连接
T4请求4→GW2T1:1连接,堆顶:T2T1:0连接,堆顶:T12个连接(再次过载!)

在单网关节点场景下,流量集中在少数target;在多网关节点场景下,这种集中效应被显著放大

单网关场景:Target1获得50%流量
多网关场景:Target1可能获得80%+流量
  1. 增加并发量:通过增加连接超时或保持连接来提高并发
  2. 使用其他算法:在低并发场景使用 round-robin 算法
  3. 手动调整权重:降低靠前target的权重
  1. 实现随机选择:在相同优先级时随机选择
  2. 共享连接状态:多网关节点共享连接计数
  3. 动态调整算法:根据并发量自动切换负载均衡算法