微服务之配置中心ConfigKeeper

2019-01-04  本文已影响6人  heishaovvv

在黑少微服务(http://www.httpshop.com/)架构中,配置中心是必不可少的基础服务。本文将深度分析配置中心的核心内容,错过2018.10.28「Spring Cloud中国社区北京站沙龙」的同学可以从本文中收获现场的分享内容。

背景

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

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

定制原因

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

image

除了上述之外,还有以下其他功能特性:

架构设计

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

1. 用户先去配置中心 添加、修改配置;

2. 应用启动时:(Spring boot应用向配置中心客户端获取配置、然后缓存配置到本地内存及本地文件缓存、应用根据配置进行启动;)

3. 不停机更新配置(调用Spring Cloud的RefreshEndpoint、通过RefreshEndpoint刷新配置)

4. 使用前后端分离架构,如果需要重新设计管理界面,也可以使用自己习惯的技术实现

image

设计的初心

通过讲解管理后台功能,理解我们当初出于什么原因为什么要这么设计?能解决哪些问题?设计时的考虑点有哪些?通过前面的阅阅读,已知ConfigKeeper有以下核心功能:

image

“权限管理

为什么要有权限管理?

image

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

我们使用开源系统越多,那么需要管理的账号就会越多,如果团队比较大的话,会增加非常大的管理成本。

“多环境管理

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

image

“配置设计

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

容易造成冲突

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

不方便修改。

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

不能做差异化配置

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

但是当系统发展到一定程度后,有些应用的并发量上来了,其数据库连接池的配置就要与其它应用有差别,这时我们还是需要将配置从此模块中拆出来。

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

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

“配置内容

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

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

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

为什么单个应用只支持单个配置? 微服务已经拆得比较小了,其配置内容也不会非常多,所以只设计为一个应用只有一个配置。而且经过我们的实践呢,一个配置是可以满足实际需要的。

支持版本控制

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

修改配置

不管是在内部推广时,还是开源后,都有人问能支持properties吗?其时最初版本是支持的,但我们在前端页面把这个功能屏蔽了,因为我们决定只支持yaml格式。

image

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

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

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

image

“实例基本信息及批量刷新

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

image

在此页面,我们实现如下功能:

客户端实现

因为随行付从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进行扩展,而不是在其基础上重新造轮子。

客户端源码解析

要想学习客户端的源码的话,可能以/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的实现类,其具体实现请大家查看源码文件。

客户端特性

使用建议

配置治理

在我们实践后发现,使用配置中心,还可以很好地对配置进行治理,比如统一使用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 SPRINGAPPLICATIONJSON (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。生产环境的应用,安全问题不可忽视,所以建议做如下处理:

调整后的配置实例如下:

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 生态功能非常丰富,为我们解决了非常多棘手问题,但很多东西要进行本地化开发后才能更好的使用。配置中心使用了不少开源技术,给我们带来了不少便利,希望通过此开源项目回馈社区,为开源社区贡献绵薄之力。

上一篇 下一篇

猜你喜欢

热点阅读