[sylar]C++高性能服务器框架——配置模块

2,246 阅读7分钟

配置模块概述

配置模块采用约定优于配置的设计思想,让程序所依赖的配置项都有一个默认值,就不需要每次都指定了,这样既简单又灵活。例如通过以下方式设置默认端口号,名称:system.port,值:8080,描述:system port

sylar::ConfigVar<int>::ptr g_int_value_config =
    sylar::Config::Lookup("system.port", (int)8080, "system port");

当对YAML文件配置项做出改变时,也会改变相应的配置参数,此时端口号被修改为5050

system:
    port: 5050      

YAML支持以下几种数据类型:

  • 对象(Map ) :类似标准库中的Map,键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)。(key: value)形式出现的数据
port: 5050

# 支持多层嵌套
system:
    port: 5050
    addr: 127.0.0.1
    
#  支持流式风格
system: { port: 5050, addr: 127.0.0.1 }
  • 数组(Sequence ) :一组按次序排列的值,又称为序列(sequence) / 列表(list),一组以区块格式(Block Format) (即“破折号+空格”) 开头的数据组成一个数组
values:
  - value1
  - value2
  - value3
​
# 支持多维数组(用缩进表示层级关系)
values:
  -
    - value1
    - value2
  -
    - value3
    - value4
  • 纯量(scalars) :单个的、不可再分的值
字符串
布尔值
整数
浮点数
Null
时间
日期

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

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

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

详解

class ConfigVarBase(配置变量的基类)

该类为抽象函数,提供三个纯虚函数供子类ConfigVar实现

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

mumber(成员变量)

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

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

其中,FromStrToStr使用仿函数片特化的方式,实现不同类型Tstring之间的相互转化,例如vectorstring之间的转化,在转化的过程中,字符串格式都是以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(构造函数)

配置名描述参数值赋值

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,设置参数时,判断vm_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();
} 

class Config(ConfigVar管理类)

mumber(成员变量)

使用静态方法返回参数,保证初始化顺序

c++保证,函数内的local static对象会在”该函数被调用期间“”首次遇上该对象之定义式“时被初始化。

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

Log和Config整合

log.yml内容格式如下

logs:
    - name: root
      level: info
      formatter: "%d%T%m%n"
      appenders:
          - type: FileLogAppender
            file: root.txt
          - type: StdoutLogAppender
    - name: system
      level: debug
      formatter: "%d%T%m%n"
      appenders:
          - type: FileLogAppender
            file: system.txt
            formatter: "%d%T[%p]%T%m%n"
          - type: StdoutLogAppender

LogAppender结构体重载==

struct LogAppenderDefine {
    int type = 0;   //1 File, 2 Stdout
    LogLevel::Level level = LogLevel::UNKNOW;
    std::string formatter;
    std::string file;
​
    bool operator==(const LogAppenderDefine& oth) const {
        return type == oth.type
            && level == oth.level
            && formatter == oth.formatter
            && file == oth.file;
    }
};

Log结构体,重载==<

struct LogDefine {
    std::string name;
    LogLevel::Level level = LogLevel::UNKNOW;
    std::string formatter;
    std::vector<LogAppenderDefine> appenders;
​
    bool operator==(const LogDefine& oth) const{
        return name == oth.name
            && level == oth.level
            && formatter == oth.formatter
            && appenders == oth.appenders;
    }
​
    bool operator<(const LogDefine& oth) const {
        return name < oth.name;
    }
};

string To LogDefine

template<>
class LexicalCast<std::string, LogDefine> {
public:
    // 仿函数
    LogDefine operator()(const std::string& v) {
        // 将文本格式YAML数据v解析为内存中YAML节点n
        YAML::Node n = YAML::Load(v);
        LogDefine ld;
        // 若未定义名为"name"的键,抛出异常
        if(!n["name"].IsDefined()) {
            std::cout << "log config error: name is null, " << n
                      << std::endl;
            throw std::logic_error("log config name is null");
        }
        // 将n["name"]值转为string赋给ld.name
        ld.name = n["name"].as<std::string>();
        // 若n定义了level,则直接赋值,否则为空
        ld.level = LogLevel::FromString(n["level"].IsDefined() ? n["level"].as<std::string>() : "");
        // 若n定义了formatter,则使用当前值,否则为默认的formatter
        if(n["formatter"].IsDefined()) {
            ld.formatter = n["formatter"].as<std::string>();
        }
        
        // 若n定义了appenders
        if(n["appenders"].IsDefined()) {
            // 遍历所有的appenders
            for(size_t x = 0; x < n["appenders"].size(); ++x) {
                auto a = n["appenders"][x];
                // 若当前appender没有定义type,输出错误日志
                if(!a["type"].IsDefined()) {
                    std::cout << "log config error: appender type is null, " << a
                              << std::endl;
                    continue;
                }
                std::string type = a["type"].as<std::string>();
                // 定义LogAppenderDefine对象 lad
                LogAppenderDefine lad;
                // 若type为FileLogAppender
                if(type == "FileLogAppender") {
                    // type置为1
                    lad.type = 1;
                    // 若没有设置文件路径,输出错误日志
                    if(!a["file"].IsDefined()) {
                        std::cout << "log config error: fileappender file is null, " << a
                              << std::endl;
                        continue;
                    }
                    // 设置文件路径
                    lad.file = a["file"].as<std::string>();
                    // 设置appender的formatter
                    if(a["formatter"].IsDefined()) {
                        lad.formatter = a["formatter"].as<std::string>();
                    }
                  // 若type为STdoutLogAppender
                } else if(type == "StdoutLogAppender") {
                    // type置为2
                    lad.type = 2;
                    // 设置appender的formatter
                    if(a["formatter"].IsDefined()) {
                        lad.formatter = a["formatter"].as<std::string>();
                    }
                  // type输入错误
                } else {
                    std::cout << "log config error: appender type is invalid, " << a
                              << std::endl;
                    continue;
                }
                ld.appenders.push_back(lad);
            }
        }
        return ld;
    }
};

