功能说明
轮询(round-robin)是负载均衡中最简单的策略:请求依次分配给后端列表中的节点,循环往复。这段代码在轮询基础上增加健康检查,跳过 healthy == false 的节点,并在所有节点都不可用时返回 None,让调用方决定降级或报错。
它适合多副本只读服务、本地连接池选择或测试环境中的简单分流。原子游标让 next 可以并发调用,不需要为每次选择加互斥锁。
关键代码
LoadBalancer 保存后端列表和一个 AtomicUsize 游标。next 先检查列表是否为空,然后最多尝试 count 次:每次用 fetch_add(1, Relaxed) 取当前下标并递增,对列表长度取模,命中健康节点就返回引用。
最多尝试 count 次保证不会因为连续遇到不健康节点而无限循环。set_healthy 需要 &mut self,可以在启动或运维操作时更新状态。
使用注意
Relaxed 只保证原子性,不提供跨线程的顺序保证;这里游标只用于计数,足够使用。set_healthy 不能在并发读取时调用,否则需要 Mutex 或原子布尔字段。
轮询假设各后端容量相同,不适合权重不同的场景。健康状态需要外部探活更新,代码本身不检测超时。游标在节点增删后会跳过或重复部分节点,变更列表时应重建负载均衡器。返回的是引用,调用期间不能修改后端列表。
验证方式
示例中 a、c 健康,b 故障,连续取四次得到 ["a", "c", "a", "c"],证明故障节点被跳过且轮询继续。此时游标已经推进到位置 6,把 b 标记为健康后再取三次,得到 ["a", "b", "c"],可以看出轮询从上次位置继续而不是重置。空列表返回 None。可以加入全部故障的场景,确认返回 None 而不是死循环。
代码示例
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Debug, Clone, PartialEq, Eq)]
struct Backend {
name: &'static str,
healthy: bool,
}
struct LoadBalancer {
backends: Vec<Backend>,
cursor: AtomicUsize,
}
impl LoadBalancer {
fn new(backends: Vec<Backend>) -> Self {
Self {
backends,
cursor: AtomicUsize::new(0),
}
}
fn next(&self) -> Option<&Backend> {
let count = self.backends.len();
if count == 0 {
return None;
}
for _ in 0..count {
let index = self.cursor.fetch_add(1, Ordering::Relaxed) % count;
let backend = &self.backends[index];
if backend.healthy {
return Some(backend);
}
}
None
}
fn set_healthy(&mut self, name: &str, healthy: bool) {
if let Some(backend) = self.backends.iter_mut().find(|item| item.name == name) {
backend.healthy = healthy;
}
}
}
fn main() {
let mut balancer = LoadBalancer::new(vec![
Backend { name: "a", healthy: true },
Backend { name: "b", healthy: false },
Backend { name: "c", healthy: true },
]);
let picks: Vec<&str> = (0..4)
.filter_map(|_| balancer.next().map(|backend| backend.name))
.collect();
println!("{picks:?}");
assert_eq!(picks, vec!["a", "c", "a", "c"]);
balancer.set_healthy("b", true);
let names: Vec<&str> = (0..3)
.filter_map(|_| balancer.next().map(|backend| backend.name))
.collect();
assert_eq!(names, vec!["a", "b", "c"]);
assert_eq!(LoadBalancer::new(Vec::new()).next(), None);
}