微服务介绍

系统架构演变

随着互联网的发展,网站应用的规模也在不断的扩大,进而导致系统架构也在不断的进行变化。从互联网早起到现在,系统架构大体经历了下面几个过程: 单体应用架构—>垂直应用架构—>分布式架构—>SOA架构—>微服务架构,当然还有悄然兴起的 Service Mesh(服务网格化)
接下来我们就来了解一下每种系统架构是什么样子的,以及各有什么优缺点。

单体应用架构

互联网早期,一般的网站应用流量较小,只需一个应用,将所有功能代码都部署在一起就可以,这样可以减少开发.部署和维护的成本。
比如说一个电商系统,里面会包含很多用户管理,商品管理,订单管理,物流管理等等很多模块,我们会把它们做成一个 web 项目,然后部署到一台 tomcat 服务器上。

优点:

  • 项目架构简单,小型项目的话,开发成本低

  • 项目部署在一个节点上,维护方便

缺点:

  • 全部功能集成在一个工程中,对于大型项目来讲不易开发和维护

  • 项目模块之间紧密耦合,单点容错率低

  • 无法针对不同模块进行针对性优化和水平扩展

垂直应用架构

随着访问量的逐渐增大,单一应用只能依靠增加节点来应对,但是这时候会发现并不是所有的模块都会有比较大的访问量.还是以上面的电商为例子,用户访问量的增加可能影响的只是用户和订单模块,但是对消息模块的影响就比较小.那么此时我们希望只多增加几个订单模块,而不增加消息模块. 此时单体应用就做不到了,垂直应用就应运而生了.

所谓的垂直应用架构,就是将原来的一个 应用拆成互不相干的几个应用,以提升效率。比如我们可以将上面电商的单体应用拆分成: 电商系统(用户管理商品管理订单管理), ·后台系统(用户管理订单管理客户管理). CMS 系统(广告管理营销管理) 这样拆分完毕之后,一旦用户访问量变大,只需要增加电商系统的节点就可以了,而无需增加后台和 CMS 的节点。

优点:

  • 系统拆分实现了流量分担,解决了并发问题,而且可以针对不同模块进行优化和水扩展
  • —个系统的问题不会影响到其他系统,提高容错率

缺点:

  • 系统之间相互独立,无法进行相互调用
  • 系统之间相互独立,会有重复的开发任务

分布式架构

当垂直应用越来越多,重复的业务代码就会越来越多。这时候,我们就思考可不可以将重复的代码抽取出来,做成统一的业务层作为独立的服务,然后由前端控制层调用不同的业务层服务呢?

这就产生了新的分布式系统架构。它将把工程拆分成表现层和服务层两个部分,服务层中包含业务逻辑,表现层只需要处理和页面的交互,业务逻辑都是调用服务层来实现。

优点:

  • 抽取公共的功能为服务层,提高代码复用性

缺点:

  • 系统之间耦合度变高,调用关系错综复杂,难以维护

SOA 架构

在分布式架构下,当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心对集群进行实时管理。此时,用于资源调度和治理中心(SOA Service Oriented Architecture)是关键。

优点:

  • 使用治理中心(ESB dubbo)解决了服务间调用关系的自动调节

缺点:

  • 服务间会有依赖关系,一旦某个环节出错会影响较大( 服务雪崩)
  • 服务关系复杂,运维、测试部署困难

微服务架构

微服务架构在某种程度上是面向服务的架构 SOA 继续发展的下一步,它更加强调服务的 “彻底拆分”。

微服务架构与 SOA 架构的不同

