跳转至

ConfigStore 架构详解

本文档面向库开发者和维护者,深入解析 ConfigStore 的实现架构、设计理念和扩展机制。

1. 设计理念

1.1 四层存储架构

ConfigStore 采用四层优先级架构,实现了配置的层次化管理和灵活覆盖:

+-----------------------------------------------+
|                Temp Layer (最高优先级)          |
|  - 仅内存存储,进程退出后丢失                    |
|  - 用于临时覆盖、测试场景                        |
+-----------------------------------------------+
|                App Layer                       |
|  - 应用级配置,相对路径 config/app.ini          |
|  - 运行时可写,应用专用配置                      |
+-----------------------------------------------+
|                User Layer                      |
|  - 用户级配置,~/.config/cfdesktop/user.ini     |
|  - 用户个性化设置,持久化存储                    |
+-----------------------------------------------+
|                System Layer (最低优先级)        |
|  - 系统级配置,/etc/cfdesktop/system.ini        |
|  - 全局默认配置,只读或需要特权写入               |
+-----------------------------------------------+

查询顺序(优先级从高到低):Temp -> App -> User -> System

这种设计允许: - 系统管理员提供全局默认值(System) - 用户自定义个人偏好(User) - 应用程序维护运行时配置(App) - 开发者进行临时测试和调试(Temp)

1.2 Pimpl 模式

ConfigStore 使用 Pimpl(Pointer to Implementation)模式实现接口与实现分离:

+------------------+           +------------------------+
|   ConfigStore    |           |   ConfigStoreImpl     |
|  (公共接口层)     | --------> |   (实现层)             |
|                  |           |                        |
| - 模板方法        |           | - QSettings 管理        |
| - KeyHelper 调用 |           | - 缓存管理              |
| - 单例继承        |           | - Watcher 机制         |
|                  |           | - 线程同步              |
+------------------+           +------------------------+

优势: 1. ABI 稳定性:实现变更不影响公共头文件,无需重新编译依赖代码 2. 编译隔离:QSettings 等依赖头文件仅在实现文件中包含 3. 接口简洁:公共 API 头文件仅暴露必要接口 4. 灵活性:可替换实现而保持接口不变

1.3 单例模式

ConfigStore 继承自 SimpleSingleton<ConfigStore>,使用 Meyer's 单例模式:

// base/include/base/singleton/simple_singleton.hpp
template <typename SingleTargetClass>
class SimpleSingleton {
    static SingleTargetClass& instance() {
        static SingleTargetClass target;  // C++11 保证线程安全
        return target;
    }
};

特点: - 线程安全:C++11 标准保证静态局部变量初始化的线程安全性 - 延迟初始化:首次调用 instance() 时才创建实例 - 自动销毁:程序退出时自动析构 - 不可复制:删除了拷贝和移动构造函数

2. 组件详解

2.1 ConfigStore:公共接口层

文件位置:desktop/base/config_manager/include/cfconfig.hpp

核心职责: 1. 提供类型安全的模板 API 2. 处理 KeyView 到 Key 的转换 3. 委托实际操作给 ConfigStoreImpl

关键模板方法

// 查询配置(带默认值)
template <typename Value>
[[nodiscard]] Value query(const KeyView key, const Value& default_value);

// 设置配置
template <typename Value>
[[nodiscard]] bool set(const KeyView key, const Value& v,
                       Layer layer = Layer::App,
                       NotifyPolicy notify_policy = NotifyPolicy::Immediate);

// 注册键
template <typename Value>
[[nodiscard]] RegisterResult register_key(const Key& key, const Value& init_value,
                                          Layer layer = Layer::App,
                                          NotifyPolicy notify_policy = NotifyPolicy::Immediate);

类型转换机制detail::any_cast): - 直接类型匹配:std::any 直接包含目标类型 - QVariant 转换:处理 Qt 类型系统 - 字符串与数值互转:增强兼容性

2.2 ConfigStoreImpl:实现层

文件位置: - 头文件:desktop/base/config_manager/src/impl/config_impl.h - 实现文件:desktop/base/config_manager/src/impl/config_impl.cpp

