配置管理
配置管理
当前微服务项目中的配置问题
- 微服务重复配置过多,维护成本高
- 业务配置变更都需要重启每一个微服务
- 网关路由配置写死,如果变更要重启网关
配置管理服务作用
- 配置共享:配置管理服务将所有配置信息保存在数据库中,所有微服务都从配置管理服务中获取配置信息,从而实现配置共享。
- 配置热更新: 配置管理服务提供配置热更新功能:当配置信息发生变更时,配置管理服务会推送变更信息给所有微服务,从而实现配置热更新。
使用nacos实现配置共享
添加配置到nacos
添加一些共享配置到nacos中,包括jdbc、mybatisPlus、日志、Swagger、OpenFeign等
- 在
nacos配置管理页面中按如下配置

注意
nacos配置管理中data-id为配置文件名称 结尾为.yaml(文件类型结尾)
配置示例Jdbc
${hm.db.host:192.168.1.1} 表示如果hm.db.host配置项不存在,则使用192.168.1.1作为默认值
spring:
datasource:
url: jdbc:mysql://${hm.db.host:192.168.1.1}:/${hm.db.port:3306}/${hm.db.data-base}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: ${hm.db.pw}
拉取共享配置
基于NacosConfig拉取共享配置代替微服务的本地配置
spring项目启动时会优先读取,本地的配置,因此Nacos需要优先读取Nacos相关的配置(bootstrap.yaml)随后再合并本地的配置,在进行初始化上下文
加入共享配置的依赖
<dependencys>
<!-- nocos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 读取bootstrap.yaml配置文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
</dependencys>
添加bootstrap.yaml配置文件
spring:
application:
name: cart-service
profiles:
active: dev # 指定环境
cloud:
nacos:
server-addr: localhost:8848 #配置注册中心地址
config:
file-extension: yaml #读取的文件类型
shared-configs: # 共享配置文件名称
- data-id: shared-jdbc.yaml
- data-id: shared-mybatisPlus.yaml
- data-id: shared-log.yaml
- data-id: shared-swagger.yaml
- data-id: shared-openFeign.yaml
配置热更新
当修改配置文件中的配置时,微服务无需重启即可使配置生效
前提条件
nacos中要有一个与微服务名有关的配置文件- 配置名称:微服务名称-项目profile(可选)-文件的后缀名:
{application.name}-{profile}.{file-extension} - 微服务中要以特定方式读取需要热更新的配置属性
@Data
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {
private int timeout;
}
示例配置
Nacos配置

CartService配置
package com.hmall.cart.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {
private Integer maxItems;
}
CartService使用
@Service
@RequiredArgsConstructor
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {
private final CartProperties cartProperties;
@Override
public Cart getCart() {
System.out.println("购物车最大数量" + cartProperties.getMaxItems());
}
}
动态路由
- 在我们项目中路由是配置在网关微服务的配置文件中,网关启动时会将路由写入缓存中,当路由发生变更时,需要重启网关微服务
- 重启网关微服务,所有的微服务都将不可用
实现
要实现动态路由,首先要将路由配置到nacos中,当nacos中路由配置发生变更时,推送最新的配置到网关,实现更新网关中的路由信息
在nacos中添加路由json配置

[
{
"id": "item-service",
"uri": "lb://item-service",
"filters": [],
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/items/**",
"_genkey_1": "/search/**"
}
}
]
},
{
"id": "cart-service",
"uri": "lb://cart-service",
"filters": [],
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/carts/**"
}
}
]
},
{
"id": "pay-service",
"uri": "lb://pay-service",
"filters": [],
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/pay-orders/**"
}
}
]
},
{
"id": "user-service",
"uri": "lb://user-service",
"filters": [],
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/users/**",
"_genkey_1": "/addresses/**"
}
}
]
},
{
"id": "trade-service",
"uri": "lb://trade-service",
"filters": [],
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/orders/**"
}
}
]
}
]
编写路由监听器类
注意
nacos中的文件名称必须与java中dataId保持一致
package com.hmall.gateway.router;
import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
@Component
@Slf4j
@RequiredArgsConstructor
public class DynamicRouteLoader {
//注入`Nacos`的配置管理器
private final NacosConfigManager nacosConfigManager;
private final RouteDefinitionWriter routeDefinitionWriter;
private final String dataId = "gateway-router.json";
private final String groupName = "DEFAULT_GROUP";
private Set<String> routeIds = new HashSet<>();
//初始化动态路由监听器
//@PostConstruct注解主要用于标记在依赖注入完成后需要执行的初始化方法。
//加入此注解后该方法会在类加载完毕时立即执行
@PostConstruct
public void initRouteListener() throws NacosException {
//项目启动时先读取配置 并加入监听器随时监听变动
String configAndSignListener = nacosConfigManager.getConfigService().getConfigAndSignListener(dataId, groupName, 5000, new Listener() {
@Override
public Executor getExecutor() {
//线程池
//返回线程池后改监听可在特定线程异步执行
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
//拿到配置后更新路由表
updateRouteInfo(configInfo);
}
});
//第一次读取到的配置也需要更新到路由表
updateRouteInfo(configAndSignListener);
}
//更新路由表
public void updateRouteInfo(String configInfo) {
log.info("监听到更新路由表:{}", configInfo);
//将配置的json文件转为RouteDefinition类型
List<RouteDefinition> list = JSONUtil.toList(configInfo, RouteDefinition.class);
//删除旧的路由信息
for (String routeId : routeIds) {
routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
}
//清空旧的集合
routeIds.clear();
//遍历每一个路由逐个更新
for (RouteDefinition routeDefinition : list) {
//更新路由表
//subscribe(): 订阅,将数据发送给订阅者
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
//将已经更新的路由id保存在set集合中
routeIds.add(routeDefinition.getId());
}
}
}