原sylar服务器框架 - 配置模块
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
类图
设计思想
约定优于配置。定义即可用。
传统的配置模块,一般都是采用回调,或者自己加载解析的模式
我们采用的定义即可用。针对内置类型,常用的stl结构(vector,set,list,map等等)都内置支持
自定义结构,只需要实现LexicalCast的偏特化类.就可以和stl数据结构无缝支持
通知机制,针对有些配置变更比较敏感的参数(比如调试开关,日志级别等等),可以自定义回调函数
当配置修改时,会自动回调相应的函数。
我们采用的配置文件为 yaml 格式。对于配置来说比较灵活。
使用举例:
sylar::ConfigVar<int64>::ptr g_max_thread_num
= sylar::Config::Lookup("thread.max_num", (int64_t)10, "max thread num");
这里定义了一个配置参数 g_max_thread_num,通过 g_max_thread_num->getValue()获取当前参数的值。
如果我们没有在配置里面定义,那么该参数的默认值就是10.
如果我们需要修改参数/读取配置文件的参数。 对应的配置文件的yaml格式如下:
thread:
max_num: 20
g_max_thread_num->getValue() == 20
对于使用方来说比较简单。不需要关注配置文件怎么加载和解析的。直接定义好,就可以使用
核心数据结构
ConfigVarBase
配置项的基类。定义了配置项类的基础接口。
配置项的基础属性:
配置名称(对于的yaml格式的路径),(getName)
配置描述(getDescription)
反序列化(fromString),序列化(toString)
具体的参数类型名称(getTypeName)
name的格式一般为: sylar.name.vvv (一般为小写'.'分隔的,字母开头数字和下划线)
对于与yaml的结构如下:
sylar:
name:
vvv: 10
/**
* @brief 配置变量的基类
*/
class ConfigVarBase {
public:
typedef std::shared_ptr<ConfigVarBase> ptr;
/**
* @brief 构造函数
* @param[in] name 配置参数名称[0-9a-z_.]
* @param[in] description 配置参数描述
*/
ConfigVarBase(const std::string& name, const std::string& description = "")
:m_name(name)
,m_description(description) {
std::transform(m_name.begin(), m_name.end(), m_name.begin(), ::tolower);
}
/**
* @brief 析构函数
*/
virtual ~ConfigVarBase() {}
/**
* @brief 返回配置参数名称
*/
const std::string& getName() const { return m_name;}
/**
* @brief 返回配置参数的描述
*/
const std::string& getDescription() const { return m_description;}
/**
* @brief 转成字符串
*/
virtual std::string toString() = 0;
/**
* @brief 从字符串初始化值
*/
virtual bool fromString(const std::string& val) = 0;
/**
* @brief 返回配置参数值的类型名称
*/
virtual std::string getTypeName() const = 0;
protected:
/// 配置参数的名称
std::string m_name;
/// 配置参数的描述
std::string m_description;
};
LexicalCast
类型转换仿函数类,用来实现自定义类型的序列化和反序列化
我们已经将内置的数据结构(int32_t,int64_t,float,double等等)
stl(vector,set,map,list,unordered_map,unordered_set等等)
都实现了相应的偏特化版本。使用这些类型无需定义LexicalCast的偏特化版本
/**
* @brief 类型转换模板类(F 源类型, T 目标类型)
*/
template<class F, class T>
class LexicalCast {
public:
/**
* @brief 类型转换
* @param[in] v 源类型值
* @return 返回v转换后的目标类型
* @exception 当类型不可转换时抛出异常
*/
T operator()(const F& v) {
return boost::lexical_cast<T>(v);
}
};
// vector偏特化版本的实现
/**
* @brief 类型转换模板类片特化(YAML String 转换成 std::vector<T>)
*/
template<class T>
class LexicalCast<std::string, std::vector<T> > {
public:
std::vector<T> operator()(const std::string& v) {
YAML::Node node = YAML::Load(v);
typename std::vector<T> vec;
std::stringstream ss;
for(size_t i = 0; i < node.size(); ++i) {
ss.str("");
ss << node[i];
vec.push_back(LexicalCast<std::string, T>()(ss.str()));
}
return vec;
}
};
/**
* @brief 类型转换模板类片特化(std::vector<T> 转换成 YAML String)
*/
template<class T>
class LexicalCast<std::vector<T>, std::string> {
public:
std::string operator()(const std::vector<T>& v) {
YAML::Node node(YAML::NodeType::Sequence);
for(auto& i : v) {
node.push_back(YAML::Load(LexicalCast<T, std::string>()(i)));
}
std::stringstream ss;
ss << node;
return ss.str();
}
};
ConfigVar
template<class T, class FromStr = LexicalCast<std::string, T>
,class ToStr = LexicalCast<T, std::string> >
class ConfigVar;
配置项的具体实现类,该类是模板类,支持扩展各种类型(自定义类型).
T: 是配置项代表的具体类型。 getValue方法返回对应类型的实例
FromStr: 实现将string转化为T的功能
ToStr: 实现将T转化为string的功能
目前内置类型和常用stl容器都可以直接使用。
自定义类型需要实现 operator== 函数,以及对应的LexcailCast偏特化版本
/**
* @brief 配置参数模板子类,保存对应类型的参数值
* @details T 参数的具体类型
* FromStr 从std::string转换成T类型的仿函数
* ToStr 从T转换成std::string的仿函数
* std::string 为YAML格式的字符串
*/
template<class T, class FromStr = LexicalCast<std::string, T>
,class ToStr = LexicalCast<T, std::string> >
class ConfigVar : public ConfigVarBase {
public:
typedef RWMutex RWMutexType;
typedef std::shared_ptr<ConfigVar> ptr;
typedef std::function<void (const T& old_value, const T& new_value)> on_change_cb;
/**
* @brief 通过参数名,参数值,描述构造ConfigVar
* @param[in] name 参数名称有效字符为[0-9a-z_.]
* @param[in] default_value 参数的默认值
* @param[in] description 参数的描述
*/
ConfigVar(const std::string& name
,const T& default_value
,const std::string& description = "")
:ConfigVarBase(name, description)
,m_val(default_value) {
}
/**
* @brief 将参数值转换成YAML String
* @exception 当转换失败抛出异常
*/
std::string toString() override {
try {
//return boost::lexical_cast<std::string>(m_val);
RWMutexType::ReadLock lock(m_mutex);
return ToStr()(m_val);
} catch (std::exception& e) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::toString exception "
<< e.what() << " convert: " << TypeToName<T>() << " to string"
<< " name=" << m_name;
}
return "";
}
/**
* @brief 从YAML String 转成参数的值
* @exception 当转换失败抛出异常
*/
bool fromString(const std::string& val) override {
try {
setValue(FromStr()(val));
} catch (std::exception& e) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::fromString exception "
<< e.what() << " convert: string to " << TypeToName<T>()
<< " name=" << m_name
<< " - " << val;
}
return false;
}
/**
* @brief 获取当前参数的值
*/
const T getValue() {
RWMutexType::ReadLock lock(m_mutex);
return m_val;
}
/**
* @brief 设置当前参数的值
* @details 如果参数的值有发生变化,则通知对应的注册回调函数
*/
void setValue(const T& v) {
{
RWMutexType::ReadLock lock(m_mutex);
if(v == m_val) {
return;
}
for(auto& i : m_cbs) {
i.second(m_val, v);
}
}
RWMutexType::WriteLock lock(m_mutex);
m_val = v;
}
/**
* @brief 返回参数值的类型名称(typeinfo)
*/
std::string getTypeName() const override { return TypeToName<T>();}
/**
* @brief 添加变化回调函数
* @return 返回该回调函数对应的唯一id,用于删除回调
*/
uint64_t addListener(on_change_cb cb) {
static uint64_t s_fun_id = 0;
RWMutexType::WriteLock lock(m_mutex);
++s_fun_id;
m_cbs[s_fun_id] = cb;
return s_fun_id;
}
/**
* @brief 删除回调函数
* @param[in] key 回调函数的唯一id
*/
void delListener(uint64_t key) {
RWMutexType::WriteLock lock(m_mutex);
m_cbs.erase(key);
}
/**
* @brief 获取回调函数
* @param[in] key 回调函数的唯一id
* @return 如果存在返回对应的回调函数,否则返回nullptr
*/
on_change_cb getListener(uint64_t key) {
RWMutexType::ReadLock lock(m_mutex);
auto it = m_cbs.find(key);
return it == m_cbs.end() ? nullptr : it->second;
}
/**
* @brief 清理所有的回调函数
*/
void clearListener() {
RWMutexType::WriteLock lock(m_mutex);
m_cbs.clear();
}
private:
RWMutexType m_mutex;
T m_val;
//变更回调函数组, uint64_t key,要求唯一,一般可以用hash
std::map<uint64_t, on_change_cb> m_cbs;
};
Config
配置集合类。 提供所有配置项的ConfigVar的统一管理功能。
加载配置文件,更新配置文件,定义配置项等等
重要的函数:
Lookup, 3个参数签名: 创建和定义配置项,同一个配置项只能创建一次否则会冲突
Lookup, 1个参数签名: 获取对应名称的配置项,用来获取上面函数创建的配置项
LoadFromConfDir: 加载配置文件,加载一个目录下面的所有yaml文件
支持多文件,不同的配置项可以分开文件定义
Visit: 便利所有配置项的方法。可以用来可视化输出
/**
* @brief ConfigVar的管理类
* @details 提供便捷的方法创建/访问ConfigVar
*/
class Config {
public:
typedef std::unordered_map<std::string, ConfigVarBase::ptr> ConfigVarMap;
typedef RWMutex RWMutexType;
/**
* @brief 获取/创建对应参数名的配置参数
* @param[in] name 配置参数名称
* @param[in] default_value 参数默认值
* @param[in] description 参数描述
* @details 获取参数名为name的配置参数,如果存在直接返回
* 如果不存在,创建参数配置并用default_value赋值
* @return 返回对应的配置参数,如果参数名存在但是类型不匹配则返回nullptr
* @exception 如果参数名包含非法字符[^0-9a-z_.] 抛出异常 std::invalid_argument
*/
template<class T>
static typename ConfigVar<T>::ptr Lookup(const std::string& name,
const T& default_value, const std::string& description = "") {
RWMutexType::WriteLock lock(GetMutex());
auto it = GetDatas().find(name);
if(it != GetDatas().end()) {
auto tmp = std::dynamic_pointer_cast<ConfigVar<T> >(it->second);
if(tmp) {
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "Lookup name=" << name << " exists";
return tmp;
} else {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name=" << name << " exists but type not "
<< TypeToName<T>() << " real_type=" << it->second->getTypeName()
<< " " << it->second->toString();
return nullptr;
}
}
if(name.find_first_not_of("abcdefghikjlmnopqrstuvwxyz._012345678")
!= std::string::npos) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name invalid " << name;
throw std::invalid_argument(name);
}
typename ConfigVar<T>::ptr v(new ConfigVar<T>(name, default_value, description));
GetDatas()[name] = v;
return v;
}
/**
* @brief 查找配置参数
* @param[in] name 配置参数名称
* @return 返回配置参数名为name的配置参数
*/
template<class T>
static typename ConfigVar<T>::ptr Lookup(const std::string& name) {
RWMutexType::ReadLock lock(GetMutex());
auto it = GetDatas().find(name);
if(it == GetDatas().end()) {
return nullptr;
}
return std::dynamic_pointer_cast<ConfigVar<T> >(it->second);
}
/**
* @brief 使用YAML::Node初始化配置模块
*/
static void LoadFromYaml(const YAML::Node& root);
/**
* @brief 加载path文件夹里面的配置文件
*/
static void LoadFromConfDir(const std::string& path, bool force = false);
/**
* @brief 查找配置参数,返回配置参数的基类
* @param[in] name 配置参数名称
*/
static ConfigVarBase::ptr LookupBase(const std::string& name);
/**
* @brief 遍历配置模块里面所有配置项
* @param[in] cb 配置项回调函数
*/
static void Visit(std::function<void(ConfigVarBase::ptr)> cb);
private:
/**
* @brief 返回所有的配置项
*/
static ConfigVarMap& GetDatas() {
static ConfigVarMap s_datas;
return s_datas;
}
/**
* @brief 配置项的RWMutex
*/
static RWMutexType& GetMutex() {
static RWMutexType s_mutex;
return s_mutex;
}
};
使用示例代码
//简单示例
#include "sylar/config.h" //包含配置模块的头文件
#include "sylar/log.h"
static sylar::Logger::ptr g_logger = SYLAR_LOG_ROOT();
static sylar::ConfigVar<int>::ptr g_int_value_config
= sylar::Config::Lookup("sylar.int_value", (int)1234, "sylar int value");
int main(int argc, char** argv) {
SYLAR_LOG_INFO(g_logger) << "int_value: " << g_int_value_config->getValue() << std::endl; //输出int_value: 1234
sylar::Config::LoadFromConfDir("conf");
SYLAR_LOG_INFO(g_logger) << "int_value: " << g_int_value_config->getValue() << std::endl; //输出int_value: 4321
return 0;
}
// conf路径下sylar_test.yml文件内容
sylar:
int_value: 4321
//自定义类型示例
#include "sylar/config.h" //包含配置模块的头文件
#include "sylar/log.h"
static sylar::Logger::ptr g_logger = SYLAR_LOG_ROOT();
struct Person {
std::string name = "(null)";
int age = 0;
bool operator==(const Person& p) const {
return name == p.name && age == p.age;
}
std::string toString() const {
std::stringstream ss;
ss << "[Person name=" << name
<< " age=" << age << "]";
return ss.str();
}
};
namespace sylar {
template<>
class LexicalCast<std::string, Person> {
public:
Person operator()(const std::string& v) {
YAML::Node node = YAML::Load(v);
Person p;
p.m_name = node["name"].as<std::string>();
p.m_age = node["age"].as<int>();
return p;
}
};
template<>
class LexicalCast<Person, std::string> {
public:
std::string operator()(const Person& p) {
YAML::Node node;
node["name"] = p.m_name;
node["age"] = p.m_age;
std::stringstream ss;
ss << node;
return ss.str();
}
};
}
static sylar::ConfigVar<int>::ptr g_person_config
= sylar::Config::Lookup("sylar.person", Person(), "sylar person");
int main(int argc, char** argv) {
SYLAR_LOG_INFO(g_logger) << g_person_config->getValue(); //输出 [Person name=(null) age=0]
sylar::Config::LoadFromConfDir("conf");
SYLAR_LOG_INFO(g_logger) << g_person_config->getValue(); //输出 [Person name=sylar age=10]
return 0;
}
// conf路径下sylar_test.yml文件内容
sylar:
person:
name: sylar
age: 10
登录后发表评论