Spring cloud config的运用与原理

摘要

spring cloud config是一个基于http协议的远程配置实现方式。通过统一的配置管理服务器进行配置管理,客户端通过https协议主动的拉取服务的的配置信息,完成配置获取。

spring cloud config 应用场景

作为一个开发而言,知道每个项目都有其需要维护的配置文件,如果项目量小而言,以人力尚可以接受。项目量一但增多,传统的维护方式就变的困难,所以需要一个统一的配置中心来维护所有服务的配置文件。再言,传统的项目配置文件配置数据发生改变,需要重启服务使其生效,spring cloud config 可以不需要进行重启对应的服务。


多样化仓库

spring cloud config 支持配置文件库除了大家所知的git,svn外,spring cloud config还支持valut,credhub,composite以及本地文件仓库。当你在配置服务中需要可以对配置中心的仓库进行显示配置,不对其配置的话默认仓库是git。


实例搭建

接下来给大家介绍一些spring cloud config 的实例搭建。当前笔者使用的spring boot版本为2.1.3.RELEASE, spring cloud 版本为Greenwich.SR1,与目前多数文章使用的spring boot1.x版本还是有些许不同的。


config-server 模块的构建

pom引入的依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

只需引入web和config-server的依赖就可以,如果是通过idea进行搭建的话,可以通过spring initializr ,勾选web ,以及spring cloud config 模块自动导入pom依赖




ideal 构建spring cloud config server模块


application.properties其中字段的配置


# 服务名称
spring.application.name=config-server
# 启动端口
server.port=7001
# 仓库地址,HTTP方式
spring.cloud.config.server.git.uri=***
# 仓库搜索路径
spring.cloud.config.server.git.search-paths=***
# 访问仓库的用户名
spring.cloud.config.server.git.username=****
# 访问仓库的密码
spring.cloud.config.server.git.password=***


大部分配置大家都熟悉,主要介绍几个重要配置,一个是spring.cloud.config.server.git.uri,这个指定了你的git仓库地址,属于必填值,不填的话或者地址错误的话启动失败会报错。username, password根据你的git仓库而言,如果是公共仓库不需要用户及密码不填也可以。其他不填的话也会报错。spring cloud config 除了支持username, password 认证外,也可以通过公私钥文件进行git认证。


spring.cloud.config.server.git.ignore-local-ssh-settings=true
spring.cloud.config.server.git.host-key=someHostKey
spring.cloud.config.server.git.host-key-algorithm=ssh-rsa
spring.cloud.config.server.git.private-key=***


使用私钥来代替用户名密码的安全验证,配置案例由官网提供的,不过大部分开发更优先于使用用户名密码配置。

启动类注解

启动一个spring cloud config server端项目只需要加入一个@EnableConfigServer注解就可以了



@EnableConfigServer
@SpringBootApplication
public class SpringCloudConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudConfigServerApplication.class, args);
    }
}


接下来研究一下@EnableConfigServer这个注解做了什么操作




@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ConfigServerConfiguration.class})
public @interface EnableConfigServer {
}


进入到这个EnableConfigServer类,发现引入了ConfigServerConfiguration类。


@Configuration
public class ConfigServerConfiguration {
    public ConfigServerConfiguration() {
    }
    @Bean
    public ConfigServerConfiguration.Marker enableConfigServerMarker() {
        return new ConfigServerConfiguration.Marker();
    }
    class Marker {
        Marker() {
        }
    }
}



进入ConfigServerConfiguration类,发现创建了一个内部类Marker,并将其注解成一个bean注入到spring容器。



import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.config.server.config.ConfigServerConfiguration.Marker;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@ConditionalOnBean({Marker.class})
@EnableConfigurationProperties({ConfigServerProperties.class})
@Import({EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class, ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class})
public class ConfigServerAutoConfiguration {
    public ConfigServerAutoConfiguration() {
    }
}


