功能说明
配置热更新最常见的错误是直接用写锁修改共享结构:读路径被迫加锁,更新瞬间还可能读到部分字段已改、部分未改的中间状态。ArcSwap 保存一个 Arc 指针,读取时复制指针,更新时整体替换,读者要么看到完整旧配置,要么看到完整新配置。
示例的 ConfigStore 提供两个方法:load 返回当前配置的 Arc 快照,reload 传入新配置并返回被替换的旧 Arc。持有旧快照的线程可以继续使用,不受更新影响,适合连接池上限、灰度开关这类需要平滑生效的参数。
关键代码
ArcSwap::from_pointee 把配置包装成可原子交换的指针。load_full 增加引用计数并返回 Arc,调用方无需加锁。swap 用新 Arc 替换旧值并返回旧 Arc,因此旧配置的释放时机由最后持有者决定。main 先保存快照,再热更新,打印旧值、快照与新值,最后断言新配置的连接数已经生效。
使用注意
配置类型应尽量不可变,更新时构造完整新值,而不是在旧值上修改字段。ArcSwap 只保证指针替换的原子性,不保证多个配置字段之间的业务一致性,需要一致视图时就把所有字段放进同一个结构体。频繁更新会产生较多 Arc 分配,但读路径始终无锁。旧快照可能被长时间持有,配置中不要放大对象或文件句柄。跨线程共享时把 store 放进 Arc。
验证方式
输出中的 previous 与 snapshot 都显示连接数八、开关关闭,而 current 显示连接数十六、开关打开,说明旧读者仍看到旧值,新读者看到新值。断言 store.load().max_connections 等于十六,程序正常结束。可以在多线程中循环读取,确认不会出现字段混合的状态。
代码示例
use arc_swap::ArcSwap;
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq)]
struct Config {
max_connections: usize,
feature_flag: bool,
}
struct ConfigStore {
current: ArcSwap<Config>,
}
impl ConfigStore {
fn new(initial: Config) -> Self {
Self {
current: ArcSwap::from_pointee(initial),
}
}
fn load(&self) -> Arc<Config> {
self.current.load_full()
}
fn reload(&self, next: Config) -> Arc<Config> {
self.current.swap(Arc::new(next))
}
}
fn main() {
let store = ConfigStore::new(Config {
max_connections: 8,
feature_flag: false,
});
let snapshot = store.load();
let previous = store.reload(Config {
max_connections: 16,
feature_flag: true,
});
println!("previous: {:?}", previous);
println!("snapshot: {:?}", snapshot);
println!("current: {:?}", store.load());
assert_eq!(store.load().max_connections, 16);
}