-
前言:
问题:
1:问:什么是coding?
1:答:跟码云、github一样,是一个代码托管平台。
2:问:什么是webhook?
2:答:webhook是一种通过callback回调,去请求某个接口或是改变page行为的方法。当用户在网站执行某个操作的时候,源网站可以发起一个HTTP请求到webhook配置的URL。配置之后,用户可以通过在一个站点触发事件,之后再调用另一个站点的行为。可以是任何操作。
3:问:什么是springcloud配置中心(Spring Cloud Config)?
3:答:在单机项目中,我们一般把配置文件比如application.yml、application.properties放在项目的根目录或是resources文件夹下。但是在分布式系统中,有多个微服务的情况下,我们一般把配置文件放在远端如github、码云或是coding上,程序从远端读取配置文件,而不从本地读取。这些配置在远端的文件,我们就可以理解为配置中心。
4:问:什么是配置中心的自动刷新?
4:答:在单机项目,我们的配置文件放在项目工程目录,更改某个配置后,只需要重启项目就可以重新读取了。但是在分布式系统中,有多个微服务的情况下,我们的配置文件放在github等远端上,更改配置后不可能一个一个去重启微服务来读取,那么就需要统一自动刷新配置。springcloud的配置中心提供了刷新的接口来实现不用重启服务,就可以刷新配置的功能。但这还谈不上自动,所以我们结合webhook的功能,在webhook中配置这个刷新接口的接口地址,就实现了在远端修改文件,触发webhook的回调方法 请求刷新配置的接口,就可以自动刷新项目的配置了。
-
实现步骤
- 在本地的sringcloud项目中至少有3个微服务程序或是微服务模块。分别是:服务注册中心(eureka-server)、配置中心(config-server)、业务模块(我的业务模块a的自定义名字是:policy-receive)。服务注册中心(eureka-server)的配置没啥好说的,配置中心和业务模块的配置稍微复杂一些,后文会讲
- 在coding或是github上(后文以coding为例)创建配置文件。配置文件必须以模块的自定义名称为前缀,以开发、测试、生产标识为后缀。远程配置文件如下
-
config-server(配置中心的配置)
3.1:下载安装启动rabbitMQ,启动成功后在浏览器输入localhost:15762,使用guest登录后出现如下界面
3.2:在配置中心模块(config-server)添加依赖,重点在spring-cloud-starter-bus-amqp、spring-boot-starter-actuator这两个jar包上,其余的是一 些springcloud的常规jar包。
3.3:在配置中心模块(config-server)修改配置文件application.yml,添加以下4个配置,分别是git连接配置,rabbit配置,微服务注册配置、刷新配置。
server: port: 8762 spring: application: name: config-server cloud: config: server: git: uri: https://xxx/xxx.git #git仓库地址 username: xxxx #git仓库用户名,如果是开源仓库可以不配 password: xxxx #git仓库密码,如果是开源仓库可以不配 rabbitmq: host: localhost port: 5672 #rabbitmq的页面端口是15762,业务端口是5762,这里配5762 username: guest #游客默认账号 password: guest #游客默认密码 eureka: client: registerWithEureka: true #是否将自己注册进去eureka,false为不注册,true注册 fetchRegistry: true #是否从eureka抓取注册信息,单点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 serviceUrl: #服务注册中心地址,服务注册模块的端口号是8761,所以此处地址为http://localhost:8761/eureka/ defaultZone: http://localhost:8761/eureka/ management: endpoints: web: exposure: include: bus-refresh # 指定刷新地址
3.4:配置中心启动文件,添加@EnableDiscoveryClient、@EnableConfigServer这两个注解。第一个注解的意思是作为微服务客户端被注册,第二个注解的意思是当前程序作为配置中心。到此为止springcloud-config已经配置完成。
- 业务微服务的配置
4.1:如上图所见,我本地跟业务相关的微服务有两个,我以其中一个为例(微服务名称为:policy-receive),以下代码和项目结构奉上。值得注意的是:1:当我们在创建一个springboot的微服务工程的时候,配置文件的名称一般都是 application.yml或application.properties。但是由于我们要实现配置文件的自动刷新,我们要将application.yml(或是application.properties)重命名为bootstrap.yml(或是bootstrap.properties)。因为bootstrap.yml的加载顺序在application.yml前面,所以只有先从bootstrap.yml中获取coding上的application文件,然后程序再读取这个远程application文件。2:远程配置文件的命名问题。coding上的配置文件命名需要遵从最基本的 客户端微服务名称+环境标识 的规则。如上文第二个步骤的远程配置文件图所示,至于环境标识如下图所示。3:如果远程配置文件中和本地bootstrap.yml这个文件中有相同的属性,那么远程的配置会替换掉本地的配置。
spring: application: name: policy-receive #微服务的名称 cloud: config: discovery: enabled: true #允许被注册中心发现 service-id: config-server #注册中心的名称 profile: dev #配置中心文件的环境标识,dev代表开发环境,uat代表测试环境,pro代表生产环境。 uri: http://localhost:8762 #配置中心的地址 main: allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册 rabbitmq: host: localhost #rabbitmq的配置,没什么好讲的 port: 5672 username: guest password: guest eureka: client: fetch-registry: true #要不要去注册中心获取其他服务的地址 register-with-eureka: true #要不要在注册中心注册 service-url: defaultZone: http://localhost:8761/eureka/ #注册中心的地址
4.2:pom文件的主要依赖(其他业务相关的依赖请忽略)
4.3:启动类添加注解,主要是@EnableEurekaClient、@EnableDiscoveryClient这两个注解。本地的基本配置已经完成。
- coding上代码仓库的webhook配置
5.1:到代码代码仓库找到项目设置,如下
这里要注意的是这个webhook的url是配置中心(config-server)的网址+路径,不能使用ip地址:端口号+路径的形式。所以我用花生壳将我的内网ip和配置中心(config-server)的端口号8762进行了内网映射。至于url的路径为什么是bus-refresh,是因为我们在上文第三个大步骤的3.3有提到,在配置文件中指定的刷新地址就是bus-refresh。在该页面代码仓库中的下拉框中选择配置文件所在的代码仓库。到此为止,coding端的webhook配置已经完成。
5.2:接下来我们进行webhook的ping测试。先将本地 注册中心、配置中心、业务程序三个微服务依次依次依次启动起来。启动起来后我们打开注册中心可以看到配置中心和业务程序以及被注册中心发现并注册了
然后我们返回到coding的webhook页面,看到列表中已经有一条我们刚才新增的webhook配置了,点击ping,进行测试
当页面上弹出已发送测试后,我们点击详情,查看发送记录,就可以看到我们刚才发送的请求的请求头和请求体。
同时可以看到我们的配置中心接收到了一条请求,请求的内容如下。说明我们的webhook配置没问题了。
5.3:接下来进行实际业务测试。
我们在业务模块新建一个controller,通过RequestMapping注解controller的路径。注意通过RefreshScope注解自动刷新。
在controller中定义一个String变量,变量的值通过Value注解注入,取的是远端配置文件中bonecp.driverClass属性的值。
再写一个方法,返回这个String变量的值。
然后我们打开postman,请求业务模块的这个接口,可以看到返回的值正是coding仓库的配置文件的值。
然后我们修改coding仓库的这个属性,点击提交。提交后直接在postman中再次请求,可以看到返回值随之改变。
到此为止我们已经实现了coding+webhook自动微服务的配置的功能。
5.4:如果webhook请求错误或是本地配置中心报错
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 62] (through reference chain: java.util.LinkedHashMap["hook"])]
如果出现以上报错,是因为webhook请求配置中心时,请求的body中包含了不能被json反序列化的参数。需要我们手动在配置中心添加对来自webhook的请求参数的过滤。
代码如下:
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
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;
String url = 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();
}
}
coding+webhook实现springcloud配置中心的自动刷新的配置到此已经完成。springcloud配置中心作为springcloud的五大组件之一,是必须要掌握的,相信我们在编程的路上会越走越远,越走越高。
文中若有遗漏或错误,欢迎评论留言讨论。码字不易,点个赞吧