在ConfigServerAutoConfiguration类里发现maker这个bean被注入进来,并引进了更多文件,简单的介绍一下各类的作用。


EnvironmentRepositoryConfiguration: 环境变量存储相关的配置类

CompositeConfiguration:组合方式的环境仓库配置类

ResourceRepositoryConfiguration:资源仓库相关的配置类

ConfigServerEncryptionConfiguration:加密断点相关的配置类

ConfigServerMvcConfiguration:对外暴露的MVC端点控制器的配置类

现在主要研究一下EnvironmentRepositoryConfiguration这个类,官方文档中也标明Environment相关为核心类。



@Configuration
@EnableConfigurationProperties({SvnKitEnvironmentProperties.class, CredhubEnvironmentProperties.class, JdbcEnvironmentProperties.class, NativeEnvironmentProperties.class, VaultEnvironmentProperties.class})
@Import({CompositeRepositoryConfiguration.class, JdbcRepositoryConfiguration.class, VaultRepositoryConfiguration.class, CredhubConfiguration.class, CredhubRepositoryConfiguration.class, SvnRepositoryConfiguration.class, NativeRepositoryConfiguration.class, GitRepositoryConfiguration.class, DefaultRepositoryConfiguration.class})
public class EnvironmentRepositoryConfiguration {
    public EnvironmentRepositoryConfiguration() {
    }
    @Bean
    @ConditionalOnProperty(
        value = {"spring.cloud.config.server.health.enabled"},
        matchIfMissing = true
    )
    public ConfigServerHealthIndicator configServerHealthIndicator(EnvironmentRepository repository) {
        return new ConfigServerHealthIndicator(repository);
    }
    @Bean
    @ConditionalOnMissingBean(
        search = SearchStrategy.CURRENT
    )
    public MultipleJGitEnvironmentProperties multipleJGitEnvironmentProperties() {
        return new MultipleJGitEnvironmentProperties();
    }


代码太长,所以只贴部分代码,从代码中可以了解到他引入了很多的仓库类,说明了spring cloud config
支持以及各种远程库的具体实现。介绍一个git仓库,对于其它仓库,本文就不再叙述



@Configuration
@Profile({"git"})
class GitRepositoryConfiguration extends DefaultRepositoryConfiguration {
    GitRepositoryConfiguration() {
    }
}



从代码中可以观察到git仓库还继承了默认的仓库配置,证实了spring cloud config默认配置是git。git仓库的实现类JGitEnvironmentRepository,JGitEnvironmentRepository实现类为MultipleJGitEnvironmentProperties。此类做了什么呢


类结构



从图中可知获得配置信息。因此,可以总结spring cloud config server启动的时候根据profile值启动对应的环境库去加载和获取配置信息。


启动准备与日志分析

启动之前要在对应的git上创建一个文件,用来存储客户端需要的配置信息。对于读者而言,创建文件轻而易举,所以笔者不做描述,运行启动类。


git上client端配置文件


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.3.RELEASE)
2019-04-11 15:47:14.912  INFO 25554 --- [           main] s.s.s.SpringCloudConfigServerApplication : No active profile set, falling back to default profiles: default
2019-04-11 15:47:16.742  INFO 25554 --- [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=02d0bffd-a788-3d22-8702-495331a8c7d5


日志并没有什么强调的,阐述的就是根据spring检查你的profile值去生成对应的仓库bean。
主要查看一下Endpoints


endpoints


从endpoints可以了解到加载bean的数量名称,bean的健康状况,以及适配的访问方式。


image.png


列举了常用的几种格式,考虑到开发的习惯不同,所以做了多种展示风格,除了properties格式,还支持yml,yaml,json格式展示。打开浏览器,访问http://localhost:7001/spring-cloud-config-client/dev/master。


server-config页面访问



可以看到正确获取到git仓库中文件的值,看下启动日志发现一条消息


image.png


image.png

换一种方式展示,看各个读者喜好。虽spring cloud config 支持properties,yml,yaml,json四种展示格式。读取文件只支持properties,yml,yaml文件格式。

远程仓库更新的时候,server端监听器监听到变化,会进行数据的同步,至此,server端算是告一段落。


spring cloud config client端搭建

pom依赖

创建一个新的模块,pom依赖相比于server端而言,只是从服务端变成client端引用。


image.png

spring.application.name与git仓库中文件的名称对应,接下来解释一下为何需要创建bootstrap配置文件。在启动spring boot项目时候,项目会加载application配置文件,而bootstrap文件会优于application加载。


启动分析

client端启动不需要加任何注解,spring boot启动的@configuration里包含了对client的端注解。


image.png


这个类的用来读取bootstrap.properties 配置文件信息并通过HTTP方式从server端拉取文件。


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.4.RELEASE)
2019-04-11 17:32:36.501  INFO 26542 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Multiple Config Server Urls found listed.
2019-04-11 17:32:36.502  INFO 26542 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:7001
2019-04-11 17:32:37.288  INFO 26542 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=spring-cloud-config-client, profiles=[dev], label=master, version=789bfe0c437dc1676a8b75b7de74dbf13dc7b52a, state=null
2019-04-11 17:32:37.288  INFO 26542 --- [           main] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource {name='configService', propertySources=[MapPropertySource {name='configClient'}, MapPropertySource {name='http://gitlab.alibaba-inc.com/wb-wxj443666/spring-cloud-study.git/config/spring-cloud-config-client-dev.properties'}]}
2019-04-11 17:32:37.298  INFO 26542 --- [           main] s.s.s.SpringCloudConfigClientApplication : No active profile set, falling back to default profiles: default
2019-04-11 17


