微服务之配置中心ConfigKeeper

在微服务架构中,配置中心是必不可少的基础服务。ConfigKeeper已开源,本文将深度分析配置中心的核心内容,错过「Spring Cloud中国社区北京沙龙-2018.10.28 」的同窗将从本篇文章中收获现场的分享内容。

背景

微服务+容器架构后,为了方便动态更新应用配置,须要把配置文件放到应用执行包以外的配置中心,这样一来,一个可执行包就能够在不一样的环境下运行,大幅度下降包的版本管理成本,也能够有效控制docker镜像的版本管理成本。传统的经过配置文件、数据库等方式已经愈来愈没法知足开发人员对配置管理的需求。对程序配置的指望值也愈来愈高:配置修改后实时生效,分环境、分集群管理配置,完善的权限、审核机制等等。因而便诞生了ConfigKeeper。html

ConfigKeeper是随行付架构部基于Spring Cloud研发的分布式配置中心,与Spring Boot、Spring Cloud应用无缝兼容。
虽然Spring Cloud 已经为咱们提供了基于git或mongodb等实现的配置中心,可是这些方案实现都过于简单,没有达到实际可用的标准。好比:没有提供统一的管理页面,不便于操做和使用;没有权限管理功能;没有数据验证功能等等。但Spring Cloud Config的核心技术仍是能够为咱们所用,没有必要从新造轮子。前端

定制的缘由

市面上已经有几款比较成型的配置中心,你们耳熟能详的携程Apollo和百度Disconf,而咱们的配置中心底层是基于Spring Cloud Config模块进行扩展的,首先来看看Apollo、Spring Cloud Config、ConfigKeeper的功能差别:java

功能点 Apollo Spring Cloud Config ConfigKeeper
配置界面 一个界面管理不一样环境、不一样集群配置 无,须要经过git操做 配置信息落入数据库中,友好页面管理
配置生效时间 实时 重启生效或者手动refresh生效 实时推送、重启生效、手动refresh生效
版本管理 界面上直接提供发布历史和回滚按钮 无,须要经过git操做 管理页面一键回滚
灰度发布 支持 不支持 支持,与Spring Cloud其余组件打通
受权、审核、审计 界面上直接支持,并且支持修改、发布权限分离 须要经过git仓库设置,且不支持修改、发布权限分离 应用分配制权限管理
实例配置监控 能够方便的看到当前哪些客户端在使用哪些配置 不支持 心跳推送,一目了然
配置获取性能 快,经过数据库访问,还有缓存支持 较慢,须要从git clone repository,而后从文件系统读取 本地式缓存文件,配置增量推送
客户端支持 原生支持全部Java和.Net应用 支持Spring应用,提供annotation获取配置 Spring、Spring Boot、Spring Cloud
支持YAML格式 不支持 支持 支持

除了上述以外,还有如下其余功能特性:nginx

  • 开发人员最习惯的就是在文件中修改配置,管理页面上提供「温馨」的富文本编辑框;
  • 全局配置约定,好比多个项目共享的配置,好比短信地址等采起约定大于配置。全局配置<应用配置;
  • 配置校验,文本修改高亮对比修改内容,防止低级错误等;

架构设计

有史以来最简单的配置中心。使用数据库保存配置是由于微服务拆分粒度相对比较细,使用的配置也会相对比较少,因此使用数据库表就够保存,流程以下:git

  • 用户先去配置中心 添加、修改配置;
  • 应用启动时:(Spring boot应用向配置中心客户端获取配置、而后缓存配置到本地内存及本地文件缓存、应用根据配置进行启动;)
  • 不停机更新配置(调用Spring Cloud的RefreshEndpoint、经过RefreshEndpoint刷新配置)
  • 使用先后端分离架构,若是须要从新设计管理界面,也可使用本身习惯的技术实现

在这里插入图片描述

设计的初衷

经过讲解管理后台功能,理解咱们当初出于什么缘由为何要这么设计?能解决哪些问题?设计时的考虑点有哪些?经过前面的阅阅读,已知ConfigKeeper有如下核心功能:
在这里插入图片描述github

权限管理

为何要有权限管理?spring

  • 1.对于企业级应用来讲,权限管理是必不能够一个需求;
  • 2.经过权限管理隔离数据,保证数据的安全性,避免误操做;
  • 3.在微服务比较多状况下,也能够经过权限自动过滤出咱们所关心的服务,不须要再本身手动过滤,减小没必要要的操做,能够提升工做效率;

在这里插入图片描述