微股务架构比 SOA 架构粒度会更加精细,让专业的人去做专业的事情((专注),目的提高效率,每个服务与股务之间互不影响,微服务架构中,每个服务必须独立部署,微服务架构更加轻巧,轻量级

SOA 架构中可能数据库存储会发生共享,微服务强调独每个服务都是单独数据库,保证每个服务于服务之间互不影响。

项目体现特征微服务架构比 SOA 架构更加适合与互联网公司敏捷开发、快速迭代版本,因为粒度非常精细。

优点:

  • 服务原子化拆分,独立打包、部署和升级,保证每个微服务清晰的任务划分,利于扩展
  • 微服务之间采用 Restful 等轻量级 http 协议相互调用

缺点:

  • 分布式系统开发的技术成本高(容错、分布式事务等)
  • 复杂性更高。各个微服务进行分布式独立部署,当进行模块调用的时候,分布式将会变得更加麻烦。

微服务架构的常见问题

—且采用微服务系统架构,就势必会遇到这样几个问题:

  • 这么多小服务,如何管理他们? (服务治理注册中心 [服务注册发现剔除]) nacos
  • 这么多小服务,他们之间如何通讯?(restul rpc dubbo feign) httpclient(“url”, 参数) springboot restTemplate(“url”, 参数) , feign
  • 这么多小服务,客户端怎么访问他们?(网关) gateway
  • 这么多小服务,一旦出现问题了,应该如何自处理?(容错)
  • 这么多小服务,一旦出现问题了,应该如何排错?(链路追踪)

对于上面的问题,是任何一个微服务设计者都不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。

常见微服务架构

  1. dubbo: zookeeper +dubbo + SpringMVcIspringBoot
    • 配套通信方式: rpc
    • 注册中心: zookeeper /redis
    • 配置中心: diamond
  2. SpringCloud: 全家桶+轻松嵌入第三方组件(Netflix)
    • 配套通信方式: http restful
    • 注册中心: eruka/consul
    • 配置中心: config
    • 断路器: hystrix
    • 网关: zuul
    • 分布式追踪系统: sleuth + zipkin
  3. SpringCloud Alibaba: Spring Cloud 以微服务为核心的分布式系统构建标准
    ‘分布式系统中的常见模式始了 Sping Cloud 一个清斯的定位,即 “模式”,也然是说 SpringCloud 是针对分布式系块开发所做的通用抽象。是标准模式的实现。这个定义非常抽象,看完之后并不能知道 SpringCloud 具体包含什么内容。再来着一下 Spring 官方给出的一个 High Light 的架构图,就可以对这套横式有更清晰的认识:

Spring Cloud Alibaba 介绍

Sping Clou Aiaba 致力于提供微服务开发的一站式解决方案。此项目包含开发微服务架构的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发微服务架构, 依托 Sping Clou Aibaba,您只需要添加一些注解和少量配置,就可以将 Sping Cloud 应用接入阿里分布式应用解决方案,通过阿里中间件来迅速搭建分布式应用系统。

可以发现 Spring Cloud Alibaba 是所有的实现方案中功能最齐全的。尤其是在 Netlix 停止更新了以后,Spring Cloud Albaba 依然在持续更新和迭代。

Spring Cloud Alibaba 环境搭建

SpringCloud Alibaba 依赖 Java 环境来运行。还需要为此配置 Maven 环境,请确保是在以下版本环境中安装使用:

  • 64 bit JDK 1.8+;
  • Maven 3.2.x+;

搭建分布式服务

  1. 基于 SpringBoot 的父 maven 项目

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.13</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <packaging>pom</packaging>

    <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
    </properties>

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

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>2.6.13</version>
    </plugin>
    </plugins>
    </build>
  2. 创建 2 个服务(订单服务 order 和库存服务 stock)

服务之间的通信

1
2
3
4
5
6
@RequestMapping("/add")
public String add() throws URISyntaxException {
System.out.println("下单成功!");
String message = restTemplate.getForObject(new URI("http://127.0.0.1:8011/stock/reduct"), String.class);
return "下单成功:" +message;
}

系统之间耦合度变高,调用关系错综复杂,难以维护

SpringCloudAibaba 引入

  • 版本说明
  • 由于 Spring Boot 3.0,Spring Boot 2.7~2.4 和 2.4 以下版本之间变化较大,目前企业级客户老项目相关 Spring Boot 版本仍停留在 Spring Boot 2.4 以下,为了同时满足存量用户和新用户不同需求,社区以 Spring Boot 3.0 和 2.4 分别为分界线,同时维护 2022.x、2021.x、2.2.x 三个分支迭代。如果不想跨分支升级,如需使用新特性,请升级为对应分支的新版本。 为了规避相关构建过程中的依赖冲突问题,我们建议可以通过 云原生应用脚手架 进行项目创建。

2021.x 分支

适配 Spring Boot 2.4,Spring Cloud 2021.x 版本及以上的 Spring Cloud Alibaba 版本按从新到旧排列如下表(最新版本用*标记):

Spring Cloud Alibaba Version Spring Cloud Version Spring Boot Version
2021.0.5.0* Spring Cloud 2021.0.5 2.6.13
2021.0.4.0 Spring Cloud 2021.0.4 2.6.11
2021.0.1.0 Spring Cloud 2021.0.1 2.6.3
2021.1 Spring Cloud 2020.0.1 2.4.2

父项目中加入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<spriing-boot.version>2.6.13</spriing-boot.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spriing-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Nacos 注册中心

什么是 Nacos

官方: 一个更易于构建云原生应用的 动态服务发现(Nacos Discovery )、服务配置(Nacos Confg)和服务管理平台。

  • 服务发现和服务健康监测
  • 动态配置服务
  • 动态 DNS 服务
  • 服务及其元教据管理

Nacos 注册中心:管理所有微服务、解决微服务之间调用关系错综复杂、难以维护的问题;

注册中心演变及其设计思想

订单服务扩展迁移后,ip 地址会变化,采用注册表维护服务地址

如果订单服务水平扩展,有性能问题,需要解决负载均衡的问题。可以使用 Nginx。

nginx 不能实时监控服务状态,手动维护服务列表比较麻烦,出现了注册中心

每次都要去注册中心拉取订单服务列表吗?注册中心宕机了怎么办?如果拉取的订单服务对应的机器宕机了怎么办?

可以采用心跳机制和定时拉取服务列表,通过客户端负载均衡控制

安装 Nacos

官网:https://nacos.io/zh-cn/docs/quick-start.html

1
2
3
4
nacos/nacos-server: latest
docker rm -f nacos
# Nacos2.0 版本相比 1.X 新增了 gRPC 的通信方式,因此需要增加 2 个端口 9848 9849
docker run --name nacos -e MODE=standalone -p 8848:8848 -p 9848:9848 -p 9849:9849 -d nacos/nacos-server:v2.2.0

Nacos Discovery Starter

https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-discovery 默认 AP

  • 服务注册: Nacos Clint 会通过发送 REST 请求的方式向 Nacos Sever 注册自己的服务,提供自身的元数据,比如 ip 地址、端口等信息。Nacos Server 接收到注册请求后,就会把这些元款据信息存储在一个双层的内存 Map 中。
  • 服务心跳: 在服务注册后,Nacos Client 会维护一个定时心跳来持续通知 Nacos Server,说明服务一直处于可用状态,防止被剔除。默认 5s 发送一次心跳。
  • 服务同步: Nacos Server 集群之间会互相同步服务实例,用来保证服务信息的一致性。leader raft
  • 服务发现 ︰服务消费者(Naoos Clent)在调用服务提供者的服务时,会发送一个 REST 求给 Nacos Sever, 获取上面注册的服务清单,并且缓存在 Nacos Cient 本地,同时会在 Nacos Clien 本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存
  • 服务健康检查: Naoos Stver 会开启一个定时任务用来检查注册服务实的健康情况,对于超过 15s 没有收到客户端心跳的实例会将它的 healthy 属性置为 fase(客户端服务发现时不会发现),如果某个实例超过 30 秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)

主流的注册中心

  • 一致性类型:通常有 CP(强一致性)、AP(最终一致性)和 CP/AP(部分顺序一致性),其中 CP 和 AP 是分布式系统中两种普遍的一致性模型。CP 注册中心通常能够提供强一致性保证,但是可能会牺牲可用性;而 AP 注册中心则追求高可用性和分区容忍性,但是会牺牲一部分一致性。
  • 一致性级别:表示注册中心提供的一致性保证级别,通常有强一致性、最终一致性和部分顺序一致性等级别。
  • 支持数据类型:表示注册中心支持的数据类型,通常有键值对、队列、分布式锁、健康检查、DNS 服务、配置等。不同的注册中心提供的数据类型和功能有所不同。
  • 备注:CAP C: 一致性 / A: 可用性 / P: 分区容错性。

需要注意的是,选择适合自己的注册中心主要取决于具体的业务场景和需求,需要根据实际情况做出选择。

搭建 Nacos-client 服务

引入依赖

父 pom 依赖中引入 Springcloud alibaba 依赖后 ,在项目中引入依赖

  1. nacos 2021 版本已经没有自带 ribbon 的整合,所以需要引入另一个支持的 jar 包 loadbalancer
  2. nacos 2021 版本已经取消了对 ribbon 的支持,所以无法通过修改 Ribbon 负载均衡的模式来实现 nacos 提供的负载均衡模式
1
2
3
4
5
6
7
8
9
10
11
<!-- 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>

修改配置文件

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 8010
spring:
application:
name: order-service
cloud:
nacos:
server-addr: 192.168.61.10:8848
discovery:
username: nacos
password: nacos
namespace: public

查看服务

启动服务后,通过日志可以看到注册到注册中心

1
2023-06-18 22:11:13.995  INFO 13668 --- [           main] c.a.c.n.registry.NacosServiceRegistry    : nacos registry, DEFAULT_GROUP order-service 192.168.61.1:8010 register finished

服务之间调用

注册 bean 的同时,添加 LoadBalanced 负载均衡注解,到这一步为止,可以实现基本的负载均衡功能,负载均衡默认配置为 轮询 配置

1
2
3
4
5
@Bean
@LoadBalanced //负载均衡注解
public RestTemplate restTemplate(){
return new RestTemplate();
}

可以通过注册的服务名称来调用

1
2
3
4
5
6
7
@RequestMapping("/add")
public String add() throws URISyntaxException {
System.out.println("下单成功!");
String message = restTemplate.getForObject(new URI("http://stock-service/stock/reduct"), String.class);
return "下单成功:" +message;

}

雪崩保护

保护阈值: 设置 0-1 之间的值 0.5
临时实例: spring.cloud.nacos.discovery.ephemeral =false,当服务宕机了也不会从服务列表中剔除

健康实例数/总实例数 < 保护阈值 ,会将不健康的实例也提供服务

服务雪崩: 服务全部不可用

注册中心配置项

