概述

什么是配置?chatGPT是这么回答的:

配置项(Configuration Item)是一个广泛使用的术语,尤其在软件开发、系统管理和IT服务管理中。它通常指的是系统或应用程序中的一个可配置的元素,可以用来调整系统或应用的行为、性能或其他特性。配置项可以是软件、硬件、文档、数据库、参数设置等。

配置项的用途

  • 定制化:通过调整配置项,可以让同一软件或系统在不同环境中运行时表现出不同的行为和特性。
  • 优化性能:通过修改配置参数,可以优化系统或应用程序的性能。
  • 提高安全性:配置安全参数和策略,增强系统或应用程序的安全性。
  • 简化管理:通过集中管理和控制配置项,可以简化系统或应用程序的管理和维护。

Sylar使用YAML作为配置文件。最简单的配置文件方式还是 .ini 那种,选择YAML是因为易读易写,同时能支持复杂的数据结构。这里支持 STL 容器(vector, list, set, map 等等),支持自定义类型(但需要实现序列化和反序列化方法,也就是两个仿函数)。YAML 语法快速简单入门:https://www.runoob.com/w3cnote/yaml-intro.html

在配置模块中主要有以下几个类:

  • class ConfigVarBase:配置变量的基类
  • class ConfigVar:配置参数模板子类,保存对应类型的参数值,通过仿函数实现string和T类型之间的相互转化
  • class Config:ConfigVar的管理类

最后将日志模块与配置模块整合起来,当配置文件相应参数做出改变时,能够通过回调函数改变相应的参数。

配置模块实现了以下功能:

  • 支持yaml格式的配置文件解析
  • 使用模板完成基础类型,复杂类型(vector、map、set等),自定义类型的序列化与反序列化
  • 利用回调机制,在加载配置时,完成配置的更新
  • 使用yaml-cpp库,实现配置文件读取
  • 约定大于配置

详解

配置变量的基类 class ConfigVarBase

虚基类。包含配置名称和配置描述两个属性。提供 toString() 和 fromString() 两个纯虚函数将参数值转换成 YAML String 和从 YAML String 转成参数的值。

// 名字
std::string m_name;
// 描述
std::string m_description;

virtual std::string toString() = 0; //转化为string
virtual bool fromString(const std::string& val) = 0;    //从string转化为相应类型
virtual std::string getTypeName() const = 0;    //获得该类型的名称
ConfigVarBase(构造函数 )

std::transform被用于将字符串m_name中的字母字符转换为小写形式并覆盖原来的字符串。所以不区分大小写

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);
    }

配置参数 class ConfigVar

