11.Config组件使用
什么是Config
# 0.说明
- https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.3.RELEASE/reference/html/#_spring_cloud_config_server
- config(配置)又称为 统一配置中心顾名思义,就是将配置统一管理,配置统一管理的好处是在日后大规模集群部署服务应用时相同的服务配置一致,日后再修改配置只需要统一修改全部同步,不需要一个一个服务手动维护。
注意:注册中心的配置是不能放到远端仓库的
# 1.统一配置中心组件流程图
统一配置中心服务端(server) :集中管理配置文件
统一配置中心客户端(client) :其实就是一个一个的微服务
前置操作
# 1. 使用gitee创建新的仓库 [github会出错,建议使用gitee]
我们将这个仓库命名为 configs 注:名字随便
# 2. 获取仓库地址
例如:我创建的仓库地址 https://gitee.com/lb2001/configs.git
Config Server 开发
首先创建一个springboot项目,引入 springboot-starter-web依赖(使其成为一个springboot应用),这个配置应用也要向服务注册中心注册。
Config Server:从远端git仓库拉取配置信息然后缓存到本地
<!--引入springboot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入consul依赖-->
<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>
# 1.引入依赖
<!--引入统一配置中心依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
# 2.开启统一配置中心服务 : 在入口类加上 @EnableConfigServer 注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer // 在入口类上加上这个注解表示是一个统一配置中心服务
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
# 3.修改配置文件
# 端口
server.port=8848
spring.application.name=CONFIGSERVER
# 注册到consul注册中心
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
# 4.直接启动服务报错
- 没有指定远程仓库的相关配置
# 5.创建远程仓库
- github创建一个仓库
# 6.复制仓库地址
- https://gitee.com/lb2001/configs.git
# 7.在统一配置中心服务中修改配置文件指向远程仓库地址
# 配置远程仓库地址
spring.cloud.config.server.git.uri=https://gitee.com/lb2001/configs.git
# 访问哪个分支
spring.cloud.config.server.git.default-label=master
# 私有库的话需要书写用户名和密码,公共库无需书写
#spring.cloud.config.server.git.username=xxx
#spring.cloud.config.server.git.password=xxx
# 8.再次启动统一配置中心
# 9.在web界面显示远端配置文件的配置信息 [三种方式]
例如: 以下三个配置文件
configclient.properties
configclient-prod.properties
configclient-dev.properties
对比下面test是configclient
xxx是哪个环境的,比如 prod 或 dev,如果找不到,那就是configclient.properties这个文件
如果找到了会把configclient.properties和configclient-xxx.properties进行一个合并
- 1. http://localhost:8848/test-xxxx.properties
- 2. http://localhost:8848/test-xxxx.json
- 3. http://localhost:8848/test-xxxx.yml
8848 是 统一配置中心服务的端口号
注:虽然配置文件后缀是 properties ,但是仍然可以访问地址中后缀是 json 和 yml 的,会自动转换格式
# 10.查看拉取配置详细信息
- http://localhost:8848/configclient/dev [client:代表远端配置名称][dev:代表远程配置的环境]
Config Client 开发
Config Client 其实就是一个一个的微服务,比如商品、类别、订单服务···
拉取 config server 的配置信息。
注意:注册中心的配置是不能放到远端仓库的
首先创建一个springboot应用,引入下面依赖(引入springboot、consul、健康检查依赖)
<!--引入springboot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入consul依赖-->
<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>
在开发时一般会有三个配置文件:
- application.properties:公共配置文件
- application-prod.properties:生产环境的配置文件
- application-dev.properties:测试环境的配置文件
公共配置文件中可以配置一个属性 spring.profiles.active ,选择生产环境还是测试环境的配置。
如果公共配置文件里设置成 : spring.profiles.active=prod 表示使用公共配置文件和生产环境配置文件作为配置文件
如果公共配置文件里设置成 : spring.profiles.active=dev 表示使用公共配置文件和测试环境配置文件作为配置文件
但是我们在使用配置中心配置时不需要spring.profiles.active属性,上面的配置文件都会放到远端仓库,真正决定使用哪个配置文件的实际上是某些必须写在本地的配置
# 1.项目中引入config client依赖
<!--引入config client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
# 2.编写配置文件 【一些必要的配置文件还是写在本地,比如注册到注册中心等】
# 注册到注册中心 注册中心的配置是不能放到远端仓库的
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
# 告诉当前configclient统一配置中心在注册中心服务id
spring.cloud.config.discovery.service-id=CONFIGSERVER
# 去注册中心获取config server
spring.cloud.config.discovery.enabled=true
#指定从仓库的那个分支拉取配置
spring.cloud.config.label=master
#指定拉取配置文件的名称
spring.cloud.config.name=configclient
#指定拉取配置文件的环境
spring.cloud.config.profile=dev
# 3.远程仓库创建配置文件
远程仓库的配置文件建议以服务名命名
- configclient.properties [用来存放公共配置]
server.port=8990
spring.application.name=CONFIGCLIENT
# 注册到 consul 注册中心 写到本地
#spring.cloud.consul.host=localhost
#spring.cloud.consul.port=8500
# 指定环境配置 使用哪个额外配置文件 本地配置文件中有配置使用什么环境
# spring.profiles.active=prod
- configclient-dev.properties [用来存放开发相关配置]
name=xiaosan
- configclient-prod.properties [用来存放生产相关配置]
name=xiaochen
在启动config client之前一定要提前将config server启动
# 4.启动客户端服务进行远程配置拉取测试
- 直接启动过程中发现无法启动直接报错
# 报错原因
- 项目中目前使用的是application.properties启动项目,使用这个配置文件在springboot项目启动过程中不会等待远程配置拉取,直接根据配置文件中内容启动,因此当需要注册中心,服务端口等信息时,远程配置还没有拉取到,所以直接报错
# 解决方案
- 应该在项目启动时先等待拉取远程配置,拉取远程配置成功之后再根据远程配置信息启动即可,为了完成上述要求springboot官方提供了一种解决方案,就是在使用统一配置中心时应该将微服务的配置文件名修改为bootstrap.(properties|yml),bootstrap.properties作为配置启动项目时,会优先拉取远程配置,远程配置拉取成功之后根据远程配置启动当前应用。
# 再次启动服务
注意:一旦使用远端配置,本地的配置文件名字必须为bootstrap.properties 或 bootstrap.yml ,在启动项目时会预先拉取配置信息到本地,然后以获取的配置启动。
手动配置刷新
现有 config client 存在一个问题,就是当远端配置发生改变的时候,它没有办法完成一个刷新,必须重启之后才能加载最新的配置。能不能让它不重启服务就完成配置的刷新呢?
所谓手动配置刷新,就是当远端 git 仓库中配置发生变化时,不需要重启服务就可以直接读取远端修改之后的配置信息。
# 1.说明
- 在生产环境中,微服务可能非常多,每次修改完远端配置之后,不可能对所有服务进行重新启动,这个时候需要让修改配置的服务能够刷新远端修改之后的配置,从而不要每次重启服务才能生效,进一步提高微服务系统的维护效率。在springcloud中也为我们提供了手动刷新配置和自动刷新配置两种策略,这里我们先使用手动配置文件刷新。
# 2.在config client端加入刷新暴露端点
# 开启所有web端点暴露,远端仓库会向指定路径/actuator/refresh以post方式发送刷新请求
management.endpoints.web.exposure.include=*
# 注:.properties后缀的配置文件中写 * .yml后缀的配置文件中写 "*"
# 3.在需要刷新代码的类中加入刷新配置的注解
在所有controller上加上@RefreshScope 这个注解
@RestController
@RefreshScope // 作用: 在不需要重启微服务的情况下,将配置刷新为最新的远端配置
public class DemoController {
@Value("${name}")
private String name;
@GetMapping("/demo")
public String demo(){
System.out.println("name = " + name);
System.out.println("demo ok!!!");
return "demo ok";
}
}
# 4.查看远程配置并启动测试
# 5.启动之后直接访问
# 6.修改远程配置
# 7.修改之后再访问
发现并没有自动刷新配置?
- 必须调用刷新配置接口才能刷新配置
# 8.手动调用刷新配置接口
在cmd中执行下面命令 或者 用Postman发送post请求给 http://localhost:8990/actuator/refresh
curl -X POST http://localhost:8990/actuator/refresh
// 注:8990是服务端口,/actuator/refresh是固定的
# 9.再次访问发现配置已经成功刷新
但是上面这种方式带来一种问题:
每一个微服务节点如果要加载最新配置信息,必须向每一个服务手动发送post方式请求,才能加载最新配置。
很多微服务都是集群,如果向每一个集群里的每一个结点都发送post请求就太麻烦了,有没有办法可以只向
config server 发送post请求,其他服务节点自动刷新呢?这就用到下面的Bus组件了。
12.Bus组件的使用
什么是Bus (AMQP RibbitMQ、Kafka)
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. --摘自官网
# 0.翻译
- https://spring.io/projects/spring-cloud-bus
- spring cloud bus使用轻量级消息代理将分布式系统的节点(微服务)连接起来。然后,可以使用它来广播状态更改(例如配置更改)或其他管理指令。AMQP(Rabbitmq等)和Kafka broker(中间件)实现包含在项目中。或者,在类路径上找到的任何springcloudstream绑定器都可以作为传输使用。
- 通俗定义: bus称之为springcloud中消息总线,主要用来在微服务系统中实现远端配置更新时通过广播形式通知所有客户端刷新配置信息,避免手动重启服务的工作
实现配置刷新原理
搭建RabbitMQ服务
# 0.下载rabbitmq安装包 [][可以直接使用docker安装更方便]
- 官方安装包下载:https://www.rabbitmq.com/install-rpm.html#downloads
[注意:][这里安装包只能用于centos7.x系统]
# 1.将rabbitmq安装包上传到linux系统中
erlang-22.0.7-1.el7.x86_64.rpm
rabbitmq-server-3.7.18-1.el7.noarch.rpm
# 2.安装Erlang依赖包
rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm
# 3.安装RabbitMQ安装包(需要联网)
yum install -y rabbitmq-server-3.7.18-1.el7.noarch.rpm
注意:默认安装完成后配置文件模板在:/usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example目录中,需要
将配置文件复制到/etc/rabbitmq/目录中,并修改名称为rabbitmq.config
# 4.复制配置文件
cp /usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example /etc/rabbitmq/rabbitmq.config
# 5.查看配置文件位置
ls /etc/rabbitmq/rabbitmq.config
# 6.修改配置文件(参见下图:)
vim /etc/rabbitmq/rabbitmq.config
将上图中配置文件中红色部分去掉%%
,以及最后的,
逗号 修改为下图:
# 7.执行如下命令,启动rabbitmq中的插件管理
rabbitmq-plugins enable rabbitmq_management
出现如下说明:
Enabling plugins on node rabbit@localhost:
rabbitmq_management
The following plugins have been configured:
rabbitmq_management
rabbitmq_management_agent
rabbitmq_web_dispatch
Applying plugin configuration to rabbit@localhost...
The following plugins have been enabled:
rabbitmq_management
rabbitmq_management_agent
rabbitmq_web_dispatch
set 3 plugins.
Offline change; changes will take effect at broker restart.
# 8.启动RabbitMQ的服务
systemctl start rabbitmq-server
systemctl restart rabbitmq-server
systemctl stop rabbitmq-server
# 9.查看服务状态(见下图:)
systemctl status rabbitmq-server
● rabbitmq-server.service - RabbitMQ broker
Loaded: loaded (/usr/lib/systemd/system/rabbitmq-server.service; disabled; vendor preset: disabled)
Active: active (running) since 三 2019-09-25 22:26:35 CST; 7s ago
Main PID: 2904 (beam.smp)
Status: "Initialized"
CGroup: /system.slice/rabbitmq-server.service
├─2904 /usr/lib64/erlang/erts-10.4.4/bin/beam.smp -W w -A 64 -MBas ageffcbf -MHas ageffcbf -
MBlmbcs...
├─3220 erl_child_setup 32768
├─3243 inet_gethost 4
└─3244 inet_gethost 4
.........
# 10.启动出现如下错误:
- 4月 21 10:10:50 bogon systemd[1]: Starting RabbitMQ broker...
- 4月 21 10:11:11 bogon rabbitmq-server[1772]: ERROR: epmd error for host bogon: address (cannot connect to host/port)
- 4月 21 10:11:11 bogon systemd[1]: rabbitmq-server.service: main process exited, code=exited, status=1/FAILURE
`解决方案`:
1. 修改主机名 vim /etc/hostname 修改为自己注解名 rabbimq 2.修改完必须重启reboot
3. vim /etc/hosts 在文件中添加: 127.0.0.1 自己主机名(rabbitmq)
# 启动 rabbitmq
systemctl start rabbitmq-server
# 10.关闭防火墙服务
systemctl disable firewalld
systemctl stop firewalld
# 11.访问web管理界面
http://192.168.204.133:15672/
# 12.登录管理界面
username: guest
password: guest
# 13.MQ服务搭建成功
实现自动配置刷新
配置统一配置中心连接到rabbitmq
# 1.在统一配置中心服务中引入bus依赖
<!--引入bus依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
# 2.配置统一配置中心连接到mq
注意:config server统一配置中心和其它微服务所用的rabbitmq的虚拟主机应该是同一个,不然不同的虚拟主机
之间具有隔离性,消息无法共享
# 通过bus组件连接到rabbitmq服务
# rabbitmq主机地址
spring.rabbitmq.host=192.168.204.133
# rabbitmq服务端口
spring.rabbitmq.port=5672
# 连接mq用户名
spring.rabbitmq.username=guest
# 连接mq密码
spring.rabbitmq.password=guest
# rabbitmq中的哪个虚拟主机
spring.rabbitmq.virtual-host=/
# 3.启动统一配置中心服务
- 正常启动
配置其他微服务连接到rabbitmq
所有微服务都需要引入bus依赖
# 1. 引入 bus 依赖
<!--引入bus依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
# 2.远端仓库配置中加入连接mq配置
# 通过bus组件连接到rabbitmq服务
# rabbitmq主机地址
spring.rabbitmq.host=192.168.204.133
# rabbitmq服务端口
spring.rabbitmq.port=5672
# 连接mq用户名
spring.rabbitmq.username=guest
# 连接mq密码
spring.rabbitmq.password=guest
# rabbitmq中的哪个虚拟主机
spring.rabbitmq.virtual-host=/
# 3.启动客户端服务
- 加入bus组件之后客户端启动报错
- 原因springcloud中默认链接不到远程服务器不会报错,但是在使用bus消息总线时会立即去连接rabbitmq,但是此时远端配置还没有拉去下来,就会失败,但是这种失败是允许的,我们要设置开启连接远程服务失败报错。
# 允许项目启动时bus组件立即连接失败,这种失败是允许的,因为获取到远端配置之后可以重新连接
spring.cloud.config.fail-fast=true
# 注:这句配置必须放到本地的配置文件中
# 4.重新启动该服务
日后修改远程仓库之后如果刷新所有微服务配置
注:中心配置服务config-server配置文件中还要加入一段配置暴露 /actuator/bus-refresh 路径
# 开启所有web端点暴露,远端仓库会向指定路径/actuator/refresh以post方式发送刷新请求
management.endpoints.web.exposure.include=*
# 5.日后如果修改远程配置后,只需要在配置中心服务通过执行post接口即可刷新系统中所有微服务的配置
curl -X POST http://localhost:8848/actuator/bus-refresh
注:可以通过在cmd中执行上面命令或者使用Postman向上面指定路径发一个post请求去刷新系统中所有微服务的配置
指定服务刷新配置
# 1.说明
- 默认情况下使用curl -X POST http://localhost:8848/actuator/bus-refresh这种方式刷新配置是全部广播形式,也就是所有的微服务都能接收到刷新配置通知,但有时我们修改的仅仅是某个服务的配置,这个时候对于其他服务的通知是多余的,因此就需要指定服务进行通知
# 2.指定服务刷新配置实现[两种方法]
注:/actuator/bus-refresh/ 必须在统一配置中心config-server暴露
向统一配置中心发送请求:
- 指定端口刷新某个具体服务:
curl -X POST http://localhost:8848/actuator/bus-refresh/configclient:9090
[注意:][8848是统一配置中心端口,configclient代表刷新哪个服务的服务id,9090是这个服务的端口]
- 指定服务id刷新服务集群节点:
curl -X POST http://localhost:8848/actuator/bus-refresh/configclient
[注意:][8848是统一配置中心端口,configclient代表刷新哪个服务的服务id]
集成webhook实现自动刷新
# 1.配置webhooks
- 说明: git仓库提供一种特有机制: 这种机制就是一个监听机制 监听就是仓库提交事件 ... 触发对应事件执行
webhooks:根据远程仓库触发对应事件发送一个web请求,这个请求默认就是post方式请求
我们可以日后可以监控版本库的提交事件,当它一提交的时候,就利用它的webhook发一个post方式的请求设置这个post请求的路径是/actuator/refresh就可以了
- javascript: 事件 事件源 html标签 事件: 触发特定动作click ... 事件处理程序:函数
- 添加webhooks
- 在webhooks中添加刷新配置接口
- 内网穿透,将内网地址转化为公网地址暴露出去,日后别人就可以用这个公网地址访问
内网穿透的网站: https://natapp.cn/
# 2. 内网穿透:将内网地址转换为公网地址暴露出去,日后别人(远程仓库)就可以用这个公网地址访问
注册登录然后实名认证之后
注:8848是config server的端口
之后下载客户端
下载之后我把其解压到了 d:/work 目录中
然后打开 cmd,cd到安装目录 d:/work,执行下面命令启动
natapp -authtoken=f0a47206fac99185
# 注:f0a47206fac99185就是自己购买的隧道的authtoken
# 3. 在远端仓库配置webhooks
这里我们配置一下 gitee 的webhooks
# 上面配置完之后会向配置的地址发一个post请求测试正确性,但是却会出现错误,是因为configserver结合webhook时响应的东西太多了,我们去掉一些,如下配置一个过滤器
# 4.解决400错误问题
- 在配置中心服务端加入过滤器进行解决(springcloud中一个坑)
- 在统一配置中心config server的filters包中创建下面这个过滤器类
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. 然后在入口类上加上@ServletComponentScan(basePackages = "xxx") // 在入口类上加上这个注解,让入口类扫描这个路径
# 6. 然后重启config server
# 7. 重启之后可以测试一下,至此 webhooks 就配置完成了。
13. SpringCloud 微服务工具集总结
# 服务间通信方式: RPC 、 Http 协议 (SpringCloud中)
# 1.服务注册中心组件: Eureka 、 Consul
# 2.服务间通信实现 :
a.RestTemplate(HttpClient对象) + Ribbon组件(springcloud)
b.openfegin(伪httpclient客户端组件 底层默认集成Ribbon) 推荐
# 3.微服务保护组件: Hystrix (防止服务雪崩现象) Hystrix DashBoard 组件 维护状态
# 4.微服务网关组件: Zuul1.x Zuul2.x(netflix组件)、Gateway(Spring 组件)
网关: 路由转发 + 过滤器(前置predicate 后置filter)
# 5.统一配置中心组件: Config (netflix)
作用: 用来将微服务中所有配置进行远程git仓库统一管理
# 6.消息总线: Bus
作用: 用来通过消息中间件将所有微服务连接到一起,利用广播模型实现配置自动刷新机制