配置项 Key 默认值 说明
服务端地址 pring.cloud.nacos.discovery.server-addr Nacos Server 启动监听的 ip 地址和端口
服务名 spring.cloud.nacos.discovery.service ${spring.application.name} 给当前的服务命名
服务分组 spring.cloud.nacos.discovery.group DEFAULT_GROUP 设置服务所处的分组
权重 spring.cloud.nacos.discovery.weight 1 取值范围 1 到 100,数值越大,权重越大
网卡名 spring.cloud.nacos.discovery.network-interface 当 IP 未配置时,注册的 IP 为此网卡所对应的 IP 地址,如果此项也未配置,则默认取第一块网卡的地址
注册的 IP 地址 spring.cloud.nacos.discovery.ip 优先级最高
注册的端口 spring.cloud.nacos.discovery.port -1 默认情况下不用配置,会自动探测
命名空间 spring.cloud.nacos.discovery.namespace 常用场景之一是不同环境的注册的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。
AccessKey spring.cloud.nacos.discovery.access-key 当要上阿里云时,阿里云上面的一个云账号名
SecretKey spring.cloud.nacos.discovery.secret-key 当要上阿里云时,阿里云上面的一个云账号密码
Metadata spring.cloud.nacos.discovery.metadata 使用 Map 格式配置,用户可以根据自己的需要自定义一些和服务相关的元数据信息
日志文件名 spring.cloud.nacos.discovery.log-name
集群 spring.cloud.nacos.discovery.cluster-name DEFAULT 配置成 Nacos 集群名称
接入点 spring.cloud.nacos.discovery.enpoint UTF-8 地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址
是否集成 Ribbon ribbon.nacos.enabled true 一般都设置成 true 即可
是否开启 Nacos Watch spring.cloud.nacos.discovery.watch.enabled true 可以设置成 false 来关闭 watch
注册的 IP 地址类型 spring.cloud.nacos.discovery.ip-type IPv4 可以配置 IPv4 和 IPv6 两种类型
临时实例 spring.cloud.nacos.discovery.ephemeral true 当服务心跳停止,移除实例

负载均衡器 Ribbon

什么是 Ribbon

目前主流的负载方案分为以下两种:

  • 集中式负载均衡,在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的(比如 F5),也有软件的(比如 Nginx).
  • 客户端根据自己的请求情况做负载均衡,Ribbon 就属于客户端自己做负载均衡,

Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套 客户端的负载均衡工具,Ribbon 客户端组件提供一系例的完善的配置,如超时,重试等。通过 Load Balancer 获取到服务提供的所有机器实例,Ribbon 会自动基于某种规则(轮询,随机)去调用这些服务。Ribbon 也可以实现我们自己的负载均衡算法。

客户端的负载均衡

例如 sping cloud 中的 Ribbon,客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡; 即在客户端就进行负载均衡算法分配.

服务端的负载均衡

例如 Nginx,通过 Nginx 进行负载均衡,先发送请求,然后通过负载均衡算法,在多个服务器之间选择一个进行访问; 即在服务器端再进行负载均衡算法分配。

常见负载均衡算法

  • 随机,通过随机选择服务进行执行,一般这种方式使用较少;
  • 轮训,负载均衡默认实现方式,请求来之后排队处理;
  • 加权轮训,通过对服务器性能的分型,给高配置,低负载的服务器分配更高的权重,均衡各个服务器的压力;。
  • 地址 Hash,通过客户端请求的地址的 HASH 值取模映射进行服务器调度。ip —> hash
  • 最小链接数,即使请求均衡了,压力不一定会均衡,最小连接数就是根据服务器的情况,比如请求积压数等参数,将请求分配到当前压力最小的服务器上。最小活跃数

Nacos 使用 Ribbon

引入依赖

由于 Netflix Ribbon 进入停更维护阶段,因此 SpringCloud 2020.0.1 版本之后 删除了 eureka 中的 ribbon, 替代 ribbon 的是 spring cloud 自带的 LoadBalancer,默认使用的是轮询的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<!-- 将ribbon排除 -->
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加loadbalancer依赖 新版本的 Nacos discovery 都已经移除了 Ribbon ,此时我们需要引入 loadbalancer 代替,才能调用服务提供者提供的服务
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

添加 @LoadBalanced 注解

1
2
3
4
5
6
7
8
@Configuration
public class MyConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}

修改 Conroller 接口, 使用服务名调用服务

1
2
3
4
5
6
7
8
9
10
11
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/add")
public String add() throws URISyntaxException {
System.out.println("下单成功!");
String message = restTemplate.getForObject(new URI("http://stock-service/stock/reduct"), String.class);
return "下单成功:" +message;

}
}

Ribbon 负载均衡策略

IRule 接口实现类结构: 在下图中,AbstractLoadBalancerRule 是负载均衡策略的抽象类,该抽象类中定义了负载均衡器 ILoaderBalancer 对象,该对象能够在具体实现选择服务策略时,获取到一些负载均衡器中维护的信息作为分配依据,并以此设计一些算法来实现针对特定场景的高效策略。

1.轮询策略

轮询策略:RoundRobinRule,按照一定的顺序依次调用服务实例。比如一共有 3 个服务,第一次调用服务 1,第二次调用服务 2,第三次调用服务 3,依次类推。此策略的配置设置如下:

1
2
3
springcloud-nacos-provider: # nacos中的服务id
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #设置负载均衡

2.权重策略

权重策略:WeightedResponseTimeRule,根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。它的实现原理是,刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大。此策略的配置设置如下:

1
2
3
springcloud-nacos-provider: # nacos中的服务id
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule

3.随机策略

随机策略:RandomRule,从服务提供者的列表中随机选择一个服务实例。此策略的配置设置如下:

1
2
3
springcloud-nacos-provider: # nacos中的服务id
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #设置负载均衡

4.最小连接数策略

最小连接数策略:BestAvailableRule,也叫最小并发数策略,它是遍历服务提供者列表,选取连接数最小的⼀个服务实例。如果有相同的最小连接数,那么会调用轮询策略进行选取。此策略的配置设置如下:

1
2
3
springcloud-nacos-provider: # nacos中的服务id
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #设置负载均衡

5.重试策略

重试策略:RetryRule,按照轮询策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回 null。此策略的配置设置如下:

1
2
3
4
5
6
ribbon:
ConnectTimeout: 2000 # 请求连接的超时时间
ReadTimeout: 5000 # 请求处理的超时时间
springcloud-nacos-provider: # nacos 中的服务 id
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #设置负载均衡

6.可用性敏感策略

可用敏感性策略:AvailabilityFilteringRule,先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例。此策略的配置设置如下:

1
2
3
springcloud-nacos-provider: # nacos中的服务id
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.AvailabilityFilteringRule

7.区域敏感策略

区域敏感策略:ZoneAvoidanceRule,根据服务所在区域(zone)的性能和服务的可用性来选择服务实例,在没有区域的环境下,该策略和轮询策略类似。此策略的配置设置如下:

1
2
3
springcloud-nacos-provider: # nacos中的服务id
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.ZoneAvoidanceRule

修改默认策略

  1. 配置类方式:如果要使用 RibbonClient 单独配置服务策略,配置类不能放到被 ComponentScan 扫描的包下,否则会被 RibbonClient 共享

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Configuration
    public class RibbonConfiguration {
    /**
    * 指定全局的负载均衡器
    * @return
    */
    @Bean
    public IRule ribbonRule() {
    // 配置 Ribbon 负载均衡策略为轮询
    return new RoundRobinRule();
    }
    }

    利用 @RibbonClient 指定微服务及其负载均衡策略

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @SpringBootApplication
    @EnableDiscoveryClient
    // @RibbonClients(defaultConfiguration = RibbonConfiguration.class)
    @RibbonClients(value = {
    @RibbonClient(name = "order-server",configuration = RibbonConfiguration.class)
    })
    public class OrderApplication {
    public static void main(String[] args) {
    SpringApplication.run(OrderApplication.class, args);
    }

    }

  2. 配置文件方式: 调用指定微服务提供的服务时,使用对应的负载均衡算法修改 application.yml

    1
    2
    3
    4
    5
    6
    7
    配置文件
    #被调用的微服务名
    mall-order :
    ribbon:
    #指定使用Nacos提供的负载均衡策略(优先调用同一集群的实例,基于随机&权重)
    NFLoadBalancerRuleclassName: com.alibaba.cloud.nacos.ribbon.NacosRule

