Spring Cloud Alibaba 基础应用

文章目录

  • Spring Cloud Alibaba 基础应用
    • Spring Cloud 简介
      • Spring Cloud 常用组件
      • Spring Cloud 版本选择
    • Nacos
      • Nacos 注册中心的原理
      • Nacos 单机运行
      • Nacos 集群运行
      • Nacos 集群启动脚本
      • Nacos 客户端连接
      • Nacos 配置中心
    • Ribbon
      • 负载均衡的两种方式
      • 实现客户端负载均衡
        • 手动使用 DiscoveryClient 来实现
        • 使用 Ribbon 实现负载均衡
      • Ribbon 重要接口
      • Ribbon 负载均衡规则
      • Ribbon 在 application 中配置
      • Ribbon 配置类
    • Feign
      • 常见HTTP客户端
      • 基础使用
      • Feign 客户端拦截器的基础使用
    • Gateway
      • 核心概念
      • 基础使用
      • 谓词工厂
      • Path 路由谓词
      • Header 路由谓词
    • Sentinel
      • 服务雪崩效应
      • 常见的熔断组件
      • Sentinel 简介
        • Sentinel 的特征
        • Sentinel 的基本概念
        • Sentinel 的功能和设计理念
      • 手动通过 Java API 定义 Sentinel 流控规则
      • 基于注解方式定义 Sentinel 流控规则
      • 基于控制台定义 Sentinel 流控规则

Spring Cloud Alibaba 基础应用

Spring Cloud Alibaba 为分布式应用开发提供一站式解决方案。它包含开发分布式应用程序所需的所有组件,使您可以轻松地使用 Spring Cloud 微服务框架开发应用程序。

Spring Cloud 简介

Spring Cloud 是一系列框架的有序集合,这些框架为我们提供了分布式系统构建工具。
我的理解是 Spring Cloud 是 Spring Boot 应用的集群。
而一系列 Spring Boot 应用在不同的场景的、运用就造就了很多 SpringCloud 组件,这些包括:服务注册、服务治理、配置中心、网关、限流熔断器、服务调用、负载均衡、消息总线等。

Spring Cloud 常用组件

组件 名称
服务注册与发现 Alibaba Nacos、Netflix Eureka、Apache Zookper
分布式配置中心 Alibaba Nacos、Spring Cloud Config
网关 Spring Cloud Gateway、Netflix Zull
限流熔断器 Alibaba Sentinel、Netflix Hystrix、 Resilience4j
服务调用 RestTemplate、Open Feign、Dubbo Spring Cloud
负载均衡 Spring Cloud LoadBalancer、Netflix Ribbon
消息总线 Spring Cloud Bus

Spring Cloud 版本选择

版本适配的 官方参考地址

Spring Cloud Alibaba Version Spring Cloud Version Spring Boot Version
2.2.6.RELEASE Hoxton.SR9 2.3.2.RELEASE

Spring Cloud Alibaba 与各组件版本关系

Spring Cloud Alibaba Version Sentinel Version Nacos Version Seata Version
2.2.6.RELEASE 1.8.1 1.4.2 1.3.0

Nacos

Nacos 是一个易于使用的平台,旨在进行动态服务发现、配置和服务管理。它可以帮助您轻松构建云原生应用程序和微服务平台。

  • 官方网站:https://nacos.io/
  • 源码官网:https://github.com/alibaba/nacos
  • 服务端下载地址:https://github.com/alibaba/nacos/releases/tag/1.4.2

Nacos 注册中心的原理

  • 首先,单个服务在启动的时候会将数据注册到 Nacos。同时,该服务作为 Nacos 的客户端也要维持一个心跳(间隔5s),向 Nacos 汇报当前服务的状态。否则在服务间进行调用的时候会导致调用到已经异常的服务,进而导致服务调用链路中断。
  • Nacos 服务端也会和其客户端维持一个心跳,通过每5秒检查一下心跳信息来判断是否超时,就是用当前时间减去上次一心跳的时间,如果超过15秒则将节点设置为非健康状态并进行广播,如果超过30秒则将节点进行移除,说明节点不可用,以确保其他服务在拉去服务列表的时候,获取的都是可用的服务。
  • 作为 Nacos 的客户端,通过一个定时任务来定时(每10s)拉取一次服务列表,以保持服务列表的最新状态,这个功能就是当某个服务挂掉的时候通过心跳会发现这服务不健康了,调用方的客户端会更新本地的服务注册列表。

Nacos 单机运行

  1. 下载并解压
  2. 命令行进入bin目录,执行如下命令
startup.cmd -m standalone

Nacos 集群运行

  1. 解压Nacos,复制三份,如下:

Spring Cloud Alibaba 基础应用_第1张图片
其目录结构如下所示,这里仅展示部分结构。

E:\workspace\nacos-server\nacos-cluster
│  
├─nacos-8848
│  ├─bin
│  ├─conf
│  │      application.properties
│  │      cluster.conf.example
│  │      nacos-mysql.sql
│  ├─status
│  └─target
│          
├─nacos-8849
│  ├─bin
│  ├─conf
│  │      application.properties
│  │      cluster.conf.example
│  │      nacos-mysql.sql
│  ├─status
│  └─target
│          
└─nacos-8850
    ├─bin
    ├─conf
    │      application.properties
    │      cluster.conf.example
    │      nacos-mysql.sql
    ├─status
    └─target
  1. 创建数据库,我这里取名为 nacos_config,并执行解压目录中的 conf/nacos-mysql.sql。这里是为 Nacos 集群配置共同的数据源,如果不配置的话,其默认的 derby 数据库是不能确保集群中所有 Nacos 节点数据一致的。

  2. 将三个 Nacos 节点的 conf/application.properties 配置其端口号和数据源即可,这里以解压的8848这一副本为例:

#*************** Spring Boot Related Configurations ***************#
### Default web context path:
server.servlet.contextPath=/nacos
### Default web server port:
server.port=8848

#*************** Network Related Configurations ***************#
### If prefer hostname over ip for Nacos server addresses in cluster.conf:
# nacos.inetutils.prefer-hostname-over-ip=false

### Specify local server's IP:
# nacos.inetutils.ip-address=