核心成员变量

class ConfigStoreImpl {
private:
    // 线程同步
    mutable std::shared_mutex mutex_;        // 读写锁
    std::mutex deferred_mutex_;              // 延迟回调专用锁

    // 路径提供者
    std::shared_ptr<IConfigStorePathProvider> path_provider_;

    // 存储(Temp 层仅使用 cache_)
    std::unordered_map<std::string, std::any> cache_;
    std::unique_ptr<QSettings> settings_system_;
    std::unique_ptr<QSettings> settings_user_;
    std::unique_ptr<QSettings> settings_app_;

    // 脏标记
    std::array<bool, 4> dirty_flags_;

    // Watcher 管理
    std::vector<WatcherEntry> watchers_;
    std::atomic<WatcherHandle> next_handle_;

    // 延迟通知
    std::vector<PendingChange> pending_changes_;
    std::vector<DeferredWatcherEvent> deferred_events_;
};

内部方法架构

每个公共方法都有对应的 _impl 版本,用于已持锁场景:

公共方法 (获取锁)              内部方法 (无锁,调用者必须持锁)
+------------------+         +------------------------+
| set()            |         | set_impl()             |
| register_key()   | ------->| register_key_impl()    |
| unregister_key() |         | unregister_key_impl()  |
| clear()          |         | clear_impl()           |
| clear_layer()    |         | clear_layer_impl()     |
+------------------+         +------------------------+

这种设计避免了在已持锁场景下的重复加锁,提高了效率。

2.3 IConfigStorePathProvider:路径提供者

文件位置:desktop/base/config_manager/include/cfconfig/cfconfig_path_provider.h

接口设计

class IConfigStorePathProvider {
public:
    virtual QString system_path() const = 0;
    virtual QString user_dir() const = 0;
    virtual QString user_filename() const = 0;
    virtual QString app_dir() const = 0;
    virtual QString app_filename() const = 0;
    virtual bool is_layer_enabled(int layer_index) const = 0;
};

默认实现DesktopConfigStorePathProvider

层级 路径格式 说明
System /etc/cfdesktop/system.ini 系统配置,需要权限写入
User ~/.config/cfdesktop/user.ini 用户配置,自动创建目录
App config/app.ini 相对路径,运行时可写
Temp (内存) 不持久化

2.4 KeyHelper:键辅助类

文件位置:desktop/base/config_manager/include/cfconfig_key.h

数据结构

struct KeyView {
    std::string_view group;  // 分组,如 "app.theme"
    std::string_view key;    // 键名,如 "name"
};

struct Key {
    std::string full_key;         // 完整键,如 "app.theme.name"
    std::string full_description; // 完整描述
};

转换逻辑

// KeyView -> Key
"app.theme" + "name" => "app.theme.name"

// Key -> KeyView
"app.theme.name" => group="app.theme", key="name"

验证规则default_policy): - 只允许字母、数字、下划线和点号 - 拒绝特殊字符防止路径遍历和注入

3. 数据流

3.1 读取流程

用户调用 ConfigStore::query()
           |
           v
    KeyHelper 转换 KeyView -> Key
           |
           v
    获取 shared_lock (读锁)
           |
           v
    ConfigStoreImpl::query()
           |
           +-> 检查 cache_ (Temp 层)
           |      |
           |      +-> 命中? 返回值
           |
           +-> 查询 App 层 (QSettings)
           |      |
           |      +-> 命中? 缓存到 cache_ 并返回
           |
           +-> 查询 User 层 (QSettings)
           |      |
           |      +-> 命中? 缓存到 cache_ 并返回
           |
           +-> 查询 System 层 (QSettings)
           |      |
           |      +-> 命中? 缓存到 cache_ 并返回
           |
           +-> 都未命中? 返回默认值
           |
           v
    释放 shared_lock
           |
           v
    类型转换 (any_cast)
           |
           v
    返回给用户

3.2 写入流程