自定义负载均衡策略

官方文档指出:自定义的负载均衡配置类不能放在 @componentScan 所扫描的当前包下及其子包下,否则我们自定义的这个配置类就会被所有的 Ribbon 客户端所共享,也就是说我们达不到特殊化定制的目的了;

要求自定义的算法:依旧是轮询策略,但是每个服务器被调用 5 次后轮到下一个服务,即以前是每个服务被调用 1 次,现在是每个被调用 5 次。

自定义算法类必须继承 AbstractLoadBalanceRule

启动类在 pers.fulsun.order 包下,所以我们新建一个包: pers.fulsun.myrule,并在该包下新建一个类:CustomeRule

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class CustomeRule extends AbstractLoadBalancerRule
{
/*
total = 0 // 当 total == 5 以后,我们指针才能往下走,
index = 0 // 当前对外提供服务的服务器地址,
total 需要重新置为零,但是已经达到过一个 5 次,我们的 index = 1
*/

private int total = 0; // 总共被调用的次数,目前要求每台被调用 5 次
private int currentIndex = 0; // 当前提供服务的机器号

public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;

while (server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers(); //当前存活的服务
List<Server> allList = lb.getAllServers(); //获取全部的服务

int serverCount = allList.size();
if (serverCount == 0) {
return null;
}

//int index = rand.nextInt(serverCount);
//server = upList.get(index);
if(total < 5)
{
server = upList.get(currentIndex);
total++;
}else {
total = 0;
currentIndex++;
if(currentIndex >= upList.size())
{
currentIndex = 0;
}
}

if (server == null) {
Thread.yield();
continue;
}

if (server.isAlive()) {
return (server);
}

// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}

@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}

@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}

配置类中增加自定义规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class ConfigBean
{
@Bean
@LoadBalanced //Ribbon 是客户端负载均衡的工具;
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}

@Bean
public IRule myRule()
{
return new CustomeRule(); //自定义负载均衡规则
}
}

主启动类添加 @RibbonClient 注解,name 和 configuration 参数很重要;

1
2
@RibbonClient(name="stock-service", configuration=ConfigBean.class)
// name 指定针对哪个服务 进行负载均衡,而 configuration 指定负载均衡的算法具体实现类。

开启饥饿加载

Ribbon 默认是采用懒加载,即第一次访问时才会去创建 LoadBalanceClient,请求时间会很长。

而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

源码对应属性配置类: RibbonEagerLoadProperties

1
2
3
4
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: userservice # 指定对userservice这个服务饥饿加载,多个使用逗号分隔

负载均衡器 LoadBalancer

什么是 Spring Cloud LoadBalancer

Spring Cloud LoadBalancer 是 Spring Cloud 官方自己提供的客户端负载均衡器, 用来替代 Ribbon。

Spring 将项目标记归档迁移到了 Spring Cloud Common 下

Spring 官方提供了两种负载均衡的客户端:

  • RestTemplate: ResTremplares 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问 http 服务的方法,能够大大提高客户端的编写效率。默认情况下,RestTemplate 默认依赖 JDK 的 HTTP 连接工具
  • webClient: webClient 是从 Spring WebFlus 5.0 版本开始提供的一个非阻塞的基于响应式编程的进行 http 请求的客户端工具。它的响应式编程基于 Reactor, webClient 中提供了标准 Http 请求方式对应的 get, post, put, delete 等方法, 可以用来发起相应的请求。

RestTemplate 整合 LoadBalancer

1
2
3
4
5
<!-- 需要引入 Spring cloud的版本管理 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

注意: nacos 2021 版本之前 nacos-discovery 中引入了 ribbon,需要移除 ribbon 的包如果不移除,也可以在 yml 中配置不使用 ribbon。

1
2
3
4
5
6
7
8
9
10
11
spring:
application:
#name: test-server #服务提供者
name: test-service #消费者
cloud:
nacos:
discovery:
server-addr: 192.168.61.10:8848
loadbalancer:
ribbon:
enabled: false #禁用ribbon

可以通过设置 spring.cloud.loadbalancer.enabled 的值为 false 来禁用 Spring Cloud LoadBalancer。

自定义策略

在 Spring 配置文件中注入 Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class MyConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}

@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);

// 返回内容为自定义负载均衡的配置类
return new CustomRule(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}

自定义负载均衡实现需要实现 ReactorServiceInstanceLoadBalancer 接口 以及重写 choose 方法

1

配置我们自定义的 LoadBalancer 策略, 这里的类为注入 Bean 的类,而非负载均衡的实现类

1
2
@LoadBalancerClients(defaultConfiguration = {NacosSameClusterConfiguration.class})

Spring Cloud LoadBalancer 原理

LoadBalancerClient

LoadBalancerClient 作为负载均衡客户端,用于进行负载均衡逻辑,从服务列表中选择出一个服务地址进行调用,其内部方法为:

1
2
3
4
5
6
7
8
publicinterfaceLoadBalancerClientextendsServiceInstanceChooser {

<T> T execute(String serviceId, LoadBalancerRequest<T> request)throws IOException;

<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request)throws IOException;

URI reconstructURI(ServiceInstance instance, URI original);
}

在 LoadBalancerClient 中存在两个 executor()方法,都是用于执行请求的,reconstructURI()方法用来重构 URL,LoadBalancerClient 的默认实现类为 BlockingLoadBalancerClient,BlockingLoadBalancerClient 对象中存在两个 choose()方法,分别实现重写了 ServiceInstanceChooser 的两个 choose()方法。

微服务调用组件 Feign

JAVA 中如何实现接口调用

  1. Httpclient: Httpclient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 Http 协议的客户端编程工具包,并且它支持 HTTP 协议最新版本和建议。HttpClient 相比传统 JDK 自带的 URL Connection,提升了易用性和灵活性,是客户端发送 HTTP 请求变得容易,提高了开发的效率。
  2. Okhttp: 一个处理网络请求的开源项目,是安卓端最火的轻量级框架,由 Square 公司贡献,用于代替 HttpUrlConnection 和 Apache HttpClient。OkHttp 拥有简洁的 API、高校的性能,并支持多种协议(HTTP/2 和 SPDY)
  3. HttpURLConnection: HttpURLConnection 是 Java 的标准类,它继承自 URLConnection,可用于向指定网站发送 GET 请求、POST 请求。HttpURLConection 使用比较复杂,不向 HttpClient 那样容易使用。
  4. RestTemplate、WbClient: RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程 HTTP 服务的方法,能够大大提高客户端的编写效率。

什么是 Feign

Feign 是 Nexflix 开发的声明式、模板化的 HTTP 客户端,其灵感来自 Retrofit、JAXRS-2.0 以及 WebSocket。Feign 可帮助我们更加便捷、优雅地调用 HTTP API。定义在服务的消费端。

Spring Cloud openfeign 对 Felgn 进行了增强,使其支持 Spring MVC 注解,另外还整合了 Ribbon 和 Nacos,从而使得 Feign 的使用更加方便,