#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
# spring.datasource.platform=mysql

### Count of DB:
db.num=1

### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=nacos
db.password.0=nacos
  1. 将三个 Nacos 节点的 conf/cluster.conf.example 更名为 conf/cluster.conf,均如下:
192.168.18.154:8848
192.168.18.154:8849
192.168.18.154:8850

这里需要注意下,本地实验的时候不要写 127.0.0.1 也不要写 localhost,写本机的真实 IP 即可,如果不知道ip,可以使用 ipconfig 查询一下,否则后面 Nacos 客户端连接的时候会抛出如下异常:

com.alibaba.nacos.api.exception.NacosException: failed to req API:/nacos/v1/ns/instance after all servers([localhost:8848, localhost:8849, localhost:8850]) tried: ErrCode:400, ErrMsg:<html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id='created'>Sat Jan 13 20:05:40 CST 2024</div><div>There was an unexpected error (type=Bad Request, status=400).</div><div>receive invalid redirect request from peer 127.0.0.1</div></body></html>
	at com.alibaba.nacos.client.naming.net.NamingProxy.reqApi(NamingProxy.java:556) ~[nacos-client-1.4.2.jar:na]
	at com.alibaba.nacos.client.naming.net.NamingProxy.reqApi(NamingProxy.java:498) ~[nacos-client-1.4.2.jar:na]
	......
	......

其错误描述是:receive invalid redirect request from peer 127.0.0.1,如下图:
Spring Cloud Alibaba 基础应用_第2张图片

  1. 启动三个 Nacos
.\nacos-8848\bin\startup.cmd -m cluster
.\nacos-8849\bin\startup.cmd -m cluster
.\nacos-8850\bin\startup.cmd -m cluster

脚本的默认模式为集群,所以可以不需要 -m cluster 参数

  1. 可以分别使用浏览器来访问 http://localhost:8848/nacoshttp://localhost:8849/nacoshttp://localhost:8850/nacos。或者可以配置一个 Nginx 作为 Nacos 前端进行代理。访问账号和密码默认都是 nacos,登录后界面如下:

Spring Cloud Alibaba 基础应用_第3张图片

Nacos 集群启动脚本

我在本机为了方便启动集群,自己写了个批处理文件,以便于开发过程中,直接可以一键启动,就省去了很多的麻烦,欢迎大家给予意见哦。源码如下:

@ECHO off
SETLOCAL enabledelayedexpansion

SET CLUSTER_CONF=%cd%\nacos-cluster\cluster.conf
SET CLUSTER_DIR=%cd%\nacos-cluster\
SET LOCAL_IP=.

IF "%1%"=="cluster" (
	ECHO cluster mode
	REM 获取本机 IP 地址
	FOR /f "skip=1 tokens=2 delims=:" %%i IN ('ipconfig ^|find /i "ipv4"') DO (
		REM 去除 IP 前面的空格
		FOR /f "delims= " %%a IN ("%%i") DO (
			SET LOCAL_IP=%%a
		)
	)
	ECHO ip=!LOCAL_IP!
	IF "!LOCAL_IP!"=="" (
		REM 未能获取到 IP,换个方法再试一次
		FOR /f "tokens=16" %%i IN ('ipconfig ^|find /i "ipv4"') DO SET LOCAL_IP=%%i
	)
 
 	REM 删除集群配置文件
 	IF EXIST !CLUSTER_CONF! DEL /F !CLUSTER_CONF!
 	
 	REM /d 用于查询目录, 去掉 /d 查询的是文件
 	for /d %%s IN (!CLUSTER_DIR!\*) DO (
 		REM 删除 data 目录和 logs 目录
 		IF EXIST %%s\data rmdir /s/q %%s\data
 		IF EXIST %%s\logs rmdir /s/q %%s\logs
 		
 		REM 获取到目录名称后面的端口号
 		for /f "tokens=2 delims=-" %%p IN ("%%~ns") DO (
 			ECHO !LOCAL_IP!:%%p>>!CLUSTER_CONF!
 		)
 	)
 	
 	REM 延迟 1s 执行
 	CHOICE /t 2 /d y /n >NUL
 	for /d %%s IN (!CLUSTER_DIR!\*) DO (
 		REM 复制集群配置文件,如果存在,则覆盖
 		COPY /Y !CLUSTER_CONF! %%s\conf\cluster.conf
 		REM 启动 Nacos
 		START %%s\bin\startup.cmd
 	)
 	REM 删除集群配置文件
 	IF EXIST !CLUSTER_CONF! DEL /F !CLUSTER_CONF!
 	
 	REM 重启 nginx
 	%cd%\..\nginx-1.22.0\nginx-restart.cmd
) ELSE (
	ECHO standalone mode
	CALL %cd%\nacos\bin\startup.cmd -m standalone
)

这个批处理程序接受一个入参,当参数为 cluster 的时候,会启动集群,否则启动单机的 Nacos。在源码中的 else 部分就是单机启动 Nacos ,比较好理解,主要是集群启动的时候,我遇到很多问题。主要还是配置集群文件 cluster.conf 的 IP和端口的问题,因为我是笔记本电脑,WiFi连接路由器的 IP 是自动分配的,难道我每次启动集群都要修改一次这个集群配置文件吗?不,我不愿意!所以,我的思路是:

  1. 先获取本机 IP 地址
  2. 遍历 nacos-cluster 下的子集目录,我刻意将三个 Nacos 子节点的目录命名为 nacos-8848nacos-8849nacos-8850 就是想遍历的时候根据名称来获取后面的数字作为端口,结合前面获取到的 IP 地址,然后组合成新的 cluster.conf 文件
  3. 将组合好的 cluster.conf 文件,分别复制到三个节点的 conf 目录下。我在复制之前使用 CHOICE 指令停滞了 2s,原因是我发现前面的双层循环好像有点延迟,导致后面复制的时候会提示找不到文件,不过后来我又发现,即便删除这个延迟 2s 的代码也能用,所以我就没删除这行。
  4. 删除临时组合的 cluster.conf 文件后重启 Nginx。当然,这一步不是必须的,可以直接把这一行删除,我只是想用 Nginx 作为 Nacos 集群的前端。nginx-restart.cmd 也是一个自己写的用于重启 Nginx 的脚本。
  5. 我在组合 cluster.conf 文件的时候会把每个 Nacos 节点的 data 和 logs 目录删除,这一步其实也不是必须的。因为我之前遇到前文提到的那个 receive invalid redirect request from peer 127.0.0.1 问题,查了好多博客都说是要将这两个目录删除,然后重启,其实没啥用,因为是我的 IP 不应该携程本地环回地址,要不然我为啥需要那么费力在脚本中获取本机的 IP 呢。因为 Nacos 默认采用的是 derby 数据库,删除 data 目录可以将他们的元数据删除,在启动后重新连接 MySQL,会同步这一数据的,所以,我还是保留了删除这俩目录的代码。

