Java微服务day04-配置管理(Nacos)

1.配置管理

提示:这里我们使用的是Nacos

1.1为什么要用配置管理

(1)网关路由在配置文件中写死了,如果变更必须重启微服务
(2)某些业务配置在配置文件中写死了,每次修改都要重启服务
(3)每个微服务都有很多重复的配置,维护成本高

1.2解决办法

使用Nacos,就能很好的去解决以上问题
Nacos不仅可以注册拉去服务,还可以配置管理器服务

在这里插入图片描述

思维导图
在这里插入图片描述

2.配置共享

提示:我们可以将冗余的配置都注册到Nacos中,这样就不用每一个微服务都去配置一些相同的配置

2.1如何使用配置共享

(1)Nacos中添加共享的配置
(2)微服务模块拉取配置

2.2配置共享详细使用

(1)可以看到,mysql 的配置除了数据库的名称不一样,其它都是基本一致
mybatisPuls中的配置也是
在这里插入图片描述
(2)在Nacos中的操作

在这里插入图片描述
然后去添加配置
在这里插入图片描述
图片名称的解析

Data ID: 这里对应的是在idea配置文件中,引入共享配置文件的名称
Group:这里一般不需要动
描述:就是自己表达一下,此文件的配置
配置格式:idea中配置文件的后缀

配置内容:
spring:
  datasource:
    url: jdbc:mysql://${hm.db.host:192.168.42.100}:${hm.db.port:3306}/${hm.db.database}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: ${hm.db.un:root}
    password: ${hm.db.pw:123}
mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
  global-config:
    db-config:
      update-strategy: not_null
      id-type: auto

这里需要注意的是:
数据库的地址: ${hm.db.host:192.168.42.100},
${}这个格式代表占位符,
然后:192.168.42.100这个是默认值,hm.db.host可以通过idea中配置文件的值,可以覆盖默认值
username: ${hm.db.un:root}同上
password: ${hm.db.pw:123}同上

(3)向Nacos中拉去配置文件
传统Spring boot项目的配置文件初始化

仅用Nacos注册中心时:
配置文件只需填写nacos.discovery的地址,无需涉及nacos.config。
数据库等业务配置放在application.yml完全正常,因为Nacos不会覆盖它。

注意:
2. 为什么数据库配置放在application.yml不会报错?
(1) 核心原因:
注册中心和配置中心是两个独立的功能:
注册中心:负责服务实例的注册、发现和健康检查(比如告诉其他服务你的服务地址和端口)。
配置中心:负责从Nacos拉取配置(比如数据库地址、第三方密钥等)。
当你只用注册中心时:
Nacos不会主动去拉取配置文件,因此application.yml中的配置(如数据库信息)会被Spring Boot正常加载,不会被覆盖或干扰。
(2) 配置加载顺序
Spring Boot启动时:

先加载bootstrap.yml(如果存在),主要用于Spring Cloud的配置中心初始化(如Nacos配置中心)。
再加载application.yml,用于加载本地的业务配置(如数据库地址、端口号等)。
如果你没有使用配置中心,那么bootstrap.yml可以省略,直接在application.yml中配置注册中心即可,此时:

数据库配置放在application.yml完全没问题,因为Nacos不会干涉这部分配置。

在这里插入图片描述

(1)我们想要在微服务中拉去共享配置(这个阶段需要在 bootstrap 阶段初始化),将拉去的共享配置与本地的application.yaml配置合并,完成项目上下文的初始化。
但是读取Nacos配置是Springcloud上下文(ApplicationContext)初始化时处理的,发生在项目的引导阶段。然后才会初始化SpringBoot上下文,去读取application.yaml。
通俗一点说,就是在拉取共享文件配置项目启动时,会先读springcloud中Nacos的配置,因为Nacos读取是在SpringClod读取完成的,如果配置写在Spring boot 的配置文件当中,springcloud 项目启动时,就会接连不上数据库,但是Nacos是在Springboot中配置的,根本连接不到Nacos,怎么办?
(2)解决办法:
SpringCloud在初始化上下文的时候会先读取一个名为bootstrap.yaml(或者bootstrap.properties)的文件,如果我们将nacos地址配置到bootstrap.yaml中,那么在项目引导阶段就可以读取nacos中的配置了。

提示:spring cloud和spring boot配置文件的思维导图如下
在这里插入图片描述
在这里插入图片描述

Q1:为什么共享配置必须放在 bootstrap.yml?
ASpring Cloud 的配置中心(如 Nacos)需要在 bootstrap 阶段初始化,此时 application.yml 尚未加载,必须通过 bootstrap.yml 提供配置中心的地址(如 server-addr)。
Q2:数据库配置能否通过 Spring Cloud 配置中心动态更新?
A:可以。将数据库配置(如 spring.datasource.url)放在配置中心中,通过 @RefreshScope 或动态刷新机制,可以在运行时更新配置。
Q3:Spring Cloud 是否必须依赖 Spring BootA:是的。Spring Cloud 是基于 Spring Boot 的扩展,用于构建分布式系统,因此需要 Spring Boot 的基础功能(如自动配置)。

