Velox_控制模块
概述
配置模块的作用为提供一个类型安全、集中式管理应用程序配置项的方式,允许开发者定义任意类型的配置变量,为它们设置默认值,并通过YAML
文件方便地加载和修改这些配置。
该模块的核心设计思想是将每一个配置项抽象为一个ConfigVar
对象。所有这些对象都由一个单例管理的Config
类进行统一存储和访问。这种设计避免了配置项散落在代码各处,使得配置的管理、维护和查阅变得非常方便。
该模块主要由以下几部分构成:
ConfigVarBase
: 一个抽象基类,定义了所有配置变量的通用接口,如获取名称、描述、类型名以及与字符串的双向转换(序列化/反序列化)。ConfigVar<T>
: 继承自ConfigVarBase
的模板类。它是配置变量的具体实现,能够存储任意类型T
的值。它还实现了一个回调机制,当配置值发生变化时,可以通知所有监听者。TypeConverter
: 一系列模板类及其偏特化,用于在各种C++类型(包括基本类型、STL容器如vector
,list
,map
,set
等)和字符串之间进行转换。该模块默认使用boost::lexical_cast
进行基础类型转换,并为复杂容器类型提供了基于yaml-cpp
的序列化和反序列化实现。对于自定义类型,只需完成对应的偏特化即可进行转换。Config
: 一个管理类,它包含一个静态的unordered_map
用于存储所有的配置变量。它提供了全局的静态方法来创建、获取和加载配置项,是与本模块交互的主要入口。
本模块通过yaml-cpp库解析YAML文件,再将从.yml
中解析出来的结构化数据更新到对应的ConfigVar
变量中。
主要特性
- 类型安全: 每个配置项在定义时都强绑定一个类型,在获取时会进行动态类型检查,防止了类型不匹配导致的运行时错误。
- 集中式管理: 所有配置项都通过一个全局的
Config
管理器进行注册和访问,便于追踪和管理。 - 支持复杂数据结构: 内置了对
std::vector
,std::list
,std::set
,std::unordered_set
,std::map
,std::unordered_map
等常用STL容器的序列化和反序列化支持。 - 基于YAML: 使用广泛流行的YAML格式作为配置文件,语法清晰,支持复杂数据结构。
- 动态更新与回调: 支持在运行时通过加载YAML文件来更新配置值。当某个配置值发生变化时,可以触发预先注册的回调函数,使应用程序能够动态地响应配置变化。
重要函数介绍
Config类
-
getDatas()
1 2 3 4 5 6 7 8 9
using ConfigVarMap = std::unordered_map<std::string, ConfigVarBase::ptr>; /** * @brief 返回所有的配置项 */ static ConfigVarMap& getDatas() { static ConfigVarMap s_datas; return s_datas; }
这里的数据存储的是基类指针
ConfigVarBase::ptr
,这是典型的多态设计。因为ConfigVar<T>
是模板类,如果用的是std::unordered_map<std::string, ConfigVar<T>::ptr>;
则只能放一种类型的配置变量(比如int
、std::string
)。 -
getOrCreateConfigVarPtr(const std::string& name, const T& default_value, const std::string& description = "")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
/** * @brief 获取/创建对应参数名的配置参数 * @param[in] name 配置参数名称 * @param[in] default_value 参数默认值 * @param[in] description 参数描述 * @details 获取参数名为name的配置参数,如果存在则直接返回 * 如果不存在, 则根据参数创建对应的配置参数 * @return 返回对应的配置参数指针, 如果参数名存在但是类型不匹配则返回 nullptr * @exception 如果参数名为空或包含非法字符[^0-9a-z_.] 抛出异常 std::invalid_argument */ template<class T> static typename ConfigVar<T>::ptr getOrCreateConfigVarPtr(const std::string& name, const T& default_value, const std::string& description = "") { // getDatas() 返回的是存储全部配置项的 unordered_map auto it = getDatas().find(name); if (it != getDatas().end()) { auto tmp = std::dynamic_pointer_cast<ConfigVar<T>>(it->second); if (tmp) { VELOX_INFO("ConfigVar [name:{}, valueType:{}] exists", name, tmp->getTypeName()); return tmp; } // 存在但类型不匹配 VELOX_ERROR( "ConfigVar name {} exists but type not {}, real type is {}:{}", name, velox::util::typeName<T>(), it->second->getTypeName(), it->second->toString()); return nullptr; } // 创建配置参数 if (!velox::util::isValidName(name)) { VELOX_ERROR("ConfigVar name invalid: {}", name); throw std::invalid_argument(name); } auto var = std::make_shared<ConfigVar<T>>(name, default_value, description); getDatas()[name] = var; return var; }
-
loadFromYaml(const YAML::Node& root)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
/** * @brief 使用 YAML::Node 更新对应配置参数 * @details 只会更新已经存在的配置参数, 未存在的配置参数不会创建, 只会在日志中 * 记录 * @param[in] root 表示当前要解析的 YAML::Node 结点 */ void Config::loadFromYaml(const YAML::Node& root) { // all_nodes 用于存储解析出来的 YAML::Node std::vector<std::pair<const std::string, const YAML::Node>> all_nodes; /** * @brief 递归地将一个YAML节点扁平化一系列"点分路径"键和对应节点的键值对 * @details 递归遍历YAML节点, 构造以点号连接的键路径, 并将每个路径及其对应 * 的 YAML::Node 添加到输出列表中, 从而实现将 YAML 的层级结构扁平化. * 只处理结点为 Map 的情况, 不会处理 Sequence (避免过度扁平不利于管理). * 第一个参数为 当前键路径的前缀 * 第二个参数为 当前处理的 YAML 节点 * 第三个参数为 存储扁平化结果 */ listAllMembers("", root, all_nodes); // 依次处理每个 YAML::Node for (const auto& node_pair : all_nodes) { // 获取该配置变量指针 const std::string& key = node_pair.first; ConfigVarBase::ptr var_ptr = getConfigVarBasePtr(key); // 判断该 key 是否已经注册, 注册则更新其值 if (var_ptr) { // 经过前面的 listAllMembers 处理, 这里的 YAML::Node 只会是标量或列表 if (node_pair.second.IsScalar()) { var_ptr->fromString(node_pair.second.Scalar()); } else { std::stringstream ss; ss << node_pair.second; var_ptr->fromString(ss.str()); } } else { // 没注册的配置, 则写入日志 VELOX_WARN("Unrecognized config key: {}", key); } } }
-
loadFromConfDir(const std::string& relative_dir_path, bool force = false)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
/** * @brief 加载指定项目子目录里面的配置文件 * @details 根据 force 参数来判断是否强制加载全部配置文件 * force == false时, 只加载更新过的配置文件 * force == true时, 加载指定目录下的全部配置文件 */ void Config::loadFromConfDir(const std::string& relative_dir_path, bool force) { // 获取项目根目录下的相对目录路径下的扩展为 .yml 的全部文件的绝对路径 // 假设项目根路径为: /home/xxx/velox, relative_dir_path = config // 那么就会去 /home/xxx/velox/config 这个目录下找到所有 .yml 文件, 并将它们 // 的绝对路径一起返回(以vector的形式返回). auto files = velox::util::listAllFilesByExt(relative_dir_path, ".yml"); // 依次处理每个配置文件 for (const auto& file : files) { // 获取该文件的最新写入时间 std::error_code ec; auto last_write_time = fs::last_write_time(file, ec); if (ec) { VELOX_WARN("Skip config file '{}': failed to get last write time: {}", file.string(), ec.message()); continue; } auto current_timestamp = velox::util::toUnixTimestamp(last_write_time); auto& cached_timestamp = getCachedTimestampForFile(file); // 在 force == false 且该文件后续没有被修改时 跳过该配置文件 if (!force && current_timestamp == cached_timestamp) { VELOX_INFO("Skip config file '{}': unchanged since last load.", file.string()); continue; } try { cached_timestamp = current_timestamp; // 将该文件最新写入时间进行缓存 YAML::Node root = YAML::LoadFile(file); loadFromYaml(root); // 调用该函数对 YAML::Node 进行解析并更新存储的值 VELOX_INFO("Loaded config file '{}'", file.string()); } catch (const std::exception& e) { VELOX_ERROR("Failed to load config file '{}': {}", file.string(), e.what()); } catch (...) { VELOX_ERROR("Failed to load config file '{}': unknown error.", file.string()); } } }
ConfigVar类
该类用于定义配置参数结构, 除了实现 ConfigVarBase
的虚函数外, 还新增了一些函数, 其中比较重要的有:
-
setValue(const T& value)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/** * @brief 设置当前参数的值 * @details 当参数的值发生变化时会调用该变量所对应的变更回调函数 */ void setValue(const T& value) { // 判断新旧值是否相同 if (value == m_val) { return; } // 更新值 T old_val = m_val; m_val = value; // m_cbs 是一个 unordered_map, 用于存储该配置参数的全部回调函数 for (const auto& [id, cb] : m_cbs) { // 调用注册过的回调函数 cb(old_val, m_val); } }
使用回调函数机制可以在该配置参数的值发生变化时进行一些处理(比如更新
log
模块的对应参数等)。 -
addListener(on_change_cb cb)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// using on_change_cb = std::function<void(const T& old_val, const T& new_val)>; // std::unordered_map<uint64_t, on_change_cb> m_cbs; /** * @brief 添加变化回调函数 * @return 返回该回调函数对应的唯一 id, 用于删除回调 */ uint64_t addListener(on_change_cb cb) { // 自动生成该回调函数的 key static uint64_t s_fun_id = 0; ++s_fun_id; m_cbs[s_fun_id] = cb; return s_fun_id; }
使用示例
Config
模块的使用都是通过调用Config
类中的静态成员函数来实现。
|
|
更多使用示例,可查看test/config/config_test.cpp
文件