把一个单体项目拆分成很多的小的项目共同提供"服务",我们称之为"微服务"

微服务拆分以后碰到的各种问题都有对应的解决方案和微服务组件,而SpringCloud框架可以说是目前Java领域最全面的微服务组件的集合了。

框架: “框架”指的是一套用于构建微服务系统的技术组件和工具集合,帮助开发者解决服务拆分后带来的各种问题。

在这里插入图片描述

SpringCloud依托于SpringBoot的自动装配能力,大大降低了其项目搭建、组件使用的成本。对于没有自研微服务组件能力的中小型企业,使用SpringCloud全家桶来实现微服务开发可以说是最合适的选择了!
SpringCloud官方文档


一般微服务项目有两种不同的工程结构:

  • 完全解耦:每一个微服务都创建为一个独立的工程,甚至可以使用不同的开发语言来开发,项目完全解耦。(大型项目)

    • 优点:服务之间耦合度低
    • 缺点:每个项目都有自己的独立仓库,管理起来比较麻烦在这里插入图片描述
  • Maven聚合:整个项目为一个Project,然后每个微服务是其中的一个Module

    • 优点:项目代码集中,管理和运维方便(中小项目)
    • 缺点:服务之间耦合,编译时间较长
      在这里插入图片描述
      domain的意思的实体

思考: 原本是单体项目时可以相互通过 DI 注入,现在不同的业务拆分成了不同的单个微服务,怎么去调用别的微服务的controllerm呢?这是我们微服务的一个挑战,这里我们引入了一些新的技术

远程调用

一 .RestTemplate
Spring给我们提供的一个API,可以方便的实现Http请求的发送。
二.OpenFeign

直接说结论 : 利用RestTemplate实现了服务的远程调用。但是远程调用的代码太复杂了而且这种调用方式,与原本的本地方法调用差异太大,编程时的体验也不统一,一会儿远程调用,一会儿本地调用。
因此,我们必须想办法改变远程调用的开发模式,让远程调用像本地方法调用一样简单。-------而这就要用到OpenFeign组件了。

OpenFeign是更简单的解决方案

OpenFeign的使用

  1. 服务的pom.xml中引入OpenFeign的依赖和loadBalancer依赖:
  <!--openFeign-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
  </dependency>
  <!--负载均衡器-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  </dependency>
  1. 需要使用远程调用的微服务的启动类上添加注解,启动OpenFeign功能:
    在这里插入图片描述
  2. 编写OpenFeign客户端
    定义一个新的接口,编写Feign客户端:
    其中代码如下:
package com.hmall.cart.client;

import com.hmall.cart.domain.dto.ItemDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@FeignClient("item-service") //这里写被调用微服务名
public interface ItemClient {

    @GetMapping("/items")
    List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}

这里只需要声明接口,无需实现方法。接口中的几个关键信息:

  • @FeignClient(“item-service”) :声明服务名称
  • @GetMapping :声明请求方式
  • @GetMapping(“/items”) :声明请求路径
  • @RequestParam(“ids”) Collection ids :声明请求参数
  • List :返回值类型

有了上述信息,OpenFeign就可以利用动态代理帮我们实现这个方法,并且向http://item-service/items发送一个GET请求,携带ids为请求参数,并自动将返回值处理为List。
我们只需要直接调用这个方法,即可实现远程调用了。

使用时只需要 IOC注入接口,这里提醒Spring 现在不推荐使用@Autowired,推荐使用构造方法注入

-如果项目使用了Lombok可以在类上添加@RequiredArgsConstructor,在需要注入的bean前面加上final
eg 引文在这里插入图片描述
或者在需要注入的上面加上@Resource 注解 引文

Feign连接池

Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:

  • HttpURLConnection:默认实现,不支持连接池
  • Apache HttpClient :支持连接池
  • OKHttp:支持连接池

因此我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,我们使用OK Http.


使用方法:

  1. 引入依赖
    在cart-service的pom.xml中引入依赖:
<!--OK http 的依赖 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>
  1. 开启连接池
    在cart-service的application.yml配置文件中开启Feign的连接池功能:
feign:
  okhttp:
    enabled: true # 开启OKHttp功能

服务注册和发现

如果一个微服务项目我们进行了多实例部署(就是一类服务找来多个人去做同一份工作,那么一般多个实例会部署在多台机器上。)
在这里插入图片描述
此时,每个微服务的实例其IP或端口不同,别的微服务如果去调用这类的微服务,他应该怎么知道实例地址? 如果知道每一个那应该调用哪个? 如果对应的服务器宕机了 别的服务怎么知道? 如果有新部署的实例别的服务怎么更新 ?

为了解决上述问题,就必须引入注册中心的概念了,接下来我们就一起来分析下注册中心的原理。

