一、spring cloud config简介
微服务架构为什么要用配置中心?
- 配置的管理问题,没有配置中心的话,多个服务需要多次修改并依次启动,另外我们也希望修改配置后可以实时生效
- 配置内容的安全性,有些配置涉及保密等问题
- 配置的修改需要重启
开源的配置中心有:Diamond(super或淘宝)、Apoll(携程)、spring cloud config、nacos(alibaba)(nacos提供服务注册、配置中心,长轮询 )、zookeeper
开源的配置中心的差异化对比:
- 权限管理
- 高可用特性
- 通信协议
- 数据更新的方式(pull或push)
- 是否支持多语言
- 是否支持灰度等等
spring cloud config包含:
- config server
- config client
spring cloud config配置中心获取配置的后缀说明:
/{application}/{profile}/{label}
application:应用名称
profile:不同的分组
label:分支
二、通过demo来学习
我们新建一个maven模块项目,项目名称叫:springcloudnetflix。springcloudnetflix项目一共包含三个项目模块,有注册中心模块、订单中心模块、配置中心模块。
首先看一下我们springcloudnetflix模块的pom依赖
1、springcloudnetflix模块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>spring-cloud-netflix</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>config-service-9090</module>
<module>eureka-service-8080</module>
<module>user-service-7070</module>
<module>order-service-6060</module>
</modules>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<spring-boot.version>2.3.4.RELEASE</spring-boot.version>
</properties>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2、eureka-service注册中心项目
(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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.example.eurekaservice</groupId>
<artifactId>eureka-service-8080</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-service-8080</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(2)、application.properties配置
server.port=8081
spring.application.name=eureka-service
#指向服务注册中心的地址
eureka.client.service-url.defaultZone=http://localhost:8081/eureka
#设置服务提供者不发起注册
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
#关闭自我保护,确保注册中心将不可用的实例删除
eureka.server.enable-self-preservation=false
#eureka server刷新readCacheMap的时间,注意,client读取的是readCacheMap,这个时间决定了多久会把readWriteCacheMap的缓存更新到readCacheMap上
#默认30s
eureka.server.responseCacheUpdateIntervalMs=3000
#eureka server缓存readWriteCacheMap失效时间,这个只有在这个时间过去后缓存才会失效,失效前不会更新,过期后从registry重新读取注册服务信息,registry是一个ConcurrentHashMap。
#由于启用了evict其实就用不太上改这个配置了
#默认180s
eureka.server.responseCacheAutoExpirationInSeconds=180
#启用主动失效,并且每次主动失效检测间隔为3s
#默认60s
eureka.server.eviction-interval-timer-in-ms=3000
#服务过期时间配置,超过这个时间没有接收到心跳EurekaServer就会将这个实例剔除
#注意,EurekaServer一定要设置eureka.server.eviction-interval-timer-in-ms否则这个配置无效,这个配置一般为服务刷新时间配置的三倍
#默认90s
eureka.instance.lease-expiration-duration-in-seconds=15
#服务刷新时间配置,每隔这个时间会主动心跳一次
#默认30s
eureka.instance.lease-renewal-interval-in-seconds=5
#eureka client刷新本地缓存时间
#默认30s
eureka.client.registryFetchIntervalSeconds=5
#eureka客户端ribbon刷新时间
#默认30s
ribbon.ServerListRefreshInterval=1000
eureka.instance.preferIpAddress=true
(3)、项目代码
package com.example.eurekaservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServiceApplication.class, args);
}
}
3、order-service订单中心项目
我们的order-service也是一个maven多模块项目,包含order-service-api和order-service-provider项目。api项目是一个maven项目,用来定义订单项目接口,以及open-feigin对外接口的封装,这样我们在其他项目中需要调用订单项目的接口时,我们只需要引入order-service-api依赖包就可以了。provider项目是一个spring项目,用来实现api接口,处理项目业务逻辑。
(1)、order-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">
<parent>
<artifactId>spring-cloud-netflix</artifactId>
<groupId>com.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-service-6060</artifactId>
<packaging>pom</packaging>
<name>order-service-6060</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<modules>
<module>order-service-api</module>
<module>order-service-provider</module>
</modules>
</project>
(2)、order-service-api项目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>order-service-6060</artifactId>
<groupId>com.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-service-api</artifactId>
<name>order-service-api</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</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-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
(3)、order-service-api项目代码
package com.example.dto;
public class OrderDto {
private String orderId;
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
}
package com.example.service;
import com.example.dto.OrderDto;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
public interface OrderService {
@GetMapping("orders")
String orders();
@PostMapping("/insertOrder")
int insert(OrderDto dto);
}
package com.example.clients;
import com.example.clients.impl.OrderServiceFeignClientImpl;
import com.example.service.OrderService;
import org.springframework.cloud.openfeign.FeignClient;
@FeignClient(value = "order-service",fallback = OrderServiceFeignClientImpl.class)
public interface OrderServiceFeignClient extends OrderService {
//封装提供open-feign的对外访问接口
}
package com.example.clients.impl;
import com.example.clients.OrderServiceFeignClient;
import com.example.dto.OrderDto;
import org.springframework.stereotype.Component;
@Component
public class OrderServiceFeignClientImpl implements OrderServiceFeignClient {
//open-feign的熔断降级处理,接口请求失败提供默认返回参数
@Override
public String orders() {
System.out.println("查询订单失败,请稍后重试!");
return "查询订单失败,请稍后重试!";
}
@Override
public int insert(OrderDto dto) {
System.out.println("新增订单失败,请稍后重试!");
return -1;
}
}
(4)、order-service-provider项目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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>order-service-6060</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.example</groupId>
<artifactId>order-service-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>order-service-provider</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>order-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</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-starter-bus-kafka</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-actuator</artifactId>-->
<!-- </dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(5)、order-service-provider项目bootstarp.yml配置
spring:
profiles:
active: prd
application:
name: order-service
cloud:
config:
discovery: #开启配置中心的服务发现以及配置中心的服务id
enabled: true
service-id: config-service
bus: #开启消息总线配置
enabled: true
trace:
enabled: true
refresh:
enabled: true
kafka: #配置kafka服务
bootstrap-servers: localhost:9092
consumer:
group-id: order-service
eureka: #配置eureka注册中心
client:
service-url:
defaultZone: http://localhost:8081/eureka
server:
port: 6061
#management:
# endpoints:
# web:
# exposure:
# include: refresh
(6)、order-service-provider项目代码
package com.example.orderserviceprovider.service.impl;
import com.example.dto.OrderDto;
import com.example.service.OrderService;
import org.springframework.stereotype.Service;
@Service
public class OrderServiceImpl implements OrderService {
public String orders() {
return "获取到了全部的订单信息";
}
public int insert(OrderDto dto) {
return 0;
}
}
package com.example.orderserviceprovider.controller;
import com.example.dto.OrderDto;
import com.example.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Value("${server.port}")
private int port;
@Autowired
private OrderService orderService;
@GetMapping("getOrder")
public String getOrder(){
return "获取订单信息,服务端口:"+port;
}
@GetMapping("orders")
public String orders(){
return orderService.orders();
}
@PostMapping("insertOrder")
public int insertOrder(){
OrderDto orderDto = new OrderDto();
orderDto.setOrderId("1");
return orderService.insert(orderDto);
}
}
下面代码中“@RefreshScope”是关键,它用来实现配置、实例的热加载,没有它,实例配置无法更新。
package com.example.orderserviceprovider.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RefreshScope
@RestController
public class ConfigController {
//hello和env配置都是我们远程配置中心的配置
@Value("${hello}")
private String txt;
@Value("${env}")
private String env;
@GetMapping("config")
public String config(){
return txt;
}
@GetMapping("env")
public String env(){
return env;
}
}
4、config-service配置中心项目
(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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.example.configservice9090</groupId>
<artifactId>config-service-9090</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>config-service-9090</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</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-starter-bus-kafka</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-monitor</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(2)、application.properties配置
spring.application.name=config-service
server.port=9090
#配置远程配置中心地址、账号、密码,这里我们配置在gitee
spring.cloud.config.server.git.uri=https://gitee.com/spring-cloud-learning/spring-cloud-config-server.git
spring.cloud.config.server.git.username=你的gitee登录账号
spring.cloud.config.server.git.password=你的gitee登录密码
spring.cloud.config.server.git.default-label=git分支 #不配置默认是master分支
eureka.client.service-url.defaultZone=http://localhost:8081/eureka
#配置actuator手动刷新配置接口名称
management.endpoints.web.exposure.include=bus-refresh
#启动bus消息总线
spring.cloud.bus.enabled=true
spring.cloud.bus.trace.enabled=true
spring.cloud.bus.refresh.enabled=true
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=config-service
(3)、项目代码
spring boot项目入口类添加“@EnableConfigServer”配置,启动config服务
package com.example.configservice9090;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@EnableConfigServer
@SpringBootApplication
public class ConfigService9090Application {
public static void main(String[] args) {
SpringApplication.run(ConfigService9090Application.class, args);
}
}
三、总结
至此,我们的项目代码就全部完成了。由于我们的项目依赖kafka来实现消息总线机制,所以我们需要先启动一个kafka服务,启动kafka服务点击此链接。启动kafka服务后,我们依次启动注册中心、配置中心、订单中心服务,通过访问order-service的“config”或“env”接口,可以看到,我们已经获取到了远程配置,或者也可以访问config-service来查看我们的远程配置(http://localhost:9090/order-service.yml)。
1、动态刷新配置文件
但是如果我们在gitee修改了我们的配置以后,我们会发现如果我们不重启我们的应用服务,我们修改的配置只有配置中心有更新,我们的order-service应用却没有更新。这时候我们就需要spring-cloud-starter-bus消息总线机制来动态更新我们的应用配置,这里我们使用了kafka,有喜欢的同学也可以使用rabbitMQ(spring-cloud-starter-amqp)或其他。引入bus后,我们在配置更新之后,我们只需要刷新config-service配置中心,将更新的消息发送给它的订阅用户,既我们的应用,然后我们的config-client端就会在收到消息后刷新配置。
我们可以向配置中心发送一个post请求:http://localhost:9090/monitor,这个请求需要携带一个json请求参数:其中“*”其实是一个通配符,可以针对所有的应用去更新,如果我们将“*”替换成order-service,它将只针对订单服务去更新远程配置
{
"path":"*"
}
我们再刷新我们的应用服务,就会发现我们的配置已经更新了。
那么我们每次更改远程配置都需要手动请求更新吗?当然是不需要了,gitee提供了一个WebHooks钩子,使我们在远程配置更改后,可以自动调用我们的刷新。当然,我们的地址要用花生壳之类的内网穿透软件映射成外网地址。
2、spring-cloud-config的安全认证
(1)、config配置中心项目添加security依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
(2)、config配置中心项目——配置文件新增配置
spring.security.user.name=qz
spring.security.user.password=qzpassword
重启config配置中心项目,访问发现以及开启了安全认证
(3) 、在客服端项目的配置文件中新增配置
spring:
cloud:
config:
uri: http://localhost:9090 #config配置中心项目地址
username: qz #配置中心设置的账号
password: qzpassword #配置中心设置的密码
这样,我们客户端就可以访问配置中心并动态获取配置文件了。