springCloud微服务做到全局配置文件动态刷新
springCloud通过cloudBus实现config动态刷新
springCloud中通过spring-config组件,可以实现配置文件外置在git仓库中,如果某个服务需要更改配置文件,也可以做到不重启服务,刷新此服务的配置文件。
config-server
spring-config-server 项目的
pom.xml:
<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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>config-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>config-server</name>
<description>config-server</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.10</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.10</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.49</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
ConfigServerApplication.java:
@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
@ComponentScan(basePackages = "com.example.controller")
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
config-server配置文件
配置文件application.yml:
server:
port: 8883
servlet:
context-path: /
spring:
application:
name: config-server
profiles:
active: local
cloud:
config:
server:
git:
uri: http://gitlab.rios.com:8080/servers/config-repo.git
clone-on-start: true
username: sam.hu
password: 12345678
default-label: master
eureka:
instance:
hostname: localhost
prefer-ip-address: true
instance-id: ${eureka.instance.hostname}:${server.port}
nonSecurePort: ${server.port}
client:
service-url:
defaultZone: http://localhost:9999/eureka
healthcheck:
enabled: true
management:
endpoints:
web:
exposure:
include: '*'
eureka
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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>eureka</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>eureka</name>
<description>Demo project for Spring Boot(spring cloud)</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.10</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.10</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
eureka配置文件
bootstrap.xml:
server:
port: 9999
servlet:
context-path: /
spring:
application:
name: eureka
eureka:
instance:
hostname: localhost
prefer-ip-address: false
nonSecurePort: ${server.port}
client:
service-url:
defaultZone: http://127.0.0.1:9999/eureka
register-with-eureka: false
app-service
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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>appservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>appservice</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.10</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.10</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
app-service配置文件
server:
port: 9000
spring:
application:
name: appservice
profiles:
active: local
cloud:
config:
label: master
uri: http://localhost:8883
eureka:
instance:
prefer-ip-address: true
nonSecurePort: 80
client:
service-url:
defaultZone: http://localhost:9999/eureka
register-with-eureka: true
management:
endpoints:
web:
exposure:
include: '*'
AppServiceApplication.java:
@SpringBootApplication
@EnableEurekaClient
@ComponentScan("com.example.controller")
public class AppServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AppServiceApplication.class, args);
}
}
为了测试动态配置文件的有效性,我写了一个测试Controller入口
HelloController.java:
package com.example.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by sam on 2018/4/30.
*/
@RestController
@RefreshScope
@Order(value = 100)
public class Hello2Controller {
@Value("${server.port}")
String port;
@Value("${turn.switch}")
private String turnSwitch;
@RequestMapping("/hello")
public String home(@RequestParam String name) {
return
"hello "+name+",i am from port:" +port + ",appName :"+ turnSwitch;
}
}
在远程仓库中添加一个appservice-local.yml的配置文件:
turn:
switch: on
全部启动,访问http://localhost:9999
发现config-server和appservice都注册到eureka上了。
我现在访问一下appservice的/hello接口:
curl http://localhost:9000/hello?name=sam
返回:
hello sam, i am from port: 9000,turnSwitch: true
我现在将appservice-local.yml
的配置文件改为:
turn:
switch: off
访问config-server先看看此服务返回的最新配置:
curl http://localhost:8883/appservice/local
发现返回的turn.switch: false
那我看看appservice那个/hello接口是否返回的是最新的值:
curl http://localhost:9000/hello?name=jack
发现返回的是:
hello jack, i am from port: 9000,turnSwitch: true
值没有改变。
如何触发读取已变更的配置文件
因为我们在配置文件配置如下内容:
management:
endpoints:
web:
exposure:
include: '*'
把所有的actuator组件的接口都暴露出来,包括config的刷新接口,所以,针对单服务,我只要请求:
curl http://localhost:9000/actuator/refresh
appservice服务的配置就更新了。
再看看/hello接口:
hello sam, i am from port: 9000,turnSwitch: false
springConfigServer的问题
springConfigServer只能解决单实例微服务的问题,如果我的微服务是集群部署的,那么我要更新配置的话,是不是要对每台微服务实例进行:
http://IP:port/actuator/refresh
这样请求效率很低,而且很傻,很low。
有没有办法,我配置文件更新了,只需要访问一次刷新请求,我所有引用的配置文件都能实时动态更新。
答案是,全家桶有解决方案,它就是Spring Cloud Bus!!!
Spring Cloud Bus
Spring cloud bus通过轻量消息代理连接各个分布的节点。这会用在广播状态的变化(例如配置变化)或者其他的消息指令。Spring bus的一个核心思想是通过分布式的启动器对spring boot应用进行扩展,也可以用来建立一个多个应用之间的通信频道。
目前唯一实现的方式是用AMQP消息代理作为通道,同样特性的设置(有些取决于通道的设置)在更多通道的文档中。
大家可以将它理解为管理和传播所有分布式项目中的消息既可,其实本质是利用了MQ的广播机制在分布式的系统中传播消息,目前常用的有Kafka和RabbitMQ。利用bus的机制可以做很多的事情,其中配置中心客户端刷新就是典型的应用场景之一,我们用一张图来描述bus在配置中心使用的机制。
以上图片有点问题,就是请求bus的地址在springboot 2.0 之后已经变成:http://localhost:8883/actuator/bus-refresh/{目标微服务}
目前网络上大部分是RabbitMQ用来实现的bus,本人将kafka的实现来集成bus。
只需要将config-server中的pom.xml加入:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-kafka</artifactId>
</dependency>
并且配置文件改为:
server:
port: 8883
servlet:
context-path: /
spring:
application:
name: config-server
kafka:
bootstrap-servers: 127.0.0.1:9092
consumer:
group-id: kafka-bus
profiles:
active: local
cloud:
config:
server:
git:
uri: http://gitlab.rios.com:8080/servers/config-repo.git
clone-on-start: true
username: sam.hu
password: 12345678
default-label: master
bus:
enabled: true
trace:
enabled: true
refresh:
enabled: true
eureka:
instance:
hostname: localhost
prefer-ip-address: true
instance-id: ${eureka.instance.hostname}:${server.port}
nonSecurePort: ${server.port}
client:
service-url:
defaultZone: http://localhost:9999/eureka
healthcheck:
enabled: true
management:
endpoints:
web:
exposure:
include: '*'
重后启动下config-server。
注意事项
- 必须保证kafka的自动生成topic没有被禁用
- 必须保证配置中心的配置中,配置了kafka消费组,其它客户端没有配置consumer的group-id配置
- bus使用的是默认的spring
如果这两个问题中的任何一个没有得到保证,则全局推送就会无法收到消费,进而不知道需要更新,
除了配置中心需要改造,appservice客户端服务也需要改造, pom.xml加入:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-kafka</artifactId>
</dependency>
配置文件为:
server:
port: 9000
spring:
application:
name: appservice
profiles:
active: local
cloud:
config:
label: master
uri: http://localhost:8883
bus:
refresh:
enabled: true
kafka:
bootstrap-servers: 127.0.0.1:9092
eureka:
instance:
prefer-ip-address: true
nonSecurePort: 80
client:
service-url:
defaultZone: http://localhost:9999/eureka
register-with-eureka: true
management:
endpoints:
web:
exposure:
include: '*'
启动两个appservice实例,端口号分别是9000,9001,注册到eureka。
然后我再将repo里的appservice-local.yml文件修改了一遍,将值从off改为on。
我希望调用一个接口,可以刷新所有引用配置。
现在只需执行:
curl -X POST http://localhost:8883/actuator/bus-refresh
只
是的,全局刷新配置。
针对某个服务进行刷新:
curl -X POST http://localhost:8883/actuator/bus-refresh/{微服务名称}
然后我们分别访问9000和9001的hello接口,则返回的值是已经改过的。
大功告成!!!!!