这个批处理程序我命名为 nacos-server.cmd,它和上文中的三个 Nacos 节点的位置结构如下所示:

E:\workspace\nacos-server
│  
│  nacos-server-1.4.2.tar.gz    ---------> 从官网下载的 Nacos 服务端程序
│  nacos-server.cmd             ---------> 运行 Nacos 的批处理程序
│  
├─nacos                         ---------> 用于 Nacos 单机运行
│  ├─bin
│  ├─conf
│  └─target
│          
└─nacos-cluster                 ---------> 用于 Nacos 集群运行
    ├─nacos-8848
    │  ├─bin
    │  ├─conf
    │  ├─status
    │  └─target
    │          
    ├─nacos-8849
    │  ├─bin
    │  ├─conf
    │  ├─status
    │  └─target
    │          
    └─nacos-8850
        ├─bin
        ├─conf
        ├─status
        └─target

为了更直观解释目录结构,可以看下图:
Spring Cloud Alibaba 基础应用_第4张图片
最后给这个批处理程序配置到系统的环境变量里,后面只需要按下 win + r,直接输入 nacos-server cluster 就可启动 Nacos 集群,输入 nacos-server 就可启动 Nacos 单机。

Nacos 客户端连接

  1. 创建 Maven 项目,并配置 POM文件,部分如下:
 <properties>
     <maven.compiler.source>8maven.compiler.source>
     <maven.compiler.target>8maven.compiler.target>
     <spring-boot.version>2.3.2.RELEASEspring-boot.version>
     <spring-cloud.version>Hoxton.SR9spring-cloud.version>
     <spring-cloud-alibaba.version>2.2.6.RELEASEspring-cloud-alibaba.version>
 properties>

 <dependencies>
     <dependency>
         <groupId>org.springframework.bootgroupId>
         <artifactId>spring-boot-starter-webartifactId>
     dependency>
     <dependency>
         <groupId>com.alibaba.cloudgroupId>
         <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
     dependency>
 dependencies>

 <dependencyManagement>
     <dependencies>
         <dependency>
             <groupId>org.springframework.bootgroupId>
             <artifactId>spring-boot-dependenciesartifactId>
             <version>${spring-boot.version}version>
             <type>pomtype>
             <scope>importscope>
         dependency>
         <dependency>
             <groupId>org.springframework.cloudgroupId>
             <artifactId>spring-cloud-dependenciesartifactId>
             <version>${spring-cloud.version}version>
             <type>pomtype>
             <scope>importscope>
         dependency>
         <dependency>
             <groupId>com.alibaba.cloudgroupId>
             <artifactId>spring-cloud-alibaba-dependenciesartifactId>
             <version>${spring-cloud-alibaba.version}version>
             <type>pomtype>
             <scope>importscope>
         dependency>
     dependencies>
 dependencyManagement>
  1. 创建 bootstrap.yml 文件或 bootstrap.properties,因为该文件加载顺序最早:

加载顺序bootstrap.yml > application.yml > application-dev(prod).yml

server:
  port: 8081
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.18.154:8848,192.168.18.154:8849,192.168.18.154:8850
        service: order-service
  • 这里的 ip 最好跟前面的集群配置中的 ip 保持一致,不过,从本地实验的结果来看,写 127.0.0.1localhost 都能最后连接上 Nacos!
  • 可以只写一个节点,但要确保该节点可用,Nacos 客户端会依次尝试连接各个节点,如果连接失败,换下一个继续尝试
  1. 在服务的启动类上开启 Nacos 的注解
@SpringBootApplication
@EnableDiscoveryClient
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class);
    }
}
  1. 启动成功后,就能在 Nacos 管理界面中看到该服务了,如下所示:

Spring Cloud Alibaba 基础应用_第5张图片

Nacos 配置中心

在开发过程中,尤其是业务领域,常常会遇到配置需要更新的情况,但是单点服务在修改配置后,需要重启应用才能生效,引入配置中心就能无痛更新配置,效率更高!

Nacos 作为配置中心,具有:

  • 统一配置文件管理
  • 提供统一标准接口,服务根据标准接口自行拉取配置
  • 支持动态更新的到所有服务

如何使用 Nacos 配置中心呢?

  1. 首先在 Nacos 管理系统,在配置管理>配置列表页面,点击右侧的 ➕按钮可以创建一个配置项。
    Spring Cloud Alibaba 基础应用_第6张图片
    创建好之后,点击下方的【发布】按钮,即可发布配置。返回列表页面即可看到刚刚创建好的配置,我这里为 Data ID 命名为 order-service-configuration 意味着该配置文件我将会为订单服务所用。
    Spring Cloud Alibaba 基础应用_第7张图片

我们可以将一个 Data ID 视作是一个配置文件,命名时也可以有后缀名,例如order-service-configuration.yaml,它隶属某个组(Group),而每个组又隶属于一个命名空间,他们三个的关系是依次被包含的关系。默认情况下,Nacos 仅一个命名空间 public,新建的配置 Data ID 也是默认隶属于 DEFAULT_GROUP

  1. 在项目中增加依赖:
<dependency>
    <groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
  1. 在配置文件中为当前服务命名

我这里依然是在 bootstrap.yml 文件中配置的,就是在之前的基础上增加了 spring.application.name=order-service-configuration,值得注意的是这里的名称跟前面的 Data Id 的名称是保持一致。

