高并发系统设计(二十六):【配置中心】成千上万的配置项要如何管理?
在大型的项目中,业务比较复杂的系统里,难免会在代码层频繁加入一些场景业务配置,那么每次都需要代码频繁提交,会很不方便。
配置中心是如何实现的?
配置中心可以算是微服务架构中的一个标配组件了。业界也提供了多种开源方案供你选择,比较出名的有携程开源的Apollo,百度开源的Disconf,360开源的QConf,Spring Cloud的组件Spring Cloud Config等等。
Apollo支持不同环境,不同集群的配置,有完善的管理功能,支持灰度发布、更改热发布等功能,在所有配置中心中功能比较齐全,推荐你使用。
配置信息如何存储?
其实,配置中心和注册中心非常类似,其核心的功能就是配置项的存储和读取。所以,在设计配置中心的服务端时,我们需要选择合适的存储组件来存储大量的配置信息,这里可选择的组件有很多。
事实上,不同的开源配置中心也使用了不同的组件,比如Disconf、Apollo使用的是MySQL;QConf使用的是ZooKeeper。我之前维护和使用的配置中心就会使用不同的存储组件,比如微博的配置中心使用Redis来存储信息,而美图用的是Etcd。
无论使用哪一种存储组件,你所要做的就是规范配置项在其中的存储结构。比如,之前使用的配置中心用Etcd作为存储组件,支持存储全局配置、机房配置和节点配置。其中,节点配置优先级高于机房配置,机房配置优先级高于全局配置。也就是说,我们会优先读取节点的配置,如果节点配置不存在,再读取机房配置,最后读取全局配置。它们的存储路径如下:
/confs/global/{env}/{project}/{service}/{version}/{module}/{key} //全局配置 /confs/regions/{env}/{project}/{service}/{version}/{region}/{module}/{key} //机房配置 /confs/nodes/{env}/{project}/{service}/{version}/{region}/{node}/{module}/{key} //节点配置
变更推送如何实现
配置信息存储之后,需要考虑如何将配置的变更推送给服务端,这样就可以实现配置的动态变更,也就是说不需要重启服务器就能让配置生效了。
一般会有两种思路来实现变更推送:一种是轮询查询的方式;一种是长连推送的方式。
轮询查询很简单,就是应用程序向配置中心客户端注册一个监听器,配置中心的客户端,定期地(比如1分钟)查询所需要的配置是否有变化,如果有变化则通知触发监听器,让应用程序得到变更通知。
这里有一个需要注意的点,如果有很多应用服务器都去轮询拉取配置,由于返回的配置项可能会很大,那么配置中心服务的带宽就会成为瓶颈。为了解决这个问题,我们会给配置中心的每一个配置项多存储一个根据配置项计算出来的MD5值。
配置项一旦变化,这个MD5值也会随之改变。配置中心客户端在获取到配置的同时,也会获取到配置的MD5值,并且存储起来。那么在轮询查询的时候,需要先确认存储的MD5值和配置中心的MD5是不是一致的。如果不一致,这就说明配置中心里存储的配置项有变化,然后才会从配置中心拉取最新的配置。
由于配置中心里存储的配置项变化的几率不大,所以使用这种方式后,每次轮询请求就只是返回一个MD5值,可以大大地减少配置中心服务器的带宽。
另一种长连的方式,它的逻辑是在配置中心服务端保存每个连接关注的配置项列表。这样,当配置中心感知到配置变化后,就可以通过这个连接,把变更的配置推送给客户端。这种方式需要保持长连,也需要保存连接和配置的对应关系,实现上要比轮询的方式复杂一些,但是相比轮询方式来说,能够更加实时地获取配置变更的消息。
而在我看来,配置服务中存储的配置变更频率不高,所以对于实时性要求不高,但是期望实现上能够足够简单,所以如果选择自研配置中心的话,可以考虑使用轮询的方式。
如何保证配置中心高可用
除了变更通知以外,在配置中心实现中另外一个比较关键的点在于如何保证它的可用性。因为对于配置中心来说,可用性的重要程度要远远大于性能。
我们一般会在服务器启动时从配置中心中获取配置,如果配置获取的性能不高,那么外在的表现也只是应用启动时间慢了,对于业务的影响不大。但是,如果获取不到配置,很可能会导致启动失败。
比如,我们把数据库的地址存储在了配置中心里,如果配置中心宕机导致我们无法获取数据库的地址,那么自然应用程序就会启动失败。因此,我们的诉求是让配置中心“旁路化”。也就是说,即使配置中心宕机,或者配置中心依赖的存储宕机,我们仍然能够保证应用程序是可以启动的。那么这是如何实现的呢?
我们一般会在配置中心的客户端上,增加两级缓存:第一级缓存是内存的缓存;另外一级缓存是文件的缓存。
配置中心客户端在获取到配置信息后,会同时把配置信息同步地写入到内存缓存,并且异步地写入到文件缓存中。内存缓存的作用是降低客户端和配置中心的交互频率,提升配置获取的性能;而文件的缓存的作用就是灾备,当应用程序重启时,一旦配置中心发生故障,那么应用程序就会优先使用文件中的配置,这样虽然无法得到配置的变更消息(因为配置中心已经宕机了),但是应用程序还是可以启动起来的,算是一种降级的方案。
- 配置存储是分级的,有公共配置,有个性的配置,一般个性配置会覆盖公共配置,这样可以减少存储配置项的数量;
- 配置中心可以提供配置变更通知的功能,可以实现配置的热更新;
- 配置中心关注的性能指标中,可用性的优先级是高于性能的,一般我们会要求配置中心的可用性达到99.999%,甚至会是99.9999%。
这里你需要注意的是,并不是所有的配置项都需要使用配置中心来存储,如果你的项目还是使用文件方式来管理配置,那么你只需要将类似超时时间等,需要动态调整的配置,迁移到配置中心就可以了。对于像是数据库地址,依赖第三方请求的地址,这些基本不会发生变化的配置项,可以依然使用文件的方式来管理,这样可以大大地减少配置迁移的成本。