sylar服务器框架 - 配置模块

sylar
2019.08.15 14:26:05阅读 5点赞 0收藏 0更多
版权声明:本文为博主原创文章,遵循 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