微服务系统,配置中心是其中重要的一环。
什么是配置中心?
配置中心的主要思想是将配置集中管理起来。它允许我们在一个地方管理不同环境和不同集群的配置信息。当配置发生变化时,配置中心能够实时地将更新推送到应用程序,从而实现动态更新,无需手动逐个修改每个实例的配置。
简而言之,配置中心就像是一个中央仓库,负责管理和分发配置信息。这样一来,我们就能够更高效地管理分布式系统的配置,降低维护成本,提高系统的可靠性和可维护性。
配置中心通常具备以下特点:
-
- 集中管理:所有的配置信息都存储在一个或多个集中的位置,便于统一管理和维护。
-
- 动态更新:应用程序可以实时接收配置的更新,而无需重启服务。
-
- 版本控制:配置信息的变更历史可以被记录和追踪,方便回滚到旧版本的配置。
-
- 权限管理:不同的用户或服务可能需要访问不同的配置信息,配置中心可以提供细粒度的权限控制。
-
- 环境隔离:可以为不同的环境(如开发、测试、生产)提供不同的配置信息。
-
- 服务发现:配置中心可以与服务发现机制集成,以便服务能够找到并使用正确的配置。
-
- 高可用性:配置中心自身需要具备高可用性,以确保应用程序始终能够访问到配置信息。
-
- 安全性:配置信息可能包含敏感数据,因此配置中心需要提供安全机制,如加密存储和传输。
-
- 监控和审计:配置中心可以提供监控配置使用情况和审计配置变更的功能。
-
- API支持:配置中心通常提供API,使得应用程序可以通过编程方式查询和更新配置。
常见的配置中心解决方案包括Spring Cloud Config、Consul、etcd、Apache ZooKeeper等。
本文将采用Spring Cloud Config方案,部署一个包含config实例。
0. 环境准备
- JAVA 21
- Spring Cloud 2023.0.1
- Spring Boot 3.2.5
- git
1. 搭建Eureka注册中心
Eureka Server本身也是一个Spring boot项目。
1.1 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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<groupId>org.example.yy</groupId>
<artifactId>eureka</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>eureka</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.1</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>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>
1.2 启动类
package org.example.yy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer {
public static void main(String[] args) {
SpringApplication.run(EurekaServer.class, args);
}
}
这里需要添加注解EnableEurekaServer来启用 Eureka Server。
1.3 application.properties
spring.config.name=eureka-server
server.port=10000
eureka.client.registerWithEureka=false
eureka.client.fetchRegistry=false
eureka.server.renewalPercentThreshold=0.49
打开Eureka Server可以看到,服务已经启动好。
2. git repo
由于这里是本地环境测试,没有使用远端的git仓库,使用的是本地git repo,目录是:/Users/yuanyao/tmp/config-repo, 在指定目录下写了一个文件:
tech.book=kotlin-version
3. Config Server
Config Server是一个提供Config的中心。这个Config Server也是一个Eureka client,因此也需要注册到注册中心。
3.1 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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example.yy</groupId>
<artifactId>config-server</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>config-server</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-data-mongodb</artifactId>-->
<!-- </dependency>-->
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.1</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>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>
这里主要添加Eureka client和Config Server的starter。
3.2 启动类
package org.example.yy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;
@EnableConfigServer
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigServer {
public static void main(String[] args) {
SpringApplication.run(ConfigServer.class, args);
}
}
这里需要添加两个注解,分别EnableDiscoveryClient和EnableConfigServer.
3.3 application.properties
server.port=10001
spring.application.name=config-server
eureka.client.serviceUrl.defaultZone=http://localhost:10000/eureka/
spring.cloud.config.server.git.uri=file:///Users/yuanyao/tmp/config-repo
spring.cloud.config.label=master
这里不但需要配置使用的Eureka注册中心的地址,还要配置git repo的地址,同时还要配置,是使用的哪个分支。
3.4 注册到注册中心
这里启动这个Spring boot项目,可以看到,已经注册到注册中心。
3.5 配置的内容
我们可以通过rest接口查看配置的内容
curl http://localhost:10001/config-repo/master
{"name":"config-repo","profiles":["master"],"label":null,"version":"0c719f8639f9c2036f6f7f1293d90606e81365a1","state":null,"propertySources":[{"name":"file:///Users/yuanyao/tmp/config-repo/application.properties","source":{"tech.book":"kotlin-version5"}}]}%
注意,这里的config-repo是其中的git仓库名,master是分支名。
这里的配置内容是JSON格式
{
"name": "config-repo",
"profiles": ["master"],
"label": null,
"version": "0c719f8639f9c2036f6f7f1293d90606e81365a1",
"state": null,
"propertySources": [{
"name": "file:///Users/yuanyao/tmp/config-repo/application.properties",
"source": {
"tech.book": "kotlin-version5"
}
}]
}
这里可以看到,我们已经读取到配置再配置中心的内容。
4.Config Client
4.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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example.yy</groupId>
<artifactId>config-client</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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-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-bootstrap</artifactId>
<version>4.1.0</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.1</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>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>
4.2 启动类
package org.example.configclient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigClient {
public static void main(String[] args) {
SpringApplication.run(ConfigClient.class, args);
}
}
4.3 controller
package org.example.configclient.web;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope
public class HelloController {
@Value("${tech.book}")
String bookName;
@GetMapping("book")
public ResponseEntity<String> book() {
return ResponseEntity.ok(bookName);
}
}
这里测试配置的内容tech.book的值,访问book接口,看返回即可。
4.4 bootstrap.properties
Config中有两个配置文件,一个是bootstrap.properties,另一个是application.properties。这两个文件的区别是:
bootstrap.properties主要用来配置一些系统级的配置,如配置中心的地址等。application.properties中主要存放应用级的配置内容。
eureka.client.serviceUrl.defaultZone=http://localhost:10000/eureka/
spring.cloud.config.discovery.service-id=CONFIG-SERVER
spring.cloud.config.discovery.enabled=true
spring.cloud.config.name=config-client
spring.cloud.config.label=master
3.4 application.properties
spring.application.name=config-client
server.port=10002
tech.book=java
4.5 启动
这个时候,再启动Config Client。这个时候,可以看到,该client也注册到注册中心了:
这个时候,我们访问接口,测试配置的值:
这里看到,已经是配置中心的值kotlin-version5了,而不是默认配置的java了。
5.动态更新值
配置中心还有一个重要功能,可以动态更新config的值,而不需要重新启动。这里有两种方案,一个是使用Spring boot actuator,这个是使用手动刷新来更新值。
另一种是使用spring config bus来进行自动更新。
5.1 actuator方式
只需要在config-client中引入starter:
<!--springcloud 使用/refresh端点手动刷新项目依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
然后再配置文件中配置,暴露所有接口:
management.endpoints.web.exposure.include=*
这个时候,我们更新git repo的值,将tech.book的值更新到version6,然后commit。
然后访问refresh接口,进行手动刷新:
curl -X POST http://localhost:10002/actuator/refresh
["config.client.version","tech.book"]%
这个时候,已经刷新完成,此时再请求端口,可以看到值已经发生改变:
5.2 spring cloud bus
挖个坑,有人想看再填
6. 总结
本文只是记录了使用spring cloud config的一个示例,其中只涉及简单的实现,原理均无涉及。但是,学习了如何使用只是第一步,下一篇更新其实现原理。