开始拉去共享的配置文件
(1)引入依赖

 <!--nacos配置管理-->
   <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
   </dependency>
 <!--读取bootstrap文件-->
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
   </dependency>

(2)创建bootstrap.yml文件

spring:
  application:
    name: cart-service # 服务名称
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.42.100 # nacos地址
      config:
        file-extension: yaml # 文件后缀名
        shared-configs: # 共享配置
          - dataId: shared-jdbc.yaml # 共享mybatis配置
          - dataId: shared-log.yaml # 共享日志配置
          - dataId: shared-swagger
注意,- 什么什么的配置代表shared-configs是一个集合

在这里插入图片描述

注意:
对于 Spring Boot 2.4.x 及以上版本,文件类型优先级如下:
.properties > .yml> .yaml 对于不同的版本也会有所不同

然后可以重启服务

3.配置热更新

3.1什么是配置热更新

(1)代码中有非常多的数据是固定,比如黑马商城购物车业务,购物车数量有一个上限,默认是10,,现在这里购物车是写死的固定值,我们应该将其配置在配置文件中,方便后期修改。
但现在的问题是,即便写在配置文件中,修改了配置还是需要重新打包、重启服务才能生效。能不能不用重启,直接生效呢?
在这里插入图片描述

这就要用到Nacos的配置热更新能力了,分为两步:
1Nacos中添加配置
2 在微服务读取配置

3.3配置热更新的使用

(1)在Nacos中添加配置
在这里插入图片描述
热更新配置文件名的定义:

项目启动后,项目会自动去nacos中寻找: cart-service-local.ymal的文件和是 cart-service.ymal的文件

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

[服务名]-[spring.active.profile].[后缀名] = cart-service.yaml
这里我们直接使用cart-service.yaml这个名称,则不管是dev还是local环境都可以共享该配置

(1)服务名:我们是购物车服务,所以是cart-service
(2)spring.active.profile:就是spring boot中的spring.active.profile,可以省略,则所有profile共享该配置,简单点说,就是idea中配置的dev,或者是local配置文件,依赖哪一个配置文件的变量
可以省略,因为都加载
(3)后缀名:例如yaml

(2) 在微服务读取配置
定义配置文件,与Nacos中配置相同

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 maxAmount;
}

在这里插入图片描述
然后依赖注入
在这里插入图片描述
在这里插入图片描述
最后项目启动后,可以通过修改naocs的信息,来改变购物车最大数量的值
在这里插入图片描述

4.动态网关

4.1动态网关的实现

(1):如果希望 Nacos 推送配置变更,可以使用 Nacos 动态监听配置接口来实现。
(2)实现的逻辑

(1)引入对应的依赖
(2)创建ConfigService,目的是连接到Nacos
(3)添加配置监听器,编写配置变更的通知处理逻辑

官方:https://nacos.io/zh-cn/docs/sdk.html
这里是官方给的监听配置

在这里插入图片描述
请求示例

String serverAddr = "{serverAddr}"; //这里是Nacos 的服务端口
String dataId = "{dataId}"; //这是在Nacos中定义的DataID
String group = "{group}";//这个是Group,基本不会变
Properties properties = new Properties(); //配置Nacos客户端连接参数,核心参数是serverAddr
properties.put("serverAddr", serverAddr);
//:Nacos客户端工厂类,通过createConfigService方法创建ConfigService实例,用于与Nacos服务器交互。
ConfigService configService = NacosFactory.createConfigService(properties);
/**
参数:
dataId:要获取的配置文件ID。
group:配置所属的分组。
timeout:超时时间(单位:毫秒),此处设置为5秒。
返回值:配置内容的字符串(如YAML/Properties格式)
**/
String content = configService.getConfig(dataId, group, 5000);
System.out.println(content); //打印
/*addListener方法:
监听指定dataId和group的配置变更。
当配置在Nacos控制台或API被更新时,触发receiveConfigInfo方法。
*/
configService.addListener(dataId, group, new Listener() {
/*
Listener接口:
receiveConfigInfo:配置变更时的回调方法,参数configInfo是更新后的配置内容。
getExecutor:返回自定义线程池(若返回null,则使用Nacos默认的线程池)
*/
	@Override
	public void receiveConfigInfo(String configInfo) {
		System.out.println("recieve1:" + configInfo);
	}
/*
线程池
*/
	@Override
	public Executor getExecutor() {
		return null;
	}
});

