分布式配置之qconf

    QConf 是一个分布式配置管理工具。 用来替代传统的配置文件,使得配置和业务代码分离,同时配置能够实时同步到客户端保证配置及时生效。

使用场景

  • 单条数据量小

  • 更新频繁(较代码而言)

  • 配置总数可能巨大,但单台机器关心配置数有限

  • 读多写少

特点

  • 一处修改,所有机器实时同步更新
  • 高效读取配置
  • 安装部署方便,使用简单
  • 服务器宕机、网络中断、集群迁移等异常情况对用户透明
  • 支持c/c++、shell、php、python、lua、java、go、node 等语言

编译安装

QConf采用CMake进行构建(CMake 版本 2.6及以上)

可以使用以下命令完成QConf的编译安装:

mkdir build && cd build
cmake ..
make
make install复制代码

使用

  • 搭建Zookeeper集群

  • 在QConf 配置文件中配置Zookeeper集群地址

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配置复制代码
  • 在QConf配置文件中指定本地机房
echo test > QCONF_INSTALL_PREFIX/conf/localidc #指定本地机房为test复制代码
  • 启动QConf
cd QCONF_INSTALL_PREFIX/bin && sh agent-cmd.sh start复制代码

使用样例

  • shell
    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服务端

    QConf使用ZooKeeper集群作为服务端提供服务。可以将单条配置内容直接存储在ZooKeeper的一个ZNode上,并利用ZooKeeper的Watch监听功能实现配置变化时对客户端的及时通知。 按照ZooKeeper的设计目标,其只提供最基础的功能,包括顺序一致,原子性,单一系统镜像,可靠性和及时性。

QConf客户端

因为ZooKeeper在接口方面只提供了非常基本的操作,并且其客户端接口原始,所以我们需要在QConf的客户端部分解决如下问题:

  • 降低与ZooKeeper的链接数 原生的ZooKeeper客户端中,所有需要获取配置的进程都需要与ZooKeeper保持长连接,在生产环境中每个客户端机器可能都会有上百个进程需要访问数据,这对ZooKeeper的压力非常大而且也是不必要的。

  • 本地缓存 当然我们不希望客户端进程每次需要数据都走网络获取,所以需要维护一份客户端缓存,仅在配置变化时更新。

  • 容错 当进程死掉,网络终端,机器重启等异常情况发生时,我们希望能尽可能的提供可靠的配置获取服务。

  • 多语言版本接口 目前提供的语言版本包括:c,php,java,python,go,lua,shell。

  • 配置更新及时 可以秒级同步到所有客户端机器。

  • 高效的配置读取 内存级的访问速度。

下面来看下QConf客户端的架构:

可以看到QConf客户端主要有:Agent、各种语言接口、连接他们的消息队列和共享内存。

在QConf中,配置以Key-Value的形式存在,业务进程给出key获得对应Value,这与传统的配置文件方式是一致的。

下面通过两个主要场景的数据流动来说明他们各自的功能和角色:

场景1.业务进程请求数据:

  1. 业务进程调用某一种语言的QConf接口,从共享内存中查找需要的配置信息。

  2. 如果存在,直接获取,否则会向消息队列中加入该配置key。

  3. Agent从消息队列中感知需要获取的配置key。

  4. Agent向ZooKeeper查询数据并注册监听。

  5. Agent将获得的配置Value序列化后放入共享内存。

  6. 业务进程从共享内存中获得最新值。

场景2.配置信息更新:

图6 数据流动-配置更新

  1. ZooKeeper通知Agent某配置项发生变化。

  2. Agent从ZooKeeper查询新值并更新Watcher。

  3. Agent用新值更新共享内存中的该配置项。

通过上面的说明,可以看出QConf的整体结构和流程非常简单。 QConf中各个组件或线程之间仅通过有限的中间数据结构通信,耦合性非常小,各自只负责自己的本职工作和一亩三分地,而不感知整体结构。

下面通过几个点来详细介绍:

无锁

根据上文提到的配置信息的特征,我们认为在QConf客户端进行的是多进程并行读取的过程,对配置数据来说读操作远多于写操作。为了尽可能的提高读效率,整个QConf客户端在操作共享内存时采用的是无锁的操作,同时为了保证数据的正确,采取了如下两个措施:

单点写

将写操作集中到单一线程,其他线程通过中间数据结构与之通信,写操作排队,用这种方法牺牲掉一些写效率。 在QConf客户端,需要对共享内存进行写操作的场景有:

  • 用户进程通过消息队列发送的需获取Key;

  • ZooKeeper 配置修改删除等触发Watcher通知,需更新;

  • 为了消除Watcher丢失造成的不一致,需要定时对共享内存中的所有配置重新注册Watcher,此时可能会需要更新;

  • 发生Agent重启、网络中断、ZooKeeper会话过期等异常情况之后,需重新拉数据,此时可能需要更新。

读验证

无锁的读写方式,会存在读到未写入完全数据的危险,但考虑到在绝对的读多写少环境中这种情况发生的概率较低,所以我们允许其发生,通过读操作时的验证来发现。共享内存数据在序列化时会带其md5值,业务进程从共享内存中读取时,利用预存的md5值验证是否正确读取。

异常处理

QConf中采取了一些处理来应对不可避免的异常情况:

  • 采用父子进程Keepalive的方式,应对Agent进程异常退出的情况;

  • 维护一份落盘数据,应对断网情况下共享内存又被清空的状况;

  • 网络中断恢复后,对共享内存中所有数据进行检查,并重新注册Watcher;

  • 定时扫描共享内存;

数据序列化

QConf 客户端中有多处需要将数据序列化通信或存储,包括共享内存,消息队列,落盘数据中的内容。 我们采取了如下协议:

图7 数据序列化协议

Agent任务

通过上面的描述,大家应该大致知道了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线程:该线程负责一些周边逻辑的调用。

www.tuicool.com/articles/BJ…


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值