跳到主要内容

设计配置中心

问题

如何用 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

维度RwLockArcSwap
读性能需要获取读锁无锁读取
写性能需要获取写锁原子指针交换
读写冲突写阻塞读不冲突
适用场景通用读多写少(配置)
为什么用 ArcSwap

配置读取是极高频操作(每次请求都可能读配置),而写入是低频的(配置变更)。ArcSwap 的无锁读取是完美匹配。


常见面试问题

Q1: 配置热更新如何保证一致性?

答案

ArcSwap::store 是原子操作——读取到的要么是旧配置,要么是新配置,不会读到半更新的状态。正在使用旧配置的请求不受影响,因为它们持有旧 Arc 的引用,旧配置在所有引用释放后才会被回收。

这就是 Rust 的 RAII + 引用计数的优势:配置更新是无缝的,不需要加锁,不需要通知。

相关链接