程序猿阵线联盟-汇总各类技术干货程序员

应用程序配置config最佳实践

2018-02-25  本文已影响0人  程序员赤小豆_gzh同名

背景&问题

对于一个系统来说, 配置文件是必不可少的, 有的系统是把配置文件放到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的问题导致配置项获取不到呢? 配置项对程序运行非常重要, 怎么保证能够更加可靠地获取呢?

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配置的一些注意事项吧.

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

上一篇 下一篇

猜你喜欢

热点阅读