Spring Cloud学习——8. Spring Cloud Config 环境下使用 Spring Cloud Bus + kafka 实现配置动态刷新(包括自动)

1.前言

  前文链接:Spring Cloud 学习——7. Spring Cloud Config

 

  前一篇文章我们学习了通过 Spring Cloud Config + git 实现分布式系统的统一配置管理。但是在实际项目中,我们只是实现配置往往是不够的,我们经常会遇到需要在项目运行时修改配置的需求。接下来我们就学习一下,如何在 Spring Cloud Config 环境下动态地(不重启服务)修改配置。

  我们分三步实现:

    第一步: 手动动态刷新单个服务实例的配置;

    第二步:手动动态刷新整个系统所有服务实例的配置;

    第三步:自动动态刷新整个系统所有服务实例的配置;

 

2.第一步,实现手动动态刷新单个服务实例的配置

  2.1.在 config-client 服务模块添加依赖(版本自选)

<dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
                <version>2.2.4.RELEASE</version>
            </dependency>
spring-boot-starter-actuator

    actuator 是一个库,通过暴露端点的方式,它可以帮助我们监控和管理 spring boot 应用,追踪、收集、审计、统计应用的运行情况。

  2.2.配置暴露端点

    在 bootstrap 配置文件中,添加配置  management.endpoints.web.exposure.include=* ,这里我们填 * ,即暴露所有端点。

  2.3.声明需要刷新配置的bean

    对需要动态刷新配置的 bean 类添加注解  @RefreshScope ,该注解的作用是告诉 spring 容器,当程序运行过程中重新向 config-server 拉取配置时,这个 bean 需要重新加载,并将配置属性字段使用对应的配置项最新获取的配置值注入。如:

@Component
@RefreshScope
public class CommonConfig {
    @Value("${username}")
    private String username;

    @Value("${nickname}")
    private String nickname;

    @Value("${version}")
    private String version;
CommonConfig

  2.4.测试验证

    2.4.1.首先编写一个接口,将配置的 version 值返回。代码如下:

@RestController
@RequestMapping("/")
public class HomeController {
    @Autowired
    CommonConfig commonConfig;
    @GetMapping("/")
    public String index(){
        return "version = " + commonConfig.getVersion();
    }
HomeController

      并同时启动该服务的两个实例,实例的服务端口分别是:7200 和 7201。

    2.4.2.执行请求接口:http://localhost:7200/,查看当前的 version 值,结果:

      

 

 

     2.4.3.接下来我们修改 git 上配置的值为 23

      

    2.4.4.重新执行上述接口请求,发现结果没变。因为我们现在还没有执行刷新的动作。

    2.4.5.现在来执行一下刷新,方法是请求接口:http://localhost:7200/actuator/refresh。注意该接口需要指定 post 请求,Content-Type:application/json,参数可以为空。该接口返回是一个空数组。

      

 

 

       查看一下控制台打印,发现在执行刷新接口的时候,服务去向 config-server 重新获取配置了,7300 是我 config-server 的端口:

      

    2.4.6.现在再执行上述查看配置的接口,结果:

      

 

 

       可以看到,7200 端口的实例上配置已经更新了。那么 7201 端口的实例上,配置是否更新了呢,执行一下:http://localhost:7201/,结果是并没有:

      

 

 

   所以到目前为止,我们实现了第一步:手动动态刷新单个服务实例的配置。接下来我们进行第二步。

 

3.第二步:实现手动动态刷新整个系统所有服务实例的配置

  回顾上一步,我们发现其实就是通过 post 服务暴露的 /actuator/refresh 接口,触发了服务去 config-server 配置中心重新拉取配置了。但是在一个分布式系统中,可能存在非常多的服务实例,如果每一台实例都要手动去 post 一下,那么运维成本是很高的,而且还会存在一段比较长的时间内同一个服务的多个实例的配置值有新的有旧的这种情况。那么我们能不能实现只 post 一次,就同时刷新了所有实例的配置呢?

  当然是可以的。要实现这一点,需要借助 Spring Cloud Bus + 消息中间件。Spring Cloud Bus 目前支持的中间件有 kafka 和 rabbitMQ,本例中,消息中间件使用 kafka。

  3.1.搭建 kafka

    请参考:centos 安装 kafka

  3.2.添加依赖

    在 config server 和 config client 都添加依赖:

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-bus-kafka -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-bus-kafka</artifactId>
                <version>2.2.0.RELEASE</version>
            </dependency>
spring-cloud-starter-bus-kafka

  3.3.配置 config server

spring.kafka.bootstrap-servers=host:port
spring.cloud.bus.refresh.enabled=true
management.endpoints.web.exposure.include=*
View Code

    其中:

       spring.kafka.bootstrap-servers  指定 kafka 的服务地址,多个使用英文逗号分隔;

