从配置文件到分布式配置管理QConf

QConf是奇虎360广泛使用的配置管理服务,现已开源: QConf Source Code。欢迎大家关注使用。

本文从设计初衷,架构实现,使用情况及相关产品比较四个方面进行介绍。

设计初衷

在分布式环境中,出于负载、容错等种种原因,几乎所有的服务都需要在不同的机器节点上部署多个实例。同时,业务项目中总少不了各种类型的配置文件。这种情况下,有时仅仅是一个配置内容的修改,便需要重新进行代码提交svn/git,打包,分发上线的流程。当部署的机器有很多时,分发上线本身也是一个很繁杂的工作。而配置文件的修改频率又远远大于代码本身。 追本溯源,我们认为所有的这些麻烦是由于我们对配置和代码在管理和发布过程中不加区分造成的。配置本身源于代码,是为了提高代码的灵活性而提取出来的一些经常变化的或需要定制的内容,而正是配置的这种天生的变化特征给我们带了很大的麻烦。 因此,我们开发了分布式配置管理系统QConf,并依托QConf在360内部提供了一整套配置管理服务,QConf致力于将配置内容从代码中完全分离出来,及时可靠高效地提供配置访问和更新服务。

整体认识

为了让大家对之后的内容有个直观的认识,先来介绍一下如果需要在自己的项目中使用QConf应该怎么做:

cmake .. make make install

图1 QConf管理界面

图2 QConf shell接口使用

需要说明的是,使用QConf后已经没有所谓的配置文件的概念,你要做的就是在需要的地方获取正确的内容,QConf认为,这才是你真正想要的。

架构介绍

了解了QConf的设计初衷和使用方式,相信大家已经对QConf有一个整体的认识并且对其实现有了大概的猜想。在介绍架构之前,还需要申明一下QConf对配置信息的定位,因为这个定位直接决定了其结构设计和组件选择。

进入主题,开始介绍QConf的架构实现:

图3 QConf整体结构

上图展示的是QConf的基本结构,从角色上划分主要包括QConf客户端,QConf服务端和QConf管理端。

QConf服务端。

QConf使用ZooKeeper集群作为服务端提供服务。众所周知,ZooKeeper是一套分布式应用程序协调服务,根据上面提到的对配置内容的定位,我们认为可以将单条配置内容直接存储在ZooKeeper的一个ZNode上,并利用ZooKeeper的Watch监听功能实现配置变化时对客户端的及时通知。 按照ZooKeeper的设计目标,其只提供最基础的功能,包括顺序一致,原子性,单一系统镜像,可靠性和及时性。另外Zookeeper还有如下特点:

关于Zookeeper,更多见:https://zookeeper.apache.org/

QConf客户端

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

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

图4 QConf客户端架构

102 103 通过上面的说明,可以看出QConf的整体结构和流程非常简单。 104 QConf中各个组件或线程之间仅通过有限的中间数据结构通信,耦合性非常小,各自只负责自己的本职工作和一亩三分地,而不感知整体结构。下面通过几个点来详细介绍: 105 106 - 无锁 107 根据上文提到的配置信息的特征,我们认为在QConf客户端进行的是多进程并行读取的过程,对配置数据来说读操作远多于写操作。为了尽可能的提高读效率,整个QConf客户端在操作共享内存时采用 的是无锁的操作,同时为了保证数据的正确,采取了如下两个措施: 108 - 单点写,将写操作集中到单一线程,其他线程通过中间数据结构与之通信,写操作排队,用这种方法牺牲掉一些写效率。在QConf客户端,需要对共享内存进行写操作的场景有: 109 1. 用户进程通过消息队列发送的需获取key; 110 2. ZooKeeper 配置修改删除等触发Watcher通知,需更新; 111 3. 为了消除watcher丢失造成的不一致,需要定时对共享内存中的所有配置重新注册watcher,此时可能会需要更新; 112 4. 发生agent重启、网络中断、ZooKeeper会话过期等异常情况之后,需重新拉数据,此时可能需要更新。 113 - 读验证,无锁的读写方式,会存在读到未写入完全数据的危险,但考虑到在绝对的读多写少环境中这种情况发生的概率较低,所以我们允许其发生,通过读操作时的验证来发现。共享内存数据在> 序列化时会带其md5值,业务进程从共享内存中读取时,利用预存的md5值验证是否正确读取。 可以看到QConf客户端主要有:agent、各种语言接口、连接他们的消息队列和共享内存。 在QConf中,配置以key-value的形式存在,业务进程给出key获得对应value,这与传统的配置文件方式是一致的。

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

图5 数据流动-业务进程请求数据

  1. 业务进程调用某一种语言的QConf接口,从共享内存中查找需要的配置信息;
  2. 如果存在,直接获取,否则会向消息队列中加入该配置key;
  3. agent消息队列中感知需要获取的配置key;
  4. agentZooKeeper查询数据并注册监听;
  5. agent将获得的配置value序列化后放入共享内存
  6. 业务进程从共享内存中获得最新值。
    • 配置信息更新

图5 数据流动-配置更新

  1. ZooKeeper通知agent某配置项发生变化;
  2. agentZooKeeper查询新值并更新watcher;
  3. agent用新值更新共享内存中的该配置项。

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

图6 数据序列化协议

图7 Agent内部结构

QConf管理端

管理端是业务修改配置的页面入口,利用数据库提供一些如批量导入,权限管理,版本控制等上层功能。 由于公司内的一些业务耦合和需求定制,当前开源的QConf管理端这边提供了一个简易的页面,和一套下层的c++接口,如下图:

图8 QConf简易管理界面

之后计划进一步完善以及跟社区合作提供更友好的界面。

QConf的结构及实现大概就介绍到这,接下来…

One More Thing

QConf 除了存储配置的基本功能外,还在公司内提供了一套简单的服务发现功能,该功能允许业务在QConf上配置一组服务,QConf会监控其服务的存活。当业务进程调用获取服务的接口时,会根据用户需求,返回全部可用服务,或某一可用服务。 不同于普通配置:

图9 提供服务发现的QConf结构

需要明确的是,目前Monitor事实上仅仅是通过查看服务端口的存活来判断的,在实际生产环境中,该功能多与实际服务提供者的监控结合,由服务提供者的监控调用QConf的相应接口实现服务的上下线。

使用方式及使用场景

目前360内部已经广泛的使用QConf。覆盖云盘、大流程、系统部、dba、图搜、影视、地图、硬件、手机卫士、广告、好搜等大部分业务。 部署国内外共51几个机房,客户端机器超两万台,稳定运行两年。

使用的方式主要包括:

QConf因为其对配置信息的定位,使得整个结构非常简单,容易部署和使用。在Github上可以找到完整代码:QConf Source Code 欢迎关注。

Table of Contents