image.png

image.png

image.png

刷新按钮点爆了值也不会发生变化。这就问题所在,只有重启client端才能看到最新值,不过这样的话就与预期结果相差太多。接下来就介绍两种动态刷新client端方案。


spring boot actuator执行器刷新

简单介绍一下spring boot actuator,是spring boot项目运行的一个监视器服务,启动项目的endpoints就是由spring boot actuator输出的,包含了对spring boot的bean的监视,健康状况的管理,可以通过/actuator 查看各种项目运行的信息。


首先,在client端的pom文件加入spring boot actuator的引用


image.png

image.png



image.png


这样就实现了动态刷新,但是存在一个问题。每次更新文件都需要子服务手动刷新来获取最新值,服务量一旦过大对于维护以及体验都很糟糕。所以接下来介绍第二种与spring cloud bus搭配实现无需子服务手动刷新即可动态获取服务端最新值。


spring cloud bus组合动态获取server端最新值

spring cloud bus 目前支持者rabbit以及kafka两种MQ,kafka主要针对大数据,读者若想了解spring cloud bus 可以参考这篇文章 spring cloud bus介绍与源码分析所以笔者目前所用的MQ是rabbit,从官方上pull下rabbit docker镜像并启动。目前spring cloud bus动态刷新获取server的最新文件有两种方式,分别是针对client端以及server端的两种方案。


client端

添加pom依赖


image.png


之前在bootstrap的配置文件中management.endpoints.web.exposure.include=*暴露了所有endpoint,如果没有配置的话,需要进行配置management.endpoints.web.exposure.include=bus-refresh暴露出bus-refresh节点。

观察下启动日志