这个权限系统是咱们最初设计的,咱们内部如今使用了一个统一的权限系统。为了下降管理成本,咱们也开发了微服务管理平台,将配置中心,注册中心,网关管理后台等一系列基础服务都接入到此平台来管理,并经过此平台统一进行权限管理;mongodb

咱们使用开源系统越多,那么须要管理的帐号就会越多,若是团队比较大的话,会增长很是大的管理成本。docker

多环境管理

配置中心的部署比较灵活,支持多环境集中式管理。可是随行付内部,为了隔离生产环境,咱们分开部署了两套配置中心,一套负责开发环境、测试环境、准生产环境的配置管理,另外一套负责生产环境的配置管理。固然开发工程师能够选择使用本地配置,不强制开发者环境与配置中心强关联。(只要考虑开发人员众多,需求同步进行)数据库

在这里插入图片描述

配置设计

先回想一下:你有使用jar将配置共享给别人,或别人将提供给你带配置的jar?答案是确定的,这应该是开发中必须面对的问题,那么使用jar共享配置会带来哪些问题呢?

容易形成冲突

以前为了统一日志的输出格式,将logback.xml打成一个jar里,让你们使用;而我去年在推新的logback配置规范时,发现与它发生冲突了。为了解决这个冲突,咱们在每一个项目中增长了个空的logbak.xml文件。

不方便修改。

须要与jar包提供方进行协调,还要确认修改是否对其它应用产生影响。

不能作差别化配置

好比有些项目为了复用数据库操做部分代码,将数据库操做以及配置都放到单独的模块,以jar的形式进行复用,若是从复用的角度来看,是很是不错的方法。

可是当系统发展到必定程度后,有些应用的并发量上来了,其数据库链接池的配置就要与其它应用有差异,这时咱们仍是须要将配置今后模块中拆出来。

经过上面的例子,能够发现配置之因此从代码中提取出,其核心做用就是为了更好适应变化。由于共享配置存在以这些问题,并且微服务架构下,尽可能仍是以服务的方式来复用业务功能。再者咱们一直要将代码进行解偶,那么配置更须要进行解偶。

出于以上种种缘由考虑,咱们在设计配置中心时,也就没有考虑设计以“组”的形式来共享配置。这也是咱们设计时争议比较大的地方。

配置内容

分为应用配置和全局配置:

  • 全局配置:是某一环境下全部应用共享的配置,好比公司的邮件服务配置;注册中心地址、公司名称、公司地址等,可能会变化,但广泛性很是高的配置。
  • 应用配置:每一个应用个性化的配置;

为何还要全局配置?这遇前面讲的组共享配置不是冲突了吗?

全局配置只是用于适应运行环境的变化而设计的,不设计到业务配置。“组”的界限不是很清楚,很容易乱,而全局配置不存在这方面的问题。

为何单个应用只支持单个配置?
微服务已经拆得比较小了,其配置内容也不会很是多,因此只设计为一个应用只有一个配置。并且通过咱们的实践呢,一个配置是能够知足实际须要的。

支持版本控制

咱们的版本设计相比Git的,要比较简单,可是相应的功能也还有的。主要职责以下:

  • 配置每被修改一次,会将旧数据及版本号保存到日志表中,更新配置内容的同量,将版本号加一
  • 支持版本比较功能:方便查看与最新版本的差别;检查在哪天作了什么调整;
  • 支持回退功能:若是配置出现问题,能够快速回退;

修改配置

无论是在内部推广时,仍是开源后,都有人问能支持properties吗?其时最第一版本是支持的,但咱们在前端页面把这个功能屏蔽了,由于咱们决定只支持yaml格式。

  • 1.properties 对中文支持不是好,而yml却没有这个问题;
  • 2.yaml能很好管理同类项配置,避免配置重复key。看过很多properties文件,配置杂乱以及同一个文件出重相同的key,不一样value的状况;不是全部的开发都是有强迫症;
  • 3.统一你们的习惯;

在这里插入图片描述

当Yml也不是彻底没有问题的,在实践过程当中,偶尔也出现有人把缩进搞错的状况。

使用Yml在线编辑器,能够很是方便编辑,好比:复制粘贴内容,就像在修改配置文件同样,尤为是批量修改时更为方便。不像其它经过key value方便管理的配置中心,每次修改都须要先找到相应的key才能进行一个个修改,很是费时费力;

Yml的JSON预览功能。当用户编辑内容时,会实时检查格式是否符合yaml格式时,若是格式是正确的,右则会正确显示其对应的json内容,若是格式不正确则,右则会提示相应的错误信息,能及时发现错误。

在这里插入图片描述

实例基本信息及批量刷新