server:
  port: 8081
spring:
  application:
    name: order-service-configuration
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.18.154:8848,192.168.18.154:8849,192.168.18.154:8850
        service: order-service
  1. 创建一个控制器来做一下测试,使用 @Value 来获取 Nacos 配置中心的配置的值
@RefreshScope
@RestController
@RequestMapping("order")
public class OrderController {
    @Value("${spring.application.name}")
    private String name;
    @Value("${nickname}")
    private String nickname;
    @Value("${service.name}")
    private String serviceName;
    @Value("${service.duration}")
    private Long serviceDuration;

    @GetMapping("")
    public String info() {
        String info = "

" + name + "

"
; info += "
    "; info += "
  • nickname=" + nickname + "
  • "
    ; info += "
  • service.name=" + serviceName + "
  • "
    ; info += "
  • service.duration=" + serviceDuration + "
  • "
    ; info += "
"
; return info; } }

访问 http://localhost:8081/order 可在页面中看到其配置值已经成功获取。需要注意以下几点:

  1. 即便是在 application.yml 中配置了 service.nameservice.duration 的值,读取的结果依然是 Nacos 配置中心。
  2. 如果在配置中心没有配置 spring.application.name 则读取本地结果,也就是 application.yml 中的配置值
  3. 如果在配置中心和本地均没有某个键,则抛出异常。
  4. 如果需要在配置中心修改了配置后,Nacos 客户端能立即更新最新的配置值,可以为其使用 @RefreshScope 注解。
  5. 可以使用 bootstrap.properties 来进行配置,附上其他拓展配置(非必需)
# nacos地址
spring.cloud.nacos.config.server-addr=192.168.18.154:8848

# 1、在配置中心配置的 Data Id 最好跟 ${spring.application.name} 保持一致
spring.application.name=order-service-configuration

# 2、指定文件后缀名称
# 加载文件为:${application.name}.${file-extension}
# 例如 order-service-configuration.yaml
spring.cloud.nacos.config.file-extension=yaml
# 3、profile: 指定环境  文件名:${application.name}-${profile}.${file-extension}
# 例如 order-service-configuration-prod.yaml
spring.profiles.active=prod
# 4、nacos自己提供的环境隔离,也就是配置命名空间,可以在Nacos 管理后台创建命名空间后,将其ID粘贴到这里
spring.cloud.nacos.config.namespace=9d72b6c1-a31b-4528-9cf0-02d8a26399dd

# 5、 自定义 group 配置,这里也可以设置为数据库配置组,中间件配置组,但是一般不用,使用默认值DEFAULT_GROUP
spring.cloud.nacos.config.group=DEFAULT_GROUP

#6、自定义Data Id的配置 共享配置(sharedConfigs)0
spring.cloud.nacos.config.shared-configs[0].data-id=common.yaml
# 可以不配置,使用默认
spring.cloud.nacos.config.shared-configs[0].group=DEFAULT_GROUP
# 这里需要设置为true,动态可以刷新,默认为false
spring.cloud.nacos.config.shared-configs[0].refresh=true

# 7、扩展配置(extensionConfigs)
# 支持一个应用有多个DataId配置,例如 mybatis.yaml datasource.yaml
spring.cloud.nacos.config.extension-configs[0].data-id=datasource.yaml
spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[0].refresh=true

配置文件生效的顺序依次是:

  1. ${application.name}-${profile}.${file- extension}
  2. ${application.name}.${file-extension}
  3. ${application.name}

拿本例来说,其优先级依次为 order-service-configuration-prod.yaml > order-service-configuration.yaml > order-service-configuration

Ribbon

负载均衡的两种方式

  1. 服务器端负载均衡

传统的方式前端发送请求会到我们的的 Nginx 上去,Nginx 作为反向代理,然后路由给后端的服务器,由于负载均衡算法是 Nginx 提供的,而 Nginx 是部署到服务器端的,所以这种方式又被称为服务器端负载均衡

  1. 客户端侧负载均衡

现在有三个实例,内容中心可以通过discoveryClient 获取到用户中心的实例信息,如果我们再订单中心写一个负载均衡 的规则计算请求那个实例,交给restTemplate进行请求,这样也可以实现负载均衡,这个算法里面,负载均衡是有订单中心提供的,而订单中心相对于用户中心是一个客户端,所以这种方式又称为客户端负负载均衡。

实现客户端负载均衡

手动使用 DiscoveryClient 来实现
@RestController
@RequestMapping("load-balancer")
public class LoadBalancerController {

    @Resource
    private DiscoveryClient client;

    /**
     * 手动使用 DiscoveryClient 来实现负载均衡,策略是:随机选择
     */
    @RequestMapping("test-discovery-client")
    public String testDiscoveryClient() {
        // 获取库存服务的所有实例集合
        List<ServiceInstance> instances = client.getInstances("stock-service");
        List<String> strings = instances.stream().map(ins -> ins.getUri().toString()).collect(Collectors.toList());
        // 从集合中随机挑选一个实例地址
        String url = strings.get(ThreadLocalRandom.current().nextInt(strings.size()));
        url += "/stock/out";
        // 调用实例所提供的接口地址,并返回结果
        RestTemplate template = new RestTemplate();
        String result = template.getForObject(url, String.class);
        return "利用 DiscoveryClient 手动随机选取进行服务调用,调用目标:" + url + ",调用出库接口的结果为:" + result;
    }

}
使用 Ribbon 实现负载均衡

前文提到,我在 pom 中引入了 spring-cloud-starter-alibaba-nacos-discovery,该组件中内置了 spring-cloud-netflix-ribbon,所以不需要单独引入依赖。所以,实际运用时,直接引入即可。

  1. 使用 @LoadBalanced 注解,开启 Ribbon 负载均衡
@Configuration
public class RibbonConfiguration {
    @Bean
    @LoadBalanced
    public RestTemplate template() {
        return new RestTemplate();
    }
}

  1. 注入 RestTemplate ,直接调用服务
@RestController
@RequestMapping("load-balancer")
public class LoadBalancerController {

    @Resource
    private RestTemplate template;

