问题

服务一般有很多依赖配置,例如访问数据库有连接字符串配置,连接池大小和连接超时配置,这些配置在不同环境(开发/测试/生产)一般不同,比如生产环境需要配连接池,而开发测试环境可能不配,另外有些参数配置在运行期可能还要动态调整,例如,运行时根据流量状况动态调整限流和熔断阀值。

传统配置文件方式虽然把配置项分离到单独的配置文件,但是修改一个配置项,需要提交版本管理,发布,重启。整个过程比较麻烦,特别是发布多台机器。整个配置上线流程跟修改代码没有太大区别。不利于集中式管理和动态调整。

解决方案

目前比较常见的做法是搭建一个运行时配置中心支持动态配置,简化架构如下图所示:

服务配置中心

动态配置存放在集中的配置服务器上,用户通过管理界面配置和调整服务配置,具体服务通过定期拉(Scheduled Pull)的方式或者服务器推(Server-side Push)的方式更新动态配置。

拉方式比较可靠,实现起来也简单,但会有延迟同时有无效网络开销(假设配置不常更新,当然可以增量更新)。 服务器推方式能及时更新配置,但是实现较复杂,一般在服务和配置服务器之间要建立长连接。

配置中心还要解决配置的版本控制和审计问题,对于大规模服务化环境,配置中心还要考虑分布式和高可用问题。

配置中心其实是比较通用的需求,业界也有很多比较成熟的开源项目:

  1. disconf: 百度开源的分布式配置管理平台。项目文档上说百度、滴滴打车、银联、网易、拉勾网等知名互联网公司在使用。挺新的项目,并且目前只支持Java语言。
  2. QConf: 360开源的分布式配置管理平台。跟disconf一样使用了zookeeper做配置存储,不同在于QConf使用了agent和共享内存的方式,并且支持多种语言。
  3. Diamond 淘宝开源的持久配置的管理系统,支持各种持久信息(比如各种规则,数据库配置等)的发布和订阅。特点是简单,存储是MySQL,并且是拉模式。
  4. Spring Cloud Config: 特点就是与Spring完美结合。

说明

1. 存储系统

其实配置文件不外乎就是一个key-value的配置项集合。当然,如果要支持多个环境,还有分组,顶多也就是加多几个字段,总体来说是一个非常简单的数据模型。另外,配置项更新并不会很频繁,配置生效时间其实要求也不会太高,数据量也不会很大。事务性方面要求会比较高的,丢失,不一致对于配置项来说是不可接受的。

所以,用普通的DB如MySQL就足够的。很多开源的配置中心采用zookeeper,其实是想利用zk的订阅功能,实现配置更新通知客户端。ZK当然是可以的,但是个人觉得这里有点大材小用,注册中心可能是更合适的应用场景。

可以使用本地内存或者Redis缓存提高查询性能。或者像阿里的Diamond,定时将配置项dump到本地文件。另外,客户端也应该做缓存,一来减少对server的压力,二来server挂掉还可以容灾。

2. 配置下发应用

以前的配置是随着应用一起发布的,应用启动时候加载本地配置文件的。现在配置数据统一发布到配置中心,所以需要一种机制讲数据下发到应用。

有两种方式:

  1. 推:这个实时性最高,但是需要应用与配置中心保持长连接。复杂性会高一些,特别是负载均衡这块。
  2. 拉:实时性相对差一些,如果不做增量更新的话对配置中心也会造成不必要的压力。不过实现会简单很多。

个人觉得对于配置中心这种业务场景,拉的方式其实是足够的。另外,采用推的方式,也可以在启动的时候全量拉取,后续只下推变更数据。

3. 变更通知

如果是推送的方式,那么server可以把每次变更都即使发送给订阅的客户端。 如果是拉取的方式,则可以通过比较client和server的数据的MD5值来实现有效变更通知。server会下发数据和md5给client,client请求的时候会带上md5,如果md5变化,则server会重新下发数据,否则返回“无变更”的状态码给client。

4. 灰度发布

有时候我们需要先把配置项灰度到某台机器看看效果如何,再决定是要全量还是回滚。

有两种解决方案:

  1. 配置项增加一个host属性,表示这个配置项只“发布”给某些IP。
  2. 定义一个优先级,客户端优先加载本地配置文件,这样如果某些机器上的应用需要特殊配置,那么可以采用老的方式上去修改其本地配置文件。

Diamond就是采用第二种方式的,这种方式还有一个好处就是兼容了老的配置方式,可以做到无缝过渡。另外,本地配置文件也起到一个本地容灾的作用。