LogDefine To string

template<>
class LexicalCast<LogDefine, std::string> {
public:
    std::string operator()(const LogDefine& i) {
        YAML::Node n;
        n["name"] = i.name;
        if(i.level != LogLevel::UNKNOW) {
            n["level"] = LogLevel::ToString(i.level);
        }
        if(!i.formatter.empty()) {
            n["formatter"] = i.formatter;
        }
​
        for(auto& a : i.appenders) {
            YAML::Node na;
            if(a.type == 1) {
                na["type"] = "FileLogAppender";
                na["file"] = a.file;
            } else if(a.type == 2) {
                na["type"] = "StdoutLogAppender";
            }
            if(a.level != LogLevel::UNKNOW) {
                na["level"] = LogLevel::ToString(a.level);
            }
​
            if(!a.formatter.empty()) {
                na["formatter"] = a.formatter;
            }
​
            n["appenders"].push_back(na);
        }
        std::stringstream ss;
        ss << n;
        return ss.str();
    }
};

按照YAML配置文件初始化Log

sylar::ConfigVar<std::set<LogDefine> >::ptr g_log_defines =
    sylar::Config::Lookup("logs", std::set<LogDefine>(), "logs config");
​
struct LogIniter {
    LogIniter() {
        // 添加变化回调函数
        g_log_defines->addListener([](const std::set<LogDefine>& old_value
                    , const std::set<LogDefine>& new_value) {
            SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "on_logger_conf_changed";
            for(auto& i : new_value) {
                auto it = old_value.find(i);
                sylar::Logger::ptr logger;
                // new有 old没有 
                if (it == old_value.end()) {
                    //新增logger
                    logger = SYLAR_LOG_NAME(i.name);
                }
                else {
                    if (!(i == *it)) {
                        // 修改的logger
                        logger = SYLAR_LOG_NAME(i.name);
                    } else {
                        continue;
                    }
                }
                // 设置level
                logger->setLevel(i.level);
                // 设置默认formatter
                if (!i.formatter.empty()) {
                    logger->setFormatter(i.formatter);
                }
​
                logger->clearAppenders();
                // 设置appenders
                for (auto &a : i.appenders) {
                    sylar::LogAppender::ptr ap;
                    // File
                    if (a.type == 1) {
                        ap.reset(new FileLogAppender(a.file));
                        // Stdout
                    } else if (a.type == 2) {
                        ap.reset(new StdoutLogAppender);
                    }
                    // 设置appenderlevel
                    ap->setLevel(a.level);
                    
                    // 设置appender的formatter
                    if(!a.formatter.empty()) {
                        LogFormatter::ptr fmt(new LogFormatter(a.formatter));
                        // 若格式无错误
                        if (!fmt->isError()) {
                            ap->setFormatter(fmt);
                        } else {
                            std::cout << "log.name" << i.name << ", appender type = " << a.type
                                      << ", formatter = " << a.formatter << "is invalid" << std::endl;
                        }
                    }
                    // 放入logger的appender中
                    logger->addAppender(ap);
                }
            }
​
            for(auto& i : old_value) {
                auto it = new_value.find(i);
                // old有 new没有
                if(it == new_value.end()) {
                    //删除logger
                    auto logger = SYLAR_LOG_NAME(i.name);
                    logger->setLevel((LogLevel::Level)100);
                    logger->clearAppenders();
                }
            }
        });
    }
};
​
// 保证在main之前初始化
static LogIniter __log_init;

总结

通过YAML配置文件可以配置系统的参数,当设置新值时,可以通过回调函数更新系统配置。举个实例讲述一下过程。

  1. 在main之前就通过static LogIniter __log_init;添加了变化回调函数
  2. 使用YAML::Node root = YAML::LoadFile("sylar/bin/conf/log.yml");加载文件
  3. 使用sylar::Config::LoadFromYaml(root)初始化配置模块,并会调用fromString()将解析出的nodestring转化为相应的类型,其中会调用setValue设置参数值并且调用变化回调函数更新logger的参数。