QConf 是一个分布式配置管理工具。 用来替代传统的配置文件,使得配置和业务代码分离,同时配置可以实时同步到客户端保证配置及时生效。php
单条数据量小html
更新频繁(较代码而言)java
配置总数可能巨大,但单台机器关心配置数有限node
读多写少python
QConf采用CMake进行构建(CMake 版本 2.6及以上)c++
可使用如下命令完成QConf的编译安装:git
mkdir build && cd build
cmake ..
make
make install复制代码
搭建Zookeeper集群github
在QConf 配置文件中配置Zookeeper集群地址shell
vi QCONF_INSTALL_PREFIX/conf/idc.conf复制代码
#all the zookeeper host configuration.
#[zookeeper]
zookeeper.test=127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183 #test机房zookeeper配置复制代码
echo test > QCONF_INSTALL_PREFIX/conf/localidc #指定本地机房为test复制代码
cd QCONF_INSTALL_PREFIX/bin && sh agent-cmd.sh start复制代码
qconf get_conf /demo/node1 # get the value of '/demo/node1'复制代码
Qconf采用Zookeeper为分布式配置数据集群,利用qconf_agent与zookeeper集群交互获取数据,业务代码经过与qconf进行交互获取配置信息,Qconf服务端与zookeeper集群交互进行配置集群数据。整体来看仍是很清晰的,其核心就是利用zookeeper分布式系统及其watcher功能。缓存
进入主题,开始介绍QConf的架构实现,下图展现的是QConf的基本结构,从角色上划分主要包括 QConf客户端 , QConf服务端 , QConf管理端 。
QConf使用ZooKeeper集群做为服务端提供服务。能够将单条配置内容直接存储在ZooKeeper的一个ZNode上,并利用ZooKeeper的Watch监听功能实现配置变化时对客户端的及时通知。 按照ZooKeeper的设计目标,其只提供最基础的功能,包括顺序一致,原子性,单一系统镜像,可靠性和及时性。
由于ZooKeeper在接口方面只提供了很是基本的操做,而且其客户端接口原始,因此咱们须要在QConf的客户端部分解决以下问题:
下降与ZooKeeper的连接数 原生的ZooKeeper客户端中,全部须要获取配置的进程都须要与ZooKeeper保持长链接,在生产环境中每一个客户端机器可能都会有上百个进程须要访问数据,这对ZooKeeper的压力很是大并且也是没必要要的。
本地缓存 固然咱们不但愿客户端进程每次须要数据都走网络获取,因此须要维护一份客户端缓存,仅在配置变化时更新。
容错 当进程死掉,网络终端,机器重启等异常状况发生时,咱们但愿能尽量的提供可靠的配置获取服务。
多语言版本接口 目前提供的语言版本包括:c,php,java,python,go,lua,shell。
配置更新及时 能够秒级同步到全部客户端机器。
高效的配置读取 内存级的访问速度。
下面来看下QConf客户端的架构:
能够看到QConf客户端主要有:Agent、各类语言接口、链接他们的消息队列和共享内存。
在QConf中,配置以Key-Value的形式存在,业务进程给出key得到对应Value,这与传统的配置文件方式是一致的。
下面经过两个主要场景的数据流动来讲明他们各自的功能和角色:
业务进程调用某一种语言的QConf接口,从共享内存中查找须要的配置信息。
若是存在,直接获取,不然会向消息队列中加入该配置key。
Agent从消息队列中感知须要获取的配置key。
Agent向ZooKeeper查询数据并注册监听。
Agent将得到的配置Value序列化后放入共享内存。
业务进程从共享内存中得到最新值。
图6 数据流动-配置更新
ZooKeeper通知Agent某配置项发生变化。
Agent从ZooKeeper查询新值并更新Watcher。
Agent用新值更新共享内存中的该配置项。
经过上面的说明,能够看出QConf的总体结构和流程很是简单。 QConf中各个组件或线程之间仅经过有限的中间数据结构通讯,耦合性很是小,各自只负责本身的本职工做和一亩三分地,而不感知总体结构。
下面经过几个点来详细介绍:
根据上文提到的配置信息的特征,咱们认为在QConf客户端进行的是多进程并行读取的过程,对配置数据来讲读操做远多于写操做。为了尽量的提升读效率,整个QConf客户端在操做共享内存时采用的是无锁的操做,同时为了保证数据的正确,采起了以下两个措施:
单点写
将写操做集中到单一线程,其余线程经过中间数据结构与之通讯,写操做排队,用这种方法牺牲掉一些写效率。 在QConf客户端,须要对共享内存进行写操做的场景有:
用户进程经过消息队列发送的需获取Key;
ZooKeeper 配置修改删除等触发Watcher通知,需更新;
为了消除Watcher丢失形成的不一致,须要定时对共享内存中的全部配置从新注册Watcher,此时可能会须要更新;
发生Agent重启、网络中断、ZooKeeper会话过时等异常状况以后,需从新拉数据,此时可能须要更新。
读验证
无锁的读写方式,会存在读到未写入彻底数据的危险,但考虑到在绝对的读多写少环境中这种状况发生的几率较低,因此咱们容许其发生,经过读操做时的验证来发现。共享内存数据在序列化时会带其md5值,业务进程从共享内存中读取时,利用预存的md5值验证是否正确读取。
QConf中采起了一些处理来应对不可避免的异常状况:
采用父子进程Keepalive的方式,应对Agent进程异常退出的状况;
维护一份落盘数据,应对断网状况下共享内存又被清空的情况;
网络中断恢复后,对共享内存中全部数据进行检查,并从新注册Watcher;
定时扫描共享内存;
QConf 客户端中有多处须要将数据序列化通讯或存储,包括共享内存,消息队列,落盘数据中的内容。 咱们采起了以下协议:
图7 数据序列化协议
经过上面的描述,你们应该大体知道了Agent所作的一些事情,下面从Agent内线程分工的角度整理一下,以下图:
图8 Agent内部结构
Send线程:ZooKeeper线程,处理网络数据包,进行协议包的解析与封装,并将Zookeeper的事件加入WaitingEvent队列等待处理。
Event 线程:ZooKeeper线程,依次获取WaitingEvent队列中的事件,并进行相应处理,这里咱们关注节点删除、节点值修改、子节点变化、会话过时等事件。对特定的事件会进行相应的操做,以节点值修改成例,Agent会按上边提到的方式序列化该节点Key,并将其加入到WaitingWriting队列,等待Main线程处理。
Msq线程:以前讲数据流动场景的时候有提到,用户进程从共享内存中找不到对应配置后,会向消息队列中加入该配置,Msq线程即是负责从消息队列中获取业务进程的取配置需求,并一样经过WaitingWriting队列发送给Main进程。
Scan线程:扫描共享内存中的全部配置,发现与Zookeeper不一致的状况时,将key值加入WaitingWriting队列。Scan线程会在ZooKeeper重连或轮询期到达时进行上述操做。
Main线程:共享内存的惟一写入线程,从Zookeeper得到数据写入共享内存,维护共享内存中的内容。
Trigger线程:该线程负责一些周边逻辑的调用。