注册中心原理

在微服务远程调用的过程中,包括两个角色:

  • 服务提供者:提供接口供其它微服务访问,比如item-service
  • 服务消费者:调用其它微服务提供的接口,比如cart-service

在大型微服务项目中,服务提供者的数量会非常多,为了管理这些服务就引入了注册中心的概念。注册中心、服务提供者、服务消费者三者间关系如下:在这里插入图片描述
流程如下:

  • 服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
  • 调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
  • 调用者自己对实例列表负载均衡,挑选一个实例
  • 调用者向该实例发起远程调用

当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?

  • 服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)
  • 当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除
  • 当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表
  • 当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表

部署

部署手则

服务注册

  1. 添加依赖
    在item-service的pom.xml中添加依赖:
<!--nacos 服务注册发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  1. 配置Nacos
    在item-service的application.yml中添加nacos地址配置:
spring:
  application:
    name: item-service # 服务名称
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848 # nacos地址
  1. 启动服务实例
    访问nacos控制台,可以发现服务注册成功在这里插入图片描述

服务发现

服务的消费者要去nacos订阅服务,这个过程就是服务发现,步骤如下:

  • 引入依赖
  • 配置Nacos地址
  • 发现并调用服务
  1. 引入依赖
    我们在微服务中的pom.xml中添加下面的依赖:
<!--nacos 服务注册发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

可以发现,这里Nacos的依赖和服务注册时一致,这个依赖中同时包含了服务注册和发现的功能。因为任何一个微服务都可以调用别人,也可以被别人调用,即可以是调用者,也可以是提供者。
因此,等一会儿cart-service启动,同样会注册到Nacos

  1. 配置Nacos地址
    在cart-service的application.yml中添加nacos地址配置:
spring:
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848
  1. 发现并调用服务
    因为我们已经知道了OpenFegin,所以没必要使用原生调用方式,而是使用OpenFeign的像调用方法一样的方式.

配置共享

添加配置

我们可以把微服务共享的配置抽取到Nacos中统一管理,这样就不需要每个微服务都重复配置了。分为两步:

  • 在Nacos中添加共享配置
  • 微服务拉取配置
    通过naocs控制面板进行配置快捷高效在这里插入图片描述
    配置进去的参数可以不写死例如:
  • 数据库ip:通过 h m . d b . h o s t : 192.168.150.101 配置了默认值为 192.168.150.101 ,同时允许通过 {hm.db.host:192.168.150.101}配置了默认值为192.168.150.101,同时允许通过 hm.db.host:192.168.150.101配置了默认值为192.168.150.101,同时允许通过{hm.db.host}来覆盖默认值
  • 数据库端口:通过 h m . d b . p o r t : 3306 配置了默认值为 3306 ,同时允许通过 {hm.db.port:3306}配置了默认值为3306,同时允许通过 hm.db.port:3306配置了默认值为3306,同时允许通过{hm.db.port}来覆盖默认值

冒号前面是默认值同时允许项目内的配置文件覆盖

比如抽取swagger配置,命名为shared-swagger.yaml

knife4j:
  enable: true
  openapi:
    title: ${hm.swagger.title:黑马商城接口文档}
    description: ${hm.swagger.description:黑马商城接口文档}
    email: ${hm.swagger.email:xxx@tttt.cn}
    concat: ${hm.swagger.concat:XX}
    url: https://www.itcast.cn
    version: v1.0.0
    group:
      default:
        group-name: default
        api-rule: package
        api-rule-resources:
          - ${hm.swagger.package}

里面没用写死(或者是需要覆盖的部分)需要在application.yaml里面配置

hm:
  swagger:
    title: 购物车服务接口文档
    package: com.hmall.cart.controller

拉取共享配置

接下来,我们要在微服务拉取共享配置。将拉取到的共享配置与本地的application.yaml配置合并,完成项目上下文的初始化。

不过,需要注意的是,读取Nacos配置是SpringCloud上下文(ApplicationContext)初始化时处理的,发生在项目的引导阶段。然后才会初始化SpringBoot上下文,去读取application.yaml。
也就是说引导阶段,application.yaml文件尚未读取,根本不知道nacos 地址,该如何去加载nacos中的配置文件呢?

SpringCloud在初始化上下文的时候会先读取一个名为bootstrap.yaml(或者bootstrap.properties)的文件,如果我们将nacos地址配置到bootstrap.yaml中,那么在项目引导阶段就可以读取nacos中的配置了在这里插入图片描述
因此,微服务整合Nacos配置管理的步骤如下:

  1. 引入依赖:
    在cart-service模块引入依赖:
  <!--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>
  1. 新建bootstrap.yaml
    在这里插入图片描述
    基本内容如下:
