目录

1. 认识微服务

1.1 单体架构

1.2 集群、分布式、微服务和分布式架构

2. Spring Cloud

2.1 Spring Cloud 实现方案

2.1.1 Spring Cloud Netflix

2.1.2 Spring Cloud Alibaba

2.2 拆分原则

2.3 REST

2.3.1 REST 概念

2.3.2 RESTful

2.3.3 RestTemplate

RestTemplate特性

2.3.3 代码实现

2.3.3.1 配置 RestTemplate

2.3.3.2 订单服务业务逻辑改造

2.4 Eureka

2.4.1 注册中心核心概念

2.4.2 CAP

2.4.3 常见注册中心

2.4.4 Eureka 代码实现

2.4.4.1 添加依赖

2.4.4.2 配置 yml

2.4.4.3 编写服务器代码

2.4.4.4 启动 Eureka

2.5 Load Balance

2.5.1 负载均衡定义

2.5.2 LoadBalancer 实现

2.5.2.1 注解实现

2.5.2.2 代码实现

2.5.3 负载均衡策略

2.5.3.1 非自定义负载均衡策略

2.5.3.2 自定义负载均衡策略

2.5.4 LoadBalancer 原理

2.6 Nacos

2.6.1 Nacos 简介

2.6.2 服务注册/服务发现

2.6.2.1 引入依赖

2.6.2.2 配置子工程 yml

2.6.2.3 配置服务器代码

2.6.2.4 启动 Nacos 

2.6.3 Nacos 负载均衡

2.6.3.1 服务下线

2.6.3.2 权重配置

2.6.3.3 开启 Nacos 负载均衡策略

2.6.3.4 同集群优先访问

2.6.3.4.1 配置集群名称

2.6.3.5 Nacos 健康状态

2.6.3.5.1 两种健康检查机制

2.6.3.5.2 Nacos 服务实例类型

2.6.3.6 Nacos 环境隔离

2.6.3.6.1 创建 Namespace

2.6.3.6.2 配置 Namespace

2.6.3.7 Nacos 配置中心

2.6.3.7.1 添加配置

2.6.3.7.2 获取配置

2.6.3.7.2.1 引入 Nacos Config 依赖

2.6.3.7.2.2 配置 yml

2.6.3.7.2.3 读取配置

2.6.3.7.2.4 设置 namespace

2.6.3.7.3 Data ID

2.6.4 Nacos 和 Eureka 启动过程对比

2.7 OpenFeign

2.7.1 OpenFeign 概念

2.7.2 OpenFeign 代码实现

2.7.2.1 Feign 注解

2.7.2.1.1 引入依赖

2.7.2.1.2 添加注解

2.7.2.1.3 编写 OpenFeign 客户端

2.7.2.1.4 流程

2.7.2.2 Feign 继承

2.7.2.2.1 引入依赖

2.7.2.2.2 编写接口

2.7.2.2.3 服务提供方

2.7.2.2.4 服务消费方

2.7.2.2.5 流程

2.7.2.3 Feign 抽取

2.7.2.3.1 引入依赖

2.7.2.3.2 编写接口

2.7.2.3.3 服务消费方

2.7.2.3.4 服务提供方

2.7.2.3.5 流程

2.7.3 Feign 注解、Feign 继承和 Feign 抽取的URL与流程总结

2.7.4 Feign 继承和抽取总结

2.8 Gateway

2.8.1 API 网关

2.8.2 网关核心功能

2.8.3 常见网关实现

2.8.4 Gateway 代码实现

2.8.4.1 引入依赖

2.8.4.2 编写 Gateway 启动类

2.8.4.3 配置 yml

2.8.4.4 Route Predicate Factories

2.8.4.4.1 Predicate

2.8.4.4.2 Route Predicate Factories

2.8.4.4.2.1 基本工作原理

2.8.4.4.2.2 具体示例

2.8.4.4.2.3 Route Predicate Factory

2.8.4.5 GateyaFilter

2.8.4.5.1 Filter 基本类型

2.8.4.5.2 GateyaFilter 的配置与使用

2.8.4.6 Default Filters

2.8.4.7 GlobalFilter

2.8.4.7.1 GlobalFilter 概念

2.8.4.7.2 GlobalFilter 代码实现

2.8.4.7.2.1 添加依赖

2.8.4.7.2.2 配置 yml

2.8.4.7.2.3 运行结果

2.8.4.8 过滤器执行顺序

2.8.4.9 自定义 GatewayFilter

2.8.4.9.1 定义配置类

2.8.4.9.2 定义 GatewayFilter

2.8.4.9.3 配置过滤器

2.8.4.10 自定义 GlobalFilter

2.9 流量控制算法

2.9.1 固定窗口算法

2.9.2 滑动窗口算法

2.9.3 漏桶算法

2.9.4 令牌桶算法


1. 认识微服务

1.1 单体架构

a)功能聚合:所有业务模块(用户管理、商品管理、订单系统等)封装在单一代码库中

b)部署单元单一化:整个系统部署在单个服务器或容器中

c)开发耦合性:各功能模块通过进程内调用通信(如Java方法调用),而非跨服务通信

典型应用场景

场景类型 说明 案例
初创企业 快速验证业务模型 天使轮电商项目
低频内部系统 OA/HR等非核心系统 企业考勤系统
确定性业务领域 业务规则稳定的传统领域 银行柜面业务系统