2019-04-15 15:47:33.198  WARN 35860 --- [           main] o.s.boot.actuate.endpoint.EndpointId     : Endpoint ID 'bus-env' contains invalid characters, please migrate to a valid format.
2019-04-15 15:47:33.205  WARN 35860 --- [           main] o.s.boot.actuate.endpoint.EndpointId     : Endpoint ID 'bus-refresh' contains invalid characters, please migrate to a valid format.
2019-04-15 15:47:33.961  INFO 35860 --- [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=baac4e8e-51e6-329d-9818-ccb41443f5f8
2019-04-15 15:47:33.979  INFO 35860 --- [           main] faultConfiguringBeanFactoryPostProcessor : No bean named 'errorChannel' has been explicitly defined. Therefore, a default PublishSubscribeChannel will be created.
2019-04-15 15:47:34.019  INFO 35860 --- [           main] faultConfiguringBeanFactoryPostProcessor : No bean named 'taskScheduler' has been explicitly defined. Therefore, a default ThreadPoolTaskScheduler will be created.
2019-04-15 15:47:34.040  INFO 35860 --- [           main] faultConfiguringBeanFactoryPostProcessor : No bean named 'integrationHeaderChannelRegistry' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created.
2019-04-15 15:47:36.006  INFO 35860 --- [           main] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'taskScheduler'
2019-04-15 15:47:36.575  INFO 35860 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-04-15 15:47:37.511  INFO 35860 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 22 endpoint(s) beneath base path '/actuator'
2019-04-15 15:47:37.635  INFO 35860 --- [           main] o.s.c.s.m.DirectWithAttributesChannel    : Channel 'spring-cloud-config-client-1.springCloudBusInput' has 1 subscriber(s).
2019-04-15 15:47:37.919  INFO 35860 --- [           main] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel errorChannel
2019-04-15 15:47:38.102  INFO 35860 --- [           main] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel springCloudBusInput
2019-04-15 15:47:38.153  INFO 35860 --- [           main] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel nullChannel
2019-04-15 15:47:38.206  INFO 35860 --- [           main] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel springCloudBusOutput
2019-04-15 15:47:38.231  INFO 35860 --- [           main] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageHandler errorLogger
2019-04-15 15:47:38.266  INFO 35860 --- [           main] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageHandler org.springframework.cloud.stream.binding.StreamListenerMessageHandler@124d26ba
2019-04-15 15:47:38.358  INFO 35860 --- [           main] o.s.i.endpoint.EventDrivenConsumer       : Adding {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
2019-04-15 15:47:38.359  INFO 35860 --- [           main] o.s.i.channel.PublishSubscribeChannel    : Channel 'spring-cloud-config-client-1.errorChannel' has 1 subscriber(s).
2019-04-15 15:47:38.359  INFO 35860 --- [           main] o.s.i.endpoint.EventDrivenConsumer       : started _org.springframework.integration.errorLogger
2019-04-15 15:47:43.762  INFO 35860 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=spring-cloud-config-client, profiles=[dev], label=master, version=f249fd7d30cd33be291ab1c0d82b6047f6695a4c, state=null
2019-04-15 15:47:43.763  INFO 35860 --- [           main] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource {name='configService', propertySources=[MapPropertySource {name='configClient'}, MapPropertySource {name='http://gitlab.alibaba-inc.com/wb-wxj443666/spring-cloud-study.git/config/spring-cloud-config-client-dev.properties'}]}
2019-04-15 15:47:43.975  INFO 35860 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: [localhost:8088]
2019-04-15 15:47:44.109  INFO 35860 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#6f2e1024:0/SimpleConnection@74ba6ff5 [delegate=amqp://guest@127.0.0.1:8088/, localPort= 64876]
2019-04-15 15:47:44.227  INFO 35860 --- [           main] o.s.c.s.m.DirectWithAttributesChannel    : Channel 'spring-cloud-config-client-1.springCloudBusOutput' has 1 subscriber(s).
2019-04-15 15:47:44.263  INFO 35860 --- [           main] c.s.b.r.p.RabbitExchangeQueueProvisioner : declaring queue for inbound: springCloudBus.anonymous.3hRrK5n5R_Cf4QbeWTpCTQ, bound to: springCloudBus
2019-04-15 15:47:44.318  INFO 35860 --- [           main] o.s.i.monitor.IntegrationMBeanExporter   : Registering MessageChannel springCloudBus.anonymous.3hRrK5n5R_Cf4QbeWTpCTQ.errors
2019-04-15 15:47:44.442  INFO 35860 --- [           main] o.s.c.stream.binder.BinderErrorChannel   : Channel 'spring-cloud-config-client-1.springCloudBus.anonymous.3hRrK5n5R_Cf4QbeWTpCTQ.errors' has 1 subscriber(s).
2019-04-15 15:47:44.443  INFO 35860 --- [           main] o.s.c.stream.binder.BinderErrorChannel   : Channel 'spring-cloud-config-client-1.springCloudBus.anonymous.3hRrK5n5R_Cf4QbeWTpCTQ.errors' has 2 subscriber(s).
2019-04-15 15:47:44.512  INFO 35860 --- [           main] o.s.i.a.i.AmqpInboundChannelAdapter      : started inbound.springCloudBus.anonymous.3hRrK5n5R_Cf4QbeWTpCTQ
2019-04-15 15:47:44.650  INFO 35860 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-04-15 15:47:44.654  INFO 35860 --- [           main] s.s.s.SpringCloudConfigClientApplication : Started SpringCloudConfigClientApplication in 26.703 seconds (JVM running for 32.515)



了解到spring cloud bus已经正常运行了。同理,可以在写一个client模块也用同样方式去启动,


笔者让client second端指定的配置文件为spring-cloud-config-client-second-dev.properties ,相应的在git仓库创建这个文件。


image.png


image.png

image.png



只发送了一次请求,可以看到所有子服务都更新了。如果只想一次刷新部分子服务怎办呢,

client ip:port/actuator/destination=param 的请求就可以, http://127.0.0.1:8080/actuator/bus-refresh/destination=customers:7005,比如这个post请求指定了只更新port等于7005的服务项目。

接下来介绍第二种改变server端来实现子服务动态刷新获取最新值的方法。


server端

在现有client端都加入了spring cloud bus 的基础上。server端和client类似,也加入一样的依赖和配置消息。唯一的差异应该在localhost:7001/actuator/bus-refresh post请求上,server端只需向自己发送一个post请求,就可以让所有子服务获取最新的文件。


这两种解决方案没多大区别,孰优孰劣也没法确定,看个人使用喜好吧。


spring cloud config的高可用配置

关于网上所用的大部分spring cloud config高可用都是通过和eureka搭配使用,让其成为一项服务注册上去。从笔者而言,因为eureka2之后的版本闭源,放弃维护后。现在很多企业也不愿采用eureka,而选择继续维护的zoomkeeper。spring zoomkepper集成了自己的配置中心,不需要专门去构建spring cloud config,所以目前用eureka去做spring cloud config高可用的话有些鸡肋,不过针对目前部分企业还在使用eureka暂时无法更换注册中心的话,还是有存在必要的,不过笔者更推荐第二种更改客户端配置实现高可用。


修改client端配置文件实现高可用

spring cloud config 在2之后的版本已经支持高可用了,只需要更改客户端的spring.cloud.config.uri=http://localhost:7001, http://localhost:7002就可以。启动和运行时会自动扫描所配置的server端是否可用,如果第一个无法使用就继续扫描下一个,直到可用为止。



main] c.c.c.ConfigServicePropertySourceLocator : Multiple Config Server Urls found listed.
2019-04-15 15:47:28.824  INFO 35860 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:7001
2019-04-15 15:47:29.197  INFO 35860 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Connect Timeout Exception on Url - http://localhost:7001. Will be trying the next url if available
2019-04-15 15:47:29.197  INFO 35860 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:7002
2019-04-15 15:47:32.274  INFO 35860 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=spring-cloud-config-client, profiles=[dev], label=master, version=f249fd7d30cd33be291ab1c0d82b6047f6695a4c, state=null



从日志可以确认到client端启动时候的server端选择,相比erueka服务注册, 更简洁也无需更多的配置。




IT家园
IT家园

网友最新评论 (0)

发表我的评论
取消评论
表情