// 测试让主线程不退出,因为订阅配置是守护线程,主线程退出守护线程就会退出。 正式代码中无需下面代码
while (true) {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

4.2 额外知识的理解

(1)

由于我们采用了spring-cloud-starter-alibaba-nacos-config自动装配,因此ConfigService已经在com.alibaba.cloud.nacos.NacosConfigAutoConfiguration中自动创建好了:
String serverAddr = "{serverAddr}";
String dataId = "{dataId}";
String group = "{group}";
Properties properties = new Properties();
properties.put("serverAddr", serverAddr);
ConfigService configService = NacosFactory.createConfigService(properties);
这里的建立连接,我们不需要手动去实现,因为我们的是start,它已经完成了自动装配

源码解析: NacosConfigProperties这个类中已经加载好了服务地址
在这里插入图片描述
已经有了服务器的地址 private String serverAddr;
在这里插入图片描述
获取服务器的端口后,他又传给了 一个NacosConfigManager类
在这里插入图片描述
这个类中跟官网一样创建了ConfigService configService = NacosFactory.createConfigService(properties);这个类,所以NacosConfigManager这个类中已经有了这个对象.而NacosConfigManager已经被自动装配了,所以可以直接注入这个Bean
在这里插入图片描述
在这里插入图片描述
(3)实现动态网关路由
引入依赖,要在

<!--统一配置管理-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--加载bootstrap-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

然后在网关gateway的resources目录创建bootstrap.yaml文件,内容如下:

```java
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.42.100:8848 #8848可以不写,默认端口
      config:
        file-extension: yaml
        shared-configs:
          - dataId: shared-log.yaml # 共享日志配置

接着,修改gateway的resources目录下的application.yml,把之前的路由移除,最终内容如下:

server:
  port: 8080 # 端口
hm:
  jwt:
    location: classpath:hmall.jks # 秘钥地址
    alias: hmall # 秘钥别名
    password: hmall123 # 秘钥文件密码
    tokenTTL: 30m # 登录有效期
  auth:
    excludePaths: # 无需登录校验的路径
      - /search/**
      - /users/login
      - /items/**

然后,在gateway中定义配置监听器
在这里插入图片描述
代码如下

package com.hmall.gateway.route;

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 com.hmall.common.utils.CollUtils;
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;

@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicRouteLoader {

    private final RouteDefinitionWriter writer;
    private final NacosConfigManager nacosConfigManager;

    // 路由配置文件的id和分组
    private final String dataId = "gateway-routes.json";
    private final String group = "DEFAULT_GROUP";
    // 保存更新过的路由id
    private final Set<String> routeIds = new HashSet<>();

    @PostConstruct
    public void initRouteConfigListener() throws NacosException {
        // 1.注册监听器并首次拉取配置
        String configInfo = nacosConfigManager.getConfigService()
                .getConfigAndSignListener(dataId, group, 5000, new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }

                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        updateConfigInfo(configInfo);
                    }
                });
        // 2.首次启动时,更新一次配置
        updateConfigInfo(configInfo);
    }

    private void updateConfigInfo(String configInfo) {
        log.debug("监听到路由配置变更,{}", configInfo);
        // 1.反序列化
        List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
        // 2.更新前先清空旧路由
        // 2.1.清除旧路由
        for (String routeId : routeIds) {
            writer.delete(Mono.just(routeId)).subscribe();
        }
        routeIds.clear();
        // 2.2.判断是否有新的路由要更新
        if (CollUtils.isEmpty(routeDefinitions)) {
            // 无新路由配置,直接结束
            return;
        }
        // 3.更新路由
        routeDefinitions.forEach(routeDefinition -> {
            // 3.1.更新路由
            writer.save(Mono.just(routeDefinition)).subscribe();
            // 3.2.记录路由id,方便将来删除
            routeIds.add(routeDefinition.getId());
        });
    }
}

接下来,我们直接在Nacos控制台添加路由,路由文件名为gateway-routes.json,类型为json:
在这里插入图片描述
配置内容如下:

[
    {
        "id": "item",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}
        }],
        "filters": [],
        "uri": "lb://item-service"
    },
    {
        "id": "cart",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/carts/**"}
        }],
        "filters": [],
        "uri": "lb://cart-service"
    },
    {
        "id": "user",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/users/**", "_genkey_1":"/addresses/**"}
        }],
        "filters": [],
        "uri": "lb://user-service"
    },
    {
        "id": "trade",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/orders/**"}
        }],
        "filters": [],
        "uri": "lb://trade-service"
    },
    {
        "id": "pay",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/pay-orders/**"}
        }],
        "filters": [],
        "uri": "lb://pay-service"
    }
]

无需重启网关,稍等几秒钟后,再次访问刚才的地址:

小结

本节主要处理三个问题
(1)用Nacos共享配置文件
(2)配置热更新
(3)实现动态路由

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值