不停机实时刷新配置是配置中心的核心需求之一。好比在生产中运行的应用,忽然因需求或性能等缘由,须要调整配置,若是咱们还须要通过修改代码,从新打包,测试并部署等一系列的操做步骤的话,那效率可想可知,所以带来的损失也可能会很是之大。ConfigKeeper使用Spring Cloud提供的RefreshEndpoint刷新配置,在最初的版本中,咱们是经过curl或Postman等工具实现此功能,但这样操做效率比较差,为此在最新版本中增长了以下功能:

在此页面,咱们实现以下功能:

  • 1.列出全部应用实例的IP、管理端口等信息
  • 2.查看应用中配置的版本是不是最新的;(很是方便核对应用版本是不是最新的;避免漏操做等问题;)
  • 3.实现灰度发布;(能够手动刷新选中的一个或多个实例的配置;)

客户端实现

由于随行付从Spring boot 1.2.2版本就开始使用Spring boot,到如今已经实现全部应用boot化,因此咱们在设计配置中心时,其客户端必需要无缝兼容Spring boot、Spring cloud应用,因此咱们就参考Spring cloud config的实现。

无缝兼容Spring boot、Spring cloud应用

为何ConfigKeeper能实现无缝兼容Spring boot、Spring cloud应用?其缘由很是简单,由于核心实现仍是由Spring cloud提供的,咱们只是在对Spring cloud进行扩展,而不是在其基础上从新造轮子。

  • 只依赖 spring-cloud-context 和 spring-cloud-commons 两个jar;
  • Spring cloud 提供PropertySourceLocator接口,方便咱们去加载外部配置,ConfigKeeper的客户端核心代码就是实现此接口;

客户端源码解析

要想学习客户端的源码的话,可能以/META-INF/spring.factories文件为入口,此文件中有以下配置:

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.suixingpay.config.client.SxfConfigServiceBootstrapConfiguration

而SxfConfigServiceBootstrapConfiguration存在以下代码:

@Bean
@ConditionalOnMissingBean(SxfConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = "suixingpay.config.enabled", matchIfMissing = true)
public SxfConfigServicePropertySourceLocator sxfConfigServicePropertySource(ApplicationContext context) {
    SxfConfigClientProperties configClientProperties = sxfConfigClientProperties(context);
    ConfigDAO configDAO = sxfConfigDAO(configClientProperties);
    return new SxfConfigServicePropertySourceLocator(configDAO, configClientProperties);
}

而SxfConfigServicePropertySourceLocator其实就是PropertySourceLocator的实现类,其具体实现请你们查看源码文件。

客户端特性

  • 支持客户端负载:若是有多个配置中心服务器实例,能够经过简单的轮询实现客户端负载,达到高可能的效果。固然也可使用nginx 反向代理实现服务端负载。
  • 支持失败后重试功能;
  • 支持本地缓存
    • 客户端从配置中心拉取最新配置后,会缓存到本地磁盘。每次去拉取配置以前,会加载本地缓存配置的版本信息,前传到服务端,若是服务端与客户端的版本一致时,接口会返回304状态,并使用本地缓存进行启动应用,当服务端与客户端的版本不一致时,会返回最新版本,并缓存到本地磁盘中。经过此缓存机制,一方面能够下降网络带宽,二是即便配置中心不可用,也不会影响应用的启动。
  • 上报应用实例信息

使用建议

配置治理

在咱们实践后发现,使用配置中心,还能够很好地对配置进行治理,好比统一使用YAML格式配置,使用配置内容更加清晰;避免了使用jar来共享配置带来的一系列问题等等。但Spring boot、Spring cloud应用可加载的配置源很是之多,还须要注意一些问题。

下面是截取https://docs.spring.io/spring-boot/docs/1.5.16.RELEASE/reference/htmlsingle/#boot-features-external-config中的内容:

  1. Command line arguments.
  2. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property)
  3. ServletConfig init parameters.
  4. ServletContext init parameters.
  5. JNDI attributes from java:comp/env.
  6. Java System properties (System.getProperties()).
  7. OS environment variables.
  8. A RandomValuePropertySource that only has properties in random.*.
  9. Profile-specific bootstrap properties outside of your packaged jar (bootstrap-{profile}.properties and YAML variants)
  10. Profile-specific bootstrap properties packaged inside your jar (bootstrap-{profile}.properties and YAML variants)
  11. Bootstrap properties outside of your packaged jar (bootstrap.properties and YAML variants).
  12. Bootstrap properties packaged inside your jar (bootstrap.properties and YAML variants).
  13. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants)
  14. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants)
  15. Application properties outside of your packaged jar (application.properties and YAML variants).
  16. Application properties packaged inside your jar (application.properties and YAML variants).
  17. 经过 PropertySourceLocator 加载配置(应用配置优先级要高于全局配置)
  18. @PropertySource annotations on your @Configuration classes.
  19. Default properties (specified using SpringApplication.setDefaultProperties).