       spring.cloud.bus.refresh.enabled=true 开启 bus 刷新功能;

  3.4.配置 config client

spring.kafka.bootstrap-servers=47.107.76.147:9092

    对比 config server 的配置,我们发现 config 和 server 都需要配置 kafka 服务地址,因为到时候刷新配置的广播是通过 kafka 发出和接收的。而 client 比 server 少了一个开启的配置,因为我们是将 server 作为最初接收 post 的节点,然后它通过 kafka 将消息广播给所有 client ,这些 client 接收到广播之后,再去 server 上面拉取配置。就是这么一个流程。

  3.5.验证测试

    3.5.1.我们先重启服务,访问一下:http://localhost:7201/,结果:

      

 

 

       然后访问:http://localhost:7200/,结果也是 23:

      

 

 

    3.5.2.现在把 git 上的配置改成 24

      

 

 

 

    3.5.2.然后执行刷新,现在我们不再 post 单个实例的刷新接口了,而是 post 配置中心(config server)的 bus 刷新接口:

      

 

 

       接口返回异常了,我们看一下 状态码:

      

      204代表接口请求是成功的,只是没有数据返回。那我们再看一下两个实例的 控制台打印,是都去 config server 拉取配置了的:

      

 

 

       

 

 

     3.5.3.验证结果是不是刷新了,分别请求两个实例的查看接口:

      

 

 

       

 

 

       可以看到,两个配置的实例都已经刷新了。

  虽然这里只是用到了同一个服务的两个实例,但是该方法是针对所有服务有效的,只要这些服务都配置了同一个配置中心并且做相同的配置即可。其实这很容易理解,因为对于配置中心来讲:不管你是一个什么服务(用户服务、订单服务,等等),你在我眼里就是一个配置客户端(config client)而已,其它的我并不关心,也就是说只要你把我配置为你的 config server,我就可以通过 bus 通知你刷新配置。

   到目前为止,我们已经实现了第二步,手动动态刷新整个系统所有服务实例的配置。

 

4.第三步,实现自动动态刷新整个系统所有服务实例的配置

  在上一步,我们已经实现了一次刷新整个系统的配置,现在我们只是需要加一个自动的功能。也就是说,只需要在配置修改的时候,触发一个向 config server 的 /actuator/bus-refresh 接口的请求就行了。

  刚好,git 服务器有一个 webhook,就支持在仓库有动作的时候,发送一个 http post 请求。配置方式(gitee 例):

    

 

 

   在我的项目中,我是将项目部署在阿里云上,并且没有对外网暴露我的 config server 的,而是通过 netflix-zuul 转发到 config server。但是在转发的过程中,git 这边收到的返回结果是异常的,看日志好像是参数解析的问题(应该是因为 zuul 对请求的头信息和参数做了一些什么事情,还没有细究 zuul),但是我又不清楚 /actuator/bus-refresh 是怎么去解析参数的,而且我们也不能更改 git 提交的参数内容。那咋办呢,那就自己定义一个接口,让 webhook 服务 post 到这个自定义的接口(这个自定义接口可以不在乎参数,所以不用解析),然后在这个接口里面使用我自己定义的参数 post 一下 /actuator/bus-refresh ,这样就可以避免双方参数不一致的问题了。代码如下:

@RestController
@RequestMapping("/")
public class HomeController {
    @GetMapping("/")
    public String home(){
        return "success";
    }

    @PostMapping("/refresh/notify")
    public String refresh(HttpServletRequest request){
        String jsonStr = "{\"ping\":\"pong\"}";
        CloseableHttpResponse response = null;
        try(CloseableHttpClient client = HttpClientBuilder.create().build()){
            HttpUriRequest httpRequest = RequestBuilder.post(request.getRequestURL().toString().replaceAll(request.getServletPath(), "/actuator/bus-refresh"))
                    .setHeader("Content-Type", "application/json")
                    .setEntity(new StringEntity(jsonStr))
                    .build();
            response = client.execute(httpRequest);
        }catch (Exception e){
            System.out.println("post /actuator/bus-refresh 出错");
        }finally {
            if (response != null){
                try {
                    response.close();
                }catch (IOException ioe){
                    System.out.println("关闭 response 异常");
                }
            }
        }
        return jsonStr;
    }
}
/refresh/notify

  查看一下 webhook 的返回结果:

    

 

   成功返回了结果。同时重新获取一下配置,是刷新了的,就不截图了。

 

  当然,也可以不自定义接口,而是:

    1.解决原始 webhook 向 zuul 请求报错的问题;

    2.或者直接将 config-server 的 /actuator/bus-refresh 暴露到外网;

 

5.完

 

posted @ 2020-02-17 20:02  不爱刺猫的鱼  阅读(1590)  评论(0编辑  收藏  举报