用户调用 ConfigStore::set()
           |
           v
    KeyHelper 转换 KeyView -> Key
           |
           v
    获取 unique_lock (写锁)
           |
           v
    ConfigStoreImpl::set()
           |
           +-> 保存旧值 (用于通知)
           |
           +-> set_impl()
           |      |
           |      +-> [Temp 层]
           |      |      更新 cache_
           |      |
           |      +-> [其他层]
           |             更新 QSettings
           |             更新 cache_
           |             mark_dirty(layer)
           |
           +-> 根据 NotifyPolicy
           |      |
           |      +-> Immediate: trigger_watchers()
           |      +-> Manual: 加入 pending_changes_
           |
           v
    释放 unique_lock
           |
           v
    execute_deferred_watchers()
           |
           +-> 获取 deferred_mutex_
           +-> 移动 deferred_events_ 到局部变量
           +-> 释放 deferred_mutex_
           +-> 执行所有回调 (无主锁)
           |
           v
    返回结果

3.3 Watcher 触发机制

配置变更发生
           |
           v
    trigger_watchers() [持锁状态]
           |
           +-> 遍历所有 watchers_
           |
           +-> 对每个 watcher
           |      |
           |      +-> match_pattern(pattern, key)
           |      |
           |      +-> 匹配? 创建 DeferredWatcherEvent
           |             加入 deferred_events_
           |
           v
    释放主锁后
           |
           v
    execute_deferred_watchers() [无锁状态]
           |
           +-> 移动 deferred_events_ 到局部
           |
           +-> 执行每个回调
                  callback(key, old_value, new_value, layer)
           |
           v
    完成

延迟回调机制的关键: 1. 在主锁内收集事件(避免回调中死锁) 2. 释放主锁后再执行回调(允许回调中访问 ConfigStore) 3. 使用独立的 deferred_mutex_ 保护事件队列

3.4 四层优先级查询图

                 查询 "app.theme.name"
                         |
        +----------------+----------------+
        |                |                |
        v                v                v
    [Temp]          [App]           [User]        [System]
  cache_         QSettings       QSettings      QSettings
    |              |               |              |
    |              |               |              |
    +---> 检查 ----+---> 检查 -----+---> 检查 ---+---> 检查
    |              |               |              |
    v              v               v              v
   命中?           命中?            命中?          命中?
    |              |               |              |
   是              否              否              否
    |              |               |              |
    v              v               v              v
 返回值    -------> 下层 ---------> 下层 --------> 默认值

4. 线程安全

4.1 读写锁实现原理

ConfigStoreImpl 使用 std::shared_mutex 实现读写分离:

mutable std::shared_mutex mutex_;

// 读操作:共享锁,允许多个读操作并发
std::shared_lock lock(mutex_);  // query(), has_key()

// 写操作:独占锁,独占访问
std::unique_lock lock(mutex_);  // set(), register_key(), etc.

并发场景分析

场景 是否允许 说明
多读并发 shared_lock 可共享
读写并发 读锁和写锁互斥
多写并发 unique_lock 独占

锁粒度: - 细粒度:每个操作方法独立加锁 - 范围:覆盖所有内部状态访问 - 短暂:仅保护临界区,回调在锁外执行

4.2 延迟回调机制

问题:如果在持锁状态下调用回调,可能导致: 1. 死锁:回调中再次访问 ConfigStore 尝试获取锁 2. 饥饿:回调耗时过长阻塞其他操作 3. 递归锁:违反设计约束

解决方案:两阶段回调

// 阶段 1:收集事件(持锁)
void trigger_watchers(...) {
    for (const auto& watcher : watchers_) {
        if (match_pattern(...)) {
            deferred_events_.push_back(...);  // 仅收集,不执行
        }
    }
}

// 阶段 2:执行回调(无锁)
void execute_deferred_watchers() {
    std::vector<DeferredWatcherEvent> events;
    {
        std::lock_guard<std::mutex> lock(deferred_mutex_);
        events = std::move(deferred_events_);
        deferred_events_.clear();
    }

    for (const auto& event : events) {
        event.callback(...);  // 安全执行,无主锁
    }
}