优势

Feign 可以做到 使用 HTTP 请求远程服务时就像调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个 HTTP 请求。它像 Dubbo 一样,consumer 直接调用接口方法调用 provider,而不是要通过常规的 HttpClient 构造请求解析返回数据。它解决了让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发。(简单地说就是 A 服务的 Service 接口去调用别的 B 服务的 Controller 接口)

Spring Cloud Alibaba 快速整合 Opne Feign

消费服务引入依赖

1
2
3
4
5
<!--openfeign远程调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

新建 Feign 接口: 编写调用接口+@FeignClient 注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package pers.fulsun.alibabacloud.order.feignclient;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

/**
* name 指定远程调用的接口所对应的服务名
* path 指定远程调用的接口所对应的类的@RequestMapping
*/
@FeignClient(name = "stock-service", path = "/stock")
public interface StockFeignService {
@RequestMapping("/reduct")
String reduct();
}

调用端在启动类上添加 @EnableFeignClients

1
2
3
4
5
6
7
8
9
10
11
12
13
package pers.fulsun.alibabacloud.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

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

发起调用,像调用本地方法一样调用远程服务,编写订单 Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package pers.fulsun.alibabacloud.order.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pers.fulsun.alibabacloud.order.feignclient.StockFeignService;

@RestController
@RequestMapping("/order")
public class OrderController {

@Autowired
StockFeignService stockFeignService;

@Autowired
private RestTemplate restTemplate;


@RequestMapping("/add")
public String add2() {
System.out.println("下单成功!");
// String message = restTemplate.getForObject(new URI("http://stock-service/stock/reduct"), String.class);
String reduct = stockFeignService.reduct();
return "Hello Feign" + reduct;
}
}

Feign 的自定义配置和使用

Feign 提供了很多的扩展机制, 让用户可以更加灵活的使用。

日志配置

有时候我们遇到 Bug,比如接口调用失败、参数没收到等问题,或者想看看调用性能,就需要配置 Feign 的日志了,以此让 Feign 把请求信息输出来。

配置类方式

定义一个配置类,指定日志级别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package pers.fulsun.alibabacloud.order.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;

/**
* 全局配置: 当使用 @Configuration 会将配置作用所有的服务提供方
* 局部配置: 如果只想针对某一个服务进行配置,就不要加 @Configuration
*/
public class FeignConfig {

@Bean
public Logger.Level feiginLoggerLevel(){
return Logger.Level.FULL;
}
}

通过源码可以看到日志等级有 4 种,分别是:

  • NONE【性能最佳, 适用于生产】: 不记录任何日志(〔默认值)。
  • BASIC【适用于生产环境追踪问题】∶仅记录请求方法、URL、响应状态代码以及执行时间。
  • HEADERS: 记录 BASIC 级别的基础上,记录请求和响应的 header。
  • . FULL【比较适用于开发及测试环境定位问题】∶记录请求和响应的 header、body 和元数据。

局部配置,让调用的微服务生效,在@FeignClient 注解中指定使用的配置类

1
2
3
4
5
@FeignClient(name = "stock-service", path = "/stock",configuration = FeignConfig.class)
public interface StockFeignService {
@RequestMapping("/reduct")
String reduct();
}

SpringBoot 默认的日志级别为 info, Feign 的 debug 日志级别不会输出, 需要调整

1
2
3
logging:
level:
pers.fulsun.alibabacloud.order.feignclient: debug

配置文件方式

1
2
3
4
5
6
7
8
9
10
# 指定Feignclient下的日志级别
logging:
level:
pers.fulsun.alibabacloud.order.feignclient: debug
feign:
client:
config:
# 对应服务名
stock-service:
loggerLevel: FULL

契约配置

Sping Cloud 在 Feign 的基础上做了扩展,使用 Sping MVC 的注解来完成 Feign 的功能。原生的 Feign 是不支持 Sping MVC 注解的,如果你想在 Sping Cloud 中使用原生的注解方式来定义客户端也是可以的,通过配置契约来改变这个配置,Spring Cloud 中默认的是 SpringMvcContract。

Spring Cloud 1 早期版本就是用的原生 Fegin。随着 netflix 的停更替换成了 Open feign

配置类方式

修改契约配置,支持 Feign 原生的注解

1
2
3
4
5
6
import feign.Contract;

@Bean
public Contract feignContract(){
return new Contract.Default();
}

配置文件方式

1
2
3
4
5
6
7
feign:
client:
config:
# 对应服务名
stock-service:
loggerLevel: FULL
contract: feign.Contract.Default # 设置为默认的契约

注意∶修改契约配置后,OrderFeignService 不再支持 springmwc 的注解,需要使用 Feign 原生的注解

1
2
3
4
5
6
7
8
9
10
11
12
import feign.Param;
import feign.RequestLine;
import org.springframework.cloud.openfeign.FeignClient;

@FeignClient(name = "stock-service", path = "/stock")
public interface StockFeignService {
@RequestLine("GET /reduct")
String reduct();

@RequestLine("GET /reduct/{id}")
String reduct2(@Param("id") int id);
}

超时时间

通过 OPtions 可以配置连接超时时间和读取超时时间,Options 的第一个参数是连接的超时时间(ms),默认值是 2s; 第二个是处理的超时时间(ms),默认是 5s。

配置类方式

1
2
3
4
5
6
7
8
9
10
11
import feign.Request;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {

@Bean
public Request.Options options() {
return new Request.Options(2000, 5000);
}
}

配置文件方式

Feign 的底层使用的是 Ribbon,但超时是按照 Feign 配置为准

1
2
3
4
5
6
7
8
9
feign:
client:
config:
# 对应服务名
stock-service:
loggerLevel: FULL
contract: feign.Contract.Default # 设置为默认的契约
connecTimeout: 2000 # 连接超时时间 默认2s
readTimeout: 5000 # 请求处理超时时间 默认5s

测试超时

修改库存服务的处理时间

1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("/stock")
public class StockController {
@RequestMapping("/reduct")
public String reduct() {
ThreadUtils.sleep(5000);
return "扣减库存成功";
}
}

调用库存服务,会得到

自定义拦截器

springmvc 拦截器是客户端到服务端进行拦截

feign 拦截器是消费端到消费提供端进行拦截

定义拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package pers.fulsun.alibabacloud.order.interceptor.feign;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CustomFeignInterceptor implements RequestInterceptor {
private Logger log = LoggerFactory.getLogger(CustomFeignInterceptor.class);

@Override
public void apply(RequestTemplate requestTemplate) {
// todo 记录认知 授权认证
requestTemplate.header("X-Forwarded-For", "origin.host.com");
log.info("feign 拦截器执行完成");
}
}

配置类指定

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class FooConfiguration {
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}

@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}

配置文件指定

1
2
3
4
5
6
7
8
9
10
11
feign:
client:
config:
# 对应服务名
stock-service:
loggerLevel: FULL
contract: org.springframework.cloud.openfeign.support.SpringMvcContract # feign.Contract.Default 设置为默认的契约
connecTimeout: 2000 # 连接超时时间 默认2s
readTimeout: 5000 # 请求处理超时时间 默认5s
requestInterceptors:
- pers.fulsun.alibabacloud.order.interceptor.feign.CustomFeignInterceptor

Nacos 配置中心