    /**
     * 使用 Ribbon 来实现负载均衡,默认策略是:ZoneAvoidanceRule
     */
    @RequestMapping("test-ribbon")
    public String testRibbon() {
        // 调用地址
        String url = "http://stock-service/stock/out";
        // 调用实例所提供的接口地址,并返回结果
        String result = template.getForObject(url, String.class);
        return "利用 Ribbon 实现负载均衡进行服务调用,调用目标:" + url + ",调用出库接口的结果为:" + result;
    }

}

Ribbon 重要接口

接口 作用 默认值
IClientConfig 读取配置 DefaultclientConfigImpl
IRule 负载均衡规则,选择实例 ZoneAvoidanceRule
IPing 筛选掉ping不通的实例 默认采用DummyPing实现,
实际上它并不会检查实例是否可用,而是始终返回true,
即默认认为所有服务实例都是可用的.
ServerList 交给Ribbon的实例列表 Ribbon: ConfigurationBasedServerList
Spring Cloud Alibaba: NacosServerList
ServerListFilter 过滤掉不符合条件的实例 ZonePreferenceServerListFilter
ILoadBalancer Ribbon的入口 ZoneAwareLoadBalancer
ServerListUpdater 更新交给Ribbon的List的策略 PollingServerListUpdater

Ribbon 负载均衡规则

规则名称 特点
RandomRule 随机选择一个Server
RetryRule 对选定的负责均衡策略机上充值机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的Server
RoundRobinRule 轮询选择,轮询index,选择index对应位置Server
WeightedResponseTimeRule 根据相应时间加权,相应时间越长,权重越小,被选中的可能性越低
ZoneAvoidanceRule 默认规则)该策略能够在多区域环境下选出最佳区域的实例进行访问。在没有Zone的环境下,类似于轮询(RoundRobinRule)

Ribbon 在 application 中配置

比如在调用库存服务的时候希望采用随机规则,调用用户服务的时候采用轮询规则,可以按照如下方式进行配置:

stock-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule

Ribbon 默认采用懒加载的方式初始化,为了使得首次调用服务的时候避免因为初始化过程而占用时间,可以使用饿加载,如下所示:

ribbon:
  eager-load:
    enabled: true
    clients: user-service,stock-service

需要注意的是:如果调用的服务名称在 ribbon.eager-load.clients 中未声明,依然会在首次调用的时候进行初始化。

Ribbon 配置类

先自定义类,在类中声明轮询规则的 Bean

/**
 * 轮询选择
 */
public class RibbonConfigurationForRoundRobinRule {
    @Bean
    public IRule rule() { return new RoundRobinRule(); }
}

先自定义类,在类中声明随机规则的 Bean

/**
 * 随机选择
 */
public class RibbonConfigurationForRandomRule {
    @Bean
    public IRule rule() { return new RandomRule(); }
}

在配置类上使用 @RibbonClients 注解对多个服务进行配置规则,也可以使用 @RibbonClient 注解对单个服务进行配置规则,未配置的服务在调用的时候默认采用 。ZoneAvoidanceRule

@Configuration
@RibbonClients({
    @RibbonClient(name = "stock-service", configuration = RibbonConfigurationForRandomRule.class),
    @RibbonClient(name = "user-service", configuration = RibbonConfigurationForRoundRobinRule.class)
})
//@RibbonClient(name = "user-service", configuration = RibbonConfigurationForRandomRule.class)
public class RibbonConfiguration {

    @Bean
    @LoadBalanced
    public RestTemplate template() {
        return new RestTemplate();
    }

}

如果 @RibbonClients 中未配置 name 的值,则针对的所有的服务采用相同的规则,即:

@Configuration
@RibbonClients({
    // 调用所有服务均采用随机规则
    @RibbonClient(configuration = RibbonConfigurationForRandomRule.class)
})
public class RibbonConfiguration {

    @Bean
    @LoadBalanced
    public RestTemplate template() {
        return new RestTemplate();
    }

}

Feign

Feign 是 Netflix 开源的声明式 HTTP 客户端

Fegin 和 OpenFeign 的区别:

  • Spring Cloud OpenFeign 对 Feign 进行了 增强,使其支持 Spring MVC 注解
  • 整合了 Ribbon 和 Eureka,从而使得 Feign 的使用更加方便

常见HTTP客户端

  • HttpClient

HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 Http 协 议的客户端编程工具包,并且它支持 HTTP 协议最新版本和建议。HttpClient 相比传统 JDK 自带的 URLConnection,提升了易用性和灵活性,使客户端发送 HTTP 请求变得容易,提高了开发的效率。

  • Okhttp

一个处理网络请求的开源项目,是安卓端最火的轻量级框架,由 Square 公司贡献,用于替代 HttpUrlConnection 和 Apache HttpClient。OkHttp 拥有简洁的 API、高效的性能,并支持多种协议 (HTTP/2 和 SPDY)。

  • HttpURLConnection

HttpURLConnection 是 Java 的标准类,它继承自 URLConnection,可用于向指定网站发送 GET 请求、 POST 请求。HttpURLConnection 使用比较复杂,不像 HttpClient 那样容易使用。

  • RestTemplate

RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程 HTTP 服务的方法,能够大大提高客户端的编写效率。

基础使用

  1. 引入依赖
 <dependency>
 	<groupId>org.springframework.cloudgroupId>
 	<artifactId>spring-cloud-starter-openfeignartifactId>
 dependency>
  1. 使用 @EnableFeignClients 注解启用 Feign 客户端
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class);
    }
}
  1. 声明 Feign 接口
@FeignClient(name = "user-service")
public interface UserService {
    @RequestMapping("/user/profile/{username}")
    String profile(@PathVariable("username") String username);
}
@FeignClient("stock-service")
public interface StockService {
    @GetMapping("/stock/out")
    String out();
}

  1. 注入并使用 Feign 接口
@RestController
@RequestMapping("feign-client")
public class FeignClientController {
    @Resource
    private StockService stockService;
    @Resource
    private UserService userService;