锁分离设计: - mutex_:保护配置数据和 watcher 列表 - deferred_mutex_:保护延迟事件队列

4.3 并发控制边界

原子操作

std::atomic<WatcherHandle> next_handle_{1};
// WatcherHandle 分配无需加锁

内存序保证: - 默认使用 memory_order_seq_cst - 确保多线程环境下 Handle 唯一性

无锁场景: - WatcherHandle 分配(原子操作) - 延迟回调执行(独立锁)

有锁场景: - 配置读写(shared_mutex) - Watcher 注册/注销(unique_lock) - 事件队列操作(deferred_mutex_)

5. 扩展点

5.1 自定义 KeyHelper

注意:当前实现中,ConfigStore 的模板方法内部直接创建了 KeyHelper 局部实例,因此通过继承扩展 KeyHelper 不会生效。如需自定义键转换逻辑,需要修改 ConfigStore 模板方法的实现。

未来扩展方向:可以通过依赖注入方式支持自定义 KeyHelper:

// 当前实现(不支持扩展)
template <typename Value>
Value ConfigStore::query(const KeyView key, const Value& default_value) {
    KeyHelper helper;  // 直接创建,无法替换
    Key k;
    if (!helper.fromKeyViewToKey(key, k)) {
        return default_value;
    }
    // ...
}

// 建议的扩展方式(需要修改源码)
class ConfigStore {
    std::unique_ptr<KeyHelper> key_helper_;  // 使用指针支持多态
public:
    void set_key_helper(std::unique_ptr<KeyHelper> helper);
};

5.2 自定义路径提供者

场景:测试、嵌入式系统、跨平台适配

实现方式:继承 IConfigStorePathProvider

#include <cfconfig/cfconfig_path_provider.h>

class TestPathProvider : public cf::config::IConfigStorePathProvider {
public:
    ~TestPathProvider() override = default;

    explicit TestPathProvider(const QString& base_dir)
        : base_dir_(base_dir) {}

    QString system_path() const override {
        return base_dir_ + "/system.ini";
    }

    QString user_dir() const override {
        return base_dir_ + "/user";
    }

    QString user_filename() const override {
        return "user.ini";
    }

    QString app_dir() const override {
        return base_dir_ + "/app";
    }

    QString app_filename() const override {
        return "app.ini";
    }

    bool is_layer_enabled(int layer_index) const override {
        // 禁用 System 层
        return layer_index != 0;  // System = 0
    }

private:
    QString base_dir_;
};

使用方式

auto test_provider = std::make_shared<TestPathProvider>("/tmp/test_config");
cf::config::ConfigStore::instance().initialize(test_provider);

5.3 扩展存储后端

场景:需要使用非 QSettings 的存储方式

实现方式:修改 ConfigStoreImpl 或创建新的实现类

注意:当前 ConfigStoreImpl 没有抽象基类,如需完全替换存储后端,需要:

  1. 方案一:直接修改 config_impl.cpp 中的存储逻辑
  2. 方案二:定义接口并重构 ConfigStoreImpl

当前存储接口摘要(需要实现的核心方法):

class ConfigStoreImpl {
    // 查询操作
    std::any query(const std::string& key, const std::any& default_value);
    std::any query(const std::string& key, Layer layer);

    // 写入操作
    bool set(const std::string& key, const std::any& value,
             Layer layer, NotifyPolicy notify_policy);
    RegisterResult register_key(const Key& key, const std::any& init_value,
                                Layer layer, NotifyPolicy notify_policy);
    UnRegisterResult unregister_key(const Key& key, Layer layer,
                                    NotifyPolicy notify_policy);

    // 持久化操作
    void sync(bool async);
    void reload();

    // Watcher 操作
    WatcherHandle watch(const std::string& pattern, Watcher callback,
                        NotifyPolicy policy);
    void unwatch(WatcherHandle handle);
    NotifyResult notify();
};

注意事项: 1. 保持与现有 ConfigStoreImpl 相同的接口签名 2. 实现相同的线程安全保证(使用 shared_mutex) 3. 处理脏标记和持久化逻辑 4. 实现 Watcher 机制和延迟回调

