大纲 前言 本文主要介绍 Sentinel 整合主流框架,比如 OpenFeign、Spring Cloud Gateway 等。
官方资源 Sentinel 整合 OpenFeign 1.0、需求说明 本案例将整合 OpenFeign 和 Sentinel,以此实现 OpenFeign 的 Fallback 服务降级处理,并使用 Sentinel 的流控规则对接口进行限流控制。特别注意,这里 OpenFeign 的服务降级处理只是在接口调用抛出业务异常时,简单执行指定的降级处理逻辑(比如返回兜底数据),并没有实现熔断的功能。若需要使用熔断功能,可以使用 Sentinel 提供的 熔断规则 。
1.1、案例目标 (1) 在服务消费方,为 Feign Client 接口实现 Fallback 服务降级处理(通过编写代码实现)。 (2) 在服务消费方,为 Feign Client 接口实现限流处理(无需编写代码,通过 Sentinel 控制台添加限流规则实现),具体的实现步骤请看下面的案例代码测试二 。 (3) 在服务提供方,当接口频繁调用触发了 Sentinel 的流控规则(也可以是熔断规则、热点规则等)时,会调用 @SentinelResource
注解里面 blockHandler
属性指定的方法进行处理。 提示
在微服务架构中,熔断、降级处理通常是在服务消费方(调用方)中实现的,而限流通常是在服务提供方中实现。值得一提的是,在复杂的业务环境下,有一些微服务应用既是服务消费方,同时也是服务提供方。
1.2、案例代码 1.2.0、版本说明 在本节的案例代码中,各个组件统一使用以下版本:
组件 版本 说明 Spring Boot 3.0.9 Spring Cloud 2022.0.2 Spring Cloud Alibaba 2022.0.0.0 Nacos Server 2.4.3 Sentinel Dashboard 1.8.7
特别注意
如果已有项目的 Spring Boot 和 Spring Cloud 版本过高,导致 Spring Cloud Alibaba Sentinel 与 OpenFeign 不兼容,可以参考 这里 的解决方案。
1.2.1、整合步骤说明 Sentinel 官方默认适配了 OpenFeign 组件,如果想使用 Sentinel,除了引入 spring-cloud-starter-alibaba-sentinel
依赖之外,还需要以下两个步骤:
在 YML 配置文件里打开 Sentinel 对 OpenFeign 的支持:feign.sentinel.enabled=true
加入 spring-cloud-starter-openfeign
依赖使 Sentinel Starter 中的自动配置类生效 1.2.2、创建父级 Pom 工程 在父工程里面配置好工程需要的父级依赖,目的是为了更方便管理与简化配置,具体 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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <properties > <hutool.version > 5.8.22</hutool.version > <lombok.version > 1.18.26</lombok.version > <spring.boot.version > 3.0.9</spring.boot.version > <spring.cloud.version > 2022.0.2</spring.cloud.version > <spring.cloud.alibaba.version > 2022.0.0.0</spring.cloud.alibaba.version > <spring.boot.test.version > 3.1.5</spring.boot.test.version > </properties > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > ${spring.boot.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 > <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 > cn.hutool</groupId > <artifactId > hutool-all</artifactId > <version > ${hutool.version}</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > ${lombok.version}</version > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <version > ${spring.boot.test.version}</version > <scope > test</scope > </dependency > </dependencies > </dependencyManagement >
1.2.3、创建 Provider 工程 为了测试 OpenFeign 远程调用的使用效果,需要创建一个服务提供者。这里创建 Provider 的 Maven 工程,由于需要将服务注册到 Nacos,工程下的 pom.xml
文件需要引入 spring-cloud-starter-alibaba-nacos-discovery
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-sentinel</artifactId > </dependency > </dependencies >
创建 Provider 的启动主类,添加注解 @EnableDiscoveryClient
,将服务注册到 Nacos
1 2 3 4 5 6 7 8 9 @SpringBootApplication @EnableDiscoveryClient public class ProviderApplication { public static void main (String[] args) { SpringApplication.run(ProviderApplication.class, args); } }
在 application.yml
文件中指定服务名称(provider-service
)、注册中心地址、Sentinel 控制台地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 server: port: 8001 spring: application: name: provider-service cloud: nacos: discovery: server-addr: 192.168 .56 .112 :8848 sentinel: transport: dashboard: 192.168 .56 .112 :8858 port: 8719 client-ip: 192.168 .2 .140
创建用于测试的 Controller 类,这里通过 @SentinelResource
注解指定 Sentinel 的资源名称,并设置 blockHandler
属性,该属性主要是针对 Sentinel 配置了控制规则后出现的违规情况(比如触发了流控规则、熔断规则、热点规则等)进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @RestController @RequestMapping("/provider") public class ProviderController { @GetMapping("/pay/get/{orderNumber}") @SentinelResource(value = "getPayByOrderNumber", blockHandler = "handlerBlockHandler") public ResultData getPayByOrderNumber (@PathVariable("orderNumber") String orderNumber) { PayDTO payDTO = new PayDTO(); payDTO.setId(1024 ); payDTO.setOrderNo(orderNumber); payDTO.setAmount(BigDecimal.valueOf(9.9 )); payDTO.setPayNo("pay:" + UUID.fastUUID()); payDTO.setUserId(1 ); return ResultData.success(JSON.toJSONString(payDTO)); } public ResultData handlerBlockHandler (String orderNumber, BlockException exception) { return ResultData.fail(ReturnCodeEnum.RC500.getCode(), "服务暂时不可用,触发 Sentinel 的控制规则!" ); } }
1.2.4、创建 Consumer 工程 为了测试 OpenFeign 远程调用的使用效果,需要创建一个服务消费者。这里创建 Consumer 的 Maven 工程,由于需要从 Nacos 获取服务列表,即作为 Nacos 的客户端,还需要引入 spring-cloud-starter-alibaba-nacos-discovery
。
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 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency > <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-openfeign</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-sentinel</artifactId > </dependency > </dependencies >
创建 Feign Client 的接口类,添加 @FeignClient
注解,并设置 value
和 fallback
属性,这是为了通过 OpenFeign 调用 Provider 提供的服务和实现服务降级处理
1 2 3 4 5 6 7 @FeignClient(value = "provider-service", fallback = ProviderFeignApiFallback.class) public interface ProviderFeignApi { @GetMapping("/provider/pay/get/{orderNumber}") ResultData getPayByOrderNumber (@PathVariable("orderNumber") String orderNumber) ; }
创建 Feign Client 的 Fallback 处理类,主要用于处理远程服务调用出错的情况(比如远程服务宕机、不可用或者调用超时),这里必须实现自定义的 Feign Client 的接口类,并将其注入进 Spring IOC 容器中
1 2 3 4 5 6 7 8 9 @Component public class ProviderFeignApiFallback implements ProviderFeignApi { @Override public ResultData getPayByOrderNumber (String orderNumber) { return ResultData.fail(ReturnCodeEnum.RC500.getCode(), "触发服务降级处理,请稍后再试!" ); } }
创建启动主类,添加 @EnableDiscoveryClient
和 @EnableFeignClients
注解
1 2 3 4 5 6 7 8 9 10 @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class ConsumerApplication { public static void main (String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }
在 application.yml
文件中配置端口号、注册中心地址、Sentinel 控制台地址等,并启用 Sentinel 对 OpenFeign 的支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 server: port: 8003 spring: application: name: consumer-service cloud: nacos: discovery: server-addr: 192.168 .56 .112 :8848 sentinel: transport: dashboard: 192.168 .56 .112 :8858 port: 8719 client-ip: 192.168 .2 .140 feign: sentinel: enabled: true
创建用于测试的 Controller 类
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController @RequestMapping("/consumer") public class ConsumerController { @Resource private ProviderFeignApi providerFeignApi; @GetMapping("/pay/get/{orderNumber}") public ResultData getPayByOrderNumber (@PathVariable("orderNumber") String orderNumber) { return providerFeignApi.getPayByOrderNumber(orderNumber); } }
1.2.5、案例代码测试一 (1) 分别启动 Nacos 服务、Sentinel 控制台、Provider 服务、Consumer 服务。
(2) 通过浏览器调用 Provider 服务的 http://127.0.0.1:8001/provider/pay/get/33
接口,让 Sentinel 控制台发现 Provider 服务的存在。
(3) 在 Sentinel 控制台的管理页面中,为 Provider 服务的 http://127.0.0.1:8001/provider/pay/get/33
接口添加流控规则,以此达到接口限流的目的,这里的资源名是在 Provider 服务中通过 @SentinelResource
注解定义的。
(4) 通过浏览器快速多次调用 Consumer 服务的 http://127.0.0.1:8003/consumer/pay/get/33
接口,发现会返回提示信息 服务暂时不可用,触发 Sentinel 的控制规则!
,说明 Sentinel 的流控规则(限流)生效了。
(5) 关闭 Provider 服务,再次通过浏览器调用 Consumer 服务的 http://127.0.0.1:8003/consumer/pay/get/33
接口,发现会返回提示信息 触发服务降级处理,请稍后再试!
,说明 OpenFeign 的服务降级处理生效了。
1.2.6、案例代码测试二 提示
@FeignClient
注解中的所有属性,Sentinel 都做了兼容。Sentinel 与 Feign 整合时,Feign Client 对应的接口的资源名定义规则为:HTTP请求方式:协议://服务名/请求路径
。 比如,在上面 ProviderFeignApi 接口中的 getPayByOrderNumber()
方法对应的资源名为:GET:http://provider-service/provider/pay/get/{orderNumber}
。 (1) 分别启动 Nacos 服务、Sentinel 控制台、Provider 服务、Consumer 服务。
(2) 通过浏览器调用 Consumer 服务的 http://127.0.0.1:8003/consumer/pay/get/33
接口,让 Sentinel 控制台发现 Consumer 服务的存在。
(3) 在 Sentinel 控制台的管理页面中,为 Consumer 服务中的 ProviderFeignApi 接口中的 getPayByOrderNumber()
方法添加流控规则,资源名是 GET:http://provider-service/provider/pay/get/{orderNumber}
。值得注意的是,这里并没有使用在 Provider 服务中通过 @SentinelResource
注解定义的资源名。
(4) 通过浏览器快速多次调用 Consumer 服务的 http://127.0.0.1:8003/consumer/pay/get/33
接口,发现会返回提示信息 触发服务降级处理,请稍后再试!
,说明 Sentinel 的流控规则(限流)生效了。
(5) 关闭 Provider 服务,再次通过浏览器调用 Consumer 服务的 http://127.0.0.1:8003/consumer/pay/get/33
接口,发现会返回提示信息 触发服务降级处理,请稍后再试!
,说明 OpenFeign 的服务降级处理生效了。
1.2.7、案例代码下载 Sentinel 整合 Gateway Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。从 1.6.0
版本开始,Sentinel 提供了 Sentinel API Gateway Adapter Common 模块,此模块中包含网关限流的规则和自定义 API 的实体和管理逻辑,可以提供以下两种资源维度的限流。Sentinel 默认不支持 URL 粒度的限流,如果需要细化到 URL 粒度,请参考 自定义 API 分组 的使用。
Route 维度:即在 Spring 配置文件中配置的 Gateway 路由条目,资源名为对应的 routeId
自定义 API 分组维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组
2.0、需求说明 本节将整合 Sentinel 和 Gateway,以此实现 Gateway 服务将请求转发(路由转发)给 Consumer 服务时进行网关流量控制(网关限流)。
2.1、案例代码一 2.1.0、案例目标 本案例主要基于 Route 维度,实现 Gateway 服务将请求转发(路由转发)给 Consumer 服务时的网关流量控制,并通过 Sentinel 提供的 API 自定义 Sentinel 的网关流控规则(即不依赖 Sentinel 控制台),完整的调用流程为 Gateway –> Consumer –> Provider。
2.1.1、版本说明 在本节的案例代码中,各个组件统一使用以下版本:
组件 版本 说明 Spring Boot 3.0.9 Spring Cloud 2022.0.2 Spring Cloud Alibaba 2022.0.0.0 Nacos Server 2.4.3
本节的案例代码除了支持上述版本的组件之外,还支持以下高版本的组件:
组件 版本组合一 版本组合二 说明 Spring Boot 3.2.0 3.2.9 Spring Cloud 2023.0.0 2023.0.1 Spring Cloud Alibaba 2022.0.0.0 2023.0.1.3 Nacos Server 2.4.3 2.4.3
2.1.2、整合步骤说明 Gateway 整合 Sentinel 时,通常只需引入 sentinel-spring-cloud-gateway-adapter
依赖,并注入对应的 SentinelGatewayFilter 实例以及 SentinelGatewayBlockExceptionHandler 实例即可。
2.1.3、创建父级 Pom 工程 在父工程里面配置好工程需要的父级依赖,目的是为了更方便管理与简化配置,具体 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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <properties > <hutool.version > 5.8.22</hutool.version > <lombok.version > 1.18.26</lombok.version > <spring.boot.version > 3.0.9</spring.boot.version > <spring.cloud.version > 2022.0.2</spring.cloud.version > <spring.cloud.alibaba.version > 2022.0.0.0</spring.cloud.alibaba.version > <spring.boot.test.version > 3.1.5</spring.boot.test.version > </properties > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > ${spring.boot.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 > <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 > cn.hutool</groupId > <artifactId > hutool-all</artifactId > <version > ${hutool.version}</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > ${lombok.version}</version > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <version > ${spring.boot.test.version}</version > <scope > test</scope > </dependency > </dependencies > </dependencyManagement >
2.1.4、创建 Provider 工程 为了测试 OpenFeign 远程调用的使用效果,需要创建一个服务提供者。这里创建 Provider 的 Maven 工程,由于需要将服务注册到 Nacos,工程下的 pom.xml
文件需要引入 spring-cloud-starter-alibaba-nacos-discovery
。
1 2 3 4 5 6 7 8 9 10 11 12 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > </dependencies >
创建 Provider 的启动主类,添加注解 @EnableDiscoveryClient
,将服务注册到 Nacos
1 2 3 4 5 6 7 8 9 @SpringBootApplication @EnableDiscoveryClient public class ProviderApplication { public static void main (String[] args) { SpringApplication.run(ProviderApplication.class, args); } }
在 application.yml
文件中指定服务名称(provider-service
)、注册中心地址、端口
1 2 3 4 5 6 7 8 9 10 11 server: port: 8001 spring: application: name: provider-service cloud: nacos: discovery: server-addr: 192.168 .56 .112 :8848
创建用于测试的 Controller 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RestController @RequestMapping("/provider") public class ProviderController { @GetMapping("/pay/get/{orderNumber}") public ResultData getPayByOrderNumber (@PathVariable("orderNumber") String orderNumber) { PayDTO payDTO = new PayDTO(); payDTO.setId(1024 ); payDTO.setOrderNo(orderNumber); payDTO.setAmount(BigDecimal.valueOf(9.9 )); payDTO.setPayNo("pay:" + UUID.fastUUID()); payDTO.setUserId(1 ); return ResultData.success(JSONUtil.toJsonStr(payDTO)); } }
2.1.5、创建 Consumer 工程 为了测试 OpenFeign 远程调用的使用效果,需要创建一个服务消费者。这里创建 Consumer 的 Maven 工程,由于需要从 Nacos 获取服务列表,即作为 Nacos 的客户端,还需要引入 spring-cloud-starter-alibaba-nacos-discovery
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <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-openfeign</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency > </dependencies >
创建 Feign Client 的接口类,用于通过 OpenFeign 调用 Provider 提供的服务
1 2 3 4 5 6 7 @FeignClient(value = "provider-service") public interface ProviderFeignApi { @GetMapping("/provider/pay/get/{orderNumber}") ResultData getPayByOrderNumber (@PathVariable("orderNumber") String orderNumber) ; }
创建启动主类,添加 @EnableDiscoveryClient
和 @EnableFeignClients
注解
1 2 3 4 5 6 7 8 9 10 @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class ConsumerApplication { public static void main (String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }
在 application.yml
文件中配置端口号、注册中心地址
1 2 3 4 5 6 7 8 9 10 11 server: port: 8003 spring: application: name: consumer-service cloud: nacos: discovery: server-addr: 192.168 .56 .112 :8848
创建用于测试的 Controller 类
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController @RequestMapping("/consumer") public class ConsumerController { @Resource private ProviderFeignApi providerFeignApi; @GetMapping("/pay/get/{orderNumber}") public ResultData getPayByOrderNumber (@PathVariable("orderNumber") String orderNumber) { return providerFeignApi.getPayByOrderNumber(orderNumber); } }
2.1.6、创建 Gateway 工程 这里创建 Gateway 的 Maven 工程,由于需要从 Nacos 获取服务列表,即作为 Nacos 的客户端,还需要引入 spring-cloud-starter-alibaba-nacos-discovery
。特别注意,为了 Gateway 可以正常实现负载均衡,即将请求转发(路由转发)给 Nacos 中已注册的微服务应用,则必须引入 spring-cloud-starter-loadbalancer
。
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 <dependencies > <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-gateway</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency > <dependency > <groupId > com.alibaba.csp</groupId > <artifactId > sentinel-spring-cloud-gateway-adapter</artifactId > <version > 1.8.6</version > </dependency > <dependency > <groupId > com.alibaba.csp</groupId > <artifactId > sentinel-transport-simple-http</artifactId > <version > 1.8.6</version > </dependency > <dependency > <groupId > javax.annotation</groupId > <artifactId > javax.annotation-api</artifactId > <version > 1.3.2</version > <scope > compile</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > </dependencies >
在 application.yml
文件中配置端口号、注册中心地址、Gateway 的路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 server: port: 8005 spring: application: name: gateway-service cloud: nacos: discovery: server-addr: 192.168 .56 .112 :8848 gateway: discovery: locator: enabled: true routes: - id: consumer_route uri: lb://consumer-service predicates: - Path=/consumer/pay/get/**
创建 Gateway 的配置类,通过 Sentinel 提供的 API 自定义 Sentinel 的网关流控规则(即不依赖 Sentinel 控制台)。由于是基于 Route 维度进行限流,Java 代码里的 consumer_route
是在 application.yml
文件中配置的 Gateway 路由的 ID
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;import org.springframework.beans.factory.ObjectProvider;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.Ordered;import org.springframework.core.annotation.Order;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.codec.ServerCodecConfigurer;import org.springframework.web.reactive.function.BodyInserters;import org.springframework.web.reactive.function.server.ServerResponse;import org.springframework.web.reactive.result.view.ViewResolver;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;import javax.annotation.PostConstruct;import java.util.Collections;import java.util.HashMap;import java.util.HashSet;import java.util.List;import java.util.Map;import java.util.Set;@Configuration public class GatewayConfiguration { private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public GatewayConfiguration (ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this .viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this .serverCodecConfigurer = serverCodecConfigurer; } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler () { return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @Bean @Order(-1) public GlobalFilter sentinelGatewayFilter () { return new SentinelGatewayFilter(); } @PostConstruct public void doInit () { initGatewayRules(); initGatewayBlockHandler(); } private void initGatewayRules () { Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("consumer_route" ).setCount(2 ).setIntervalSec(1 )); GatewayRuleManager.loadRules(rules); } private void initGatewayBlockHandler () { BlockRequestHandler handler = new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest (ServerWebExchange exchange, Throwable t) { Map<String, String> map = new HashMap<>(); map.put("errorCode" , String.valueOf(HttpStatus.SERVICE_UNAVAILABLE.value())); map.put("errorMessage" , "系统繁忙,请稍后再试!" ); return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS).contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(map)); } }; GatewayCallbackManager.setBlockHandler(handler); } }
创建启动主类,添加 @EnableDiscoveryClient
注解
1 2 3 4 5 6 7 8 9 @SpringBootApplication @EnableDiscoveryClient public class GatewaySentinelApplication { public static void main (String[] args) { SpringApplication.run(GatewaySentinelApplication.class, args); } }
2.1.7、案例代码测试 (1) 分别启动 Nacos 服务、Gateway 服务、Provider 服务、Consumer 服务。 (2) 通过浏览器快速多次调用 Gateway 服务的 http://127.0.0.1:8005/consumer/pay/get/33
接口,发现会返回限流的提示信息(如下),说明 Gateway 整合 Sentinel 成功实现网关限流。 1 2 3 4 { "errorMessage" : "系统繁忙,请稍后再试!" , "errorCode" : "503" }
2.1.8、案例代码下载 2.2、案例代码二 2.2.0、案例目标 本案例主要基于 Route 维度,实现 Gateway 服务将请求转发(路由转发)给 Consumer 服务时的网关流量控制,并使用 Sentinel 控制台添加网关流控规则,完整的调用流程为 Gateway –> Consumer –> Provider。
2.2.1、版本说明 在本节的案例代码中,各个组件统一使用以下版本:
组件 版本 说明 Spring Boot 3.0.9 Spring Cloud 2022.0.2 Spring Cloud Alibaba 2022.0.0.0 Nacos Server 2.4.3 Sentinel Dashboard 1.8.7
本节的案例代码除了支持上述版本的组件之外,还支持以下高版本的组件:
组件 版本组合一 版本组合二 说明 Spring Boot 3.2.0 3.2.9 Spring Cloud 2023.0.0 2023.0.1 Spring Cloud Alibaba 2022.0.0.0 2023.0.1.3 Nacos Server 2.4.3 2.4.3 Sentinel Dashboard 1.8.7 1.8.7
2.2.2、整合步骤说明 这里使用 Sentinel Starter 来整合 Gateway,因此需要引入 spring-cloud-alibaba-sentinel-gateway
依赖,而且还需要添加 spring-cloud-starter-gateway
依赖来让 spring-cloud-alibaba-sentinel-gateway
模块里的 Spring Cloud Gateway 自动配置类生效。同时在 YML 配置文件里将 spring.cloud.sentinel.filter.enabled
配置项设置为 false
(若在 Sentinel 网关流控控制台上看到了 URL 资源,就说明此配置项没有置为 false
)。Sentinel 网关流控的默认粒度是 Route 维度以及自定义 API 分组维度,默认不支持 URL 粒度。如果需要细化到 URL 粒度,请参考 自定义 API 分组 的使用。
2.2.3、创建父级 Pom 工程 在父工程里面配置好工程需要的父级依赖,目的是为了更方便管理与简化配置,具体 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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <properties > <hutool.version > 5.8.22</hutool.version > <lombok.version > 1.18.26</lombok.version > <spring.boot.version > 3.0.9</spring.boot.version > <spring.cloud.version > 2022.0.2</spring.cloud.version > <spring.cloud.alibaba.version > 2022.0.0.0</spring.cloud.alibaba.version > <spring.boot.test.version > 3.1.5</spring.boot.test.version > </properties > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > ${spring.boot.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 > <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 > cn.hutool</groupId > <artifactId > hutool-all</artifactId > <version > ${hutool.version}</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > ${lombok.version}</version > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <version > ${spring.boot.test.version}</version > <scope > test</scope > </dependency > </dependencies > </dependencyManagement >
2.2.4、创建 Provider 工程 为了测试 OpenFeign 远程调用的使用效果,需要创建一个服务提供者。这里创建 Provider 的 Maven 工程,由于需要将服务注册到 Nacos,工程下的 pom.xml
文件需要引入 spring-cloud-starter-alibaba-nacos-discovery
。
1 2 3 4 5 6 7 8 9 10 11 12 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > </dependencies >
创建 Provider 的启动主类,添加注解 @EnableDiscoveryClient
,将服务注册到 Nacos
1 2 3 4 5 6 7 8 9 @SpringBootApplication @EnableDiscoveryClient public class ProviderApplication { public static void main (String[] args) { SpringApplication.run(ProviderApplication.class, args); } }
在 application.yml
文件中指定服务名称(provider-service
)、注册中心地址、端口
1 2 3 4 5 6 7 8 9 10 11 server: port: 8001 spring: application: name: provider-service cloud: nacos: discovery: server-addr: 192.168 .56 .112 :8848
创建用于测试的 Controller 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RestController @RequestMapping("/provider") public class ProviderController { @GetMapping("/pay/get/{orderNumber}") public ResultData getPayByOrderNumber (@PathVariable("orderNumber") String orderNumber) { PayDTO payDTO = new PayDTO(); payDTO.setId(1024 ); payDTO.setOrderNo(orderNumber); payDTO.setAmount(BigDecimal.valueOf(9.9 )); payDTO.setPayNo("pay:" + UUID.fastUUID()); payDTO.setUserId(1 ); return ResultData.success(JSONUtil.toJsonStr(payDTO)); } }
2.2.5、创建 Consumer 工程 为了测试 OpenFeign 远程调用的使用效果,需要创建一个服务消费者。这里创建 Consumer 的 Maven 工程,由于需要从 Nacos 获取服务列表,即作为 Nacos 的客户端,还需要引入 spring-cloud-starter-alibaba-nacos-discovery
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <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-openfeign</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency > </dependencies >
创建 Feign Client 的接口类,用于通过 OpenFeign 调用 Provider 提供的服务
1 2 3 4 5 6 7 @FeignClient(value = "provider-service") public interface ProviderFeignApi { @GetMapping("/provider/pay/get/{orderNumber}") ResultData getPayByOrderNumber (@PathVariable("orderNumber") String orderNumber) ; }
创建启动主类,添加 @EnableDiscoveryClient
和 @EnableFeignClients
注解
1 2 3 4 5 6 7 8 9 10 @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class ConsumerApplication { public static void main (String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }
在 application.yml
文件中配置端口号、注册中心地址
1 2 3 4 5 6 7 8 9 10 11 server: port: 8003 spring: application: name: consumer-service cloud: nacos: discovery: server-addr: 192.168 .56 .112 :8848
创建用于测试的 Controller 类
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController @RequestMapping("/consumer") public class ConsumerController { @Resource private ProviderFeignApi providerFeignApi; @GetMapping("/pay/get/{orderNumber}") public ResultData getPayByOrderNumber (@PathVariable("orderNumber") String orderNumber) { return providerFeignApi.getPayByOrderNumber(orderNumber); } }
2.2.6、创建 Gateway 工程 这里创建 Gateway 的 Maven 工程,由于需要从 Nacos 获取服务列表,即作为 Nacos 的客户端,还需要引入 spring-cloud-starter-alibaba-nacos-discovery
。特别注意,为了 Gateway 可以正常实现负载均衡,即将请求转发(路由转发)给 Nacos 中已注册的微服务应用,则必须引入 spring-cloud-starter-loadbalancer
。
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 <dependencies > <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-gateway</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-sentinel</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-alibaba-sentinel-gateway</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > </dependencies >
在 application.yml
文件中配置端口号、注册中心地址、Sentinel 控制台地址、Gateway 的路由,并将 spring.cloud.sentinel.filter.enabled
配置项设置为 false
(若在 Sentinel 网关流控控制台上看到了 URL 资源,就说明此配置项没有置为 false
)。
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 server: port: 8005 spring: application: name: gateway-service cloud: nacos: discovery: server-addr: 192.168 .56 .112 :8848 sentinel: transport: dashboard: 192.168 .56 .112 :8858 port: 8719 client-ip: 192.168 .2 .140 filter: enabled: false gateway: discovery: locator: enabled: true routes: - id: consumer_route uri: lb://consumer-service predicates: - Path=/consumer/pay/get/**
创建一个配置类,用于自定义 Gateway 触发 Sentinel 控制规则(如流控规则、熔断规则等)后的处理逻辑。这是为了解决在 spring-cloud-alibaba-sentinel-gateway
模块中,Sentinel 与 Gateway 存在适配的 Bug ,详细说明请看 这里 。
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 import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;import jakarta.annotation.PostConstruct;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.web.reactive.function.BodyInserters;import org.springframework.web.reactive.function.server.ServerResponse;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;import java.util.HashMap;import java.util.Map;@Configuration public class GatewayConfiguration { @PostConstruct public void doInit () { initGatewayBlockHandler(); } public void initGatewayBlockHandler () { BlockRequestHandler handler = new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest (ServerWebExchange exchange, Throwable t) { Map<String, String> map = new HashMap<>(); map.put("errorCode" , String.valueOf(HttpStatus.SERVICE_UNAVAILABLE.value())); map.put("errorMessage" , "系统繁忙,请稍后再试!" ); return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS).contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(map)); } }; GatewayCallbackManager.setBlockHandler(handler); } }
创建启动主类,添加 @EnableDiscoveryClient
注解
1 2 3 4 5 6 7 8 9 @SpringBootApplication @EnableDiscoveryClient public class GatewaySentinelApplication { public static void main (String[] args) { SpringApplication.run(GatewaySentinelApplication.class, args); } }
2.1.7、案例代码测试 (1) 分别启动 Nacos 服务、Sentinel 控制台服务、Gateway 服务、Provider 服务、Consumer 服务。
(2) 通过浏览器调用 Gateway 服务的 http://127.0.0.1:8005/consumer/pay/get/33
接口,让 Sentinel 控制台发现 Gateway 服务的存在。
(3) 通过浏览器访问 Sentinel 控制台的管理页面,添加网关流控规则(如下图所示)。这里的 API 类型
选择 “Route ID”,而 API 名称
就是在 application.yml
配置文件里定义的 Gateway 路由的 ID。
提示
Sentinel 网关流控规则的属性说明可参考 官方文档 ,其中核心属性如下: Burst Size
:应对突发请求时额外允许处理的请求数目。针对请求属性
:参数限流配置(即 Sentinel 的热点规则 )。如果不配置(不勾选),则代表不针对请求参数进行限流,即该网关流控规则将会被转换成普通流控规则,否则会转换成热点规则。(4) 通过浏览器快速多次调用 Gateway 服务的 http://127.0.0.1:8005/consumer/pay/get/33
接口,发现会返回限流的提示信息(如下),说明 Gateway 整合 Sentinel 成功实现网关限流。 1 2 3 4 { "errorMessage" : "系统繁忙,请稍后再试!" , "errorCode" : "503" }
2.1.8、案例代码下载 2.3、案例总结说明 案例一:
引入 sentinel-spring-cloud-gateway-adapter
依赖,并注入对应的 SentinelGatewayFilter 实例以及 SentinelGatewayBlockExceptionHandler 实例。 通过 Sentinel 提供的 API 自定义 Sentinel 的网关流控规则(即不依赖 Sentinel 控制台)。 案例二:
直接引入 spring-cloud-alibaba-sentinel-gateway
,通常不需要自己添加 Configuration 类。 通过 Sentinel 控制台的可视化界面添加网关流控规则。 特别注意
(1) 若使用 Spring Cloud Alibaba Sentinel 数据源模块,需要注意 Sentinel 网关流控规则的数据源类型是 gw-flow
,若将网关流控规则的数据源类型指定为 flow
则不会生效。 (2) 上述的案例一和案例二可以结合使用,也就是说可以通过 Sentinel 提供的 API 自定义 Sentinel 的网关流控规则,同时还可以通过 Sentinel 控制台添加网关流控规则。推荐使用案例二的整合方式,然后将 Sentinel 控制台的网关流控规则持久化到 Nacos 中。 (3) 特别注意,如果将案例一和案例二结合使用,建议不要使用 Sentinel 提供的 API 来自定义 Sentinel 的自定义 API 分组,否则 Sentinel 控制台可能会无法正常发现 Gateway 服务,即使在 Gateway 服务启动时加上 JVM 启动参数 -Dcsp.sentinel.app.type=1
也无法解决(原因不明),该启动参数用于将微服务应用标记为 API Gateway 类型。 2.4、自定义 API 分组 用户自定义的 API 定义分组(ApiDefinition),可以看做是一些 URL 匹配的组合。比如,可以定义一个 API 分组叫 my_api
,请求 Path 模式为 /foo/**
和 /baz/**
的都归到 my_api
这个 API 分组下面。那么在限流的时候,可以针对这个自定义的 API 分组维度进行限流。
官方文档
Sentinel 默认不支持 URL 粒度的限流,如果需细化到 URL 粒度,可以参考以下内容来自定义 API 分组,或者参考 官方文档 。
2.4.0、使用 API 自定义 API 分组 本节是在上面案例一 的代码基础上修改而来,用于演示如何使用 Sentinel 提供的 API 自定义 API 分组,以此实现 Gateway 的网关限流。
2.4.0.0、YML 配置 将 Gateway 服务的 YML 配置信息替换为以下内容:
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 server: port: 8005 spring: application: name: gateway-service cloud: nacos: discovery: server-addr: 192.168 .56 .112 :8848 gateway: discovery: locator: enabled: true routes: - id: consumer_route uri: lb://consumer-service predicates: - Path=/consumer/** - id: baidu_route uri: https://www.baidu.com predicates: - Path=/baidu/** filters: - RewritePath=/baidu/(?<segment>.*), /$\{segment}
2.4.0.1、核心配置类 将项目里的 GatewayConfiguration 类的代码替换为以下内容:
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;import org.springframework.beans.factory.ObjectProvider;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.Ordered;import org.springframework.core.annotation.Order;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.codec.ServerCodecConfigurer;import org.springframework.web.reactive.function.BodyInserters;import org.springframework.web.reactive.function.server.ServerResponse;import org.springframework.web.reactive.result.view.ViewResolver;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;import javax.annotation.PostConstruct;import java.util.Collections;import java.util.HashMap;import java.util.HashSet;import java.util.List;import java.util.Map;import java.util.Set;@Configuration public class GatewayConfiguration { private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public GatewayConfiguration (ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this .viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this .serverCodecConfigurer = serverCodecConfigurer; } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler () { return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @Bean @Order(-1) public GlobalFilter sentinelGatewayFilter () { return new SentinelGatewayFilter(); } @PostConstruct public void doInit () { initCustomizedApis(); initGatewayRules(); initGatewayBlockHandler(); } private void initCustomizedApis () { Set<ApiDefinition> definitions = new HashSet<>(); ApiDefinition api1 = new ApiDefinition("some_customized_api" ).setPredicateItems(new HashSet<ApiPredicateItem>() {{ add(new ApiPathPredicateItem().setPattern("/consumer/user" )); add(new ApiPathPredicateItem().setPattern("/consumer/pay/get/**" ) .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); ApiDefinition api2 = new ApiDefinition("another_customized_api" ).setPredicateItems(new HashSet<ApiPredicateItem>() {{ add(new ApiPathPredicateItem().setPattern("/jd/**" ) .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); definitions.add(api1); definitions.add(api2); GatewayApiDefinitionManager.loadApiDefinitions(definitions); } private void initGatewayRules () { Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("consumer_route" ).setCount(2 ).setIntervalSec(1 )); rules.add(new GatewayFlowRule("some_customized_api" ).setResourceMode( SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME).setCount(2 ).setIntervalSec(1 )); GatewayRuleManager.loadRules(rules); } private void initGatewayBlockHandler () { BlockRequestHandler handler = new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest (ServerWebExchange exchange, Throwable t) { Map<String, String> map = new HashMap<>(); map.put("errorCode" , String.valueOf(HttpStatus.SERVICE_UNAVAILABLE.value())); map.put("errorMessage" , "系统繁忙,请稍后再试!" ); return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS).contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(map)); } }; GatewayCallbackManager.setBlockHandler(handler); } }
2.4.0.2、案例代码测试 由于 Sentinel 默认支持使用 Gateway 的路由 ID 作为资源名,因此上面的 Route ID(如 consumer_route
)会被标识为 Sentinel 的资源。另外,上面通过 Sentinel 提供的 API 自定义了 API 分组 some_customized_api
,所以 API 分组名称(如 some_customized_api
)也会被标识为 Sentinel 的资源。当快速多次访问网关的 URL 为 http://127.0.0.1:8005/consumer/pay/get/33
的时候,请求会被限流,对应的统计会加到 consumer_route
和 some_customized_api
这两个资源上面;而当快速多次访问网关 URL 为 http://127.0.0.1:8005/baidu/
的时候,请求不会被限流,只会对应到 baidu_route
资源上面,这是因为没有给 baidu_route
资源设置网关流控规则(限流)。
特别注意
有的时候 Spring Cloud Gateway 会自己在 route
名称前面拼一个前缀,如 ReactiveCompositeDiscoveryClient_xxx
这种。请在 Sentinel 控制台上,观察簇点链路页面实际的资源名。
2.4.0.3、案例代码下载 2.4.1、Sentinel 控制台自定义 API 分组 本节直接使用上面案例二 的代码(无需修改任何内容),用于演示如何通过 Sentinel 控制台自定义 API 分组,以此实现 Gateway 的网关限流。
2.4.1.0、自定义 API 分组的步骤 (1) 在 Sentinel 控制台里,删除所有与 Gateway 服务相关的网关流控规则,这是为了避免影响测试效果。
(2) 通过浏览器调用 Gateway 服务的 http://127.0.0.1:8005/consumer/pay/get/33
接口,让 Sentinel 控制台发现 Gateway 服务的存在。
(3) 在 Sentinel 控制台里,添加自定义 API(即 API 分组),这里自定义 API 分组的名称为 consumer-api
,在该分组下面有两个 API,分别是 /consumer/user
和 /consumer/pay/get/**
。
(4) 在 Sentinel 控制台里,添加网关流控规则,这里 API 类型
选择 “API 分组”,而 API 名称
则选择之前添加的自定义 API 分组 consumer-api
。
提示
Sentinel 网关流控规则的属性说明可参考 官方文档 ,其中核心属性如下: Burst Size
:应对突发请求时额外允许处理的请求数目。针对请求属性
:参数限流配置(即 Sentinel 的热点规则 )。如果不配置(不勾选),则代表不针对请求参数进行限流,即该网关流控规则将会被转换成普通流控规则,否则会转换成热点规则。(5) 通过浏览器快速多次调用 Gateway 服务的 http://127.0.0.1:8005/consumer/pay/get/33
接口,发现会返回限流的提示信息(如下),说明 Gateway 使用 Sentinel 自定义 API 分组成功实现网关限流。 1 2 3 4 { "errorMessage": "系统繁忙,请稍后再试!", "errorCode": "503" }