spring:
  application:
    name: cart-service # 服务名称
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.150.101 # nacos地址
      config:
        file-extension: yaml # 文件后缀名
        shared-configs: # 共享配置
          - dataId: shared-jdbc.yaml # 共享mybatis配置
          - dataId: shared-log.yaml # 共享日志配置
          - dataId: shared-swagger.yaml # 共享日志配置

然后剔除掉application.yaml里面重复的内容或者是注册到naocs里面的内容,使得原本的配置文件得到了精简
eg:

server:
  port: 8082
feign:
  okhttp:
    enabled: true # 开启OKHttp连接池支持
hm:
  swagger:
    title: 购物车服务接口文档
    package: com.hmall.cart.controller
  db:
    database: hm-cart

配置热更新

热更新是一种允许在不重新启动应用程序的情况下更新代码或资源的技术。

有很多的业务相关参数,将来可能会根据实际情况临时调整。例如购物车业务,购物车数量有一个上限,默认是10.
原本购物车是写死的固定值,我们应该将其配置在配置文件中,方便后期修改。
但现在的问题是,即便写在配置文件中,修改了配置还是需要重新打包、重启服务才能生效。能不能不用重启,直接生效呢?
这就要用到Nacos的配置热更新能力了,分为两步:

  • 在Nacos中添加配置
  • 在微服务读取配置
  1. 添加配置到Nacos
    首先,我们在nacos中添加一个配置文件,将购物车的上限数量添加到配置中:在这里插入图片描述
    注意文件的dataId格式:[服务名]-[spring.active.profile].[后缀名]
    (这样微服务会自动拉取这类配置,无需在bootstrap中配置)

文件名称由三部分组成:

  • 服务名:我们是购物车服务,所以是cart-service
  • spring.active.profile:就是spring boot中的spring.active.profile,可以省略,则所有profile共享该配置
  • 后缀名:例如yaml

直接使用cart-service.yaml这个名称,则不管是dev还是local环境都可以共享该配置。

  1. 在微服务读取配置
    cart-service中新建一个属性读取类:
    在这里插入图片描述
@Data
@Component
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {
    private Integer maxAmount;
}

接着,在业务中使用该属性加载类:
在这里插入图片描述

动态路由

DynamicRouteLoader
DynamicRouteLoader 是一个 Spring Cloud Gateway 的组件,用于从 Nacos 动态加载和更新路由配置。它通过监听 Nacos 配置中心的变化,实时更新网关的路由信息。

DynamicRouteLoader 类:
负责从 Nacos 动态加载路由配置,并在配置发生变化时更新网关的路由表。
使用 NacosConfigManager 从 Nacos 获取配置数据,并监听配置变化。
依赖注入:
NacosConfigManager:用于与 Nacos 配置中心交互,获取配置数据。
RouteDefinitionWriter:用于更新 Spring Cloud Gateway 的路由定义。
routeIds:用于存储当前已加载的路由 ID,方便后续更新时删除旧路由。
初始化方法 initRouteConfig:
在项目启动时调用(通过 @PostConstruct 注解)。
从 Nacos 获取初始的路由配置(dataId 为 gateway-routes.json,group 为 DEFAULT_GROUP)。
添加一个监听器,当 Nacos 中的配置发生变化时,自动调用 updateConfigInfo 方法更新路由。
配置更新方法 updateConfigInfo:
解析从 Nacos 获取的配置信息(JSON 格式),并将其转换为 RouteDefinition 对象列表。
删除旧的路由配置(通过 routeIds 中存储的路由 ID)。
清空 routeIds,然后遍历新的路由配置,将其保存到 RouteDefinitionWriter 中,并更新 routeIds。

package com.hmall.gateway.routers;

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;

@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicRouteLoader {

    private final NacosConfigManager nacosConfigManager;

    private final RouteDefinitionWriter writer;

    private final String dataId = "gateway-routes.json";

    private final String group = "DEFAULT_GROUP";

    private final Set<String> routeIds = new HashSet<>();

    @PostConstruct
    public void initRouteConfig() throws NacosException {
        // 项目启动时, 拉取配置信息, 并添加监听器
        String configInfo = nacosConfigManager.getConfigService()
                .getConfigAndSignListener(dataId, group, 5000, new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }

                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        updateConfigInfo(configInfo);
                    }
                });
        // 读取到配置,更新路由表
        updateConfigInfo(configInfo);
    }

    public void updateConfigInfo(String configInfo){
        log.debug("监听获取路由配置信息:{}", configInfo );
        // 解析配置信息, 转为RouteDefinition
        List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
        // 删除旧的路由表信息
        routeIds.forEach(e->{
            writer.delete(Mono.just(e)).subscribe();
        });
        // 清空id 表
        routeIds.clear();
        // 遍历路由信息
        for (RouteDefinition routeDefinition: routeDefinitions) {
            // 更新路由表
            writer.save(Mono.just(routeDefinition)).subscribe();
            routeIds.add(routeDefinition.getId());
        }
    }
}