    /**
     * 使用 Feign 来实现调用服务
     */
    @RequestMapping("test-ribbon")
    public String testRibbon() {
        // 调用实例所提供的接口地址,并返回结果
        String result = stockService.out();
        String resp = "利用 feign 进行服务调用,调用出库接口的结果为:" + result;
        result = userService.profile("nano");
        return resp += "
调用目标:调用查询用户信息的结果为:"
+ result; } }

Feign 客户端拦截器的基础使用

这里以追加某个请求头信息为例,可以为应用中所有的 Feign 接口在调用服务的时候带有特定的值,如下所示:

@Component
public class FeignClientRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        template.header("Authorization", RandomStringUtils.randomAlphanumeric(10));
    }
}

这样一来,目标服务可以在控制器类中使用 @RequestHeader 注解获取由 Feign 拦截器统一追加的请求头信息了。

Gateway

Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关框架,定位于取代 Netflix Zuul。相比 Zuul 来说,Spring Cloud Gateway 提供更优秀的性能,更强大的有功能。

  • Spring Cloud Gateway 是由 WebFlux + Netty + Reactor 实现的响应式的 API 网关。
  • Spring Cloud Gateway 它不能在传统的 Servlet 容器中工作,也不能构建成 War 包。
  • Spring Cloud Gateway 旨在为微服务架构提供一种简单且有效的 API 路由的管理方式,并基于 Filter 的方式提供网关的基本功能。
  • Spring Cloud Gateway 作为流量的入口,提供路由转发、权限校验、安全认证、监控、限流等功能。

核心概念

  • 路由(route)

路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组谓 词工厂、一组Filter组成。如果谓词为真,则说明请求的URL和配置的路由匹配。

  • 谓词(predicates)

即java.util.function.Predicate , Spring Cloud Gateway使用Predicate实现路由的匹配条件。

  • 过滤器(Filter)

SpringCloud Gateway中 的filter分为Gateway FilIer和Global Filter。Filter可以对请求和响应进行处理。

路由就是转发规则,谓词就是是否走这个路径的条件,过滤器可以为路由添加业务逻辑,修改请求以及响应。

基础使用

  1. 引入依赖

因为 Spring Cloud Gateway 它不能在传统的 Servlet 容器中工作,也不能构建成 War 包。所以,如果父工程如果引入了 Servlet 容器,则需要将其排除

<dependencies>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
        <scope>testscope>
    dependency>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-gatewayartifactId>
    dependency>
dependencies>
  1. 编写配置
server:
  port: 9090
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848,localhost:8849,localhost:8850
    gateway:
      # 是否开启网关
      enabled: true
      discovery:
        locator:
          # 默认值是 false,如果设为true开启通过微服务创建路由的功能,
          # 即可以通过微服务名访问服务。但不建议打开,因为这样暴露了服务名称
          enabled: true

需要注意的是:spring.cloud.gateway.discovery.locator.enabled 默认是 false,为了不暴露服务的名称,一般我们不这样配置。

  1. 编写启动入口类
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class);
    }
}

谓词工厂

spring-cloud-gateway 提供了很多的谓词来配置路由,最为关键的配置项莫过于 routes 集合中的 predicates 了。详情参考参考官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

predicate 可作名词译为谓词,也可作动词译为断言。不少人将它翻译为断言,我个人认为它应该作名词译为谓词更合适,因为说明文档和配置文件中它都以复数形式出现,而且通过源码可以得知,predicates 接受收的是一个 List 类型的 RouteDefinition 集合。

Path 路由谓词

刚开始接触的时候,这个是很容易弄错的谓词,尤其是之前了解过 zuul 的朋友,这个 path 跟 zuul 的还有点区别。比如在订单服务中有个控制器是这样的:

@RestController
@RequestMapping("test")
public class TestController {
    @RequestMapping("info")
    public String info() { return "订单服务"; }
}

这个控制器在订单服务中的访问地址是:http://localhost:8081/test/info,那在网关中为了使用 Path 来匹配上这个可以怎么配置呢?