官方文档: https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config
Nacos 提供用于存储配置和其他元数据的 key vale 存储,为分布式系统中的外部化配置提供服务器端和客户端支持。使用 Sping Cloud Alibaba Nacos Config ,您可以在 Nacos Server 集中管理你 Spring Cloud 应用的外部属性配置。

Spring Cloud Alibaba Nacos Config 是 Spring Cloud Config Server 和 Client 的替代方案,客户端和服务器上的概念与 Spring Environment 和 PropertySource 有着一致的抽象,在 特殊的 bootstrap 阶段( SpringBoot 启动阶段),配置被加载到 Spring 环境中。当应用程序通过部署管道从开发到测试再到生产时,您可以管理这些环境之间的配置,并确保应用程序具有迁移时需要运行的所有内容

接入配置中心

以上面项目的服务消费者 order 为例,先增加如下依赖

1
2
3
4
5
6
7
8
9
10
<!-- nacos 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--Spring Cloud 新版本默认将 Bootstrap 禁用,需要将依赖引入到工程中-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

增加一个 controller

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RequestMapping("/order")
public class ConfigController {
@Value("${user.name}")
private String username;

@GetMapping("/config")
public String config() {
return "hello config: " + username;
}
}

在 nacos 的控制面板-配置管理-配置列表中, 创建配置。将 application.yml 中的配置信息维护到 nacos 上。添加定义的 user.name 属性。

将项目内部的 yml 配置文件删除,新建一个 bootstrap.properties 的配置文件,在 bootstrap.properties 中配置 Nacos server 的地址和应用名

注意: Spring Boot 配置文件的加载顺序,依次为 bootstrap.properties -> bootstrap.yml -> application.properties -> application.yml ,其中 bootstrap.properties 配置为最高优先级

1
2
3
4
5
6
#配置文件的DataId
spring.application.name=order-service
spring.cloud.nacos.config.server-addr=192.168.61.131:8848,192.168.61.132:8848,192.168.61.133:8848
spring.cloud.nacos.config.prefix=order-service
#DataId的拓展名
spring.cloud.nacos.config.file-extension=yaml

访问 url: http://localhost: 8010/order/config

最佳实践:

Namespace: 代表不同环境,如开发,测试

Group: 代表项目,如课程项目,电商项目

DataId: 每个项目下往往有若干个工程(为服务),每个配置集(DataId)是一个工程的主配置文件

dataId

在 Nacos Spring Cloud 中,dataId 的完整格式如下:

1
${prefix}-${spring.profiles.active}.${file-extension}
  • prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix 来配置。
  • spring.profiles.active 即为当前环境对应的 profile,详情可以参考 Spring Boot 文档注意:当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}
  • file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 propertiesyaml 类型。
  1. 通过 Spring Cloud 原生注解 @RefreshScope 实现配置自动更新:

@RefreshScope 动态刷新

回到 nacos 配置中心,修改 consumer 的配置文件中的 user.name 属性, 重新获取后还是原来的值

Bean 的作用域:

作用域 描述
singleton(单例) 每一个 Spring IoC 容器都拥有唯一的一个实例对象(默认作用域)
prototype(原型) 一个 Bean 定义,任意多个对象
request(请求) 每一个 HTTP 请求都有自己的 Bean 实例(只在基于 web 的 Spring ApplicationContext 中可用)
session(会话) 一个 Bean 的作用域为 HTTPsession 的生命周期(只有基于 web 的 Spring ApplicationContext 才能使用)
global session(全局会话) 一个 Bean 的作用域为全局 HTTPSession 的生命周期。通常用于门户网站场景(只有基于 web 的 Spring ApplicationContext 才能使用)

c’c’c’c’c’c 例如创建 Scope = singleton 的 Bean 时,IOC 会保存实例在一个 Map 中,保证这个 Bean 在一个 IOC 上下文有且仅有一个实例。

SpringCloud新增了一个自定义的作用域:refresh(可以理解为“动态刷新”),同样用了一种独特的方式改变了 Bean 的管理方式,使得其可以通过外部化配置(.properties)的刷新,在应用不需要重启的情况下热加载新的外部化配置的值。

RefreshScope 主要做了以下动作:

  1. 单独管理 Bean 生命周期
    创建 Bean 的时候如果是 RefreshScope 就缓存在一个专门管理的 ScopeMap 中,这样就可以管理 Scope 是 Refresh 的 Bean 的生命周期了(所以含 RefreshScope 的其实一共创建了两个 bean)。
  2. 重新创建 Bean
    外部化配置刷新之后,会触发一个动作,这个动作将上面的 ScopeMap 中的 Bean 清空,这样这些 Bean 就会重新被 IOC 容器创建一次,使用最新的外部化配置的值注入类中,达到热加载新值的效果。

修改 Controller 文件:@RefreshScope 注解功能为动态刷新,能够动态刷新使用托管到 nacos 配置中心的配置文件的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.cloud.context.config.annotation.RefreshScope;

@RefreshScope
@RestController
@RequestMapping("/order")
public class ConfigController {
@Value("${user.name}")
private String username;

@GetMapping("/config")
public String config() {
return "hello config: " + username;
}
}

多环境配置

项目开发的时候,生产环境和测试环境的一些配置可能会不一样,有时候一些功能也可能会不一样,所以可能会在上线的时候手工修改这些配置信息。开发环境的配置数量并不是越多越好,一般而言有如下几种:

  • dev:开发环境

  • test:测试环境

  • prod:生成环境

  • pre:预发布环境

Spring 为我们提供了 Spring Boot Profile 这个功能(Maven 也为我们提供了 Maven Profile)。我们只需要在启动的时候添加一个虚拟机参数,激活自己环境所要用的 Profile 就可以了。操作起来很简单,只需要为不同的环境编写专门的配置文件,如:application-dev.ymlapplication-prod.yml, 启动项目时只需要增加一个命令参数 --spring.profiles.active=环境配置 即可

1
java -jar 1.0.0-SNAPSHOT.jar --spring.profiles.active=prod

跟服务名相同的 dataid 的配置文件,称之为默认的配置文件

除了默认的配置文件,其他的配置文件必须写上后缀

配置文件的优先级: 指定 profile > 默认配置文件,优先级大的会覆盖优先级小的,并且会形成互补

Nacos Config Profile

spring-cloud-starter-alibaba-nacos-config 在加载配置的时候,不仅仅加载了以 DataId 为 ${spring.application.name}.${file-extension:properties} 为前缀的基础配置,还加载了 DataId 为 ${spring.application.name}-${profile}.${file-extension:properties} 的基础配置。

如果遇到多套环境下的不同配置,可以通过 Spring 提供的 ${spring.profiles.active} 这个配置项来配置。在 nacos 配置中心新增两个配置 order-service-prod.yamlorder-service.yaml,这里调整端口号

在提供者中配置中心进行读取的配置文件 bootstrap.properties

1
2
3
4
5
6
7
8
# 指定profiles
spring.profiles.active=prod
#配置文件的DataId
spring.application.name=order-service
spring.cloud.nacos.config.server-addr=192.168.61.131:8848,192.168.61.132:8848,192.168.61.133:8848
spring.cloud.nacos.config.prefix=order-service
#DataId的拓展名
spring.cloud.nacos.config.file-extension=yaml

指定 DataId

配置文件的优先级: 优先级大的会覆盖优先级小的,且形成互补

profile > 默认配置文件 > extension-configs > shared-configs [下标越大优先级越大]

1
# bootstrap.yml

Sentinel 流量防卫

什么是 Sentinel

