← 中文资源

简体中文版本

ArcSwap 原子配置热更新

通过原子交换 Arc 实现配置热更新,读线程持有旧快照继续工作,不会读到半更新状态。

阅读英文原文

功能说明

配置热更新最常见的错误是直接用写锁修改共享结构:读路径被迫加锁,更新瞬间还可能读到部分字段已改、部分未改的中间状态。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);
}