从上面内容可见,Spring boot是支持很是多种方式加载配置的,并且支持重复配置以及支持覆盖,即相同key的配置,先加载的内容会被后加载的覆盖,为了方便后期维护,尽可能遵照如下原则:

  1. 尽可能避免同一key在多个地方配置的状况;
  2. 若是第1种状况不可避免,那么要注意各个配置中的优化级,好比ConfigKeeper中全局配置的优先级要低于应用配置;
  3. 约定配置位置
    可配置的比较那么多,在团队中每一个人使用的方法不同,抛必形成混乱,因此须要你们提早作好约定,好比:哪些配置经过命令行来配置,那些配置放到bootstrap 文件中,那些放到application 文件中。
  4. 拒绝使用jar共享配置

是否是全部的配置均可以经过配置中心来实时刷新?

相信不少人都会有这样的误区:全部的配置都是能够经过配置中心来实时刷新,否则配置中心的就没有多大意义了。为了解答这个问题,我先来看RefreshEndpoint都作了哪些事情:

public synchronized Set<String> refresh() {
    Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
    // 加载最新配置到Environment
    addConfigFilesToEnvironment(); 
    Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
    // 发送EnvironmentChangeEvent
    this.context.publishEvent(new EnvironmentChangeEvent(context, keys));
    // 清空RefreshScope缓存
    this.scope.refreshAll(); 
    return keys;
}

经过上面的源码,咱们能够看出其RefreshEndpoint主要作了三件事情:

  1. 加载最新配置到Environment
  2. 发送EnvironmentChangeEvent
  3. 清空RefreshScope缓存

因此咱们要想获取最新配置配置,能够经过如下途径:

  1. 直接经过Environment获取,好比:

    String applicationName = environment.getProperty("spring.application.name");
  2. 处理EnvironmentChangeEvent,好比对于线程池大小的调整,咱们能够监听EnvironmentChangeEvent,当接收到EnvironmentChangeEvent时,关闭原来的线程池,前从新实例化新的线程池;

    Spring boot官方建议咱们尽可能咱们使用@ConfigurationProperties管理配置,那么它是否能自动刷新配置呢?其实它是能够的,由于在ConfigurationPropertiesRebinder中会监听EnvironmentChangeEvent,详细内容请查看org.springframework.cloud.context.properties. ConfigurationPropertiesRebinder。
  3. 在实例化bean时增长@RefreshScope, 好比:

    @Autowired
     private DefaultUserProperties userProperties;
    
     @RefreshScope // 支持动态刷新
     @Bean(name="defaultUser")
     public UserDO defaultUser() {
         UserDO userDO=new UserDO();
         userDO.setId(userProperties.getId());
         userDO.setName(userProperties.getName());
         return userDO;
     }

    Spring cloud 为了实现运行时动态刷新,增长了RefreshScope(org.springframework.cloud.context.scope.refresh.RefreshScope类),会将加了@RefreshScope的bean放入RefreshScope中,当刷新RefreshScope时,会清空缓存,当下次使用这些bean时会从新实例些这些bean。

安全提示

经过RefreshEndpoint 刷新的话,就须要开启Spring boot Endpoint相关功能,而Spring boot Endpoint若是不作特殊处理的话,很容易被探测到,引起一些安全问题。好比:

server:
  port: 8080
management:
  security:
    enabled: false

那么很容易去调用Spring boot Endpoint。生产环境的应用,安全问题不可忽视,因此建议作以下处理:

  • management.port 与 server.port 设置不一样的值,而且此端口不容许外网访问;
  • 增长安全验证;
  • 修改management.context-path
  • 生产环境的management相关配置,尽可能与其它环境的配置要有差别,不能彻底同样。

调整后的配置实例以下:

server:
  port: 8080
management:
  security:
    enabled: true
  context-path: /_ops
  port: 9098 
security:
  basic:
    enabled: true
    path: ${management.context-path}/**, /swagger-ui.html, /v2/api-docs, /druid/**
  user:
    name: ma
    password: xxxxxx

开源地址

Spring 生态功能很是丰富,为咱们解决了很是多棘手问题,但不少东西要进行本地化开发后才能更好的使用。配置中心使用了很多开源技术,给咱们带来了很多便利,但愿经过此开源项目回馈社区,为开源社区贡献绵薄之力。

https://github.com/sxfad/

https://gitee.com/sxfad/

相关文章
相关标签/搜索