基于zookeeper的配置管理中心
作者:李世海
链接:https://www.jianshu.com/p/01388f06e75d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
zookeeper是什么:
https://zookeeper.apache.org/
zookeeper的用途
- 数据发布与订阅(配置中心)
- 负载均衡
- 命名服务(Naming Service)
- 分布式通知/协调
- 集群管理与Master选举
- 分布式锁
- 分布式队列
为什么要实现这样的一个实例:
当在一个集群规模的环境中,多台同类型的应用使用同样的配置文件,为了避免登陆每台机器修改配置,为了减少人为的修改导致配置不一致,为了实现配置文件的统一管理、版本控制,那么就有必要实现一个配置管理中心的应用。
实例用到的软件
python (3.4.2)
zookeeper (3.4.8)
kazoo (2.2.1)
tornado (4.3)
配置管理中心实现了哪些功能
0. 标准的key value的配置文件几个
1. 基于web页面的配置文件的增删改查
2. 同一个app下多个配置文件
3. 版本管理,当前版本,历史版本管理
4. 数据持久化保存
5. 非标准的key value配置文件的管理
6. 客户端watcher的实现
配置管理中心的原理

zookeeper节点的设计
/conf
|
|-------appid
| |
| |------conf_name
|
|-----key1
|
|-----key2
|
|------version
|
|---history
|
|---current
- conf: zookeeper的跟节点
- appid: app的名称
- conf_name: 配置文件名称
- key: 配置文件中的key,对应存储的值为value
- version: 版本控制节点
- history: 历史版本
- current: 当前版本
- 例如想管理一个名称叫app1的应用,它的配置文件为app1.conf
- app1.conf的内容为:
- vim app1.conf
- host=127.0.0.1
- username='hans'
- 那么对应的节点为:
- /conf/app1/app1.conf/host
web界面展示
显示

创建

create.png
代码分析
主要代码
handler.py import time import os import shutil from os import path as os_path from tornado.web import RequestHandler from tornado.options import options from tornado.web import HTTPError from kazoo.exceptions import NodeExistsError class CreateHandler(RequestHandler): @staticmethod def persistence_conf(filename, content): if not os.path.exists(os.path.dirname(filename)): os.makedirs(os.path.dirname(filename)) with open(filename, 'w') as f: f.write(content) @staticmethod def copy_file(src, dst): if os.path.isdir(src) and os.path.isdir(dst): for f in os.listdir(src): if os.path.isfile(os_path.join(src, f)): shutil.copy(os_path.join(src, f), os_path.join(dst, f)) def write_history(self, node, version): self.application.zk.ensure_path(os_path.join(node, 'history')) try: node = os_path.join(node, 'history') self.application.zk.create(os_path.join(node, version), b'') except NodeExistsError: raise HTTPError(500, reason='{0} is already exist !') except Exception as e: raise HTTPError(500, reason=str(e)) def write_current(self, node, version): self.application.zk.ensure_path(os_path.join(node, 'current')) try: node = os_path.join(node, 'current') self.application.zk.create(os_path.join(node, 'version'), version.encode()) except NodeExistsError: self.application.zk.set(os_path.join(node, 'version'), version.encode()) except Exception as e: raise HTTPError(500, reason=str(e)) def get(self): self.render('create.html') def post(self): appid = self.get_argument('appid') conf_name = self.get_argument('conf_name') content = self.get_argument('content') # 创建节点/conf/emnp/jdbc.properties node = os_path.join(options.root, appid, conf_name) self.application.zk.ensure_path(node) # 创建子节点 for line in content.split('\n'): try: n = line.split('=') k, v = n[0].strip(), n[1].strip() self.application.zk.create(os_path.join(node, k), v.encode()) except IndexError: pass except NodeExistsError: # 更新节点 self.application.zk.set(os_path.join(node, k), v.encode()) except Exception as e: raise HTTPError(500, reason=str(e)) # 持久化数据 version = str(time.strftime("%Y%m%d%H%M%S", time.localtime(time.time()))) latest_dir = '' new_dir = '/'.join([options.workspace, options.root, appid, version]) path = '/'.join([options.workspace, options.root, appid]) try: dirs = [d for d in os.listdir(path)] if dirs: latest_dir = os_path.join(path, max(dirs)) os.makedirs(new_dir) CreateHandler.copy_file(latest_dir, new_dir) except FileNotFoundError: pass file_path = '/'.join([new_dir, conf_name]) CreateHandler.persistence_conf(file_path, content) # 写history node = '/'.join([options.root, appid, 'version']) self.write_history(node, version) # 写current self.write_current(node, version) self.redirect('/') class IndexHandler(RequestHandler): def get(self): """ test = { "app1":{ "current_version":"2016", "history_version":['2015','2016'], "conf_files":["app1.conf","app2.conf","app2.conf"] }, "app2": { "current_version": "1900", "history_version": ['1901', '1900'], "conf_files": ["cc1.conf", "cc2.conf", "cc3.conf"] } } """ data = {} node = os_path.join(options.root) appids = self.application.zk.get_children(node) conf_files = None for app in appids: conf_files = self.application.zk.get_children(os_path.join(node, app)) conf_files.remove('version') current_version, _ = self.application.zk.get(os_path.join(node, app, 'version', 'current', 'version')) history_version = self.application.zk.get_children(os_path.join(node, app, 'version', 'history')) data[app] = { "current_version": current_version.decode('utf-8'), "history_version": history_version, "conf_files": conf_files, } self.render('index.html', apps=data) class ShowHandler(RequestHandler): def get(self, *args, **kwargs): content = '' data = self.get_argument('data') appid,conf_name,current_version= data.split('(') path = '/'.join([options.workspace,options.root,appid,current_version,conf_name]) with open(path) as f: content = f.read() conf_content = { 'appid':appid, 'conf_name':conf_name, 'content':content } self.render('show.html',conf_content=conf_content) class EditHandler(RequestHandler): def get(self, *args, **kwargs): content = '' data = self.get_argument('data') appid, conf_name, current_version = data.split('(') path = '/'.join([options.workspace, options.root, appid, current_version, conf_name]) with open(path) as f: content = f.read() conf_content = { 'appid': appid, 'conf_name': conf_name, 'content': content } self.render('edit.html', conf_content=conf_content) class DeleteHandler(RequestHandler): def get(self, *args, **kwargs): content = '' data = self.get_argument('data') appid, conf_name, current_version = data.split('(') node = os_path.join(options.root,appid,conf_name) try: self.application.zk.delete(node,recursive=True) except Exception as e: raise HTTPError(500, reason=str(e)) self.redirect('/')
github地址:https://github.com/hansdcr/config_center
TODO
1. 非标准配置文件的管理 2. 非标准配置管理的watcher客户端
同类型的开源方案
1. 王阿晶,邹仕洪: [基于ZooKeeper的配置信息存储方案的设计与实现](http://wenku.baidu.com/view/ee86ca90daef5ef7ba0d3c7d.html)
2. 淘宝diamod实现:[http://code.taobao.org/p/diamond/src/](http://code.taobao.org/p/diamond/src/), 2012
3. disconf github: [https://github.com/knightliao/disconf](https://github.com/knightliao/disconf), 2014
4. [百度BJF配置中心](http://wiki.babel.baidu.com/twiki/bin/view/Main/CAP-CC#%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%831.0%E5%BF%AB%E9%80%9F%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97.pptx), 2014