跳到主要内容

设计负载均衡器

问题

如何用 Rust 实现不同策略的负载均衡器?

答案

负载均衡策略实现

use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use rand::Rng;

pub trait LoadBalancer: Send + Sync {
fn select(&self) -> usize; // 返回后端索引
}

/// Round-Robin
pub struct RoundRobin {
count: AtomicUsize,
total: usize,
}

impl LoadBalancer for RoundRobin {
fn select(&self) -> usize {
self.count.fetch_add(1, Ordering::Relaxed) % self.total
}
}

/// 加权 Round-Robin
pub struct WeightedRoundRobin {
weights: Vec<u32>,
current: AtomicUsize,
}

impl LoadBalancer for WeightedRoundRobin {
fn select(&self) -> usize {
let total_weight: u32 = self.weights.iter().sum();
let pos = self.current.fetch_add(1, Ordering::Relaxed) as u32 % total_weight;

let mut cumulative = 0;
for (i, &weight) in self.weights.iter().enumerate() {
cumulative += weight;
if pos < cumulative {
return i;
}
}
0
}
}

/// P2C(Power of Two Choices)
/// 随机选两个,取负载最小的
pub struct P2C {
loads: Vec<AtomicUsize>, // 每个后端的当前负载
}

impl LoadBalancer for P2C {
fn select(&self) -> usize {
let mut rng = rand::thread_rng();
let a = rng.gen_range(0..self.loads.len());
let mut b = rng.gen_range(0..self.loads.len());
while b == a {
b = rng.gen_range(0..self.loads.len());
}

let load_a = self.loads[a].load(Ordering::Relaxed);
let load_b = self.loads[b].load(Ordering::Relaxed);

if load_a <= load_b { a } else { b }
}
}

算法对比

算法时间复杂度优点缺点
Round-RobinO(1)简单均匀不考虑后端负载
加权 RRO(n)考虑后端性能差异需要手动配置权重
最少连接O(n)动态感知负载锁竞争
P2CO(1)自适应 + 低开销随机性
一致性哈希O(log n)会话亲和节点变更影响大
推荐

P2C(Power of Two Choices)是目前最推荐的算法——O(1) 时间复杂度,自适应负载,理论上在大规模系统中接近最优分发。gRPC 默认使用 P2C。


常见面试问题

Q1: P2C 为什么比随机选择好?

答案

数学证明(Balls into Bins 问题):

  • 随机选择:最大负载约 O(lognloglogn)O(\frac{\log n}{\log \log n})
  • P2C:最大负载约 O(loglogn)O(\log \log n)

P2C 只是多选了一次候选,就将负载不均衡指数级降低。这就是 "The Power of Two Choices" 的含义。

相关链接