设计配置中心
问题
如何用 Rust 设计一个支持热更新的配置中心客户端?
答案
架构设计
基于 ArcSwap 的热更新配置
use arc_swap::ArcSwap;
use serde::Deserialize;
use std::sync::Arc;
#[derive(Debug, Deserialize, Clone)]
pub struct AppConfig {
pub database_url: String,
pub max_connections: u32,
pub feature_flags: FeatureFlags,
}
#[derive(Debug, Deserialize, Clone)]
pub struct FeatureFlags {
pub enable_new_ui: bool,
pub enable_cache: bool,
}
/// 可热更新的配置管理器
pub struct ConfigManager {
config: Arc<ArcSwap<AppConfig>>,
}
impl ConfigManager {
pub fn new(initial: AppConfig) -> Self {
Self {
config: Arc::new(ArcSwap::from_pointee(initial)),
}
}
/// 读取当前配置(无锁,极快)
pub fn get(&self) -> arc_swap::Guard<Arc<AppConfig>> {
self.config.load()
}
/// 热更新配置(原子替换)
pub fn update(&self, new_config: AppConfig) {
self.config.store(Arc::new(new_config));
// 所有后续的 get() 都会读到新配置
// 正在使用旧配置的代码不受影响(Arc 引用计数保护)
}
/// 启动后台监听配置变化
pub fn start_watching(&self) {
let config = self.config.clone();
tokio::spawn(async move {
loop {
// 模拟从配置中心获取最新配置
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
// 实际场景:从 etcd/Consul 获取
// let new_config = fetch_from_config_center().await;
// config.store(Arc::new(new_config));
}
});
}
}
ArcSwap vs RwLock
| 维度 | RwLock | ArcSwap |
|---|---|---|
| 读性能 | 需要获取读锁 | 无锁读取 |
| 写性能 | 需要获取写锁 | 原子指针交换 |
| 读写冲突 | 写阻塞读 | 不冲突 |
| 适用场景 | 通用 | 读多写少(配置) |
为什么用 ArcSwap
配置读取是极高频操作(每次请求都可能读配置),而写入是低频的(配置变更)。ArcSwap 的无锁读取是完美匹配。
常见面试问题
Q1: 配置热更新如何保证一致性?
答案:
ArcSwap::store 是原子操作——读取到的要么是旧配置,要么是新配置,不会读到半更新的状态。正在使用旧配置的请求不受影响,因为它们持有旧 Arc 的引用,旧配置在所有引用释放后才会被回收。
这就是 Rust 的 RAII + 引用计数的优势:配置更新是无缝的,不需要加锁,不需要通知。