• 多线程程序如何简单高效地读取配置中心上的配置?


    本文限定为主动从配置中心读取配置方法,不限定配置中心类型,比如可使用DB作为配置中心。

    程序和配置中心间存在网络开销,因此需避免每次业务处理或请求都从配置中心读取配置。

    规避网络开销,可采取本地缓存配置,每隔指定时间只读取一次配置中心,比如每秒读取一次配置中心。

    假设每秒读取一次配置中心,这样每次的开销减少到每秒只有一次网络开销,此时可观察到性能毛刺,这毛刺是因为每次读取配置中心时的性能抖动(下降)。

    需要将读取配置中心从业务线程中移除,为此引入配置线程,由配置线程专门负责从配置中心读取配置,业务线程不直接从配置中心读取配置。

    struct Config { // 配置
      string a;
      string b;
    };
    
    class ConfigThread { // 配置线程
    public:
      void run() {
        while (!stop()) {
          std::this_thread::sleep_for(std::chrono::milliseconds(1000));
          read_config_from_config_center(); // 从配置中心读取配置
          
          std::unique_lock<std::shared_mutex> lock(_mutex);
          update_config(); // 更新本地的 _config
        }
      }
      
      void get_config(struct Config* config) const {
        std::shared_lock<std::shared_mutex> lock(_mutex);
        *config = _config;
      }
      
    private:
      // 注:C++17 才支持 shared_mutex 锁
      mutable std::shared_mutex _mutex; // 用来保护 _config 的读取写锁
      struct Config _config;
    };
    
    class WorkThread { // 业务线程
    public:
      void run() {
        while (!stop()) {
          struct Config config;
          _config_thread->get_config(&config);
        }
      }
    
    private:
      std::shared_ptr<ConfigThread> _config_thread;
    };
    

    从上面代码可以看到,有了新的矛盾点,即读取锁碰撞问题(注:锁带来的性能问题主要是锁碰撞,而非一次系统调用)。

    当配置线程在加写锁更新配置时,仍然会产生毛刺。简单点可使用读优先读写锁来降低影响,但不能根本解决问题。

    可使用尝试锁替代读锁来根本性解决问题:

    struct Config { // 配置
      string a;
      string b;
    };
    
    class ConfigThread { // 配置线程
    public:
      void run() {
        while (!stop()) {
          std::this_thread::sleep_for(std::chrono::milliseconds(1000));
          read_config_from_config_center(); // 从配置中心读取配置
          
          std::unique_lock<std::shared_mutex> lock(_mutex);
          update_config(); // 更新本地的 _config
        }
      }
      
      bool get_config(struct Config* config) const {
        if (!_mutex.try_lock_shared())
          return false;   
        *config = _config;
        _mutex.unlock();
        return true
      }
      
    private:
      mutable std::shared_mutex _mutex; // 用来保护 _config 的读取写锁
      struct Config _config;
    };
    
    class WorkThread { // 业务线程
    public:
      void run() {
        while (!stop()) {
          const struct Config& config = get_config();
        }
      }
    
    private:
      const struct Config& get_config() {
        struct Config config;
        if (_config_thread->get_config(&config))
          _config = config;
        // 如果没有读取到新的配置,则仍然使用老的配置
        return _config;
      }
    
    private:
      std::shared_ptr<ConfigThread> _config_thread;
      struct Config _config; // 线程本地配置
    };
    

    上述 WorkThread::get_config() 每次都要加一次锁,因为加的是读尝试锁,所以对性能影响很少,大多数都可忽略。如果仍然对性能有影响,可采取每秒只调用一次 ConfigThread::get_config(),来减少锁的调用次数。

    局限性:
    本文介绍的方法仅适合配置比较少的场景,是多还是少,主要依据消耗的内存大小,当消耗的内存在可接受范围内则可定义为少。

    此外,一个问题是:配置更新时间点不一致,但即使每次都去读取配置中心,仍然不一致问题。

  • 相关阅读:
    猴子分香蕉
    打鱼晒网
    质数/素数
    三角形-->九九乘法表
    eclipse 导入gradle引入多模块项目,引入eclipse后变成了好几个工程
    linux 命令基础大全
    SQL Server 增加链接服务器
    Postgresql数据库部署之:Postgresql 存在session 会话不能删除数据库
    Postgresql数据库部署之:Postgresql本机启动和Postgresql注册成windows 服务
    Git常用命令使用大全
  • 原文地址:https://www.cnblogs.com/aquester/p/14155345.html
Copyright © 2020-2023  润新知