目录
一、SpringCloud Alibaba Sentinel 概述及控制台的搭建
1、SpringCloud Alibaba Sentinel 概述
2、Sentinel 控制台的搭建
2.1、下载地址
2.2、启动sentinel
2.3、访问sentinel
3、 SpringCloud Alibaba Sentinel 控制台有哪些能力
4、常见的限流方案
5、流控规则高级说明
5.1、流控模式
5.2、流控效果
二、项目中使用sentinel的限流功能
1、添加依赖
2、yml配置(spring:cloud下)
3、编写限流controller接口
4、编写自定义通用的限流处理逻辑
三、使用sentinel 保护 RestTemplate 服务间调用
1、配置RestTemplate
1.1、yml配置文件配置
1.2、代码配置
2、配置服务异常降级之后的处理方法和限流后的处理方法
3、使用 Sentinel 保护 RestTemplate 服务间调用
四、使用sentinel 对 fegin 熔断降级支持
1、配置fegin
2、编写fegin client
3、编写Sentinel 对 OpenFeign 接口的降级策略
4、编写controller测试使用fegin的熔断降级
五、使用sentinel 对 RestTemplate 熔断降级支持
1、编写服务熔断降级后的策略(controller中fallback中的方法和类中的方法参数必须一致)
2、编写controller测试使用RestTemplate的熔断降级
五、Sentinel 结合 Nacos 实现限流规则持久化
1、SpringCloud Alibaba Sentinel 配置信息
2、 整合nacos实现限流规则持久化
2.1、添加依赖
2.2、编写限流接口
2.3、yml配置(spring:cloud下)
2.4、在nacos上编写指定限流接口的配置
3、 整合nacos实现熔断规则持久化
3.1、添加依赖
3.2、yml配置
3.3、在nacos上编写熔断规则。
3.4、熔断策略讲解
3.5、编写fegin client
3.6、编写Sentinel 对 OpenFeign 接口的降级策略
3.7、编写controller测试使用fegin的熔断降级
六、Gateway集成Sentinel实现网关限流(不论那种方式硬编码中的样板代码必须添加在Gateway)
1、引入依赖
2、编写配置文件(spring:cloud下)
3、硬编码方式实现
4、本地文件方式实现
4.1、编写路由规则
4.2、编写路由规则分组
4.3、编写配置文件
5、nacos方式实现
5.1、nacos上编写路由规则
5.2、nacos上编写路由规则分组
5.3、编写yml配置文件
一、SpringCloud Alibaba Sentinel 概述及控制台的搭建
1、SpringCloud Alibaba Sentinel 概述
2、Sentinel 控制台的搭建
2.1、下载地址
Releases · alibaba/Sentinel · GitHub
2.2、启动sentinel
java -Dserver.port=7777 -Dcsp.sentinel.dashboard.server=localhost:77
77 -Dproject.name=zhangkechen-sentinel-dashboard -jar sentinel-dashboard-1.8.6.jar
2.3、访问sentinel
地址:http://localhost:7777/
3、 SpringCloud Alibaba Sentinel 控制台有哪些能力
4、常见的限流方案
- 静态窗口限流(每个自然秒中可接受的请求数量,0-1,1-2,不能解决突发的问题)
- 动态窗口限流(滑动窗口,比如现在2.5秒,他计算的范围为1.5-2.5秒,不能解决突发的问题)
- 漏桶限流(把请求放到桶(队列)里面,规定一秒钟消费多少个请求,桶有上线,超过容量全部拒绝)
- 令牌桶限流(桶中放令牌,有上线,进:某个程序定时给桶中生成令牌,生成是会判断桶是否达到上限,请求进来后拿到令牌执行业务,否则失败。)
- 令牌大闸:(开始时就初始化令牌个数,请求进来后拿到令牌执行业务,否则失败。)
5、流控规则高级说明
5.1、流控模式
- 直接:默认,指的是直接对当前资源做限流。
- 关联:对目标的限流是有条件的,需要关联的资源限流时,目标才会限流。两个接口的关联是实时的,当关联的资源限流时,目标资源同步开启限流。
- 链路:当入口资源为配置的资源名称进来的请求,才会开始目标资源的限流。需要在配置文件配置Sping.cloud.sentinel.web-context-unify=false。流控模式时链路时必须关闭这个开关,默认是true,为true时可以在控制台-簇点链路界面看到所有请求都在一个链路下面。
5.2、流控效果
- 快速失败-》表示达到单机阈值的话直接拒绝请求。
- Warm Up-》预热,规则是coldFactor为3,即请求QPS从(闽值 /3)开始,经多少预热时长才逐渐升至设定的QPS闽值,假设单机阈值为100,预热时长为10,则表示从33开始经过十秒上升到100。
- 排队等待-》编写等待时长,单位毫秒。假设单机阈值为10,等待时长为500,当进来100个请求是,90个是等待状态,这些请求等待500毫秒,在500毫秒内如果满足单机阀值的要求(单机阀值小于10),就可以执行,否则拒绝。
二、项目中使用sentinel的限流功能
1、添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2、yml配置(spring:cloud下)
sentinel:
# 配置 sentinel dashboard 地址
transport:
dashboard: 127.0.0.1:7777
port: 8719 # 会在应用对应的机器上启动一个 Http Server, 该 Server 会与 Sentinel 控制台做交互
# 服务启动直接建立心跳连接
eager: true
datasource:
3、编写限流controller接口
/**
* <h1>基于 Sentinel 控制台配置流控规则</h1>
* Sentinel 是懒加载的, 先去访问一下, 就可以在 Sentinel Dashboard 看到了
* */
@Slf4j
@RestController
@RequestMapping("/dashboard")
public class RateLimitController {
/**
* <h2>在 dashboard 中 "流控规则" 中按照资源名称新增流控规则</h2>
* */
@GetMapping("/by-resource")
@SentinelResource(
value = "byResource",
blockHandler = "qinyiHandleBlockException",
blockHandlerClass = QinyiBlockHandler.class
)
public CommonResponse<String> byResource() {
log.info("coming in rate limit controller by resource");
return new CommonResponse<>(0, "", "byResource");
}
/**
* <h2>在 "簇点链路" 中给 url 添加流控规则</h2>
* */
@GetMapping("/by-url")
@SentinelResource(value = "byUrl")
public CommonResponse<String> byUrl() {
log.info("coming in rate limit controller by url");
return new CommonResponse<>(0, "", "byUrl");
}
}
4、编写自定义通用的限流处理逻辑
/**
* <h1>自定义通用的限流处理逻辑</h1>
* */
@Slf4j
public class QinyiBlockHandler {
/**
* <h2>通用限流处理方法</h2>
* 这个方法必须是 static 的
* */
public static CommonResponse<String> qinyiHandleBlockException(BlockException exception) {
log.error("trigger qinyi block handler: [{}], [{}]",
JSON.toJSONString(exception.getRule()), exception.getRuleLimitApp());
return new CommonResponse<>(
-1,
"flow rule trigger block exception",
null
);
}
}
三、使用sentinel 保护 RestTemplate 服务间调用
1、配置RestTemplate
1.1、yml配置文件配置
# 开启或关闭 @SentinelRestTemplate 注解
resttemplate:
sentinel:
enabled: true
1.2、代码配置
/**
* <h1>开启服务间的调用保护, 需要给 RestTemplate 做一些包装</h1>
* */
@Slf4j
@Configuration
public class SentinelConfig {
/**
* <h2>包装 RestTemplate</h2>
* */
@Bean
@SentinelRestTemplate(
fallback = "handleFallback", fallbackClass = RestTemplateExceptionUtil.class,
blockHandler = "handleBlock", blockHandlerClass = RestTemplateExceptionUtil.class
)
public RestTemplate restTemplate() {
return new RestTemplate(); // 可以对其做一些业务相关的配置
}
}
2、配置服务异常降级之后的处理方法和限流后的处理方法
/**
* <h1>RestTemplate 在限流或异常时的兜底方法</h1>
* */
@Slf4j
public class RestTemplateExceptionUtil {
/**
* <h2>限流后的处理方法</h2>
* */
public static SentinelClientHttpResponse handleBlock(HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution,
BlockException ex) {
log.error("Handle RestTemplate Block Exception: [{}], [{}]",
request.getURI().getPath(), ex.getClass().getCanonicalName());
return new SentinelClientHttpResponse(
JSON.toJSONString(new JwtToken("qinyi-imooc-block"))
);
}
/**
* <h2>异常降级之后的处理方法</h2>
* */
public static SentinelClientHttpResponse handleFallback(HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution,
BlockException ex) {
log.error("Handle RestTemplate Fallback Exception: [{}], [{}]",
request.getURI().getPath(), ex.getClass().getCanonicalName());
return new SentinelClientHttpResponse(
JSON.toJSONString(new JwtToken("qinyi-imooc-block"))
);
}
}
3、使用 Sentinel 保护 RestTemplate 服务间调用
/**
* <h1>使用 Sentinel 保护 RestTemplate 服务间调用</h1>
* */
@Slf4j
@RestController
@RequestMapping("/sentinel-rest-template")
public class SentinelRestTemplateController {
private final RestTemplate restTemplate;
public SentinelRestTemplateController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
/**
* <h2>从授权服务中获取 JwtToken</h2>
* 1. 流控降级:
* 是针对于簇点链路中的 http://127.0.0.1:7000/ecommerce-authority-center/authority/token
* 2. 容错降级: 对于服务不可用时不能生效
* */
@PostMapping("/get-token")
public JwtToken getTokenFromAuthorityService(
@RequestBody UsernameAndPassword usernameAndPassword) {
String requestUrl =
"http://127.0.0.1:7000/ecommerce-authority-center/authority/token";
log.info("RestTemplate request url and body: [{}], [{}]",
requestUrl, JSON.toJSONString(usernameAndPassword));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return restTemplate.postForObject(
requestUrl,
new HttpEntity<>(JSON.toJSONString(usernameAndPassword), headers),
JwtToken.class
);
}
}
四、使用sentinel 对 fegin 熔断降级支持
1、配置fegin
# 打开 Sentinel 对 Feign 的支持
feign:
sentinel:
enabled: true
spring:
cloud:
openfeign:
lazy-attributes-resolution: true
2、编写fegin client
/**
* <h1>通过 Sentinel 对 OpenFeign 实现熔断降级</h1>
* */
@FeignClient(
value = "e-commerce-imooc",
fallback = SentinelFeignClientFallback.class
)
public interface SentinelFeignClient {
@RequestMapping(value = "qinyi", method = RequestMethod.GET)
CommonResponse<String> getResultByFeign(@RequestParam Integer code);
}
3、编写Sentinel 对 OpenFeign 接口的降级策略
/**
* <h1>Sentinel 对 OpenFeign 接口的降级策略</h1>
* */
@Slf4j
@Component
public class SentinelFeignClientFallback implements SentinelFeignClient {
@Override
public CommonResponse<String> getResultByFeign(Integer code) {
log.error("request supply for test has some error: [{}]", code);
return new CommonResponse<>(
-1,
"sentinel feign fallback",
"input code: "+ code
);
}
}
4、编写controller测试使用fegin的熔断降级
/**
* <h1>OpenFeign 集成 Sentinel 实现熔断降级</h1>
* */
@Slf4j
@RestController
@RequestMapping("/sentinel-feign")
public class SentinelFeignController {
private final SentinelFeignClient sentinelFeignClient;
public SentinelFeignController(SentinelFeignClient sentinelFeignClient) {
this.sentinelFeignClient = sentinelFeignClient;
}
/**
* <h2>通过 Feign 接口去获取结果</h2>
* */
@GetMapping("/result-by-feign")
public CommonResponse<String> getResultByFeign(@RequestParam Integer code) {
log.info("coming in get result by feign: [{}]", code);
return sentinelFeignClient.getResultByFeign(code);
}
}
五、使用sentinel 对 RestTemplate 熔断降级支持
1、编写服务熔断降级后的策略(controller中fallback中的方法和类中的方法参数必须一致)
/**
* <h1>Sentinel 回退降级的兜底策略</h1>
* 都需要是静态方法 fallback中的方法和类中的方法参数必须一致
* */
@Slf4j
public class QinyiFallbackHandler {
/**
* <h2>getTokenFromAuthorityService 方法的 fallback</h2>
* */
public static JwtToken getTokenFromAuthorityServiceFallback(
UsernameAndPassword usernameAndPassword
) {
log.error("get token from authority service fallback: [{}]",
JSON.toJSONString(usernameAndPassword));
return new JwtToken("imooc-qinyi-fallback");
}
/**
* <h2>ignoreException 方法的 fallback</h2>
* */
public static JwtToken ignoreExceptionFallback(Integer code) {
log.error("ignore exception input code: [{}] has trigger exception", code);
return new JwtToken("imooc-qinyi-fallback");
}
}
2、编写controller测试使用RestTemplate的熔断降级
package com.imooc.ecommerce.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.fastjson.JSON;
import com.imooc.ecommerce.fallback_handler.QinyiFallbackHandler;
import com.imooc.ecommerce.vo.JwtToken;
import com.imooc.ecommerce.vo.UsernameAndPassword;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
/**
* <h1>Sentinel 提供容错降级的功能 服务未启动(容错了)</h1>
* */
@SuppressWarnings("all")
@Slf4j
@RestController
@RequestMapping("/sentinel-fallback")
public class SentinelFallbackController {
/** 注入没有增强的 RestTemplate */
private final RestTemplate restTemplate;
public SentinelFallbackController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@PostMapping("/get-token")
@SentinelResource(
value = "getTokenFromAuthorityService",
fallback = "getTokenFromAuthorityServiceFallback",
fallbackClass = { QinyiFallbackHandler.class }
)
public JwtToken getTokenFromAuthorityService(
@RequestBody UsernameAndPassword usernameAndPassword) {
String requestUrl =
"http://127.0.0.1:7000/ecommerce-authority-center/authority/token";
log.info("RestTemplate request url and body: [{}], [{}]",
requestUrl, JSON.toJSONString(usernameAndPassword));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return restTemplate.postForObject(
requestUrl,
new HttpEntity<>(JSON.toJSONString(usernameAndPassword), headers),
JwtToken.class
);
}
/**
* <h2>让 Sentinel 忽略一些异常</h2>
* */
@GetMapping("/ignore-exception")
@SentinelResource(
value = "ignoreException",
fallback = "ignoreExceptionFallback",
fallbackClass = { QinyiFallbackHandler.class },
exceptionsToIgnore = { NullPointerException.class }
)
public JwtToken ignoreException(@RequestParam Integer code) {
if (code % 2 == 0) {
throw new NullPointerException("yout input code is: " + code);
}
return new JwtToken("qinyi-imooc");
}
}
五、Sentinel 结合 Nacos 实现限流规则持久化
1、SpringCloud Alibaba Sentinel 配置信息
2、 整合nacos实现限流规则持久化
2.1、添加依赖
<!-- Sentinel 使用 Nacos 存储规则 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2.2、编写限流接口
/**
* <h2>在 dashboard 中 "流控规则" 中按照资源名称新增流控规则</h2>
* */
@GetMapping("/by-resource")
@SentinelResource(
value = "byResource",
blockHandler = "qinyiHandleBlockException",
blockHandlerClass = QinyiBlockHandler.class
)
public CommonResponse<String> byResource() {
log.info("coming in rate limit controller by resource");
return new CommonResponse<>(0, "", "byResource");
}
2.3、yml配置(spring:cloud下)
sentinel:
# 配置 sentinel dashboard 地址
transport:
dashboard: 127.0.0.1:7777
port: 8719 # 会在应用对应的机器上启动一个 Http Server, 该 Server 会与 Sentinel 控制台做交互
# 服务启动直接建立心跳连接
eager: true
datasource:
# 名称任意, 代表数据源
ds:
nacos:
# NacosDataSourceProperties.java 中定义
server-addr: ${spring.cloud.nacos.discovery.server-addr}
dataId: ${spring.application.name}-sentinel
namespace: ${spring.cloud.nacos.discovery.namespace}
groupId: DEFAULT_GROUP
data-type: json
# 规则类型: com.alibaba.cloud.sentinel.datasource.RuleType
# FlowRule 就是限流规则
rule-type: flow
2.4、在nacos上编写指定限流接口的配置
[
{
"resource": "byresource",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
- resource:资源名称。
- limitApp:针对来源,可以编写成服务名称,默认为default。
- grade:阈值类型,QPS(1)、并发线程数(0)。
- count:单机阈值。
- strategy:流控模式,直接(0)、关联(1)、链路(2)。
- controlBehavior:流控效果,快速失败(0)、Warm Up(1)、排队等待(2)。
- clusterMode:是否集群,是(true)、否(false)。
- warmUpPeriodSec:预热时长,单位秒。
- maxQueueingTimeMs:超时时间,单位毫秒。
- refResource:关联资源名称
3、 整合nacos实现熔断规则持久化
3.1、添加依赖
<!-- Sentinel 使用 Nacos 存储规则 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
3.2、yml配置
feign:
sentinel:
enabled: true
spring:
cloud:
openfeign:
lazy-attributes-resolution: true
sentinel:
datasource:
degrade:
nacos:
dataId: sentinel-batch-degrade
groupId: DEFAULT_GROUP
namespace: train
ruleType: degrade
serverAddr: 127.0.0.1:8848
transport:
dashboard: localhost:18080
port: 8719
3.3、在nacos上编写熔断规则。
[{
"resource":"GET:http://business/hello",
"grade":0,
"count": 201,
"timeWindow":11,
"minRequestAmount":6,
"statIntervalMs":1000,
"slowRatioThreshold":0.3
}]
- resource:资源名称。
- grade:熔断策略,慢比例调用(0)、异常比例(1)、异常数(2)。
- count:最大响应时间(最大TR),单位毫秒。
- timeWindow:熔断时长,单位秒。
- minRequestAmount:最小请求数。
- statIntervalMs:统计时长,单位毫秒。
- slowRatioThreshold:比例阀值。
[{
"resource":"GET:http://business/hello",
"grade":1,
"count": 0.3,
"timeWindow":11,
"minRequestAmount":6,
"statIntervalMs":1000
}]
- resource:资源名称。
- grade:熔断策略,慢比例调用(0)、异常比例(1)、异常数(2)。
- count:比例阀值。
- timeWindow:熔断时长,单位秒。
- minRequestAmount:最小请求数。
- statIntervalMs:统计时长,单位毫秒。
[{
"resource":"GET:http://business/hello",
"grade":2,
"count":3,
"timeWindow":11,
"minRequestAmount":6,
"statIntervalMs":1000
}]
- resource:资源名称。
- grade:熔断策略,慢比例调用(0)、异常比例(1)、异常数(2)。
- count:异常数。
- timeWindow:熔断时长,单位秒。
- minRequestAmount:最小请求数。
- statIntervalMs:统计时长,单位毫秒。
3.4、熔断策略讲解
- 慢比例调用:当最大RT=201,比例阀值=0.3,熔断时长=11,最小请求数=6,统计时间=1000,表示当请求资源时,当满足在一秒钟内,请求数超过6个,并且30%的请求时间超过201毫秒这个条件时,接口熔断11秒。
- 异常比例:当比例阀值=0.3,熔断时长=3,最小请求数=6,统计时间=1000,表示当请求资源时,当满足在一秒钟内,请求数超过6个,并且异常比例达到30%这个条件时,接口熔断3秒。
- 异常数:当异常数=3,熔断时长=3,最小请求数=6,统计时间=1000,表示当请求资源时,当满足在一秒钟内,请求数超过6个,并且异常数超过3个这个条件时,接口熔断3秒。
3.5、编写fegin client
/**
* <h1>通过 Sentinel 对 OpenFeign 实现熔断降级</h1>
* */
@FeignClient(
value = "e-commerce-imooc",
fallback = SentinelFeignClientFallback.class
)
public interface SentinelFeignClient {
@RequestMapping(value = "qinyi", method = RequestMethod.GET)
CommonResponse<String> getResultByFeign(@RequestParam Integer code);
}
3.6、编写Sentinel 对 OpenFeign 接口的降级策略
/**
* <h1>Sentinel 对 OpenFeign 接口的降级策略</h1>
* */
@Slf4j
@Component
public class SentinelFeignClientFallback implements SentinelFeignClient {
@Override
public CommonResponse<String> getResultByFeign(Integer code) {
log.error("request supply for test has some error: [{}]", code);
return new CommonResponse<>(
-1,
"sentinel feign fallback",
"input code: "+ code
);
}
}
3.7、编写controller测试使用fegin的熔断降级
/**
* <h1>OpenFeign 集成 Sentinel 实现熔断降级</h1>
* */
@Slf4j
@RestController
@RequestMapping("/sentinel-feign")
public class SentinelFeignController {
private final SentinelFeignClient sentinelFeignClient;
public SentinelFeignController(SentinelFeignClient sentinelFeignClient) {
this.sentinelFeignClient = sentinelFeignClient;
}
/**
* <h2>通过 Feign 接口去获取结果</h2>
* */
@GetMapping("/result-by-feign")
public CommonResponse<String> getResultByFeign(@RequestParam Integer code) {
log.info("coming in get result by feign: [{}]", code);
return sentinelFeignClient.getResultByFeign(code);
}
}
六、Gateway集成Sentinel实现网关限流(不论那种方式硬编码中的样板代码必须添加在Gateway)
1、引入依赖
<!-- 集成 Sentinel, 在网关层面实现限流 -->
<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>
<!-- Sentinel 使用 Nacos 存储规则 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2、编写配置文件(spring:cloud下)
sentinel:
# 配置 sentinel dashboard 地址
transport:
dashboard: 127.0.0.1:7777
port: 8719 # 会在应用对应的机器上启动一个 Http Server, 该 Server 会与 Sentinel 控制台做交互
# 服务启动直接建立心跳连接
eager: true
3、硬编码方式实现
package com.imooc.ecommerce.config;
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 com.alibaba.csp.sentinel.slots.block.RuleConstant;
import lombok.extern.slf4j.Slf4j;
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.*;
/**
* <h1>Gateway 集成 Sentinel 实现限流</h1>
* */
@Slf4j
@Configuration
public class SentinelGatewayConfiguration {
//=====================================================样板间式代码开始=====================================================
/** 视图解析器 */
private final List<ViewResolver> viewResolvers;
/** HTTP 请求和响应数据的编解码配置 */
private final ServerCodecConfigurer serverCodecConfigurer;
/**
* <h2>构造方法</h2>
* */
public SentinelGatewayConfiguration(
ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer
) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* <h2>限流异常处理器, 限流异常出现时, 执行到这个 handler</h2>
* */
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// 默认会返回错误 message, code 429
return new SentinelGatewayBlockExceptionHandler(
this.viewResolvers,
this.serverCodecConfigurer
);
}
/**
* <h2>限流过滤器, 是 Gateway 全局过滤器, 优先级定义为最高</h2>
* */
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
/**
* 自定义限流异常返回
*/
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map result = new HashMap();
result.put("code","500016");
result.put("msg","接口被限流了");
return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromObject(result));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
//=====================================================样板间式代码结束=====================================================
//=====================================================硬编码方式实现限流规则============================================//
/**
* <h2>初始化限流规则</h2>
* */
// @PostConstruct
public void doInit() {
log.info("---------------------------------------------------");
// 加载网关限流规则
log.info("load sentinel gateway rules (code define)");
initGatewayRules();
// 加载自定义限流异常处理器
initBlockHandler();
log.info("---------------------------------------------------");
}
/**
* <h2>硬编码网关限流规则</h2>
* */
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
GatewayFlowRule rule = new GatewayFlowRule();
// 指定限流模式, 根据 route_id 做限流, 默认的模式
rule.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID);
// 指定 route_id -> service id
rule.setResource("e-commerce-nacos-client");
// 按照 QPS 限流
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 统计窗口和限流阈值
rule.setIntervalSec(60);
rule.setCount(3);
// rules.add(rule);
// 限流分组, Sentinel 先去找规则定义, 再去找规则中定义的分组
rules.add(
new GatewayFlowRule("nacos-client-api-1")
.setCount(3).setIntervalSec(60)
);
rules.add(
new GatewayFlowRule("nacos-client-api-2")
.setCount(1).setIntervalSec(60)
);
// 加载到网关中
GatewayRuleManager.loadRules(rules);
// 加载限流分组
initCustomizedApis();
}
/**
* <h2>自定义限流异常处理器</h2>
* */
private void initBlockHandler() {
// 自定义 BlockRequestHandler
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange,
Throwable throwable) {
log.error("------------- trigger gateway sentinel rule -------------");
Map<String, String> result = new HashMap<>();
result.put("code", String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value()));
result.put("message", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());
result.put("route", "e-commerce-nacos-client");
return ServerResponse
.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(result));
}
};
// 设置自定义限流异常处理器
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
/**
* <h2>硬编码网关限流分组</h2>
* 1. 最大限制 - 演示
* 2. 具体的分组
* */
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
// nacos-client-api 组, 最大的限制
ApiDefinition api = new ApiDefinition("nacos-client-api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
// 模糊匹配 /imooc/ecommerce-nacos-client/ 及其子路径的所有请求
add(new ApiPathPredicateItem()
.setPattern("/imooc/ecommerce-nacos-client/**")
// 根据前缀匹配
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
// nacos-client-api-1 分组
ApiDefinition api1 = new ApiDefinition("nacos-client-api-1")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem()
// 精确匹配 /imooc/ecommerce-nacos-client/nacos-client/service-instance
.setPattern("/imooc/ecommerce-nacos-client" +
"/nacos-client/service-instance"));
}});
// nacos-client-api-2 分组
ApiDefinition api2 = new ApiDefinition("nacos-client-api-2")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem()
// 精确匹配 /imooc/ecommerce-nacos-client/nacos-client/project-config
.setPattern("/imooc/ecommerce-nacos-client" +
"/nacos-client/project-config"));
}});
definitions.add(api1);
definitions.add(api2);
// 加载限流分组
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}
4、本地文件方式实现
4.1、编写路由规则
gateway-flow-rule-sentinel.json(路由规则,resource可以为接口名称、微服务名称、分组名称,resourceMode=1,resourceMode=0表示分组)
[
{
"resource": "e-commerce-nacos-client",
"resourceMode": 0,
"count": 3,
"intervalSec": 60
},
{
"resource": "nacos-client-api",
"resourceMode": 1,
"count": 1,
"intervalSec": 60
}
]
4.2、编写路由规则分组
gateway-flow-rule-api-sentinel.json(路由规则分组,表示resource中是apiname时,该分组中的接口路径都按照resource中的限流规则)
[
{
"apiName": "nacos-client-api",
"predicateItems": [
{
"pattern": "/imooc/ecommerce-nacos-client/nacos-client/project-config"
},
{
"pattern": "/imooc/ecommerce-nacos-client/**",
"matchStrategy": 1
}
]
}
]
4.3、编写配置文件
sentinel:
eager: true
transport:
client-ip: 8720
dashboard: 127.0.0.1:7777
datasource:
dsl.file:
file: classpath:gateway-flow-rule-sentinel.json
ruleType: gw-flow
ds2.file:
file: classpath:gateway-flow-rule-api-sentinel.json
ruleType: gw-api-group
5、nacos方式实现
5.1、nacos上编写路由规则
gateway-flow-rule-sentinel.json(路由规则,resource可以为接口名称、微服务名称、分组名称,resourceMode=0为roteId,resourceMode=1表示api分组)
[
{
"resource": "e-commerce-nacos-client",
"resourceMode": 0,
"count": 3,
"intervalSec": 60
},
{
"resource": "nacos-client-api",
"resourceMode": 1,
"count": 1,
"intervalSec": 60
}
]
5.2、nacos上编写路由规则分组
gateway-flow-rule-api-sentinel.json(路由规则分组,表示resource中是apiname时,改分组中的接口路径都按照resource中的限流规则,matchStrategy=1表示为模糊匹配,默认为精确匹配)
[
{
"apiName": "nacos-client-api",
"predicateItems": [
{
"pattern": "/imooc/ecommerce-nacos-client/nacos-client/project-config"
},
{
"pattern": "/imooc/ecommerce-nacos-client/**",
"matchStrategy": 1
}
]
}
]
5.3、编写yml配置文件
sentinel:
eager: true
transport:
client-ip: 8720
dashboard: 127.0.0.1:7777
datasource:
# 名称任意, 代表数据源
ds1:
nacos:
# NacosDataSourceProperties.java 中定义
server-addr: ${spring.cloud.nacos.discovery.server-addr}
dataId: gateway-flow-rule-sentinel
namespace: ${spring.cloud.nacos.discovery.namespace}
groupId: DEFAULT_GROUP
data-type: json
# 规则类型: com.alibaba.cloud.sentinel.datasource.RuleType
# FlowRule 就是限流规则
rule-type: gw_flow
# 名称任意, 代表数据源
ds2:
nacos:
# NacosDataSourceProperties.java 中定义
server-addr: ${spring.cloud.nacos.discovery.server-addr}
dataId: gateway-flow-rule-api-sentinel
namespace: ${spring.cloud.nacos.discovery.namespace}
groupId: DEFAULT_GROUP
data-type: json
# 规则类型: com.alibaba.cloud.sentinel.datasource.RuleType
# FlowRule 就是限流规则
rule-type: gw_api_group