Sentinel 是以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性的框架。为了防止微服务架构中的 服务雪崩 问题

Sentinel 的组成

  • 核心库 ( Java 客户端 ):不依赖任何框架 / 库,同时对 Dubbo / Spring Cloud 等框架也有支持

  • 控制台:基于 SpringBoot,打包后可直接运行,不需要运行额外的容器

服务雪崩

在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以通过 HTTP/RPC 相互调用,在 Spring Cloud 中可以用 RestTemplate + LoadBalanceClientFeign 来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证 100% 可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet 容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的 “雪崩” 效应。为了解决这个问题,业界提出了 熔断器模型

较底层的服务如果出现故障,会导致 雪崩。通过对特定的服务的调用,如果其不可用达到一个阀值,熔断器将会被打开。熔断器打开后,为了避免连锁故障,通过 fallback 方法可以直接返回一个固定值,这就是服务熔断

Sentinel 特点

  • 丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷(削峰填谷:对于突然到来的大量请求,通过配置流控规则,以稳定的速度逐步处理这些请求,从而避免流量突刺造成系统负载过高)、集群流量控制、实时熔断下游不可用应用等

  • 完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况

  • 广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架 / 库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel

  • 完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等

Sentinel 控制台

Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。另外,鉴权在生产环境中也必不可少。这里,我们将会详细讲述如何通过简单的步骤就可以使用这些功能。Sentinel 控制台最少应该包含如下功能

  • 查看机器列表以及健康情况: 收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。

  • 监控 (单机和集群聚合): 通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。

  • 规则管理和推送: 统一管理推送规则。

  • 鉴权: 生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。

启动控制台

官方 GitHub Release 页面 页面下载最新版本的控制台 JAR 包。 注意:启动 Sentinel 控制台需要 JDK 1.8 及以上版本。注意:从 Sentinel 1.6.0 开始,控制台需要登录,默认用户名和密码都是 sentinel

1
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.6.jar

用户可以通过如下参数进行配置

  • -Dsentinel.dashboard.auth.username=sentinel 用于指定控制台的登录用户名为 sentinel
  • -Dsentinel.dashboard.auth.password=123456 用于指定控制台的登录密码为 123456;如果省略这两个参数,默认用户和密码均为 sentinel
  • -Dserver.servlet.session.timeout=7200 用于指定 Spring Boot 服务端 session 的过期时间,如 7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟

Sentinel 客户端接入

引入 pom 文件,服务端与消费端都要引入

1
2
3
4
5
<!-- Sentinel 客户端 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

修改配置文件, 开启 sentinel 功能,这里以消费者为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
spring:
application:
name: service-provider
cloud:
nacos:
discovery:
server-addr: 192.168.61.131:8848
# 熔断限流
sentinel:
enabled: true # 是否开启。默认为 true 开启
eager: true # 是否饥饿加载。默认为 false 关闭,在首次使用到 Sentinel 才进行初始化
transport:
# sentinel 控制台端口
dashboard: 192.168.61.130:8080

#开启 fegin 对 sentinel 的支持
feign:
sentinel:
enable: true

management:
endpoints:
web:
exposure:
include: "*"

创建服务熔断类,新建一个 fallback 的包,编写以下熔断类,实现的必须接口是 Feign接口,如果产生了熔断就会走这个实现类里面

1
2
3
4
5
6
7
8
9
10
11
@Component
package pers.fulsun.alibabacloud.order.feignclient.fallback;

import pers.fulsun.alibabacloud.order.feignclient.StockFeignService;

public class StockFeignServiceFallback implements StockFeignService {
@Override
public String reduct() {
return "Stock服务熔断";
}
}

将使用 Feign 调用远程服务的注解属性增加一个指定的熔断类, 这样如果产生了异常,就会走熔断类

1
2
3
4
5
@FeignClient(name = "stock-service", path = "/stock", fallback = StockFeignServiceFallback.class)
public interface StockFeignService {
@RequestMapping("/reduct")
String reduct();
}

流控控制

  • 资源名:必须是当前服务里面的某一个接口路径,(相对路径即可) 如:/order/orderTest
  • 阈值类型:QPS: 在规定时间范围类型请求的数据(10/1s); 线程数代表的并发量
  • 单机阈值:请求数(qps: 10 个请求:0-10 正常;> 10 表示我接口要降级;线程数:单位时间内同一个时间:并发数 10,0-10 正常,> 10 接口降级)

设置 QPS 为 1s 一个请求

流控策略

一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:

  • resource: 资源名,即限流规则的作用对象服务路径 /test/test

  • count: 限流阈值

  • grade: 限流阈值类型(QPS 或并发线程数)

  • limitApp: 流控针对的调用来源,若为 default 则不区分调用来源

  • strategy : 调用关系限流策略

    • 直接:当 api 大达到限流条件时,直接直接 fallback。
    • 关联:当关联的资源到达阈值,就限流自己. 如电商的 下订单 和 支付两个操作,需要优先保障 支付, 可以根据 支付接口的 流量阈值,来对订单接口进行限制,从而保护支付的目的。
    • 链路:只记录指定路上的流量,指定资源从入口资源进来的流量,如果达到阈值,就进行限流,api 级别的限流。
  • controlBehavior : 流量控制效果(直接拒绝、warm Up、匀速排队)

    • 快速失败:直接报错

    • warm up :预热限流,慢慢增加请求数

    • 排队等待:匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。阈值必须设置为 QPS。

链路案例

流控模式中的关联类型和链路类型的区别:

  • 关联 ,假设 A 规则关联 B, 那么 A 资源是受限制的, 下单关联支付,根据支付流量限制下单接口
  • 链路 ,假设 A 规则绑定了一个链路,关联 B,那么受限制的是 B。

链路模式:只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。例如有两条请求链路:

1
2
3
4
5
6
7
/test1   -->    /common
/test2 --> /common

只希望统计从/test2进入到/common的请求,对/test2 进行限流,则可以这样配置:

资源名: /common
入口资源: /test2

需求:有查询订单和创建订单业务,两者都需要查询商品。针对从查询订单进入到查询商品的请求统计,并设置限流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 在 OrderController 中,添加 /order/query 端点 /order/save 端点
@RestController
@RequestMapping("/order")
public class OrderController {

@Autowired
OrderService orderService;

@RequestMapping("/query")
public String query() throws URISyntaxException {
System.out.println("下单成功!");
String message = orderService.queryGoods();
return "查询成功:" + message;
}

@RequestMapping("/save")
public String save() throws URISyntaxException {
System.out.println("下单成功!");
String message = orderService.queryGoods();
return "保存成功:" + message;
}
}

在 stockFeignService 中添加一个 queryGoods 方法; Sentinel 默认只标记 Controller 中的方法为资源,如果要标记其它方法,需要利用@SentinelResource 注解,可以通过 value 属性指定资源的名称,以便在 Sentinel 的规则配置中进行相应的流量控制设置。

1
2
3
4
5
6
7
@Service
public class OrderService {
@SentinelResource("queryGoods")
public String queryGoods(){
return "查询商品完成,剩余:" + new Random().nextInt(100);
}
}

配置流控规则如下

测试结果发现 /order/query 被流控了,/save 接口正常访问

熔断策略

Sentinel 提供以下几种熔断策略:

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

@SentinelResource 注解

注意:注解方式埋点不支持 private 方法。

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。