server:
  port: 9090
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848,localhost:8849,localhost:8850
    gateway:
      enabled: true
      routes:
        - id: PathRoutePredicateFactory
          uri: lb://order-service
          predicates:
            - Path=/test/**

这样配置很容易让人误以为: Path 配置的 /test/** 是将所有以 /test 开头的全部转发到订单服务且以 /test 为前缀。想着会将 /test 和控制器中的 /test/info 拼凑起来得到的 /test/test/info 才是最终访问的地址,其实正确的访问地址是: http://localhost:9090/test/info,是不是有点意想不到。

需要注意的是,Path 配置的路径是带匹配路径转发请求,会匹配订单服务中所有以 /test 开头的请求路径。

现在换个匹配词,如果我们将这里谓词的 Path 修改为 /v2/**,就会匹配订单服务中所有以 /v2 开头的请求路径,就拿刚刚的测试控制器为例,它在订单服务中单独使用的时候,就是 /test/info ,那也就意味着无法访问到订单服务的这个测试控制器了。为了能匹配上,我们就得修改控制器代码如下:

@RestController
@RequestMapping(path = {"test", "v2/test"})
public class TestController {
    @RequestMapping("info")
    public String info() {
        return "订单服务";
    }
}

这样就能使得 http://localhost:9090/v2/test/infohttp://localhost:8081/test/info 都能访问通了,当然,出来个 http://localhost:8081/v2/test/info 也能用。但这样做代价太大了,这需要改动订单服务中所有的控制器的请求地址,全部增加一个 /v2 前缀。不,我不愿意!

不改动控制器代码的情况,怎么配置能达到预期的效果呢?修改配置如下:

server:
  port: 9090
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848,localhost:8849,localhost:8850
    gateway:
      enabled: true
      routes:
        - id: PathRoutePredicateFactory
          uri: lb://order-service
          predicates:
            - Path=/v2/**
          filters:
            - StripPrefix=1

StripPrefix 配置项的目的就是截掉匹配模式不转发,也就是把 /v2 截掉不转发了。这样不仅能使得 http://localhost:9090/v2/test/infohttp://localhost:8081/test/info 都能访问通了,而且控制器的代码也不需要挨个增加前缀。

Header 路由谓词

如果所有有需求是,所有发往用户服务的请求头上都必须有 X-Request-Id,且值必须是数字,可以使用如下配置:

spring:
    gateway:
      routes:
        - id: HeaderRoutePredicateFactory
          uri: lb://user-service
          predicates:
            - Header=X-Request-Id, \d+

例如用户服务有可用路径是 http://localhost:8083/user/profile 那么就可以在 postman 中做测试为 http://localhost:9090/user/profile 设置 X-Request-Id 请求头,请求同样能正常返回结果,没有该请求头则返回 404。

Sentinel

Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于 服务容错 的综合性解决方案。它以流量为切入点, 从流量控制熔断降级系统负载保护 等多个维度来保护服务的稳定性。

服务雪崩效应

微服务架构是一种分布式的架构,体系中的服务一般无法保证 100% 可用。如果一个服务出现了问题,调用这个服务就会出现线程阻塞的情况。此时产生并发,就会出现多条线程阻塞等待,进而导致服务瘫痪。这种由于服务与服务之间的依赖性,会使得单点故障会传播,进而会对整个微服务系统造成灾难性的严重后果,这就是 服务雪崩效应

发生这种事故的原因很多,最常见原因: 程序Bug,大流量请求,硬件故障,缓存击穿等等。为了防止雪崩的扩散,我们就要提升服务的容错性,常见的容错思路有隔离、超时、限流、熔断、降级这几种。

  • 隔离

物理隔离:在不同用户的所在地区,我们可以把某个服务(比如 user-service)的实例分为好几个组,比如上海组,北京组,这样就进行了物理隔离。用户在北京调用该服务的时候,即便是出现阻塞或异常,也不会影响上海的朋友。

队列隔离:在某个实例的程序内部,我们可以为每个服务的调用都会创建一个队列,这样进行了队列隔离。当然也可以设置一个单独的线程池进行线程池隔离。

  • 超时

在上游服务调用下游服务的时候,上游服务设置一个最大响应时间,如果超过这个时间,下游未作出反应,上游服务就断开请求,释放掉线程。

  • 限流

限制请求核心服务提供者的流量,使大流量拦截在核心服务之外,这样可以更好的保证核心服务提供者不出问题,对于一些出问题的服务可以限制流量访问。可以考虑的一些限流算法:计数器固定窗口算法、计数器滑动窗口算法、漏桶算法、令牌桶算法等。

  • 熔断

当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。感觉是不是跟超时有点像,其实,超时也是熔断的一种措施。

服务熔断一般有三种状态:

  1. 熔断关闭状态(Closed):服务没有故障时的状态,表示一切正常。
  2. 熔断开启状态(Open):服务调用存在故障,后续对该服务接口的调用不再经过网络,直接执行本地的 fallback 方法。
  3. 半熔断状态(Half-Open):尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。

这三种状态就像保险丝一样,熔断关闭就是保险丝还好好的,半熔断就是保险丝烧红了,还在坚持用,熔断开启就是保险丝已经烧断了。

  • 降级

所谓降级就是我们调用的服务异常超时等原因不能正常返回的情况下,我们返回一个缺省的值。由于降级经常和熔断一起使用,所以就会有熔断降级的说法。

常见的熔断组件

  • Hystrix :Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止 级联失败,从而提升系统的可用性与容错性。

  • Resilience4J :Resilicence4J一款非常轻量、简单,并且文档非常清晰、丰富的熔断工具,这也是Hystrix官方推 荐的替代产品。不仅如此,Resilicence4j还原生支持Spring Boot 1.x/2.x,而且监控也支持和prometheus等多款主流产品进行整合。

  • Sentinel :Sentinel 是阿里巴巴开源的一款断路器实现,本身在阿里内部已经被大规模采用,非常稳定。

下面是三个组件在各方面的对比:

Sentinel Hystrix resilience4j
隔离策略 信号量隔离(并发线程数限流) 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于慢调用比例、异常比例、异常数 基于异常比例 基于异常比例、响应时间
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于 RxJava) Ring Bit Buffer
动态规则配置 支持近十种动态数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件的形式 接口的形式
基于注解的支持 支持 支持 支持
单机限流 基于 QPS,支持基于调用关系的限流 有限的支持 Rate Limiter
集群流控 支持 不支持 不支持
流量整形 支持预热模式与匀速排队控制效果 不支持 简单的 Rate Limiter 模式
系统自适应保护 支持 不支持 不支持
热点识别/防护 支持 不支持 不支持
多语言支持 Java/Go/C++ Java Java
Service Mesh 支持 支持 Envoy/Istio 不支持 不支持
控制台 提供开箱即用的控制台,可配置规则、实时监控、机器发现等 简单的监控查看 不提供控制台,可对接其它监控系统

Sentinel 简介

Sentinel 的特征
  • 丰富的应用场景:Sentinel承接了阿里巴巴近 10 年的双十一大促流量的核心场景, 例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel提供了实时的监控功能。通过控制台可以看到接入应用的单台机器秒级数据, 甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel提供开箱即用的与其它开源框架/库的整合模块, 例如与 Spring Cloud、Dubbo、gRPC 的整合。只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。
  • 完善的 SPI 扩展点:Sentinel提供简单易用、完善的 SPI (Service Provider Interface)。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有Java 运行时环境,同时对Dubbo/Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。
Sentinel 的基本概念
  • 资源:就是Sentinel要保护的东西*

资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。

  • 规则:就是用来定义如何进行保护资源的

围绕资源的实时状态设定的规则,可以包括流量控制规则熔断降级规则以及系统保护规则。所有规则可以动态实时调整

Sentinel 的功能和设计理念
  • 流量控制

流量控制在网络传输中是一个常用的概念,它用于调整网络包的数据。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。

  • 熔断降级

当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败(或者其他处理方式),避免影响到其它的资源而导致级联故障。

手动通过 Java API 定义 Sentinel 流控规则

  1. 引入依赖
<dependency>
    <groupId>com.alibaba.cspgroupId>
    <artifactId>sentinel-coreartifactId>
    <version>1.8.1version>
dependency>
  1. 编写手动配置
@RestController
@RequestMapping("sentinel/resource")
public class SentinelResourceController {

    public static final String PROTECTED_RESOURCE_NAME = "Hello Sentinel!";

    /**
     * 获取受保护的资源名称
     */
    @GetMapping("name")
    public ResponseEntity<String> getProtectedResourceName() {
        Entry entry = null;
        // 务必保证 finally 会被执行
        try {
            // 资源名可使用任意有业务语义的字符串,注意数目不能太多(超过 1K)。
            // 超出几千请作为参数传入而不要直接作为资源名
            entry = SphU.entry(PROTECTED_RESOURCE_NAME);
            // 被保护的业务逻辑
            String resp = "手动设置 Sentinel 保护的资源名称已被获取:";
            return ResponseEntity.ok(resp + PROTECTED_RESOURCE_NAME);
        } catch (BlockException ex) {
            // 资源访问阻止,被限流或被降级
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body("被限流");
        } catch (Exception ex) {
            // 若需要配置降级规则,需要通过这种方式记录业务异常
            Tracer.traceEntry(ex, entry);
        } finally {
            // 务必保证 exit,务必保证每个 entry 与 exit 配对
            if (entry != null) {
                entry.exit();
            }
        }
        return ResponseEntity.notFound().build();
    }

    /**
     * 定义流控规则
     */
    @PostConstruct
    private void init() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //设置受保护的资源
        rule.setResource(PROTECTED_RESOURCE_NAME);
        // 设置流控规则 QPS
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 设置受保护的资源阈值,即 QPS 超过 1 就限流
        rule.setCount(1);
        rules.add(rule);
        // 加载配置好的规则
        FlowRuleManager.loadRules(rules);
    }
}