5.4 自定义 NotifyPolicy

当前实现: - Manual:累积变更,手动调用 notify() 触发 - Immediate:立即触发 Watcher

扩展可能性: - Batched:按时间窗口批量触发 - Debounced:防抖,短时间多次变更只触发一次 - Conditional:基于条件判断是否触发

实现位置cfconfig_notify_policy.h

6. 性能考虑

6.1 缓存策略

当前实现: - 查询时缓存命中值到 cache_ - Temp 层完全基于内存缓存 - 写入时同步更新缓存

潜在优化: 1. LRU 缓存限制大小 2. 延迟写入减少磁盘 I/O 3. 预加载热点配置

6.2 锁竞争优化

当前措施: 1. 读写分离(shared_mutex) 2. 延迟回调(锁外执行) 3. 细粒度锁(deferred_mutex_)

进一步优化: 1. 分片锁:按 key 哈希分片 2. 无锁结构:使用原子操作替代锁 3. 读写分离:CQRS 模式

6.3 持久化优化

当前实现: - sync() 主动同步 - 析构时自动同步 - 脏标记避免无意义写入

潜在优化: 1. 异步写入:后台线程定期同步 2. 批量写入:累积变更后批量写入 3. 增量同步:仅同步变更部分

7. 故障处理

7.1 文件访问失败

System 层: - 构造时检查文件存在性 - 不存在时跳过初始化 - 运行时返回空值

User/App 层: - 自动创建目录(QDir().mkpath()) - 写入失败返回 false - 不抛出异常

7.2 类型转换失败

策略:返回默认值 - any_cast 失败时使用默认值 - 不中断程序流程 - 记录日志(需配合日志系统)

7.3 Watcher 异常

当前设计: - 不捕获回调中的异常 - 异常向上传播

改进建议: - 捕获并记录异常 - 继续执行其他 Watcher - 提供错误回调机制

8. 调试支持

8.1 状态检查

// 获取待通知变更数量
std::size_t pending = ConfigStore::instance().pending_changes();

// 检查键是否存在
bool exists = ConfigStore::instance().has_key(key_view);

// 检查特定层
bool exists_in_app = ConfigStore::instance().has_key(key_view, Layer::App);

8.2 日志建议

建议在以下关键点添加日志: 1. 配置加载/保存 2. Watcher 触发 3. 路径提供者初始化 4. 错误和异常情况

8.3 测试支持

路径提供者:使用 MockPathProvider

#include <cfconfig/cfconfig_path_provider.h>

class MockPathProvider : public cf::config::IConfigStorePathProvider {
public:
    ~MockPathProvider() override = default;

    explicit MockPathProvider(const QString& base_dir)
        : base_dir_(base_dir) {}

    QString system_path() const override {
        return base_dir_ + "/system.ini";
    }

    QString user_dir() const override {
        return base_dir_ + "/user";
    }

    QString user_filename() const override {
        return "user.ini";
    }

    QString app_dir() const override {
        return base_dir_ + "/app";
    }

    QString app_filename() const override {
        return "app.ini";
    }

    bool is_layer_enabled(int layer_index) const override {
        return true;  // 启用所有层
    }

private:
    QString base_dir_;
};

// 使用方式
auto mock_provider = std::make_shared<MockPathProvider>("/tmp/test_config");
cf::config::ConfigStore::instance().initialize(mock_provider);

单元测试:参考 test/config_manager/config_store_test.cpp

9. 总结

ConfigStore 架构的核心优势:

  1. 分层设计:四层优先级实现灵活的配置覆盖
  2. Pimpl 模式:接口稳定,实现可替换
  3. 线程安全:读写锁 + 延迟回调确保并发安全
  4. 可扩展:路径提供者、KeyHelper 等支持自定义
  5. 性能优化:缓存策略、锁分离、延迟执行

适用场景: - 桌面应用程序配置管理 - 需要多层级配置的系统 - 多线程环境下的配置访问 - 需要配置变更通知的场景


文档版本:1.0 创建日期:2026-03-17 维护者:CFDesktop Team