@SentinelResource 注解包含以下属性:

  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项(默认为 EntryType.OUT)
  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。
    • blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。
    • blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • fallback / fallbackClass:fallback 函数名称,可选项,
    • 用于在抛出异常的时候提供 fallback 处理逻辑。
    • fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。
    • fallback 函数签名和位置要求:
      • 返回值类型必须与原函数返回值类型一致;
      • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
      • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。
    • 默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。
    • 若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。
    • defaultFallback 函数签名要求:
      • 返回值类型必须与原函数返回值类型一致;
      • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
      • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
  • 同时指定了 blockHandler 和 fallback ,则 blockHandler 优先级更高
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 原函数
@SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
public String hello(long s) {
return String.format("Hello at %d", s);
}

// Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
public String helloFallback(long s) {
return String.format("Halooooo %d", s);
}

// Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
public String exceptionHandler(long s, BlockException ex) {
// Do some log here.
ex.printStackTrace();
return "Oops, error occurred at " + s;
}

// 这里单独演示 blockHandlerClass 的配置.
// 对应的 `handleException` 函数需要位于 `ExceptionUtil` 类中,并且必须为 public static 函数.
@SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
public void test() {
System.out.println("Test");
}

Gateway API 网关

什么是网关

网关 = 路由转发 + 过滤器

路由转发:接收一切外界请求,转发到后端的微服务上去
过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等,这些都可以通过过滤器完成(其实路由转发也是通过过滤器实现的)

在业界比较流行的网关,有下面这些:

  • Ngnix+lua 使用 nginx 的反向代理和负载均衡可实现对 api 服务器的负载均衡及高可用 lua 是一种脚本语言, 可以来编写一些简单的逻辑, nginx 支持 lua 脚本
  • Kong 基于 Nginx+Lua 开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。 问题:只支持 Http 协议;二次开发,自由扩展困难;提供管理 API,缺乏更易用的管控、配置方式。
  • Zuul Netflix 开源的网关,功能丰富,使用 JAVA 开发,易于二次开发 问题:缺乏管控,无法动态配置;依赖组件较多;处理 Http 请求依赖的是 Web 容器,性能不如 Nginx
  • Spring Cloud GatewaySpring 公司为了替换 Zuul 而开发的网关服务,将在下面具体介绍。

Note:SpringCloud alibaba 技术栈中并没有提供自己的网关,我们可以采用 Spring Cloud Gateway 来做网关

为什么使用网关

一个微服务架构可以包含数十到数百个相同功能服务 (但端口不同)。API 网关可以为外部用户提供一个统一的入口,这个入口独立于内部微服务组件

Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,Spring Cloud Gateway 旨在为微服务架构提供一种简单而有效的统一的 API 路由管理方式。Spring Cloud Gateway 作为 Spring Cloud 生态系中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控 / 埋点,和限流等。

优点:

  • 性能强劲:是第一代网关 Zuul 的 1.6 倍
  • 功能强大:内置了很多实用的功能,例如转发、监控、限流等
  • 设计优雅,容易扩展

缺点:

  • 其实现依赖 Netty 与 WebFlux,不是传统的 Servlet 编程模型,学习成本高
  • 不能将其部署在 Tomcat、Jetty 等 Servlet 容器里,只能打成 jar 包执行
  • 需要 Spring Boot 2.0 及以上的版本,才支持

Gateway API 功能

  • 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0

  • 动态路由 ( 根据请求分发到不同服务 )

  • Predicates 和 Filters 作用于特定路由

  • 集成 Hystrix 熔断器

  • 集成 Spring Cloud DiscoveryClient

  • 易于编写的 Predicates 和 Filters

  • 限流

  • 路径重写

工作流程

  1. Gateway Client 向 Gateway Server 发送请求
  2. 请求首先会被 HttpWebHandlerAdapter 进行提取组装成网关上下文
  3. 然后网关的上下文会传递到 DispatcherHandler,它负责将请求分发给 RoutePredicateHandlerMapping
  4. RoutePredicateHandlerMapping 负责路由查找,并根据路由断言判断路由是否可用
  5. 如果过断言成功,由 FilteringWebHandler 创建过滤器链并调用
  6. 请求会一次经过 PreFilter–微服务–PostFilter 的方法,最终返回响应

快速入门

通过浏览器访问 api 网关, 然后通过网关将请求转发到用户微服务, 新建项目 gateway, 引入网关依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependencies>
<!--1. 引入注册中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--2. 引入配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--Spring Cloud 新版本默认将 Bootstrap 禁用,需要将依赖引入到工程中-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--3. 引入网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>

添加 application.yml 文件,内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server:
port: 8000
spring:
application:
name: gateway-service
cloud:
gateway:
routes:
# 1. After: 当超过指定时间后,访问http://localhost:8000 就会跳转到 http://www.baidu.com
- id: after-predicate
uri: http://www.baidu.com
predicates:
- After=2023-06-12T10:12:15.670+08:00[Asia/Shanghai]
nacos:
discovery:
username: nacos
password: nacos
namespace: public
server-addr: 192.168.61.131:8848,192.168.61.132:8848,192.168.61.133:8848

访问 http://localhost: 8000 就会跳转到 http://www.baidu.com

断言

Predicate(断言, 谓词) 用于进行条件判断,只有断言都返回真,才会真正的执行路由。断言就是说: 在 什么条件下 才能进行路由转发

SpringCloud Gateway 包括许多内置的断言工厂,所有这些断言都与 HTTP 请求的不同属性匹配。

具体如下:

  • 基于 Datetime 类型的断言工厂此类型的断言根据时间做判断,主要有三个:

    类型 说明 示例
    AfterRoutePredicateFactory 接收一个日期参数,判断请求日期是否晚于指定日期 -After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
    BeforeRoutePredicateFactory 接收一个日期参数,判断请求日期是否早于指定日期
    BetweenRoutePredicateFactory 接收两个日期参数,判断请求日期是否在指定时间段内
  • 基于远程地址、Cookie 、Header、Host 的断言工厂

    类型 说明 示例
    RemoteAddrRoutePredicateFactory 接收一个 IP 地址段,判断请求主机地址是否在地址段中 - RemoteAddr=192.168.1.1/24
    CookieRoutePredicateFactory 接收两个参数,cookie 名字和一个正则表达式。 判断请求 cookie 是否具有给定名称且值与正则表达式匹配 - Cookie=chocolate, ch.s
    HeaderRoutePredicateFactory 接收两个参数,标题名称和正则表达式。 判断请求 Header 是否具有给定名称且值与正则表达式匹配 - Header=X-Request-Id, \d+
    HostRoutePredicateFactory 接收一个参数,主机名模式。判断请求的 Host 是否满足匹配规则。 - Host=**.testhost.org
    MethodRoutePredicateFactory 接收一个参数,判断请求类型是否跟指定的类型匹配 - Method=GET
    PathRoutePredicateFactory 接收一个参数,判断请求的 URI 部分是否满足路径规则 - Path=/foo/{segment}
    QueryRoutePredicateFactory 接收两个参数,请求 param 和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配 - Query=baz, ba.
    WeightRoutePredicateFactory 接收一个 [组名, 权重], 然后对于同一个组内的路由按照权重转发 - Weight=group1, 5
    ReadBodyPredicateFactory 缓存请求体,这样可以在后续多次读取请求体

内置路由断言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
application:
name: gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: stock_route
uri: lb://stock-service
predicates:
- Path=/stock/**
- Before=2023-07-01T00:00:00.000+08:00 #限制请求时间在2023-07-01之前
- Method=GET #限制请求方式为GET