应用程序配置config最佳实践

作者: 程序员赤小豆_gzh同名 | 来源:发表于2018-02-25 17:03 被阅读0次

    背景&问题

    对于一个系统来说, 配置文件是必不可少的, 有的系统是把配置文件放到conf文件夹下面, 系统启动的时候加载进来, 有的是统一放到外部的某个地方, 方便统一管理. 这样设计配置文件的问题显而易见, 一旦需要修改配置文件, 需要重启或重新部署该应用. 重启你懂的, 用户们在使用呢, 怎么能突然no service呢. 金主爸爸们不能得罪啊, 难道要半夜起来重启吗? 有没有什么好的方法可以无需重启就直接无缝衔接到新的配置项呢?

    最近在看Microsoft的CloudDesignPattern, 写的非常详细, 各种云计算中的软件开发最佳实践都提到了, 推荐大家下载PDF查阅. 这本手册中就有提到Runtime Reconfiguration Pattern, 结合工作中的实践来做一个小小的总结.

    解决方法

    step0: 前提

    首先需要一套全局配置中心, 专门用于存储公司内部各个应用的配置项, 解决配置混乱分散的问题. 用户可以在配置中心创建和修改配置, 修改配置有相应的版本控制和容灾措施. 有了这套系统之后, 其他应用就可以通过API获取配置项. 具体的配置中心搭建可以通过淘宝开源的Diamond来实现, 这个人写的XDiamond也很清晰(http://blog.csdn.net/hengyunabc/article/details/47777807)

    有了这套中心化的配置管理服务, 就可以开始安全, 高效, 实时地获取配置了. 通过调用全局配置的API, 获取该项目的配置项, 这个过程中也有许多注意事项.

    step1: 可靠地获取配置

    有了上述的配置系统, 剩下的事情就是怎么call API的问题了. 通过HTTP请求获取配置信息, 难免遇到网络拥塞, 请求失败, 或者server一时间忙碌, 无响应等. 怎么避免这些并非client side的问题导致配置项获取不到呢? 配置项对程序运行非常重要, 怎么保证能够更加可靠地获取呢?

    • 首先想到的就是Retry. 如果这个请求不是我的问题, 是服务器的问题, 或者网络问题, 重新请求一次会成功的话, 那么可以选择一定的backoff时间后重试.
    • 重试不能一直无限重试下去. 需要设定一个timeout的时间, 如果多次尝试都没有结果, 说明服务挂了, 或者系统出现其他的故障. 必要的添加一些logging, 比如请求失败的原因, 方便后续发现问题.
    • 并非所有的请求失败都要重试. 针对特定的HTTP code来重发请求, 比如请求返回401(unauthorized), 重试多少次都是没有用的, 因为你的认证没有通过. 一般来说, 500, 502, 504这些server端的问题, 可以进行重试.
      通过下面的retry session, 一股脑解决上述问题!
    import requests
    from requests.adapters import HTTPAdapter
    from requests.packages.urllib3.util.retry import Retry
    
    
    def requests_retry_session(
        retries=3,
        backoff_factor=0.3,
        status_forcelist=(500, 502, 504),
        session=None,
    ):
        session = session or requests.Session()
        retry = Retry(
            total=retries,
            read=retries,
            connect=retries,
            backoff_factor=backoff_factor,
            status_forcelist=status_forcelist,
        )
        adapter = HTTPAdapter(max_retries=retry)
        session.mount('http://', adapter)
        session.mount('https://', adapter)
        return session
    

    这里有个高大上的python retry best practice. 覆盖了重试过程中需要考虑的各种问题, 有空可以研读一下https://www.peterbe.com/plog/best-practice-with-retries-with-requests

    step2: 有效地更新配置

    获取应用的配置信息, 可以通过主动pull的方式, 也可以通过被动push的方式, push方式需要配置管理中心有消息通知的服务.
    简单说说主动去pull配置的一些注意事项吧.

    • 间隔一定的时间去更新缓存的配置. HTTP请求难免会花费一定的时间, 遇到网络不好的时候, 请求配置时间会更加长, 因此会给你的应用程序带来一定的延迟. 我们可以把获取到的配置缓存到内存中, 超过一定的时间后可以再重新获取并更新.
    import time
    class ConfigReader(object):
        # 初始化一个配置更新时间
        last_update_time = None
    
        def load_config(self):
            if self.last_update_time is None or time.time() - self.last_update_time > 60:
                # initialization and load config every 1 minutes
                config_values = requests_retry_session().get('your request url').json()
                app_env = 'production or whatever'
                self.do_update(app_env, config_values)
    
        @classmethod
        def do_update(cls, app_env, config_values):
              # 记得要更新配置的时间
              cls.last_update_time = time.time()
              cls.db_config = config_values.get('db_config')
              ...
               
    

    如上所示, @classmethod声明了函数do_update()为类函数, cls指向该类, 在该函数内设置的变量为类变量, 类变量(attribute)的好处是绑定在class上的, 即使你在程序的各个地方都创建了ConfigReader对象, 只要有一个对象在调用的时候发现超时了需要再次调用do_update, 并更新了诸如db_config等attribute的值, 那么所有ConfigReader对象的attribute db_config都是最新的.

    下面提供一段代码仅供理解:

    # -*- coding: utf-8 -*-
    import time
    class ConfigReader(object):
        # 初始化一个配置更新时间
        last_update_time = None
    
        def load_config(self):
            if self.last_update_time is None or time.time() - self.last_update_time > 6:
                # initialization and load config every 1 minutes
                config_values = dict(db_config="localhost:3306")
                app_env = 'production or whatever'
                print "do load config"
                self.do_update(app_env, config_values)
            else:
                print "not yet to load config"
    
        @classmethod
        def do_update(cls, app_env, config_values):
              # 记得要更新配置的时间
              cls.last_update_time = time.time()
              cls.db_config = config_values.get('db_config')
    
    config_reader1 = ConfigReader()
    config_reader1.load_config()
    print "reader1 update time: %s" % config_reader1.last_update_time
    
    config_reader2 = ConfigReader()
    config_reader2.load_config()
    print "reader2 update time: %s" % config_reader2.last_update_time
    
    print "sleep 6 second, see if it load again..."
    time.sleep(6)
    config_reader2.load_config()
    
    print "reader1 update time: %s" % config_reader1.last_update_time
    print "reader2 update time: %s" % config_reader2.last_update_time
    

    输出结果:

    do load config
    reader1 update time: 1519571163.71
    not yet to load config
    reader2 update time: 1519571163.71
    sleep 6 second, see if it load again...
    do load config
    reader1 update time: 1519571169.71
    reader2 update time: 1519571169.71
    

    从上述代码的输出结果可见, attribute是跟类绑定的, 即使我创建了config_reader1config_reader2, 不管中间谁执行了load_config()方法, 它们的last_update_time变量是保持一致的.

    当然缓存有很多方法, 比如可以写到本地文件, 看文件的最后更新时间来决定是否更新缓存. 像上述的方法, 简单的保存在内存中, 适合数据量不大的情景. 通过以上的可靠获取配置, 有效更新配置的方法, 就能够实现实时地去修改配置项, 无需重启应用啦.

    Reference

    相关文章

      网友评论

        本文标题:应用程序配置config最佳实践

        本文链接:https://www.haomeiwen.com/subject/eeerxftx.html