配置管理

配置管理

当前微服务项目中的配置问题

  • 微服务重复配置过多,维护成本高
  • 业务配置变更都需要重启每一个微服务
  • 网关路由配置写死,如果变更要重启网关

配置管理服务作用

  • 配置共享:配置管理服务将所有配置信息保存在数据库中,所有微服务都从配置管理服务中获取配置信息,从而实现配置共享。
  • 配置热更新: 配置管理服务提供配置热更新功能:当配置信息发生变更时,配置管理服务会推送变更信息给所有微服务,从而实现配置热更新。

使用nacos实现配置共享

添加配置到nacos

添加一些共享配置到nacos中,包括jdbcmybatisPlus、日志、SwaggerOpenFeign

  • 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配置

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配置

nacos动态路由

[
	{
		"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中的文件名称必须与javadataId保持一致

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());
        }
    }
}

上次更新 2025/12/25 20:30:03