前言
在上一节11.Config统一配置中心之手动刷新中我们是现在手动刷新获取远端Git仓库的配置文件。我们也知道了手动刷新这种方式的诸多问题。这一节我们将要学习springcloud官方提供的自动刷新组件–spring-cloud-bus,以下简称Bus
什么是Bus
Spring Cloud Bus links nodes of a distributed system with a lightweight message broker. This can then be used to broadcast state changes (e.g. configuration changes) or other management instructions. AMQP and Kafka broker implementations are included with the project. Alternatively, any Spring Cloud Stream binder found on the classpath will work out of the box as a transport.
官网给出了这么一大坨解释,具体是什么意思呢?
Bus称之为SpringCloud中的消息总线,主要用来在微服务系统中实现远端配置更新时通过广播形式通知所有客户端刷新配置信息,避免手动重启服务的工作。
Bus利用轻量级消息中间件将分布式系统中所有的服务连接到一起
当配置文件(当前状态)发生改变时,Bus利用消息中间件的广播特性通知响应的关联服务更新自身配置(当前状态)
刷新原理
刷新实现
安装RabbitMQ
上面我们说了Bus的工作原理是利用了轻量级消息中间件将分布式系统中所有的服务连接到一起。这里我们的消息中间件选择RabbitMQ。
RabbitMQ的具体安装教程参考文章RabbitMQ入门之基础理论
从上面的原理图上我们能够看到不论是Config Server还是Config Client都需要连接上RabbitMQ,下面我们先看下Config Server。
Config Server端配置
1.pom.xml
首先我们需要在pom.xml文件中引入bus的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud_parent</artifactId>
<groupId>com.christy</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>07.springcloud_config_server</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入consul client依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 引入健康检查依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--引入统一配置中心-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!--引入bus依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
</dependencies>
</project>
2.application.properties
server.port=8910
spring.application.name=CONFIGSERVER
#注册服务中心
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
# 配置远程仓库
# 指定仓库的url
spring.cloud.config.server.git.uri=https://gitee.com/tide001/config-server.git
# 指定访问的分支
spring.cloud.config.server.git.default-label=master
# 如果是私有仓库 需要配置用户名和密码
#spring.cloud.config.server.git.username=
#spring.cloud.config.server.git.password=
# 配置RabbitMQ
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
3.重新启动Config Server
上面两步配置完毕后Config Server端的配置就算是结束了,现在我们重启Config Server。重启完Server端以后我们可以看到在RabbitMQ的Queue里面多了一个名字为Queue springCloudBus.anonymous.frByGl79R7y0HX0RSHMggg
的队列
Config Client端配置
1.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud_parent</artifactId>
<groupId>com.christy</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>07.springcloud_config_client</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- 引入springboot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入consul client依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 引入健康检查依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 引入统一配置中心客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!--引入bus依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
</dependencies>
</project>
这个bus依赖在所有需要自动刷新的节点上都需要配置
2.修改配置文件
client端的配置文件仍需要配置RabbitMQ,只不过这个我们需要配置在远端git仓库中的配置文件configclient.properties中
server.port=8911
spring.application.name=CONFIGCLIENT
# spring.profile.active=dev
# 配置RabbitMQ
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
client端在上述两步完成后仍然不能重亲启动,否则会报以下错误
这个是什么原因导致的呢?
我们在引入bus组件后,在项目(系统)启动的时候会去加载RabbitMQ的配置以启动并连接消息中间件。这个时候我们从远端仓库中加载的配置文件尚未拿到,所以报了这个错
解决方案
在bootstrap.properties中新增
spring.cloud.config.fail-fast=true
,意思是在启动时还没有拉取远端配置完成时的失败都是允许的
3.bootstrap.properties
# 配置注册中心
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
# 告诉当前Config Client统一配置中心在注册中心的服务id
spring.cloud.config.discovery.service-id=CONFIGSERVER
# 开启当前Config Client 根据服务id去注册中心获取
spring.cloud.config.discovery.enabled=true
# 获取那个配置文件 1.确定分支 2.确定文件名 3.确定环境
spring.cloud.config.label=master
spring.cloud.config.name=configclient
spring.cloud.config.profile=dev
management.endpoints.web.exposure.include=*
# 在启动时还没有拉取远端配置完成时的失败都是允许的
spring.cloud.config.fail-fast=true
4.测试
现在我们重新启动Config Client,发现这次是可以正常启动的。我们修改远端的配置文件如下:
此时我们去访问http://localhost:8911/test/hello
,发现结果并不是我们修改的结果
这是因为我们在Config Server端的配置少了一步:通过执行post接口刷新配置。通过上一篇文章11.Config统一配置中心之手动刷新我们知道想要执行post接口刷新配置必须开启web端点暴露
这里需要注意:
上一篇手动刷新配置是在Client端执行post接口进行刷新配置
而本节说的自动刷新配置执行post接口是在Server端执行post接口进行刷新配置,这相较于Client端节省了大量的工作
开启Server端的web端点暴露
1.application.properties
server.port=8910
spring.application.name=CONFIGSERVER
#注册服务中心
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
# 配置远程仓库
# 指定仓库的url
spring.cloud.config.server.git.uri=https://gitee.com/tide001/config-server.git
# 指定访问的分支
spring.cloud.config.server.git.default-label=master
# 如果是私有仓库 需要配置用户名和密码
#spring.cloud.config.server.git.username=
#spring.cloud.config.server.git.password=
# 配置RabbitMQ
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
# 开启所有的web端点暴露
management.endpoints.web.exposure.include=*
2.重新启动Config Server
3.执行POST接口
我们这里直接在命令行的方式来执行,小伙伴们也可以在Postman中执行。打开命令行输入curl -X POST http://localhost:8910/actuator/bus-refresh
4.测试
我们再次在浏览器中输入http://localhost:8911/test/hello
,发现结果已经更新
curl -X POST http://localhost:8910/actuator/bus-refresh
这种方式是通过广播的方式通知所有连接到RabbitMQ的微服务同时更新,这显然是不合理而且浪费资源的。我们可以在上述命令的后面追加SERVER-ID来通过Config Server通知指定的SERVER刷新自身的配置:curl -X POST http://localhost:8910/actuator/bus-refresh/SERVER-ID
WebHook实现自动刷新
上面我们实现了配置的自动刷新,但是依然存问题:在Config Server端我们仍然需要手动执行POST访问,有没有更好的办法来自动执行Server端的POST访问?这就是我们下面要提到的webhook
什么 WebHook?
hook就是钩子,WebHooks是git(gitee|github)提供的一项功能,可以添加多个webHook
webHook能够根据仓库的触发事件去执行对应的操作。比如当我们修改了远程git仓库中的配置文件,当我们点击提交的时候它会自动发送一个POST的web请求,如果我们将该请求的url设置成Config Server的地址这就实现了自动执行Server端的POST访问,从而实现自动刷新的功能。
实现步骤
1.新增webHook
点击添加webHook
,按照下图填写相关信息,点击添加
即可
上图中的url是使用内网穿透映射的外网地址,具体的使用方法见文章natapp内网穿透速学教程
2.测试webHook
添加完毕后我们点击测试,发现返回400。如下图
3.解决方案
想要解决步骤2中的400,我们需要在Config Server中添加一个UrlFilter,如下
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@Component
public class UrlFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
String url = new String(httpServletRequest.getRequestURI());
//只过滤/actuator/bus-refresh请求
if (!url.endsWith("/bus-refresh")) {
chain.doFilter(request, response);
return;
}
//获取原始的body
String body = readAsChars(httpServletRequest);
System.out.println("original body: "+ body);
//使用HttpServletRequest包装原始请求达到修改post请求中body内容的目的
CustometRequestWrapper requestWrapper = new CustometRequestWrapper(httpServletRequest);
chain.doFilter(requestWrapper, response);
}
@Override
public void destroy() {
}
private class CustometRequestWrapper extends HttpServletRequestWrapper {
public CustometRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
byte[] bytes = new byte[0];
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return byteArrayInputStream.read() == -1 ? true:false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
}
public static String readAsChars(HttpServletRequest request) {
BufferedReader br = null;
StringBuilder sb = new StringBuilder("");
try {
br = request.getReader();
String str;
while ((str = br.readLine()) != null) {
sb.append(str);
}
br.close();
}
catch (IOException e) {
e.printStackTrace();
}
finally {
if (null != br) {
try {
br.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}
5.测试webHook
我们重启Config Server,然后测试我们上面添加的webHook,结果正常
6.测试自动刷新
我们启动Config Client,浏览器访问http://localhost:8911/test/hello
,结果如下
此时,我们去git远程仓库修改配置文件如下
浏览器再次访问http://localhost:8911/test/hello
,结果如下
从上面的结果可以看到我们已经成功使用webHook实现了通过Config Server自动执行POST请求实现Config Client配置文件的自动刷新O(∩_∩)O哈哈~
本系列专题源码已经上传至gitee:https://gitee.com/tide001/springcloud_parent,欢迎下载交流