NacosConfigManager
NacosConfigManager 是 Spring Cloud Alibaba Nacos 提供的一个核心类,用于管理与 Nacos 配置中心的交互。它封装了 Nacos 配置客户端的操作,简化了从 Nacos 获取配置、监听配置变化等功能的实现。
NacosConfigManager 的作用

  • 配置管理:
    • 提供从 Nacos 配置中心获取配置的能力。
    • 支持动态监听配置变化,实时更新本地配置。
  • 简化操作:
    • 封装了 Nacos 客户端的底层操作,开发者无需直接操作 Nacos 的原生 API。
      与 Spring Cloud 集成:
    • 作为 Spring Cloud Alibaba 的一部分,与 Spring Cloud 的配置管理机制无缝集成。

NacosConfigManager 的核心方法

获取配置

String getConfig(String dataId, String group, long timeoutMs) throws NacosException;

  • 作用:从 Nacos 配置中心获取指定 dataId 和 group 的配置内容。
  • 参数:
    dataId:配置的唯一标识符(如 gateway-routes.json)。
    group:配置的分组(如 DEFAULT_GROUP)。
    timeoutMs:获取配置的超时时间(毫秒)。
    返回值:配置内容的字符串形式。

添加监听器

void addListener(String dataId, String group, Listener listener) throws NacosException;

  • 作用:为指定 dataId 和 group 的配置添加监听器,当配置发生变化时触发回调。
  • 参数:
    dataId:配置的唯一标识符。
    group:配置的分组。
    listener:监听器接口,实现 receiveConfigInfo 方法以处理配置变化。
    获取 ConfigService
ConfigService getConfigService();

  • 作用:获取底层的 ConfigService 对象,用于直接操作 Nacos 配置客户端。
  • 返回值:ConfigService 实例。

RouteDefinitionWriter
RouteDefinitionWriter 是 Spring Cloud Gateway 提供的一个核心接口,用于动态管理路由定义(RouteDefinition)。它允许在运行时添加、删除或更新路由,从而实现动态路由的功能。

RouteDefinitionWriter 的作用

  • 动态路由管理
    • 提供添加、删除和更新路由的能力。
    • 支持在运行时修改路由配置,无需重启网关服务。
  • 与 Spring Cloud Gateway 集成
    • 作为 Spring Cloud Gateway 的一部分,与网关的路由机制无缝集成。
    • 响应式编程支持
      • 基于 Reactor 的响应式编程模型,所有操作返回 Mono<Void>

RouteDefinitionWriter 的核心方法
保存路由

Mono<Void> save(Mono<outeDefinition> route);

  • 作用:保存一个路由定义。
  • 参数:
    • route:RouteDefinition 对象,表示一个路由规则。
    • 返回值:Mono,表示操作完成后的信号。

删除路由

Mono<Void> delete(Mono<String> routeId);

  • 作用:根据路由 ID 删除一个路由定义。
  • 参数:
    • routeId:路由的唯一标识符。
    • 返回值:Mono<Void>,表示操作完成后的信号。

路由配置
在 nacos 中添加 gateway-routes.json 配置文件信息实现动态路由

配置项说明
id路由的唯一标识符,值为 item
uri路由的目标服务地址,lb://item-service 表示通过负载均衡访问 item-service
predicates路由的匹配条件。
name使用 Path 断言,表示根据请求路径匹配。
args路径匹配规则。
_genkey_0/items/,匹配以 /items/ 开头的请求。
_genkey_1/search/,匹配以 /search/ 开头的请求。
filters路由过滤器列表,当前为空。
[
    {
		"id": "item",
		"uri": "lb://item-service",
		"predicates": [{
			"name": "Path",
			"args": {
				"_genkey_0": "/items/**",
				"_genkey_1": "/search/**"
			}
		}],
		"filters": []
	},
    {
		"id": "user",
		"uri": "lb://user-service",
		"predicates": [{
			"name": "Path",
			"args": {
				"_genkey_0": "/users/**",
				"_genkey_1": "/addresses/**"
			}
		}],
		"filters": []
	},
    {
		"id": "cart",
		"uri": "lb://cart-service",
		"predicates": [{
			"name": "Path",
			"args": {
				"_genkey_0": "/carts/**"
			}
		}],
		"filters": []
	}
]

————————————————
版权声明:动态路由部分为CSDN博主「心之语歌」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ling_zhi_xin/article/details/145410254

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