1.2 集群、分布式、微服务和分布式架构

    • 如果你的系统是分布式集群(如基于Raft的ETCD或Redis集群),Raft算法已覆盖领导者选举,无需哨兵。
    • 如果你的系统是简单架构(如单主从),哨兵机制是必要的,以提供自动故障转移和稳定性。
    • 在返回成功响应给客户端之前,必须等待集群中半数以上的节点(即大多数节点)响应并确认

    分布式:指将一个系统拆分为多个子系统(或服务),每个子系统部署在不同的服务器上,多个服务器协同合作完成一个特定任务。每个子系统专注于特定功能,整体系统通过通信机制(如网络协议)集成。

    • 核心特点
      • 每个节点功能不同:服务器负责不同的业务模块,一个节点失效可能导致其负责的业务不可访问。
      • 目标:实现模块化,提高系统灵活性和可维护性,通过分工协作处理复杂任务。

      集群:指将一个完整的系统部署到多个服务器上,每个服务器(称为节点)都能提供系统的所有服务。多个节点通过负载均衡机制调度任务,确保系统的高可用性和可扩展性。

      • 核心特点
        • 每个节点功能相同:所有服务器执行相同的任务,可以相互替代。如果一个节点失效,负载均衡会将请求转发到其他可用节点,系统整体不受影响。
        • 目标:提高系统性能和可靠性,通过并行处理分散负载。

      微服务:微服务架构是分布式架构的一种拓展,它将系统拆分为更细粒度的服务,每个服务只负责一个单一功能(微小到不能再拆),服务独立部署和运行,并通过轻量级协议(如RESTRPC)通信。

      • 核心特点
        • 粒度更细:服务专注于单一业务能力,例如订单管理或用户认证,避免重复代码。
        • 独立性强:每个服务可以独立开发、部署和扩展,故障隔离性好(一个服务问题不影响整体)。
        • 目标:提升开发效率和系统的可扩展性,强调专业化和精细分工。

      集群、分布式和微服务的区别与联系

      区别:

      • 概念层面

        • 集群:多个计算机做同样的事(同质节点),强调冗余和负载均衡。
        • 分布式:多个计算机做不同的事(异构节点),强调业务拆分和协作。
        • 微服务:一种特殊分布式架构,服务粒度更小(微小到单一功能),强调专业化和独立部署。
      • 功能层面

        • 集群:每个节点功能相同且可替代,节点失效不影响整体服务。
        • 分布式:节点功能不同,一个节点失效可能导致部分业务中断。
        • 微服务:每个服务高度专业化,功能单一,失效影响仅限于该服务。
      • 实践层面

        • 集群常用于提高单点服务的可靠性(如Web服务器集群)。
        • 分布式用于处理复杂系统(如电商平台拆分为订单、支付等子系统)。
        • 微服务是分布式的优化版本,更注重细粒度拆分和服务治理。

      联系:

      • 协作关系

        • 集群和分布式常配合使用:例如,分布式系统中的某个子系统(如支付服务)可能由一个集群实现(多个节点提供相同支付服务),以提高其可用性。
        • 微服务架构是分布式的拓展:微服务架构通常基于分布式原则,但拆分更精细。在实践中,微服务系统往往依赖集群机制来部署每个微服务(如多个节点运行相同的用户认证服务)。
        • 整体架构:分布式架构是基础,微服务是其高级形式;集群是构建块,用于增强分布式的可靠性。实际应用中,三者常融合,统称为“分布式架构”

      2. Spring Cloud

      Spring Cloud 是一个用于构建分布式微服务架构的一站式解决方案。它通过整合多种开源技术,提供了一套工具集来简化微服务开发中的常见问题处理。根据官网介绍,Spring Cloud 的目标是让开发人员能够快速构建和部署分布式服务,包括配置管理、服务发现、熔断、智能路由等功能。这些工具可以在任何分布式环境中高效工作,帮助开发者实现高可用、可扩展的微服务系统。

      Spring Cloud 不是由 Spring 团队从头研发的框架,而是基于 Spring Cloud 规范,整合了多个优秀的开源框架。它利用 Spring Boot 的简化配置风格,将这些组件封装起来,屏蔽了底层复杂实现,为开发者提供了开箱即用的体验。主要功能包括:

      • 分布式版本配置:管理不同环境下的配置。
      • 服务注册和发现:自动注册和发现服务实例。
      • 路由:智能路由请求到目标服务。
      • 服务调用:简化服务间的通信。
      • 负载均衡:分配请求负载,提高系统稳定性。
      • 断路器:防止故障扩散,提升系统韧性。
      • 分布式消息:处理异步消息传递。

      这些功能通过 Spring Cloud 的“大管家”角色,统一管理微服务组件,使开发更高效。

      2.1 Spring Cloud 实现方案

      Spring Cloud 本身是一个规范,具体实现由不同公司提供。最主流的实现方案包括 Spring Cloud Netflix 和 Spring Cloud Alibaba,它们在组件和技术上各有侧重。

      2.1.1 Spring Cloud Netflix

      这是 Netflix OSS(开源软件)在 Spring Cloud 规范下的实现,曾长期作为官方默认方案。然而,Netflix 在 2018 年前后宣布核心组件(如 Hystrix、Ribbon、Zuul)进入维护状态,不再积极更新。核心组件包括:

      • Eureka:服务注册和发现。
      • Zuul:服务网关。
      • Ribbon:负载均衡。
      • Feign:服务调用。
      • Hystrix:断路器(提供熔断和限流功能)。
      • Hystrix Dashboard:监控面板。

      2.1.2 Spring Cloud Alibaba

      这是阿里巴巴在 Spring Cloud 规范下的实现,被视为第二代主流方案。它吸收了 Netflix 的优点,并进行了性能优化,目前正处于高速发展阶段。核心组件包括:

      • Nacos:服务注册、发现和配置中心(比 Eureka 更强大)。
      • Sentinel:熔断和限流(替代 Hystrix)。
      • Seata:分布式事务解决方案。
      • Dubbo:高性能服务调用(替代 Feign)。
      • 支持 Spring Cloud Gateway:作为服务网关

      2.2 拆分原则

      a)单一职责原则

      单一职责原则强调每个微服务应专注于一个特定功能或业务领域,避免职责混杂。这类似于面向对象设计中的类设计:一个服务只做一件事,且只有一个变更原因。这样能提高可维护性和可扩展性。

      b)服务自治

      服务自治要求每个微服务能独立开发、测试、构建、部署和运行,包括拥有自己的存储和配置。这确保服务之间解耦,提升系统的弹性和可维护性。

      c)单项依赖

      单向依赖原则要求服务间依赖关系为单向(如A依赖B),避免循环依赖(A→B→C→A)或双向依赖(A↔B)。如果无法避免,可使用消息队列等异步机制解耦。

      d)合适性

      微服务架构没有“万能”标准,合适性优于盲目追求业界领先方案。许多优秀架构是随着业务增长逐步演进的,而非一蹴而就,避免过度设。

      e)横向拆分

      横向拆分基于业务功能或领域驱动设计(Domain-Driven Design, DDD),将系统分解为多个独立的服务,每个服务负责一个具体的业务能力。这种拆分强调业务边界,确保服务间耦合度低。

      f)纵向拆分

      纵向拆分基于技术栈或系统层次,将系统按功能层(如表示层、业务逻辑层、数据层)拆分为独立服务。这种拆分更注重技术解耦,而非业务功能。

      2.3 REST

      2.3.1 REST 概念

      REST(Representational State Transfer,表现层状态转移)是一种软件架构风格,它描述了在网络中客户端(Client)和服务器(Server)的交互方式,核心思想是通过资源的状态转移来实现高效通信。以下是REST的核心概念:

      • 资源:网络上的所有实体(如文本、图片、视频或数据库记录)都被抽象为资源,每个资源有一个唯一的标识符(URI)。
      • 表现层:资源的表现形式,如JSON、XML、HTML或二进制格式,客户端和服务器通过协商选择合适的表现层。
      • 状态转移:客户端与服务器的交互过程仅通过HTTP协议实现,涉及数据的增删改查操作,导致资源状态的变化。

      2.3.2 RESTful

      RESTful是满足REST架构风格的形容词,指代那些遵循REST原则的程序或接口。RESTful API(REST风格的网络接口)是REST的具体应用,具有以下主要特征:

      • 资源导向:API以资源为中心,每个资源通过URI标识,例如用户数据可能以JSON格式表现。
      • 统一接口:操作资源时严格使用HTTP方法:
        • GET:获取资源(例如 GET /blog/{blogId}查询博客)。
        • POST:创建资源。
        • PUT:更新资源。
        • DELETE:删除资源(例如 DELETE /blog/{blogId}删除博客)。
      • 无状态性:每个请求包含所有必要信息,服务器不保存客户端状态,提高可扩展性。
      • 可缓存性:响应可被缓存,优化性能。

      RESTful API基于HTTP协议,工具如 Spring 的 RestTemplate 简化了HTTP调用,自动处理连接管理。

      2.3.3 RestTemplate

      在查询订单信息时,需根据订单中的产品ID获取产品详细信息,并将二者合并返回。也就是说订单服务需要从产品服务中通过 url 来发起 HTTP 请求,可以通过 Spring 框架的 RestTemplate 实现服务器间的HTTP调用。

      RestTemplate特性
      特性 说明
      同步调用 阻塞式等待HTTP响应
      RESTful风格强制 严格遵循HTTP方法语义
      连接自动管理 自动处理HTTP连接建立/关闭
      简化参数传递 支持URL模板变量(如{id}

      2.3.3 代码实现

      2.3.3.1 配置 RestTemplate
      @Configuration
      public class BeanConfig {
          @Bean
          public RestTemplate restTemplate() {
              RestTemplate restTemplate = new RestTemplate();
              return restTemplate;
          }
      }
      

      2.3.3.2 订单服务业务逻辑改造
      @Service
      public class OrderService {
          @Autowired
          private OrderMapper orderMapper;
          @Autowired
          private RestTemplate restTemplate;
          public OrderInfo selectOrderById(Integer id) {
              OrderInfo orderInfo = orderMapper.selectOrderById(id);
              // url 用来调用 product 服务器
              String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();
              ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
      
              orderInfo.setProductInfo(productInfo);
              return orderInfo;
          }
      }

      关键点说明

      a)URL动态拼接
      通过订单中的 productId 动态构造产品服务 API 地址:String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();

      b)HTTP请求方法
      restTemplate.getForObject() 执行GET请求,将响应自动反序列化为 ProductInfo 对象

      c)数据融合
      通过 orderInfo.setProductInfo(productInfo); 将产品对象附加到订单对象

      Maven中的DependencyManagement和Dependencies区别

      在Maven项目管理中,dependencies 和 dependencyManagement 是用于处理项目依赖的关键机制。

      Dependencies(直接依赖)

      • 作用:直接在项目中添加依赖的JAR包。当你在项目的 pom.xml 文件中定义依赖时,这些依赖会被立即下载并包含在项目的构建路径中。
      • 继承特性:如果项目是父项目(packaging类型为pom),子项目会自动继承这些依赖。子项目无需显式声明即可使用父项目中定义的依赖。
      • 版本控制:依赖的版本在定义时直接指定,子项目无法覆盖版本(除非重新定义整个依赖)。
      • 适用场景:适合为单个项目或共享依赖添加具体依赖项,尤其当所有子模块都需要相同依赖时。

      DependencyManagement(依赖管理)

      • 作用:仅声明依赖的版本和范围,不直接添加JAR包。它提供了一种集中管理依赖版本的机制,确保依赖版本的一致性。
      • 继承特性:依赖不会被自动添加到项目中。子项目需要显式声明依赖(在 dependencies中),但可以省略版本号。
      • 版本控制
        • 如果子项目声明依赖时未指定版本,则从父项目的 dependencyManagement 继承版本。
        • 如果子项目指定了版本,则使用子项目的版本(覆盖父项目定义)。
      • 父项目要求:父项目必须将打包方式(packaging)设置为 pom,以表明它是管理项目而非可执行JAR。
      • 适用场景:适合多模块项目中统一管理依赖版本,避免版本冲突。

      主要区别总结

      特性 Dependencies DependencyManagement
      依赖添加 直接添加JAR包到项目 仅声明依赖版本,不添加JAR包
      继承行为 子项目自动继承依赖 子项目需显式声明依赖才能使用
      版本管理 版本在定义时固定,子项目无法覆盖 子项目可继承版本或指定覆盖版本
      打包要求 无特殊要求 父项目必须设置为<packaging>pom</packaging>
      典型用途 为项目添加具体依赖 在多模块项目中统一版本控制

      2.4 Eureka

      在微服务架构中,硬编码服务生成 URL(如 restTemplate 提到的 String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId(); ) 会导致维护困难。所以我们需要引入注册中心。

      2.4.1 注册中心核心概念

      • 服务提供者(Server):提供接口的服务实例。启动时向注册中心注册自身信息(如IP、端口),并定期发送心跳以维持存活状态。如果心跳超时,注册中心会自动注销该实例。
      • 服务消费者(Client):调用其他服务的实例。在需要调用服务时,先查询注册中心获取可用服务列表,然后基于列表发起请求。
      • 注册中心(Registry):维护服务注册信息,动态更新实例状态,并提供查询接口。

      2.4.2 CAP

      在分布式系统中,注册中心的设计受 CAP 理论约束:

      • 一致性(C):所有节点数据强一致。
      • 可用性(A):每个请求都能获得响应(即使数据可能过时)。
      • 分区容错性(P):系统在发生网络分区时仍能提供服务

      CAP 理论指出:分布式系统中最多同时满足两个特性,由于网络分区不可避免,所以注册中心只有CA、CP两种架构:

      • CP架构:优先保证数据一致性(C),但可能牺牲可用性(A)。例如,在网络异常时,拒绝请求以维护数据一致。
      • AP架构:优先保证可用性(A),但可能返回不一致数据。例如,在网络异常时,返回缓存数据(即使旧版本),确保服务不中断。

      注册中心的选择取决于业务需求:

      • 高可用场景(如电商大促):倾向AP架构,保证服务连续。
      • 数据敏感场景(如金融交易):倾向CP架构,确保数据准确。

      2.4.3 常见注册中心

      基于 CAP 理论,主流注册中心的设计差异如下:

      注册中心 CAP模型 特点 适用场景
      Zookeeper CP 强一致性,数据准确;但网络分区时可能拒绝服务。 数据一致性要求高的系统
      Eureka AP 高可用,容忍数据不一致;心跳机制简单,适合弹性云环境。 高并发、高可用应用
      Nacos AP/CP 默认AP,可配置为CP;支持服务发现和配置管理,灵活性高。 混合云和微服务生态
      • Eureka优势:作为Spring Cloud默认组件,易于集成,心跳机制(如30秒超时)确保实例状态实时更新,适合需要快速故障转移的场景。
      • Nacos优势:由Spring Cloud Alibaba支持,提供动态配置和 DNS 功能,社区活跃,适合复杂微服务架构。

      2.4.4 Eureka 代码实现

      2.4.4.1 添加依赖
              // Eureka 启动类需要的依赖
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
              </dependency>
      
              // 双端服务器需要的依赖
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
              </dependency>

      2.4.4.2 配置 yml
      // Eureka 启动类需要配置的 yml
      eureka:
        instance:
          hostname: localhost
        client:
          fetch-registry: false # 表示是否从Eureka Server获取注册信息,默认为true.因为这是一个单点的Eureka Server,不需要同步其他的Eureka Server节点的数据,这里设置为false
          register-with-eureka: false # 表示是否将自己注册到Eureka Server,默认为true.由于当前应用就是Eureka Server,故而设置为false.
          service-url:
            # 设置与Eureka Server的地址,查询服务和注册服务都需要依赖这个地址
            defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      
      
      //服务器需要配置的 yml
      #Eureka Client
      eureka:
        client:
          service-url:
            defaultZone: http://127.0.0.1:10010/eureka/
      

      2.4.4.3 编写服务器代码
      @Slf4j
      @Service
      public class OrderService {
          @Autowired
          private OrderMapper orderMapper;
          @Autowired
          private RestTemplate restTemplate;
      
          public OrderInfo selectOrderById(Integer id) {
              OrderInfo orderInfo = orderMapper.selectOrderById(id);
              String url = "http://product-service/product/" + orderInfo.getProductId(); // 替换 IP:端口 为服务名称
              ProductInfo product = restTemplate.getForObject(url, ProductInfo.class);
              orderInfo.setProductInfo(product);
              return orderInfo;
          }
      
          }
      

      代码解析:

      • 核心逻辑:selectOrderById 方法通过订单ID查询订单信息,然后使用轮询(round-robin)策略从服务发现中获取产品服务实例,并调用其API获取产品详情。

      DiscoveryClient

      Spring Cloud 提供的服务发现接口,用于从注册中心(如 Eureka、Consul)动态获取服务实例信息。

      List<ServiceInstance> getInstances(String serviceId)
      
      • 参数:serviceId 为注册中心中的服务名
      • 返回值:包含所有可用实例的列表(ServiceInstance 对象集合,也就是 Eureka 中所有注册的服务器)

      List<ServiceInstance> instances 

      存储 discoveryClient.getInstances("服务器ID") 返回的服务实例集合,每个 ServiceInstance 包含多个关键属性

      方法 说明 示例值
      getUri() 实例完整访问路径 http://192.168.1.10:8080
      getHost() 实例IP地址 192.168.1.10
      getPort() 服务端口 8080
      getServiceId() 注册的服务名 "product-service"
      getMetadata() 元数据键值对 {"zone":"east-1"}

      示例用法:
       

      // 获取第一个实例的URI
      String baseUrl = instances.get(0).getUri().toString(); 
      // 获取实例的方法
      String getUrl = getUri + "/product/" + orderInfo.getProductId();
      

      AtomicInteger 

      AtomicInteger 提供原子操作的计数器,解决多线程环境下的并发问题。

      ​​​​​​核心方法

      方法 说明 示例
      getAndIncrement() 获取当前值后+1(线程安全) count.getAndIncrement()
      get() 获取当前值 count.get()
      set(int newValue) 设置新值 count.set(0)

      2.4.4.4 启动 Eureka

      修改完服务器后,在远程调用时,就可以通过 eureka 拉取其他服务器的信息,实现服务器之间的调用。

      2.5 Load Balance

      在分布式系统中,服务消费者 通过服务发现(如 Eureka)获取服务提供者的实例列表。如果服务提供者有多个实例(例如,启动在不同端口的多个服务),但消费者代码仅选择第一个实例处理所有请求,会导致流量不均衡。

      2.5.1 负载均衡定义

      负载均衡(Load Balance)是分布式系统的核心组件,用于在高并发、高可用场景下,将请求流量合理分配到多个资源(如服务器实例)。核心目标:

      • 提高吞吐量:通过并行处理请求,减少单点瓶颈。
      • 增强可用性:当某个实例故障时,流量可自动转移到健康实例。
      • 优化资源利用:根据实例配置(如 CPU、内存)动态分配负载,避免资源浪费。

      负载均衡大致可以分为两种实现,服务端负载均衡和客户端负载均衡。

      服务端负载均衡

      • 原理:请求先到达一个中央负载均衡器(如 Nginx),该设备根据算法(如轮询、加权轮询)选择一个后端服务器转发请求。
      • 优势:集中管理,易于配置和监控。
      • 缺点:单点故障风险;增加网络延迟。
      • 适用场景:Web 应用、API 网关等。

      客户端负载均衡

      • 原理:负载均衡逻辑集成在客户端库中。客户端从注册中心(如 Eureka)获取服务列表,并在发送请求前本地执行算法选择实例。
      • 优势:减少单点依赖;降低延迟;更灵活(可自定义算法)。
      • 缺点:客户端实现复杂;需维护服务发现机制。
      • 适用场景:微服务架构(如 Spring Cloud 应用)。
      • 常见工具
        • Ribbon:早期 Spring Cloud 默认实现,支持多种算法(如轮询、随机)。
        • Spring Cloud LoadBalancer:官方维护的替代方案,更轻量且易扩展。

      2.5.2 LoadBalancer 实现

      2.5.2.1 注解实现

      Spring Cloud LoadBalancer 是 Spring Cloud 生态中用于实现客户端负载均衡的核心组件,自 2020.0.1 版本起替代了 Ribbon。它通过拦截请求、动态获取服务列表并应用负载均衡策略,简化了微服务间的调用。

      针对于客户端实现负载均衡非常简单,我们可以使用 Spring Cloud LoadBalancer 的集成来实现。

      a)为 RestTemplate 添加注解:在定义 RestTemplate Bean 时使用 @LoadBalanced 注解,启用负载均衡,

      @Configuration
      public class BeanConfig {
          @LoadBalanced
          @Bean
          public RestTemplate restTemplate() {
              RestTemplate restTemplate = new RestTemplate();
              return restTemplate;
      
          }
      }

      b)使用服务名称代替 IP 和端口:在请求 URL 中,将硬编码的 IP 和端口替换为服务注册中心 (如Eureka) 中的服务名称。

          public OrderInfo selectOrderById(Integer id) {
              OrderInfo orderInfo = orderMapper.selectOrderById(id);
              String url = "http://product-service/product/" + orderInfo.getProductId(); // 替换 IP:端口 为服务名称
              ProductInfo product = restTemplate.getForObject(url, ProductInfo.class);
              orderInfo.setProductInfo(product);
              return orderInfo;
          }
      

      2.5.2.2 代码实现
      @Slf4j
      @Service
      public class OrderService {
          @Autowired
          private OrderMapper orderMapper;
          @Autowired
          private RestTemplate restTemplate;
          @Autowired
          private DiscoveryClient discoveryClient;
      
          public List<ServiceInstance> instances;
          @PostConstruct
          public void init(){
              instances = discoveryClient.getInstances("product-service");
          }
      
          public AtomicInteger count = new AtomicInteger(1);
      
          public OrderInfo selectOrderById(Integer id) {
              OrderInfo orderInfo = orderMapper.selectOrderById(id);
              Integer index = count.getAndIncrement() % instances.size();
              //获取 product-server 不加任何 RequesMapping 的 url
              String getUri = instances.get(index).getUri().toString();
              System.out.println(getUri);
              String getUrl = getUri + "/product/" + orderInfo.getProductId();
      
              log.info("调取 uri:" + getUri);
              ProductInfo productInfo = restTemplate.getForObject(getUrl, ProductInfo.class);
              orderInfo.setProductInfo(productInfo);
              return orderInfo;
          }
      
      
      }

      代码解析:

      核心逻辑:selectOrderById 方法通过订单ID查询订单信息,然后使用轮询(round-robin)策略从服务发现中获取产品服务实例,并调用其API获取产品详情。

      DiscoveryClient

      Spring Cloud 提供的服务发现接口,用于从注册中心(如 Eureka、Consul)动态获取服务实例信息。

      List<ServiceInstance> getInstances(String serviceId)
      
      • 参数:serviceId 为注册中心中的服务名
      • 返回值:包含所有可用实例的列表(ServiceInstance 对象集合,也就是 Eureka 中所有注册的服务器)

      List<ServiceInstance> instances 

      存储 discoveryClient.getInstances("服务器ID") 返回的服务实例集合,每个 ServiceInstance 包含多个关键属性。

      方法 说明 示例值
      getUri()

      实例完整访问路径

      从IP到端口号

      http://192.168.1.10:8080
      getHost() 实例IP地址 192.168.1.10
      getPort() 服务端口 8080
      getServiceId() 注册的服务名 "product-service"
      getMetadata() 元数据键值对 {"zone":"east-1"}

      示例用法:
       

      // 获取第一个实例的URI
      String baseUrl = instances.get(0).getUri().toString(); 
      // 获取实例的方法
      String getUrl = getUri + "/product/" + orderInfo.getProductId();
      

      AtomicInteger 

      AtomicInteger 提供原子操作的计数器,解决多线程环境下的并发问题。

      ​​​​​​核心方法 

      方法 说明 示例
      getAndIncrement() 获取当前值后+1(线程安全) count.getAndIncrement()
      get() 获取当前值 count.get()
      set(int newValue) 设置新值 count.set(0)

      2.5.3 负载均衡策略

      2.5.3.1 非自定义负载均衡策略

      • 轮询策略(Round Robin):按顺序依次分配请求到每个可用实例。例如,有三个实例 A、B、C,请求顺序为 A→B→C→A→... 实现简单且公平。
      • 随机策略(Random):从可用实例中随机选择一个处理请求。例如,每个请求独立随机分配到 A、B 或 C。

      默认策略是轮询(通过 RoundRobinLoadBalancer 实现)。若需切换为随机策略,需自定义配置。

      2.5.3.2 自定义负载均衡策略

      逻辑上先定义一个算法对象,通过@Bean 将其加载到 Spring 容器中。

      public class CustomLoadBalancerConfiguration {
      
      	@Bean
      	ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
      			LoadBalancerClientFactory loadBalancerClientFactory) {
      		String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
      		return new RandomLoadBalancer(loadBalancerClientFactory
      				.getLazyProvider(name, ServiceInstanceListSupplier.class),
      				name);
      	}
      }

      定义完上述 Bean 后要注意三点。

      首先:通过 @LoadBalancer 或 @LoadBalancer 注解为 RestTemplate 类服务指定当前策略;

      @LoadBalancerClient(name = "product-service", configuration = CustomLoadBalancerConfiguration.class)
      @Configuration
      public class BeanConfig {
          @LoadBalanced
          @Bean
          public RestTemplate restTemplate() {
              RestTemplate restTemplate = new RestTemplate();
              return restTemplate;
      
          }
      }

      其次:不要加 @Configuration 注解;

      最后:这个类要在启动类扫描范围之下。

      2.5.4 LoadBalancer 原理

      LoadBalancer 是 Spring Cloud 中用于实现客户端负载均衡的核心组件,其核心实现依赖于 LoadBalancerInterceptor。这个类拦截 RestTemplate 的请求,并通过负载均衡算法将服务 ID 替换为真实的服务实例地址。

      基于源码,LoadBalancer 的工作流程可分为四个清晰步骤:

      a)请求拦截: LoadBalancerInterceptor 拦截 RestTemplate 请求,获取原始 URI(如 http://product-service/product )。

      b)服务 ID 提取:从 URI 中解析主机名作为服务 ID(如 product-service),这是 Eureka 注册的唯一标识符。

      c)负载均衡选择:使用 BlockingLoadBalancerClient.choose 方法,根据服务 ID 从 Eureka 获取服务列表。在 BlockingLoadBalancerClient.choose  中,loadBalancer.choos(request) 调用具体负载均衡策略的逻辑,负载均衡策略(如轮询或随机)选择目标实例 (如 http://192.168.1.1:8080)。

      d)请求执行与替换:将原始 URI 中的服务 ID 替换为真实实例地址,执行 HTTP 请求,并返回响应。

      2.6 Nacos

      2.6.1 Nacos 简介

      Nacos(Dynamic Naming and Configuration Service)是阿里巴巴开源的一款云原生服务管理平台,自2018年7月正式开源以来,已成为国内开发者广泛使用的首选组件。

      Nacos的优势在于:高替代性、高易用性与拓展性、社区活跃度高、多语言支持。

      2.6.2 服务注册/服务发现

      2.6.2.1 引入依赖

      a)在父工程的 pom 文件中的<dependencyManagement> 中引入 Spring Cloud Alibaba 依赖:

      <properties>
          <spring-cloud-alibaba.version>2022.0.0.0-RC2</spring-cloud-alibaba.version>
      </properties>
      <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-alibaba-dependencies</artifactId>
          <version>${spring-cloud-alibaba.version}</version>
          <type>pom</type>
          <scope>import</scope>
      </dependency>
      

      注意在引入 Spring Cloud Alibaba 时,版本要遵循 Spring Cloud 的标准,要对应好各版本的对应关系。

      b)在子工程中引入 Nacos 和 Load Balance 依赖

      // alibaba 依赖
      <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      </dependency>
      // Load Balance 依赖
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-loadbalancer</artifactId>
      </dependency>
      

      2.6.2.2 配置子工程 yml
      spring:
        application:
          name: order-service
        cloud:
          nacos:
            discovery:
              server-addr: 127.0.0.1:8848

      2.6.2.3 配置服务器代码
      @Service
      public class OrderService {
          @Autowired
          private OrderMapper orderMapper;
          @Autowired
          private RestTemplate restTemplate;
          public OrderInfo selectOrderById(Integer id) {
              OrderInfo orderInfo = orderMapper.selectOrderById(id);
              System.out.println(orderInfo.getProductId());
              String url = "http://product-service/product/" + orderInfo.getProductId();
              ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
              orderInfo.setProductInfo(productInfo);
              return orderInfo;
          }
      }

      2.6.2.4 启动 Nacos 

      2.6.3 Nacos 负载均衡

      当生产环境比较恶劣时,我们可以对服务器进行精细的流量控制。Nacos 支持多种负载均衡策略,包括权重、同机房、同地域等。

      2.6.3.1 服务下线

      当某一个节点上接口性能较差时,我们可以对该节点进行下线。

      2.6.3.2 权重配置

      除了对该节点进行下线之外,我们还可以通过点击编辑来配置权重进行流量控制

      2.6.3.3 开启 Nacos 负载均衡策略

      由于 Spring Cloud LoadBalance 组件自身有负载均衡配置方式,所以默认不使用 Nacos 的权重属性配置。我们需要手动开启 Nacos 的负载均衡策略,让权重配置生效。

      #开启nacos的负载均衡策略 
      spring.cloud.loadbalancer.nacos.enabled=true

      2.6.3.4 同集群优先访问

      在 Nacos 中,集群是基于实例的元数据(如机房位置)定义的。当实例注册到 Nacos 时,会指定一个集群名称,这相当于将同机房的实例划分为一个逻辑组。Nacos 的服务发现机制会维护这些集群信息:

      • 同集群实例共享相同的网络环境(如局域网),访问延迟较低。
      • 服务消费者在调用提供者时,优先选择与自己相同集群的实例。
      • 如果同集群实例不可用(如宕机或高负载),Nacos 会自动触发故障转移,切换到其他集群的实例。

      2.6.3.4.1 配置集群名称

      可以通过下述代码来配置集群名称

      spring:
        application:
          name: order-service
        cloud:
          nacos:
            discovery:
              server-addr: 127.0.0.1:8848
              cluster-name: "xxxx"

      2.6.3.5 Nacos 健康状态
      2.6.3.5.1 两种健康检查机制

      Nacos作为注册中心,通过健康检查机制监控服务实例的状态,确保服务调用的可靠性。Nacos提供了两种机制:

      • 客户端主动上报机制临时实例(客户端)定期向Nacos服务器发送心跳信号,报告自身健康状态。
        • 默认心跳间隔为5秒。
        • 如果Nacos在15秒内未收到心跳,会将实例标记为“不健康”;超过30秒未收到心跳,则从服务列表中删除实例。
        • 适用于对实例状态变化敏感的场景,能快速剔除故障节点。
      • 服务器端反向探测机制:非临时实例,Nacos服务器主动向服务实例发送探测请求,检查其健康状态。
        • 默认探测间隔为20秒。
        • 如果探测失败,实例被标记为“不健康”,但不会立即删除。
        • 适用于需要高可用性的场景,确保实例不会被意外移除。

      2.6.3.5.2 Nacos 服务实例类型

      Nacos将服务实例分为两类,决定了使用哪种健康检查机制,默认情况下是临时实例 。 :

      • 临时实例
        • 默认类型。
        • 使用客户端主动上报机制。
        • 如果实例宕机超过指定时间(如30秒),会从服务列表中自动剔除。
        • 优点:轻量级,适合动态环境(如云原生应用),能快速响应故障。

      非临时实例(永久实例)

      • 使用服务器端反向探测机制。
      • 如果实例宕机,Nacos仅将其标记为“不健康”,但不会从服务列表中删除。
      • 优点:保证服务列表稳定性,适合关键服务(如数据库或核心微服务),避免因网络抖动导致误

      可以通过配置 yml 来设置是否为临时实例

      spring:
        cloud:
          nacos:
            discovery:
              ephemeral: false  # 设置为非临时实例
      

      重点:Nacos 服务实例类型不允许改变,想要改变服务类型只能删除重新配置。

      2.6.3.6 Nacos 环境隔离

      在企业开发中,服务通常分为开发环境、测试环境和生产环境,以确保各环境的独立性和稳定性。这些环境需要严格隔离,防止相互干扰(例如开发环境的调试信息影响生产环境)。Nacos作为配置管理和服务发现平台,通过  namespace(命名空间)机制来实现这种隔离。不同namespace的服务无法互相发现或通信,从而保证环境安全。

      2.6.3.6.1 创建 Namespace

      可以创建通过配置 namespace 来严格隔离不同环境

      2.6.3.6.2 配置 Namespace

      可以通过配置 yml 来设置归属哪个 namespace

      spring:
        application:
          name: order-service
        cloud:
          nacos:
            discovery:
              server-addr: 127.0.0.1:8848
              cluster-name: "xxxx"
              namespace: c2e58eca-db11-4433-8764-dd0abec10972

      2.6.3.7 Nacos 配置中心

      在微服务架构中,配置通常嵌入代码中,会带来服务部署麻烦、开发冲突风险等问题。Nacos作为配置中心,提供了集中管理微服务配置的能力,解决了传统配置方式的问题。

      服务管理命名空间 不等于 配置管理命名空间

      2.6.3.7.1 添加配置

      在Nacos控制台添加配置项:

      • Data ID:设置为项目名称。
      • 配置格式:支持properties或yaml。

      2.6.3.7.2 获取配置

      启动 Nacos 服务之后,项目启动时会先获取 Nacos 的配置文件 bootstrap.yml,随后和项目的 application.yml 文件合并,且 Nacos 的配置文件强制前缀为 bootstrap。

      2.6.3.7.2.1 引入 Nacos Config 依赖
              <dependency>
                  <groupId>com.alibaba.cloud</groupId>
                  <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
              </dependency>
              <!-- SpringCloud 2020.*之后版本需要引⼊bootstrap-->
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-bootstrap</artifactId>
              </dependency>
      

      2.6.3.7.2.2 配置 yml
      spring:
        application:
          name: product-service #需要和配置命名空间 想要读取的名字一样
        cloud:
          nacos:
            config:
              server-addr: 127.0.0.1:8848

      2.6.3.7.2.3 读取配置

      通过 @Value(${}) 来获取 Nacos 上名字相同的配置参数,如果想要数据支持热更新,则需要加上 @RefreshScope 注解。

      要注意:spring.application.name 需要和 Data ID 一样,@Value(${}) 注解中花括号里的名字需要和读取的配置参数名字相同。

      2.6.3.7.2.4 设置 namespace

      Nacos 配置管理的命名空间和服务列表的命名空间是分别设置的,默认都是 public。Nacos 命名空间配置依然在 bootstrap.properties 中进行配置。

      spring.cloud.nacos.config.namespace=2112837a-6526-4aa4-8d89-2424d3e7d79c
      

      2.6.3.7.3 Data ID

      Data ID 由三部分组成

      dataId 的标准格式为:dataId = {prefix} -{spring.profiles.active} .{file-extension}  其中:

      • prefix:默认为bootstrap.properties 里 spring.application.name 的值。可通过配置项 spring.cloud.nacos.config.prefix 覆盖。
      • spring.profiles.active:表示当前环境 profile。如果 spring.profiles.active 为空,则连接符 - 被省略,格式简化为 prefix. file-extension。
      • file-extension:配置文件的数据格式,支持 .properties 和 .yml 。默认为 .properties ,可通过 spring.cloud.nacos.config.file-extension 配置。

      读取顺序如下:

      • 优先读取 product-service-dev.properties(dataId 完整格式)。
      • 其次读取 product-service.properties(无 profile 版本)。
      • 最后读取 product-service(仅 prefix)。 

      2.6.4 Nacos 和 Eureka 启动过程对比

      比较项 Nacos Eureka
      注册机制 推送模式(基于长连接,服务器给所有已连接的Nacos客户端推送) 拉取模式(基于 Eureka 客户端从Eureka服务器定期拉取)
      心跳间隔 默认每 5 秒发送心跳 默认每 30 秒发送心跳
      心跳超时时间 15 秒未心跳自动注销  90 秒未心跳标记下线
      服务列表更新延迟 推送机制实时通知,\leq 1秒生效 拉取模式,更新延迟 \leq 拉取间隔(默认 30秒)
      CAP模型 支持AP模式(临时节点,优先可用性)和CP模式(持久节点,强一致性) 始终AP模式(最终一致性)
      注册延迟(CP模式) CP模式下 100 毫秒到$1$秒延迟(Raft协议强一致性检查) 无(AP模式无强一致性检查延迟)
      优势 高实时性,服务启动后订阅者几乎立即感知,适合动态环境(如云原生应用) 简单可靠,拉取模式减少服务器压力,适合稳定环境
      劣势 推送模式可能增加服务器负载,尤其在大量服务同时启动时 服务列表更新延迟,可能导致启动后服务不可达(如新服务在 30 秒内未被发现)
      推荐场景 需要实时服务发现(如微服务快速扩缩容)、混合AP/CP需求 系统简单、对延迟不敏感、资源受限

      推拉详解:

      2.7 OpenFeign

      使用RestTemplate进行微服务间通信,存在以下问题:

      a)URL 拼接问题:手动构建 URL,灵活性高但容易出错,尤其是当 URL 路径复杂或包含多个参数时,维护困难。

      b)代码可读性差:RestTemplate 调用分散在业务逻辑中,风格不同一,导致代码臃肿,难以阅读和拓展。

      在 Spring Cloud 中,微服务通信通常基于 HTTP 和 PRC,基于 HTTP 常用工具由 RestTemplate 和 OpenFeign。其中 OpenFegin 提供声明式 API,能有效解决上述问题。

      RPC 通信方式

      • 原理概述:RPC(远程过程调用)是一种通过网络从远程计算机请求服务的机制,目标是像调用本地方法一样调用远程方法。RPC 不局限于特定协议,可以在传输层(如 TCP、UDP)或应用层(如 HTTP)上实现。它在网络四层模型中跨越传输层和应用层,核心优势是高效、低延迟,适用于高性能场景。
      • 常见 RPC 框架:RPC 框架通常提供序列化、服务发现、负载均衡等功能。以下是几个主流框架:
        • Dubbo:由 Apache 维护,是一个高性能 Java RPC 框架,支持多种协议(如 Dubbo 协议、HTTP)。它强调服务治理能力,如熔断、监控。适用于大规模分布式系统。
        • Thrift:由 Apache 开发,支持多语言(如 Java、Python、C++),通过 IDL(接口定义语言)定义服务接口。Thrift 可以生成客户端和服务端代码,高效但配置较复杂。
        • gRPC:由 Google 开发,基于 HTTP/2 协议,使用 Protocol Buffers 作为序列化工具。支持双向流、超时控制等特性,适合云原生环境。gRPC 在跨语言场景中表现优异。
      • 与 HTTP 的区别:RPC 通常比 HTTP 更高效,因为它使用二进制协议(如 gRPC 的 Protobuf),减少了开销。但 RPC 可能更耦合于特定语言或框架,调试稍复杂。在 SpringCloud 中,RPC 不是默认选择,但可以通过集成(如使用 Spring Cloud Alibaba 支持 Dubbo)实现。

      2.7.1 OpenFeign 概念

      OpenFeign是一个声明式的Web Service客户端框架,主要用于简化微服务架构中的服务间调用。它允许开发者像调用本地服务一样调用远程服务,大大降低了分布式系统的复杂性。

      OpenFeign的核心思想是声明式编程:通过定义接口和添加注解来实现远程调用,无需编写复杂的HTTP客户端代码。例如:

      • 开发者只需创建一个接口,类似于定义本地服务接口。
      • 使用注解(如 @FeignClient )指定目标服务名和路径。
      • 调用时,OpenFeign自动处理HTTP请求、序列化和反序列化,类似于controller调用service的简单方式。

      2.7.2 OpenFeign 代码实现

      2.7.2.1 Feign 注解
      2.7.2.1.1 引入依赖

      仅需要再调用方客户端添加依赖即可

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>

      2.7.2.1.2 添加注解

      在调用方启动类添加 @EnableFeignClients 开启 OpenFeign 功能

      @EnableFeignClients
      @SpringBootApplication
      public class OrderServiceApplication {
          public static void main(String[] args) {
              SpringApplication.run(OrderServiceApplication.class, args);
          }
      }
      

      2.7.2.1.3 编写 OpenFeign 客户端
      @FeignClient(value = "product-service",path = "/product")
      public interface ProductApi {
          @RequestMapping("/{id}")
          ProductInfo gerProductById(@PathVariable("id") Integer id);
          @RequestMapping("/p1")
          String returnId (@RequestParam("id") Integer id);
          @RequestMapping("/p2")
          String getNameAndId (@RequestParam("id") Integer id, @RequestParam("name") String name);
          @RequestMapping("/p3")
          String returnInfo (@SpringQueryMap ProductInfo productInfo);
          @RequestMapping("/p4")
          String returnInfoJson (@RequestBody ProductInfo productInfo);
      }
      
      2.7.2.1.4 流程

              a)消费方 Controller 调用服务消费方 Feign 的 productApi 接口

              b)Feign 接口一般从服务注册中心调用服务提供方 Controller 层下相同路径的方法

              c)地址的发现从服务注册中心返回给 Feign 接口,再从 Feign 接口返回给消费方 Controller 

              d)理解 URL 拼接过程:@FeignClient().path + 接口方法的 URL =  要调用的 Controller 方法的 URL(服务提提供方的实际路径)

      @FeignClient 注解的作用与参数说明

      @FeignClient  是 Spring Cloud OpenFeign 中的一个核心注解,用于声明式地定义 REST 客户端接口。它允许开发者通过简单的接口定义来调用其他微服务,而无需手动编写 HTTP 请求代码。Feign 底层集成了服务发现和负载均衡机制(如 Spring Cloud LoadBalancer),简化了微服务间的通信。该注解只能应用于接口上。

      参数详解:

      a)value

      • 作用:指定 Feign 客户端的名称,通常对应目标微服务的服务 ID(例如在 Nacos 注册中心中注册的服务名称)。这个名称用于服务发现,Feign 会根据它从服务注册中心获取可用的实例列表。
      • 负载均衡:底层默认使用 Spring Cloud LoadBalancer 实现负载均衡,自动将请求分发到多个实例。

      b)path

      • 作用:定义当前 Feign 客户端接口的统一请求路径前缀。所有在该接口中定义的方法路径都会自动添加这个前缀,简化 URI 管理。
      • 使用场景:当目标微服务的API存在公共基础路径时,通过设置该路径(例如  /productController,设置到 product 服务器的 Controller 层)来简化API调用,避免重复指定路径前缀。

      2.7.2.2 Feign 继承

      Feign 的继承方式允许将公共接口定义封装在独立模块中,服务提供方实现接口,消费方直接继承该接口创建 Feign 客户端。

      2.7.2.2.1 引入依赖

      给服务消费方接口添加依赖

      <dependencies>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-web</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-openfeign</artifactId>
          </dependency>
      </dependencies>
      

      2.7.2.2.2 编写接口

      创建一个子工程命名为 product-api 作为接下来将要使用的接口。代码如下:

      public interface ProductInterface {
          @RequestMapping("/{productId}")
          ProductInfo getProductById(@PathVariable("productId") Integer productId);
          @RequestMapping("/p1")
          String getProductByName1(Integer id) ;
          @RequestMapping("/p2")
          String getProductByName(@RequestParam("id") Integer id, @RequestParam("name") String name);
          @RequestMapping("/p3")
          String getInfo (ProductInfo productInfo) ;
          @RequestMapping("/p4")
          String getInfoJson (@RequestBody ProductInfo productInfo);
      }
      

      目录结构如下:

      通过打 Jar 包放在本地 Maven 仓库的方式来模拟服务器之间的调度。

      2.7.2.2.3 服务提供方

      服务提供方 ProductInterface 接口的实现类,代码如下:

      @RestController
      @RequestMapping("/product")
      public class ProductController implements ProductInterface {
      
          @Autowired
          public ProductService productService;
          
          @RequestMapping("/{productId}")
          public ProductInfo getProductById(@PathVariable("productId") Integer productId) {
              return productService.selectProductById(productId);
          }
          @RequestMapping("/p1")
          public String getProductByName1(Integer id) {
              return "product-server 接收到参数 id = " + id + " name = " ;
          }
          @RequestMapping("/p2")
          public String getProductByName(Integer id,String name) {
              return "product-server 接收到参数 id = " + id + " name = " + name;
          }
          @RequestMapping("/p3")
          public String getInfo (ProductInfo productInfo) {
              return productInfo.toString();
          }
          @RequestMapping("/p4")
          public String getInfoJson (@RequestBody ProductInfo productInfo) {
              return productInfo.toString();
          }
      }

      核心区别

      特性 extends (继承) implements (实现)
      适用对象 类继承类、接口继承接口 类实现接口
      数量限制 单继承(类只能继承一个父类) 多实现(类可实现多个接口)
      关系本质 "是一个" (is-a) 关系 "具有能力" (can-do) 关系
      方法实现 可直接使用父类已实现的方法 必须实现接口中所有抽象方法
      构造器调用 子类必须调用父类构造器 (super()) 不涉及构造器调用

      2.7.2.2.4 服务消费方

      服务消费方继承 ProductInterface 接口,代码如下:

      @FeignClient(value = "product-service",path = "/product")
      public interface ProductApi extends ProductInterface {}

      服务消费方通过 Spring Boot 的自动注入,在控制端自动注入继承  ProductInterface 接口的类,以此来调用  ProductInterface 接口里的方法。代码如下:

      @RestController
      @RequestMapping("/product")
      public class ProductApiController {
      
          @Autowired
          private ProductApi productApi;
      
          @RequestMapping("/{id}")
          ProductInfo getProductInfo(@RequestParam("id") Integer id) {
              return  productApi.getProductById(id);
          }
          @RequestMapping("/o1")
          public String returnId (@RequestParam("id") Integer id){
              return productApi.getProductByName1(id);
          }
          @RequestMapping("/o2")
          public String returnId2 (@RequestParam("name") String name,@RequestParam("id") Integer id){
              return productApi.getProductByName(id,name);
          }
          @RequestMapping("/o3")
          public String returnId3 (ProductInfo productInfo){
              return productApi.getInfo(productInfo);
          }
          @RequestMapping("/o4")
          public String returnId4 (ProductInfo productInfo){
              return productApi.getInfoJson(productInfo);
          }
      
      }
      
      2.7.2.2.5 流程

              a)服务消费方 Controller 调用服务消费方 productApi 接口

              b)服务消费方 productApi 接口处理请求,此过程中涉及 Feign 在运行时自动生成代理实现,将接口方法调用转换为 HTTP 请求。基于服务注册与发现 (如 Eureka) ,请求会被路由到服务器提供方的对应端点。

              c)服务提供方方法执行,需实现了相同的公共接口 (且方法名需要和接口下的方法名相同),通常在 Controller 层中,提供方 Controller 接受 HTTP 请求并执行具体业务逻辑。地址的发现通过 HTTP 响应返回给服务消费方  Feign 客户端,然后消费方 Controller 获取数据。

              d)理解 URL 拼接过程:服务消费方 API 下的@FeignClient.path + 父接口方法 URL = 要调用的 Controller 方法的 URL(服务提提供方的实际路径)

      2.7.2.3 Feign 抽取

      在企业开发中,将Feign接口抽取为一个独立模块(而非官方推荐的继承方式)是常见做法。这能提高代码重用性、解耦服务提供方和消费者,并简化维护。

      2.7.2.3.1 引入依赖
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
      

      2.7.2.3.2 编写接口
      @FeignClient(value = "product-service",path = "/product")
      public interface ProductApi {
          @RequestMapping("/{id}")
          ProductInfo gerProductById(@PathVariable("id") Integer id);
          @RequestMapping("/p1")
          String returnId (@RequestParam("id") Integer id);
          @RequestMapping("/p2")
          String getNameAndId (@RequestParam("id") Integer id, @RequestParam("name") String name);
          @RequestMapping("/p3")
          String returnInfo (@SpringQueryMap ProductInfo productInfo);
          @RequestMapping("/p4")
          String returnInfoJson (@RequestBody ProductInfo productInfo);
      }
      

      目录结构如下:

      通过打 Jar 包放在本地 Maven 仓库的方式来模拟服务器之间的调度。

      2.7.2.3.3 服务消费方

      服务消费方使用 product-api 依赖,并且指定启动类扫描 ProducApi 接口

      @EnableFeignClients(clients = {ProductApi.class})
      @SpringBootApplication
      public class OrderServiceApplication {
          public static void main(String[] args) {
              SpringApplication.run(OrderServiceApplication.class, args);
          }
      }
      
      @RestController
      @RequestMapping("/product")
      public class ProductApiController {
          @Autowired
          private ProductApi productApi;
      
          @RequestMapping("/{id}")
          ProductInfo getProductInfo(@RequestParam("id") Integer id) {
              return  productApi.gerProductById(id);
          }
      
          @RequestMapping("/o1")
          public String returnId (@RequestParam("id") Integer id){
              return productApi.returnId(id);
          }
          @RequestMapping("/o2")
          public String returnId2 (@RequestParam("name") String name,@RequestParam("id") Integer id){
              return productApi.getNameAndId(id,name);
          }
          @RequestMapping("/o3")
          public String returnId3 (ProductInfo productInfo){
              return productApi.returnInfo(productInfo);
          }
          @RequestMapping("/o4")
          public String returnId4 (@RequestBody ProductInfo productInfo){
              return productApi.returnInfoJson(productInfo);
          }
          
      }

      2.7.2.3.4 服务提供方
      @RestController
      @RequestMapping("/product")
      public class ProductController {
      
          @Autowired
          public ProductService productService;
      
          @RequestMapping("/{productId}")
          public ProductInfo getProductById(@PathVariable("productId") Integer productId) {
              return productService.selectProductById(productId);
          }
          @RequestMapping("/p1")
          public String getProductByName1(Integer id,String name) {
              return "product-server 接收到参数 id = " + id + " name = " + name;
          }
          @RequestMapping("/p2")
          public String getProductByName(Integer id,String name) {
              return "product-server 接收到参数 id = " + id + " name = " + name;
          }
      
          @RequestMapping("/p3")
          public String getInfo (ProductInfo productInfo) {
              return productInfo.toString();
          }
          @RequestMapping("/p4")
          public String getInfoJson (@RequestBody ProductInfo productInfo) {
              return productInfo.toString();
          }
      }
      

      2.7.2.3.5 流程

              a)服务消费方 Controller 调用公共 productApi 接口

              b)公共 productApi 接口处理请求,此过程中涉及 Feign 在运行时自动生成代理实现,将接口方法调用转换为 HTTP 请求。基于服务注册与发现 (如 Eureka) ,请求会被路由到服务器提供方的对应端点。

              c)服务提供方方法执行,通常在 Controller 层中,提供方 Controller 接受 HTTP 请求并执行具体业务逻辑。地址的发现通过 HTTP 响应返回给服务消费方  Feign 客户端,然后再传到消费方 Controller 获取数据。

              d)理解 URL 拼接过程:公共接口 @FeignClient.path + 公共接口方法的 URL = 要调用的 Controller 方法的 URL (服务提提供方的实际路径)

      2.7.3 Feign 注解、Feign 继承和 Feign 抽取的URL与流程总结

      Feign 注解

      • 消费方定义 Feign 接口,使用 @FeignClient 注解指定服务名和路径。
      • Feign 接口方法也需添加 URL 注解。
      • Feign 运行时自动生成代理,将方法调用转为 HTTP 请求。
      • URL 拼接:消费方 Feign 接口中 @FeignClient.path  + 消费方 Feign 接口方法 URL → 调用提供方 Controller。
      • 流程:消费方 Controller 调用消费方 Feign 接口 → Feign 从注册中心获取提供方地址 → 发送请求到提供方 Controller → 返回响应。

      Feign 继承

      • 定义公共接口(包含方法签名和 URL 注解),服务提供方 Controller 实现该接口。
      • 消费方 Feign 接口继承同一公共接口。
      • Feign 运行时生成代理,基于接口方法映射请求。
      • URL 拼接: 消费方 Feign 接口中 @FeignClient.path  + 父接口方法 URL → 调用提供方 Controller(需实现相同方法)。
      • 为什么需要提供方实现接口:确保方法签名(名称、参数、返回类型)完全一致,否则调用会失败;这通过接口契约强制类型安全,但增加了提供方的约束。
      • 流程:消费方 Controller 调用消费方 Feign 接口 →消费方 Feign 接口调用继承的公共API接口 →公共API接口从注册中心获取提供方地址→ 发送请求到提供方 Controller → 返回响应。

      Feign 抽取

      • 公共接口独立定义(不要求提供方实现),消费方 Feign 接口直接使用该接口。
      • 提供方 Controller 只需路径匹配,无需实现接口。
      • Feign 运行时生成代理,仅基于路径和参数映射请求。
      • URL 拼接: 公共接口中 @FeignClient.path + 公共接口方法 URL → 调用提供方 Controller(路径匹配即可)。
      • 为什么不需要提供方实现接口:Feign 抽取只依赖 HTTP 契约(路径和参数),而非 Java 接口契约;这减少了耦合,但需手动确保路径一致性
      • 流程:消费方 Controller 调用实现 Feign 的公共API接口→公共API接口从注册中心获取提供方地址→ 发送请求到提供方 Controller → 返回响应。

      2.7.4 Feign 继承和抽取总结

      方面 继承模式 抽取模式
      设计目标 代码层面的复用,减少同一项目内的重复 架构层面的复用,确保跨服务 API 一致性
      实现方式 使用 Java 接口继承(extends 创建独立模块,通过依赖管理共享
      适用场景 同一服务或模块内的简单复用 跨多个微服务或独立项目的复杂复用
      灵活性 较灵活,易于添加新方法 更严格,修改接口需更新模块版本
      依赖关系 无外部依赖,纯代码结构 需要构建工具(如 Maven)管理模块依赖
      维护性 适合小规模项目,但继承链过长易混乱 适合大规模系统,提升全局一致性

      设计目标

      • Feign 注解:目标是简化 HTTP 调用,通过注解定义接口,避免手动编写 HTTP 客户端代码。强调轻量化和灵活性,适合快速开发。
      • Feign 继承:目标是实现接口共享和类型安全,通过公共接口确保消费方和提供方的方法签名一致。减少重复代码,但增加耦合。
      • Feign 抽取:目标是解耦和重用,将接口独立抽取为公共模块,消费方直接调用,提供方无需实现接口。强调低耦合和高维护性。
      • 关键区别:Feign 继承强制接口实现以保障一致性,而 Feign 抽取通过公共接口定义路径,不要求提供方实现,从而降低依赖。

      简单说:继承模式像在“一个文件内”复用代码,而抽取模式像在“多个项目间”共享代码库。

      2.8 Gateway

      2.8.1 API 网关

      在微服务架构中,服务注册、发现、负载均衡和远程调用等问题已通过技术如Eureka、Nacos、Spring Cloud LoadBalance和OpenFeign得到解决。然而,这些微服务接口直接对外暴露,带来了安全隐患和重复开发负担。

      API网关(简称网关)是一个专门的服务,作为整个微服务架构的唯一入口。它类似于设计模式中的门面模式(Facade模式),充当外部客户端与内部服务之间的“门卫”。所有外部请求都必须先经过网关,由网关进行调度和过滤,再转发到目标微服务。这样,网关可以统一处理公共逻辑,避免在每个微服务中重复实现。

      2.8.2 网关核心功能

      • 权限控制:网关作为入口,对用户请求进行统一校验(如身份验证和授权)。如果校验失败,请求会被拦截,防止未授权访问。例如,网关可以检查Token或API密钥。
      • 动态路由:网关不处理业务逻辑,而是根据配置规则(如URL路径或请求头)将请求转发到特定微服务。
      • 负载均衡:当目标服务有多个实例时,网关在路由过程中实现负载均衡(如轮询或随机算法),确保流量均匀分布,提高系统可用性。
      • 限流:在高并发场景下,网关可限制请求流量(如每秒请求数),防止单个服务过载。

      2.8.3 常见网关实现

      业界有多种成熟的网关解决方案,包括开源产品。以下是两种主流实现:

      • Zuul
        Zuul是由Netflix开源的API网关组件,是Spring Cloud Netflix子项目的核心部分。它可以与Eureka、Ribbon、Hystrix等组件集成,提供路由、过滤和负载均衡功能。Zuul 1.x曾是Spring Cloud的推荐网关,但Netflix在2018年宣布Zuul进入维护状态,不再开发新特性。因此,Zuul在性能和新功能支持上存在局限,适合旧系统维护,但不推荐新项目使用。

      • Spring Cloud Gateway
        Spring Cloud Gateway是Spring官方推出的全新API网关项目,基于Spring和SpringBoot技术开发,旨在替代Zuul。它为微服务提供了简单高效的请求转发机制,并支持安全性、监控和弹性等横切关注点。关键优势包括:

        • 高性能:根据官方测试报告,Spring Cloud Gateway的RPS(每秒请求数)是Zuul 1.6倍,处理能力更强。
        • 现代特性:支持动态路由配置、过滤器链和响应式编程,易于扩展。
        • 与Spring生态无缝集成:如与Nacos、Sentinel等配合使用。

      2.8.4 Gateway 代码实现

      2.8.4.1 引入依赖
              <!--⽹关-->
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-gateway</artifactId>
              </dependency>
              <!--基于nacos实现服务发现依赖-->
              <dependency>
                  <groupId>com.alibaba.cloud</groupId>
                  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
              </dependency>
              <!--负载均衡-->
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
              </dependency>

      2.8.4.2 编写 Gateway 启动类
      @SpringBootApplication
      public class GetWayApplication {
          public static void main(String[] args) {
              SpringApplication.run(GetWayApplication.class, args);
          }
      }
      

      2.8.4.3 配置 yml
      server:
        port: 10030 # ⽹关端⼝
      spring:
        application:
          name: gateway # 服务名称
        cloud:
          nacos:
            discovery:
              server-addr: 120.53.241.96:8848
          gateway:
            routes: # ⽹关路由配置
              - id: product-service #路由ID, ⾃定义, 唯⼀即可
                uri: lb://product-service #⽬标服务地址
                predicates: #路由条件
                 - Path=/product/**
              - id: order-service
                uri: lb://order-service
                predicates:
                  - Path=/order/**,/feign/**,/product/**
      

      配置详解:

      a)id字段

      • 含义:自定义路由的唯一标识符,用于区分不同的路由规则。
      • 要求:必须全局唯一,不能重复。这有助于在配置管理、日志追踪或动态更新时快速定位特定路由。

      b) uri 字段

      • 含义:指定目标服务的地址,请求匹配后将被转发到这里。
      • 支持格式
        • 普通URI:直接指定静态服务地址,例如http://example.com/api。适用于固定后端服务。
        • 负载均衡URI(lb://):以 lb:// 开头,表示从服务注册中心(如Eureka、Consul或Nacos)动态获取服务实例。例如 lb://user-service,其中:
          • lb 表示负载均衡机制,自动在多个实例间分配请求。
          • :// 后加服务名称(如 user-service)必须在注册中心已注册。
      • 注意事项:使用 lb:// 时,需确保网关已集成服务发现组件,否则路由会失败。

      c)predicates字段

      • 含义:路由匹配条件,基于请求属性(如路径、方法、头信息等)决定是否执行该路由。只有所有条件都满足时,请求才会被代理到uri指定的地址。
      • 常见类型
        • Path:匹配请求路径规则,例如 Path=/api/** 表示所有以 /api/ 开头的路径都匹配。
        • Method:匹配HTTP方法,例如 Method=GET 只处理GET请求。
        • 其他:如 Header(匹配头信息)、Query(匹配查询参数)、 Host(匹配域名)等。
      • 工作逻辑:predicates是一个列表,可以包含多个条件。所有条件必须同时满足(逻辑AND),路由才会生效。例如,如果配置为 Path/users/** 和 Method=POST,则只对POST请求且路径匹配 /users/**  的请求进行路由。
      • 注意事项:条件表达式需遵循特定语法(如Ant风格路径),错误配置可能导致路由不生效。

      在API网关(如Spring Cloud Gateway或类似系统)的路由配置中,定义多个路由(routes)使用相同的路径谓词(predicate),例如两个路由都设置了 Path/users/**,通常不会直接导致错误(如编译错误或运行时异常)。但是,这可能会引起路由冲突,导致请求被意外处理。

      • 在网关系统中,路由是按配置顺序(从上到下)评估的。谓词 Path=/product/**用于匹配所有以 /product/ 开头的请求路径(例如/product/item 或/product/detail)。
      • 如果两个路由都有相同的 Path=/product/** 谓词:
        • 第一个匹配的路由会处理请求,后续匹配的路由会被忽略。

      2.8.4.4 Route Predicate Factories

      在网关技术中,路由谓词工厂(Route Predicate Factories)用于定义路由规则的核心组件,它们基于请求的各种属性(如路径、主机头、方法等)创建布尔谓词(predicates),以决定请求是否匹配特定路由。这有助于实现动态路由、负载均衡和安全控制。

      2.8.4.4.1 Predicate

      Predicate 是 Java 8 引入的一个函数式编程接口,属于 java.util.function 包。它接收一个输入参数(类型为泛型 T),并返回一个布尔值( boolean),常用于条件过滤、请求参数校验等场景。由于它是一个函数式接口(使用 @FunctionalInterface 注解),因此可以简洁地通过 lambda 表达式或方法引用来实现。

      @FunctionalInterface
          public interface Predicate<T> {
              boolean test(T t);
              // 其他默认方法(如 and, or 等)
      }
      

      代码演示:

              a)定义 Predicate

      class StringPredicate implements Predicate<String>{
          @Override
          public boolean test(String str) {
              return str.isEmpty();
          }
      }
      

              b)使用 Predicate

      public class PredictTest {
          public static void main(String[] args) {
              Predicate<String> predicate = new StringPredicate();
              System.out.println(predicate.test(""));       // 输出 true,因为空字符串满足 isEmpty()
              System.out.println(predicate.test("666")); // 输出 false,因为非空字符串不满足
          }
      }
      

              c)运行结果

      Predicate 其他方法:

      方法名 参数 返回类型 说明
      isEqual Object targetRef Predicate<T> 比较两个对象是否相等,参数可以为null。返回一个Predicate,用于测试其他对象是否与targetRef相等。
      test T t boolean 判断条件,传入一个参数,根据逻辑返回布尔值。
      and Predicate<? super T> other Predicate<T> 短路与操作,组合当前Predicate和另一个Predicate,相当于条件A && 条件B。
      or Predicate<? super T> other Predicate<T> 短路或操作,组合当前Predicate和另一个Predicate,相当于条件A
      negate Predicate<T> 返回当前Predicate的逻辑否定,相当于!条件A。

      2.8.4.4.2 Route Predicate Factories

      Route Predicate Factories(路由断言工厂)是 Spring Cloud Gateway 的核心组件之一,负责将配置文件中的路由规则字符串转换为具体的路由匹配条件。这些工厂通过谓词(Predicate)机制实现,即一个函数来判断请求是否匹配特定路由规则。

      2.8.4.4.2.1 基本工作原理
      • 在配置文件中,Path=/product/** 只是字符串形式。
      • Route Predicate Factory 会读取这些字符串,并将其解析为可执行的匹配逻辑。
      • 例如,Path=/product/**  会被对应的工厂处理,生成一个谓词函数,该函数检查请求的 URL 是否以 /product 开头。
      • 整个过程在网关启动时完成,运行时使用这些谓词来高效过滤请求。

      2.8.4.4.2.2 具体示例
      • 上述提到的 Path=/product/** 规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory实现的。
      • 工作流程:
        1. 配置文件定义:spring.cloud.gateway.routes.predicates
          = Path=/product/**。
        2. PathRoutePredicateFactory 解析字符串  /product/**,将其转换为一个 Java 函数。
        3. 该函数在请求到来时执行:检查请求路径是否匹配前缀 /productt,如果是,则路由到相应服务。
      • 这种机制允许动态处理各种 URL 模式,支持通配符和正则表达式。

      2.8.4.4.2.3 Route Predicate Factory

      Spring Cloud Gateway 内置了多种 Route Predicate Factory,覆盖 HTTP 请求的不同属性:

      谓词名称 描述 示例值
      After 匹配指定日期之后的请求 After=2017-01-20T17:42:47.789-07:00[America/Denver]
      Before 匹配指定日期之前的请求 Before=2017-01-20T17:42:47.789-07:00[America/Denver]
      Between 匹配两个指定时间之间的请求,datetime2必须在datetime1之后 Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
      Cookie 请求中包含指定Cookie,且该Cookie值符合指定的正则表达式 Cookie=chocolate, ch.p
      Header 请求中包含指定Header,且该Header值符合指定的正则表达式 Header=X-Request-Id, \d+
      Host 请求必须是访问某个host(根据请求中的Host字段进行匹配) Host=.somehost.org,.anotherhost.org
      Method 匹配指定的请求方式 Method=GET,POST
      Path 匹配指定规则的路径 Path=/red/{segment},/blue/{segment}
      RemoteAddr 请求者的IP必须为指定范围 RemoteAddr=192.168.1.1/24

      2.8.4.5 GateyaFilter

      在 Spring Cloud Gateway 中,过滤器用于在请求处理前后添加自定义逻辑。Predicate 负责路由匹配,而 Filter 则处理请求和响应的中间操作。

      2.8.4.5.1 Filter 基本类型
      • Pre 类型过滤器:在请求被转发到后端服务之前执行。常见用途包括鉴权、限流等。例如,检查请求头或参数。
      • Post 类型过滤器:在请求执行完成后,将结果返回给客户端之前执行。常用于修改响应头或日志记录。
      • 根据作用范围,Filter 分为:
        • GatewayFilter:应用于单个路由或一组路由(通过配置文件定义)。
        • GlobalFilter:应用于所有路由,对所有请求生效(通过编程驱动的全局过滤器接口)。
        • Default-filters:通过配置文件定义实现全局过滤效果

      2.8.4.5.2 GateyaFilter 的配置与使用

      GatewayFilter 在 application.yml 中配置,每个过滤器有固定逻辑。添加路径为 spring.cloud.geteway.routes.filters。

      过滤器名称 功能描述 参数说明
      AddRequestHeader 为当前请求添加Header Header名称及值<br>示例:X-Request-red, blue
      AddRequestParameter 为当前请求添加参数 参数名称及值<br>示例:red, blue
      AddResponseHeader 为响应结果添加Header Header名称及值<br>示例:X-Response-Red, Blue
      RemoveRequestHeader 从当前请求删除Header Header名称<br>示例:X-Request-Foo
      RemoveResponseHeader 从响应结果删除Header Header名称<br>示例:X-Response-Foo
      RequestRateLimiter 网关全局请求限流(令牌桶算法) replenishRate:令牌填充速度(请求/秒)<br>burstCapacity:令牌桶容量<br>requestedTokens:单请求消耗令牌数(默认1)
      Retry 对失败请求进行重试 retries:重试次数(默认3)<br>statuses:触发重试的HTTP状态码(如BAD_REQUEST
      RequestSize 限制请求包大小 maxSize:最大字节数(默认5,000,000)<br>超限返回413 Payload Too Large

      2.8.4.6 Default Filters

       GateyaFilter 添加在指定路由下,所以只对当前路由生效,若需要对全局路由生效,可以使用 spring.cloud.gateway.default-filters 这个属性需要一个 filter 的列表。

      • "列表"的含义:这里的列表(List)是一个有序集合,可以包含多个过滤器定义。每个过滤器定义代表一个具体的操作(如添加请求头、修改路径等)。
      • 列表的作用:可以使多个过滤器到所有路由上。网关会按列表顺序执行这些过滤器(从上到下)。
      • 列表中的元素:每个元素是一个过滤器的配置,通常包括:
        • 过滤器名称:例如 AddRequestHeader(添加请求头)或 RewritePath(重写路径)。
        • 参数:过滤器所需的参数,例如键值对(如头名称和值)

      定义规则如下所示:

      spring:
        cloud:
          gateway:
            default-filters:
              - AddRequestHeader=X-Request-ID, 12345  # 第一个过滤器:添加请求头
              - AddResponseHeader=X-Response-ID, 67890 # 第二个过滤器:添加响应头
      

      2.8.4.7 GlobalFilter
      2.8.4.7.1 GlobalFilter 概念

      上述 GateyaFilter 和 Default Filters 都是通过配置文件生效,而 GlobalFilter 通过编程驱动,自动应用到所有路由,无需在路由配置中显式声明。

      GlobalFilter是Spring Cloud Gateway提供的一个接口,它允许开发者在所有路由请求上应用自定义逻辑。无论路由配置如何,GlobalFilter都会在请求处理链中被执行。

      • GlobalFilter:作用于所有路由请求,是全局性的。
      • GatewayFilter:作用于特定路由配置,是局部性的(例如,只在配置了该过滤器的路由上生效)。

      GlobalFilter可以通过 Order 接口或 @Order 注解设置优先级(order值),值越小优先级越高。GatewayFilter的顺序由路由配置中的顺序决定。

      2.8.4.7.2 GlobalFilter 代码实现
      2.8.4.7.2.1 添加依赖
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-actuator</artifactId>
              </dependency>

      2.8.4.7.2.2 配置 yml
      spring:
        cloud:
          gateway:
            metrics:
              enabled: true  # 开启Gateway的指标收集功能(如请求计数、延迟等)
      management:
        endpoints:
          web:
            exposure:
              include: "*"  # 暴露所有管理端点(健康检查/指标/shutdown等)
          endpoint:
            health:
              show-details: always  # 始终显示健康检查的详细信息(包括组件状态)
            shutdown:
              enabled: true  # 启用应用关闭端点(可通过HTTP请求优雅停止服务)
      

      2.8.4.7.2.3 运行结果

      我们可以通过在开启 Gateway 的端口下的 actuator 能看到所有在 yml 配置下的网关信息。

      2.8.4.8 过滤器执行顺序

      在Spring Cloud Gateway中,当请求路由后,网关会将项目中的所有过滤器(包括GatewayFilter和GlobalFilter)合并到一个统一的过滤器链中,并按照特定的顺序执行。

       过滤器链的合并与排序

      • 网关在运行时,会将当前项目中的GatewayFilter和GlobalFilter合并到一个集合中。
      • 这个集合会按照每个过滤器的 order 值进行排序。order 是一个 int 类型的属性,默认值为0。
      • 排序规则是:order 值越小,优先级越高,执行顺序越靠前

      order值相同时的执行优先级

      • 如果多个过滤器的 order 值相等(即order_1 = order_2),网关会按照以下固定优先级顺序执行:
        • defaultFilter:网关的默认过滤器(优先级最高)。
        • GatewayFilter:用户定义的路由级过滤器。
        • GlobalFilter:全局过滤器(优先级最低)。

      2.8.4.9 自定义 GatewayFilter

      在Spring Cloud Gateway中,自定义过滤器允许您在请求处理链中添加自定义逻辑,例如日志记录、权限验证或数据转换。过滤器分为pre-filter(请求处理前)和post-filter(响应处理后)两种类型。

      逻辑上设计一个类,这个类继承 AbstractGatewayFilterFactory 并且实现 Ordered 接口类中的 apply 和 getOrder 方法。

      2.8.4.9.1 定义配置类
      @Data
      public class CustomConfig {
          private String name;
      }

      这个类是一个自定义的配置类,用于接收在 YAML 配置文件中定义的参数。参数是指应用程序级别的配置参数,用于控制过滤器的行为。具体示例如下:

      spring:
        application:
          name: gateway # 服务名称
        cloud:
          nacos:
            discovery:
              server-addr: 127.0.0.1:8848
          gateway:
            routes: # ⽹关路由配置
              - id: product-service #路由ID, ⾃定义, 唯⼀即可
                uri: lb://product-service #⽬标服务地址
                predicates: #路由条件
                 - Path=/product/**
                filters:
                  - name: Custom #自定义 GatewayFilter 类名字
                    args:
                      name: custom config name # 自定义配置类的 成员变量名
              - id: order-service
                uri: lb://order-service
                predicates:
                  - Path=/order/**,/feign/**,/product1/**

      这里的 name 和 enableLog 就是通过 YAML 配置的参数。

      2.8.4.9.2 定义 GatewayFilter
      @Slf4j
      @Service
      public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomConfig> implements Ordered {
      
          // 构造函数,指定配置类类型
          public CustomGatewayFilterFactory() {
              super(CustomConfig.class);
          }
          @Override
          public GatewayFilter apply(CustomConfig config) {
              return new GatewayFilter() {
                  @Override
                  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                      // pre 类型过滤器
                      log.info("Pre Filter  config{}", config);
      
                      //执行请求
                      return chain.filter(exchange).then(Mono.fromRunnable(
                              () -> log.info("Post Filter  config{}", config)
                      ));
                  }
              };
          }
          @Override
          public int getOrder() {
              return  Ordered.LOWEST_PRECEDENCE;
      
          }
      
      }

      代码详解:

      a) CustomGatewayFilterFactory

      自定义 GatewayFilter 类必须以 GatewayFilterFactory 结尾,Spring 会自动提取前缀 Custom 作为过滤器名称 (在 YAML 中配置时使用)。super() 方法的作用是

      b) apply

      • 参数 config:从 YAML 传入的配置对象
      • exchange:代表 HTTP 请求-响应交互 (类似上下文),提供访问请求头、参数等。
      • chain:过滤器链,调用 chain.filter(exchange) 继续执行下一个过滤器
      • then:写 post-filter 逻辑代码
      • Mono.fromRunnable:用于异步执行 post-filter 逻辑。

      c)getOrder 方法

      实现 Ordered 接口,返回优先值,用于设置过滤器优先级。

      d)super()

      在 CustomGatewayFilterFactory 的构造函数中,super() 的作用是调用父类 AbstractGatewayFilterFactory 的构造函数,并制定配置类的类型。

      2.8.4.9.3 配置过滤器

      设置完 GatewayFilter 之后还需要在 .yml 文件配置过滤器

      spring:
        cloud:
          gateway:
            routes: # ⽹关路由配置
              - id: product-service #路由ID, ⾃定义, 唯⼀即可
                uri: lb://product-service #⽬标服务地址
                predicates: #路由条件
                  - Path=/product/**
                filters:
                  - name: Custom
                    args:
                      name: custom filter
      

      2.8.4.9.4 运行结果

      注解要使用 @Componet,Spring Cloud Gateway通过 GatewayFilterFactory 接口加载过滤器自动配置会显式查找带有 @Componet  GatewayFilterFactory 实现类。

      当使用 @Service 时:可能触发Spring AOP的代理机制 (如事务代理) 致过滤器工厂实际类型变为 Proxy ,破坏接口继承链。

      注解与代理的关系

      注解 是否必然触发代理 说明
      @Service ❌ 否 仅标记服务层Bean,若无AOP增强则无代理
      @Component ❌ 否 通用Bean标记,默认不代理
      @Configuration ✅ 是 特殊代理机制:为维护单例,Spring强制使用CGLIB代理
      @Repository ⚠️ 可能 若开启异常转换(默认开启)则触发代理
      @Controller ❌ 否 通常无代理需求
      @Configuration 默认触发CGLIB代理

      • 强制代理原因:防止@Bean方法重复调用,保障单例语义。
      • 禁用代理:可通过属性关闭(但破坏单例保证)

      代理模式(Proxy Pattern)

      代理模式是一种结构型设计模式,为其他对象提供一种代理以控制对这个对象的访问。核心思想是通过引入代理对象,在客户端和目标对象之间增加一层间接保护,常用于实现访问控制、延迟初始化、日志记录等功能。

      2.8.4.10 自定义 GlobalFilter

      以下是一个简单的Java示例,展示如何实现一个GlobalFilter来记录请求日志。示例使用Spring Boot和Spring Cloud Gateway的依赖

      @Component
      public class LoggingGlobalFilter implements GlobalFilter, Ordered {
      
          @Override
          public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
              // 记录请求信息:路径、方法等
              String path = exchange.getRequest().getPath().toString();
              String method = exchange.getRequest().getMethod().name();
              System.out.println("GlobalFilter applied - Request: " + method + " " + path);
      
              // 继续执行过滤器链
              return chain.filter(exchange);
          }
      
          @Override
          public int getOrder() {
              // 设置优先级,较低值表示更高优先级(例如,-1 表示早于其他过滤器执行)
              return -1;
          }
      }
      

      参数详解:

      a)ServerWebExchange exchange:

      • 这是一个核心参数,代表 HTTP 请求和响应的交换对象。它封装了请求和响应的所有信息,例如:
        • exchange.getRequest():获取当前请求对象(类型为 ServerHttpRequest),包含路径、方法、头信息等。
        • exchange.getRespose():获取响应对象(类型为 ServerHttpRespose),可用于修改响应状态、头信息等。
      • 作用:允许过滤器访问和修改请求/响应数据。

      b)GatewayFilterChain chain 

      • 代表过滤器链对象。它维护了当前请求要执行的所有过滤器的顺序。
      • 关键方法:chain.filter(exchange) 用于调用链中的下一个过滤器。如果不调用此方法,过滤器链会中断,请求不会继续传递到下游服务。
      • 在代码中, return chain.filter(exchange)确保在记录日志后,请求继续被处理(例如,路由到目标服务)。

      c)chain.filter(exchange).then()

              该表达式主要用于 异步操作链中的条件过滤与结果处理

      2.9 流量控制算法

      流量控制用于限制请求速率,防止系统过载

      2.9.1 固定窗口算法

      固定窗口算法将时间划分为固定大小的窗口(例如,每1分钟一个窗口),并在每个窗口内计数请求。如果请求数超过预设阈值,则拒绝额外请求。

      • 原理:假设窗口大小为 T 秒,阈值(允许的最大请求数)为 N 。每个窗口开始时计数器重置。
      • 优点:实现简单,计算开销小。
      • 缺点:在窗口边界附近,请求可能集中爆发,导致在限定时间内实际速率超过限制。例如,在窗口结束前瞬间涌入大量请求,新窗口又立即开始,可能导致短时间内请求数超过 N。
        • 数学表示:实际峰值速率可能达到 2N/T,而不是预期的平均速率 N/T。

      2.9.2 滑动窗口算法

      滑动窗口算法通过动态维护一个时间窗口来解决固定窗口的边界问题。窗口以一定间隔滑动(例如,每1秒滑动一次),计数基于过去$T$秒内的请求。

      • 原理:窗口大小为 1 秒,滑动步长为 \Delta t(例如,\Delta t = 1 秒)。算法持续跟踪窗口内的请求数,并在滑动时更新计数。
      • 优点:比固定窗口更平滑,能更精确地控制平均速率 r(r = N/T),避免边界爆发问题。设置滑动时间 \Delta t 是关键:较小的\Delta t(如0.1秒)提高精度,但增加计算开销。
      • 缺点:实现较复杂,需要存储请求时间戳,内存占用较高。

      2.9.3 漏桶算法

      漏桶算法 (Leaky Bucket) 将请求视为水滴,放入一个桶中,桶以固定速率泄漏处理请求。类似于队列结构。

      • 原理:桶有容量 C (最大队列长度)。请求到达时,如果桶未满,则入队;桶以恒定速率 r 泄漏(处理请求)。速率 r 是固定的,例如每秒处理 r 个请求。
      • 优点:输出速率稳定,适合平滑流量。
      • 缺点:应急流量(突发流量)处理不好。因为速率固定,桶满时新请求会被拒绝或等待,无法处理突发。例如,如果突发请求超过 C,则直接丢弃。

      2.9.4 令牌桶算法

      令牌桶算法每秒向桶中添加令牌,请求到达时消耗令牌。桶中令牌可以积累,从而处理应急流量。

      • 原理:桶以固定速率 r 生成令牌(例如每秒 r 个),桶容量为 C 。请求需要消耗一个令牌才能处理;如果桶空,则拒绝请求。
      • 优点:能有效处理突发流量,因为令牌可以积累(最多 C 个)。当突发请求到达时,如果有足够令牌,则立即处理。
      • 缺点:实现稍复杂,需要维护令牌计数;如果生成速率 r 过低,可能限制正常流量。

      如果觉得对你有帮助的话,请给博主一键三连吧,这对我真的很重要

      (>人<;) 求你了~
      (๑・́ω・̀๑) 拜托啦~
      (≧∇≦)ノ 求求你啦~
      (ಥ_ಥ) 真的求你了…
      (;へ:) 行行好吧~

      Logo

      火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

      更多推荐