/* 
 *  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 {};
  • 对于每种类型的配置,在对应的 ConfigVar 模板类实例化时都要提供其 FromStr 和 ToStr 两个仿函数,用于实现该类型和 YAML 字符串的相互转换。
  • 配置参数模板子类。继承自 ConfigVarBase。
  • 保存对应类型 T 的参数值。
  • 根据不同的非内置基本类型 T,FromStr 和 ToStr 具有不同的模板偏特化实现。
  • 内含一个 std::map,存放配置变更的回调函数。
    提供 setValue() 和 getValue() 函数,其中在 setValue() 函数中,会调用 std::map 中存放的所有回调函数进行通知配置变更。
  • 关于回调函数,提供 addListener()、delListener()、getListener()、clearListener() 四个函数给外界使用。
    其中,FromStr和ToStr使用仿函数片特化的方式,实现不同类型T与string之间的相互转化,例如vector与string之间的转化,在转化的过程中,字符串格式都是以YAML为标准。
//F from_type, T to_type
template<class F, class T>
class LexicalCast {
public:
    T operator() (const F &v) {
        return boost::lexical_cast<T>(v);
    }
};
​
// string To vector
// "[1, 2, 3]" ——> [1, 2, 3]
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;
    }
};
​
// vector To string
// [1, 2, 3] ——> - 1
//               - 2
//               - 3             
template<class T>
class LexicalCast<std::vector<T>, std::string> {
public:
    std::string operator() (const std::vector<T>& v) {
        YAML::Node node;
        for (auto& i : v) {
            node.push_back(YAML::Load(LexicalCast<T, std::string>()(i)));
        }
        std::stringstream ss;
        ss << node;
        return ss.str();
    }
};

mumber(成员变量)

    // 参数值
    T m_val;
    // 变更回调函数组, uint64_t key(要求唯一,一般可以用hash)
    // typedef std::function<void(const T &old_value, const T &new_value)> on_change_cb;
    std::map<uint64_t, on_change_cb> m_cbs;
    // 读写锁
    mutable RWMutexType m_mutex;

函数

// 构造函数
// 给配置名、描述、参数值赋值
ConfigVar(const std::string& name
        , const T& defult_val
        , const std::string& description = "")
    : ConfigVarBase(name, description)
    , m_val(defult_val) {
​
    }

// toString(从参数转为string)
// 若成功,返回转化后的string,失败打出日志,异常以及值的类型
std::string toString() override {
    try{
        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: " << typeid(m_val).name() << "to String";
    }
    return "";
}

// fromString(从string转为值)
// 从YAML String转为参数值,失败打出日志,异常以及值的类型
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 " << typeid(m_val).name()
            << " - " << val;
    }
    return false;
}

// getValue(获取)和setValue(设置参数)
// 获取参数直接返回m_val,设置参数时,判断v与m_val是否相同,若相同,直接返回,若不相同,则回调变更函数
// 获取参数
const T getValue() const {
    RWMutexType::ReadLock lock(m_mutex);
    return m_val;
}
​
// 设置参数
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;
}

// addListener(添加变化回调函数)
// 返回该回调函数对应的唯一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;
}

// delListener(删除回调函数)
void delListener(uint64_t key) {
    RWMutexType::WriteLock lock(m_mutex);
    m_cbs.erase(key);
}

// getListener(获取回调函数)
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;
}

// clearListener(清除回调函数)
void clearListener() {
    RWMutexType::WriteLock lock(m_mutex);
    m_cbs.clear();
} 

ConfigVar管理类 Config

Config是ConfigVar的管理类,用来管理全部的ConfigVar全特化后的对象。他通过单例模式进行管理,整个服务进程只有一个对象。

没有任何静态成员变量,通过GetDatas函数内的静态的map进行管理,以及用GetMutex函数内的静态的锁加解锁。

Lookup函数用于创建或获取对应参数名的配置参数。传入三个参数,字符串形式的配置参数名称,T类型的参数默认值,字符串形式的参数描述。首先判断是否存在参数名称的配置,若存在,则从map中取出这个配置项(ConfigVarBase类型的指针,指向了ConfigVar的某个全特化的类的对象)。并且通过dynamic_pointer_cast,转换成ConfigVar类型的智能指针。若tmp为nullptr的话,说明map中存在着相同名字的配置项,但是map中配置项的参数类型和传入的默认值的参数默认值不一样,返回nullptr,否则就返回正确的配置参数。接着判断参数名是否包含非法字符,存在就抛出一个异常。然后创建一个ConfigVar类型的指针指向的ConfigVar类型的对象,添加到map中。

还有一个重载的Lookup函数用于查找配置参数,传入配置参数名称,返回对应配置参数名称的配置参数。若不存在或者类型错误的话,都会返回nullptr。

LoadFromYaml函数用于使用yaml初始化配置模块,参数传入一个yaml对象。函数内有名为ListAllMember的函数,采用类似遍历二叉树的方式遍历。

LoadFromConfDir用于加载path文件夹里面的配置文件。

LookupBase用于返回配置参数的基类(他跟上面的Lookup的区别是一个返回ConfigVar类型的指针,一个返回基类指针)。

Visit函数用于遍历配置模块里面所有配置项,并且将配置项作为传入函数的参数进行执行。

用法如下:

sylar::ConfigVar<int>::ptr g_int_value_config = sylar::Config::Lookup("system.port", (int)8080, "system port");
  				|
				|
			   \|/
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "before: " << g_int_value_config->getValue();
  				|
				|
			   \|/
YAML::Node root = YAML::LoadFile("/home/sylar/workspace/sylar/bin/conf/test.yml");
sylar::Config::LoadFromYaml(root);
				|
				|
			   \|/
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "after: " << g_int_value_config->getValue();

成员变量:

// typedef std::unordered_map<std::string, ConfigVarBase::ptr> ConfigVarMap;
// 返回所有的配置项
static ConfigVarMap& GetDatas() {
    static ConfigVarMap s_datas;
    return s_datas;
}
​
// 配置项的RWMutex
static RWMutexType& GetMutex() {
    static RWMutexType s_mutex;
    return s_mutex;
}

Lookup(获取/创建对应参数名的配置参数)

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()) {
            // 将ConfigVarBase转换为ConfigVar
            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 << " exitst but type not "
                                                  << typeid(T).name() << ", real_type = " << it->second->getTypeName()
                                                  << " " << it->second->toString();
                return nullptr;
            }
        }
    
        // 用于在当前字符串中查找第一个不属于指定字符集合的字符,并返回该字符位置的索引。如果没有找到任何字符,则返回 std::string::npos。
        // name不全在 "abcdefghigklmnopqrstuvwxyz._012345678" 中 
        // name中有非法字符,抛出异常
        if (name.find_first_not_of("abcdefghigklmnopqrstuvwxyz._012345678")
                != std::string::npos) {
            SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name invalid" << name;
            throw std::invalid_argument(name);
        }
        
        // 若没有,则创建一个新的ConfigVar
        // typename:用于告诉编译器 ConfigVar<T>::ptr 是一个类型而不是成员变量。
        typename ConfigVar<T>::ptr v(new ConfigVar<T>(name, default_value, description));
        GetDatas()[name] = v;
        return v;
}

Lookup(查找配置参数)

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);
}

LookupBase(查找配置参数,返回配置参数的基类)

ConfigVarBase::ptr Config::LookupBase(const std::string& name) {
    RWMutexType::ReadLock lock(GetMutex());
    auto it = GetDatas().find(name);
    return it == GetDatas().end() ? nullptr : it->second;
}

LoadFromYaml(使用YAML::Node初始化配置模块)

// 递归方式,遍历YAML格式的配置文件中的所有成员,将每个节点的名称和值存在list中
static void ListAllMember(const std::string& prefix,
                          const YAML::Node& node,
                          std::list<std::pair<std::string, const YAML::Node> >& output) {
    // prefix字符不合法
    if (prefix.find_first_not_of("abcdefghigklmnopqrstuvwxyz._012345678") 
            != std::string::npos) {
        SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "Config invalid name: " << prefix << " ! " << node;
        return;
    }
    output.push_back(std::make_pair(prefix, node));
    // 若解析的是map
    if (node.IsMap()) {
        for (auto it = node.begin();
                 it != node.end(); ++it) {
            // 若前缀为空,说明为顶层,prefix为key的值,否则为子层,prefix为父层加上当前层。it->second为当前node
            ListAllMember(prefix.empty() ? it->first.Scalar() : prefix + "." + it->first.Scalar(), it->second, output);
        }
    }
}
​
void Config::LoadFromYaml(const YAML::Node& root) {
    // 将root中的所有节点信息保存到all_nodes中
    std::list<std::pair<std::string, const YAML::Node> > all_nodes;
    ListAllMember("", root, all_nodes);
    
    // 遍历list
    for(auto &i : all_nodes) {
        std::string &key = i.first;
        if (key.empty()) {
            continue;
        }
        
        // 无视大小写
        std::transform(key.begin(), key.end(), key.begin(), ::tolower);
        // 查找名为key的配置参数
        ConfigVarBase::ptr var = LookupBase(key);
        // 若找到
        if (var) {
            // 若为纯量,则调用fromString(会调用setValue设值)
            if (i.second.IsScalar()) {
                var->fromString(i.second.Scalar());
            // 否则为数组,将其转换为字符串
            } else {
                std::stringstream ss;
                ss << i.second;
                var->fromString(ss.str());
            }
        }
    }
}

Visit(遍历配置模块里面所有配置项)

void Config::Visit(std::function<void(ConfigVarBase::ptr)> cb) {
    RWMutexType::ReadLock lock(GetMutex());
    ConfigVarMap& m = GetDatas();
    for (auto it = m.begin();
         it != m.end(); ++it) {
        cb(it->second);
    }
}