QConf 是奇虎 360 广泛使用的配置管理服务,现已开源 QConf Source Code,欢迎大家关注使用。本文从设计初衷,架构实现,使用情况及相关产品比较四个方面进行介绍。
设计初衷
在分布式环境中,出于负载、容错等种种原因,几乎所有的服务都需要在不同的机器节点上部署多个实例。同时,业务项目中总少不了各种类型的配置文件。这种情况下,有时仅仅是一个配置内容的修改,便需要重新进行代码提交 git,打包,分发上线的流程。当部署的机器有很多时,分发上线本身也是一个很繁杂的工作。而配置文件的修改频率又远远大于代码本身。追本溯源,我们认为所有的这些麻烦是由于我们对配置和代码在管理和发布过程中不加区分造成的。配置本身源于代码,是为了提高代码的灵活性而提取出来的一些经常变化的或需要定制的内容,而正是配置的这种天生的变化特征给我们带了很大的麻烦。因此,我们开发了分布式配置管理系统 QConf,并依托 QConf 在 360 内部提供了一整套配置管理服务,QConf 致力于将配置内容从代码中完全分离出来,及时可靠高效地提供配置访问和更新服务。
整体认识
为了让大家对之后的内容有个直观的认识,先来介绍一下如果需要在自己的项目中使用 QConf 应该怎么做:
- 首先,部署 QConf,QConf 采用 cmake 构建,依次执行如下命令。
cmake |
- 之后,通过 Zookeeper 客户端或 QConf 管理界面 在 Zookeeper 上建立自己的节点结构,节点完整路径作为 QConf 的 key 值,以 360 公司内部 QConf 管理界面为例:
- 最后,选择所需语言版本的 QConf 库,并在需要获得配置内容的代码位置,直接调用 Qconf 客户端接口,并放心每次取得的都是最新鲜出炉的配置内容。
需要说明的是,使用 QConf 后已经没有所谓的配置文件的概念,你要做的就是在需要的地方获取正确的内容,QConf 认为,这才是你真正想要的。
架构介绍
了解了 QConf 的设计初衷和使用方式,相信大家已经对 QConf 有一个整体的认识并且对其实现有了大概的猜想。在介绍架构之前,还需要申明一下 QConf 对配置信息的定位,因为这个定位直接决定了其结构设计和组件选择。
- 单条数据量小
- 更新频繁(较代码而言)
- 配置总数可能巨大,但单台机器关心配置数有限
- 读多写少
进入主题,开始介绍 QConf 的架构实现:
上图展示的是 QConf 的基本结构,从角色上划分主要包括 QConf 客户端,QConf 服务端和 QConf 管理端。
QConf服务端
QConf 使用 ZooKeeper 集群作为服务端提供服务。众所周知,ZooKeeper 是一套分布式应用程序协调服务,根据上面提到的对配置内容的定位,我们认为可以将单条配置内容直接存储在 ZooKeeper 的一个 ZNode 上,并利用 ZooKeeper 的 Watch 监听功能实现配置变化时对客户端的及时通知。按照 ZooKeeper 的设计目标,其只提供最基础的功能,包括顺序一致,原子性,单一系统镜像,可靠性和及时性。另外 Zookeeper 还有如下特点:
- 类文件系统的节点组织
- 稳定,无单点问题
- 订阅通知机制
关于 Zookeeper,更多见 https://zookeeper.apache.org/
QConf客户端
但在接口方面,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 序列化后放入 共享内存;
- 业务进程从 共享内存 中获得最新值。
- 配置信息更新
- 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 客户端中有多处需要将数据序列化通信或存储,包括共享内存,消息队列,落盘数据中的内容。采取如下协议:
- agent 任务通过上面的描述,大家应该大概了解了 agent 所做的一些事情,下面从 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 线程:该线程负责一些周边逻辑的调用,包括:
- dump 操作:将共享内存的内容同步一份到本地,QConf 采用的 gdbm;
- feedback 操作:QConf 支持更新反馈的功能,可向用户指定 web 服务以一定的格式发送反馈;
- script 操作:在某些情况下,业务希望当配置变化时,做一些自定义的操作,QConf 支持配置变化时调用用户脚本,agent 按一种固定的约定在配置发生变化时调用对应的脚本。
QConf管理端
管理端是业务修改配置的页面入口,利用数据库提供一些如批量导入,权限管理,版本控制等上层功能。由于公司内的一些业务耦合和需求定制,当前开源的 QConf 管理端这边提供了一个简易的页面,和一套下层的 c++ 接口,如下图:
之后计划进一步完善以及跟社区合作提供更友好的界面。
QConf 的结构及实现大概就介绍到这,接下来…
One More Thing
QConf 除了存储配置的基本功能外,还在公司内提供了一套简单的服务发现功能,该功能允许业务在 QConf 上配置一组服务,QConf 会监控其服务的存活。当业务进程调用获取服务的接口时,会根据用户需求,返回全部可用服务,或某一可用服务。不同于普通配置:
- 结构上多一个 Monitor 的角色,来监控所有服务的存活, 如下图:
- 提供对应的客户端接口,get_host 获取某一可用服务,get_allhost 获取所有可用服务
- 管理端页面对应的展示方式及操作,尤其是对指定服务的添加删除,上线下线
需要明确的是,目前 Monitor 事实上仅仅是通过查看服务端口的存活来判断的,在实际生产环境中,该功能多与实际服务提供者的监控结合,由服务提供者的监控调用 QConf 的相应接口实现服务的上下线。
使用方式及使用场景
目前 360 内部已经广泛的使用 QConf。覆盖云盘、大流程、系统部、dba、图搜、影视、地图、硬件、手机卫士、广告、好搜等大部分业务。部署国内外共 51 几个机房,客户端机器超两万台,稳定运行两年。
使用的方式主要包括:
- 简单配置公司内使用最广泛的用法,QConf 非常适合经常需要变动的配置使用,如开关信息、版本信息、推荐信息、超时时间等。
- 服务方式这种方式多被服务提供者采用,如 dba,系统部等,采用上述的服务配置的方式,通过 QConf 向公司的所有业务提供存储,计算及 web 服务。
QConf 因为其对配置信息的定位,使得整个结构非常简单,容易部署和使用。在 Github 可以找到完整代码,QConf Source Code 欢迎关注。
相关文章 »