因为设置限流规则是 QPS 不能超过 1,所以当1s 内访问 http://localhost:8083/sentinel/resource/name 频次过高时就会触发限流结果。

基于注解方式定义 Sentinel 流控规则

  1. 引入依赖
<dependency>
    <groupId>com.alibaba.cspgroupId>
    <artifactId>sentinel-annotation-aspectjartifactId>
    <version>1.8.1version>
dependency>

需要注意的是,sentinel-annotation-aspectj 已经包含了 sentinel-core,不需要重复引入

  1. 声明配置类
@Configuration
public class SentinelAspectConfiguration {
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
}
  1. 声明异常处理类
@Slf4j
public class SentinelException {
    public static ResponseEntity<String> fallback(Throwable e) {
        log.info("出现业务异常");
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return ResponseEntity.status(status).body("出现业务异常");
    }
    public static ResponseEntity<String> block(BlockException e) {
        log.info("出现限流");
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body("被限流");
    }
}
  1. 在控制器中测试限流规则
@RestController
@RequestMapping("sentinel/annotation/resource")
public class SentinelAnnotationResourceController {

    public static final String PROTECTED_RESOURCE_NAME = "基于注解的 Sentinel 使用!";

    /**
     * 获取受保护的资源名称
     */
    @GetMapping("name")
    @SentinelResource(value = PROTECTED_RESOURCE_NAME,
        fallback = "fallback", fallbackClass = SentinelException.class,
        blockHandler = "block", blockHandlerClass = SentinelException.class
    )
    public ResponseEntity<String> getProtectedResourceName() {
        String resp = "手动设置 Sentinel 保护的资源名称已被获取:";
        return ResponseEntity.ok(resp + PROTECTED_RESOURCE_NAME);
    }

    /**
     * 定义流控规则
     */
    @PostConstruct
    private void init() {
        System.out.println("设置资源");
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //设置受保护的资源
        rule.setResource(PROTECTED_RESOURCE_NAME);
        // 设置流控规则 QPS
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 设置受保护的资源阈值,即 QPS 超过 1 就限流
        rule.setCount(1);
        rules.add(rule);
        // 加载配置好的规则
        FlowRuleManager.loadRules(rules);
    }

}

需要注意的是,前一个控制器中也有设置限流规则,所以需要将前一个控制器中的 @PostConstruct 注释掉,否则可能后面的不会生效,可以考虑把这两端代码合并到一个方法中,然后将两个资源都加载到流控规则管理器中。

因为设置限流规则是 QPS 不能超过 1,所以当1s 内访问 http://localhost:8083/sentinel/annotation/resource/name 频次过高时就会触发限流结果。

基于控制台定义 Sentinel 流控规则

Sentinel 提供一个轻量级的控制台, 它提供机器发现、单机资源实时监控以及规则管理等功能。

  1. 下载官方 Sentinel 包:https://github.com/alibaba/Sentinel/releases/download/1.8.1/sentinel-dashboard-1.8.1.jar

  2. 直接使用命令执行该 jar 文件

java -Dserver.port=7070 -Dcsp.sentinel.dashboard.server=localhost:7070 -Dproject.name=Dashboard -jar sentinel-dashboard-1.8.1.jar
  1. 在项目中引入依赖

因为刚刚我们使用了 SentinelResourceAspect 作为 Sentinel 的流控管理,此时需要将其移除,以避免冲突。

<dependency>
    <groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
  1. 配置 Sentinel 控制台的地址

这里的端口号跟第二步启动 Sentinel 的时候设置的端口保持一致

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:7070
  1. 启动服务,然后请求服务中的一个地址,我这里是请求库存服务的出库地址 http://localhost:8082/stock/out,可以看到自己输出的结果

  2. 访问 Sentinel 控制台 http://localhost:7070,登录账号和密码默认都是 sentinel,进来后可以看到左侧会出现跟库存服务名称一样的菜单,点击簇点链路可以看到如下图所示的界面
    Spring Cloud Alibaba 基础应用_第8张图片

  3. 接下来就可以在这里设置流控了,点击该页面的流控 按钮,为 /stock/out 设置流控。我这里为 QPS 设置为 1,也就意味着 1s 内超频访问就会被限流。
    Spring Cloud Alibaba 基础应用_第9张图片

  4. 然后模拟请求 /stock/out 被限流后页面会显示 Blocked by Sentinel (flow limiting)。访问越快,被拒绝的就越多。然后回到Sentinel控制台 实时监控簇点链路界面,会很直观的看到他的通过数和拒绝数。

你可能感兴趣的:(软件开发笔记,#,Java,Nacos,Ribbon,Feign,Gateway,Sentinel,Spring,Cloud)