【Spring Cloud Alibaba】Sentinel 流量控制
- 1 搭建SpringCloud Alibaba Sentinel控制台
- 2 项目集成
- 3 降级
- 3.1 接口降级
- 3.1.2 硬编码限流
- 3.1.2 控制面板限流
- 3.2 RestTemplate 降级
- 3.3 Feign 降级
- 4 Sentinel 存储
- 4.1 SpringCloud Alibaba Sentinel 结合 Nacos
- 5 Gateway 结合 Sentinel
- 5.1 添加依赖
- 5.2 硬编码配置
- 5.3 JSON文件配置方式
- 5.3 Nacos存储配置方式
Sentinel是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助您保障微服务的稳定性
SpringCloud Alibaba Sentinel流量控制方向
- 资源的调用关系,例如资源的调用链路,资源和资源之间的关系
- 运行指标,例如QPS、线程池、系统负载等
- 控制的效果,例如直接限流、冷启动、排队等
1 搭建SpringCloud Alibaba Sentinel控制台
Sentinel提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能
获取并启动Sentinel Dashboard(控制台)
1.下载控制台Jar包: https://github.com/alibaba/Sentinel/releases
2. java -Dserver.port=7777 -Dcsp.sentinel.dashboard.server=localhost:7777 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar
3.从 Sentinel 1.6.0起,Sentinel Dashboard引入了基本的登录功能,默认的用
户名密码都是sentinel
2 项目集成
创建子模块,配置maven
<artifactId>e-commerce-sentinel-client</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<!-- 模块名及描述信息 -->
<name>e-commerce-sentinel-client</name>
<description>Sentinel Client</description>
<dependencies>
<!-- 创建工程需要的两个依赖 -->
<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>
<!-- Sentinel 适配了 Feign, 可以实现服务间调用的保护 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Sentinel 使用 Nacos 存储规则 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- web 工程 -->
<dependency>
<groupId>cn.flowboot.e.commerce</groupId>
<artifactId>e-commerce-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!--
SpringBoot的Maven插件, 能够以Maven的方式为应用提供SpringBoot的支持,可以将
SpringBoot应用打包为可执行的jar或war文件, 然后以通常的方式运行SpringBoot应用
-->
<build>
<finalName>${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
3 降级
SpringCloud Alibaba Sentinel对降级功能的支持
3.1 接口降级
3.1.2 硬编码限流
FlowRuleCodeController
package cn.flowboot.e.commerce.controller;
import cn.flowboot.e.commerce.block.handler.MyBlockHandler;
import cn.flowboot.e.commerce.vo.CommonResponse;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* <h1>流控规则硬编码</h1>
*
* @version 1.0
* @author: Vincent Vic
* @since: 2022/03/17
*/
@Slf4j
@RestController
@RequestMapping("/code")
public class FlowRuleCodeController {
/**
* 初始化流控规则
*/
@PostConstruct
public void init(){
//流控规则集合
List<FlowRule> flowRules = new ArrayList<>();
//创建流控规则
FlowRule flowRule = new FlowRule();
//设置流控规则QPS,限流阈值类型CQPS,并发线程数
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//流量控制手段
flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
//设置受保护的资源
flowRule.setResource("flowRuleCode");
//设置受保护的资源的阈值
flowRule.setCount(1);
flowRules.add(flowRule);
//加载配置好的规则
FlowRuleManager.loadRules(flowRules);
}
/**
* <h1>采用硬编码的限流规则的Controller方法</h1>
* @return
*/
@GetMapping("/flow-rule")
//@SentinelResource(value = "flowRuleCode")
//@SentinelResource(value = "flowRuleCode",blockHandler = "handleException")
@SentinelResource(value = "flowRuleCode",blockHandler = "myHandleException",blockHandlerClass = MyBlockHandler.class)
public CommonResponse<String> flowRuleCode(){
log.info("request flowRuleCode");
return CommonResponse.success();
}
/**
* <h2>当限流异常抛出时,指定调用的方法</h2>
* 是一个兜底策略
*/
public CommonResponse handleException(BlockException e){
log.error("has block exception : [{}]", JSONObject.toJSONString(e.getRule()));
return CommonResponse.fail("flow rule exception",e.getClass().getCanonicalName());
}
}
上文flowRuleCode方法注解使用到通用限流处理MyBlockHandler ,可切换注释使用不同使用策略
package cn.flowboot.e.commerce.block.handler;
import cn.flowboot.e.commerce.vo.CommonResponse;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
/**
* <h1>自定义通用处理逻辑</h1>
*
* @version 1.0
* @author: Vincent Vic
* @since: 2022/03/17
*/
@Slf4j
public class MyBlockHandler {
/**
* <h2>通用限流处理方法</h2>
* 这个方法必须是static的
**/
public static CommonResponse<String> myHandleException(BlockException e){
log.error("has block exception : [{}], [{}]", JSONObject.toJSONString(e.getRule()),e.getRuleLimitApp());
return CommonResponse.fail("trigger flow rule exception",e.getClass().getCanonicalName());
}
}
3.1.2 控制面板限流
RateLimitController
package cn.flowboot.e.commerce.controller;
import cn.flowboot.e.commerce.block.handler.MyBlockHandler;
import cn.flowboot.e.commerce.vo.CommonResponse;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* <h1>基于Sentinel 控制台配置流控规则</h1>
* Sentinel 是懒加载的,先去访问一下,就可以在 Sentinel Dashboard看到了
* @version 1.0
* @author: Vincent Vic
* @since: 2022/03/17
*/
@Slf4j
@RestController
@RequestMapping("/rate")
public class RateLimitController {
/**
* <h2>在 dashboard 中“流控规则”中按照资源名称新增流控规则</h2>
* @return
*/
@GetMapping("/by-resource")
@SentinelResource(value = "byResource",blockHandler = "myHandleException",blockHandlerClass = MyBlockHandler.class)
public CommonResponse<String> byResource(){
log.info("coming in rate limit controller by resource");
return CommonResponse.success();
}
/**
* <h2>在 "触点链路" 中给URL添加流控规则</h2>
* @return
*/
@GetMapping("/by-url")
@SentinelResource(value = "byUrl")
public CommonResponse<String> byUrl(){
log.info("coming in rate limit controller by Url");
return CommonResponse.success("byUrl");
}
}
添加流控规则,两个相同名称,一个是硬编码指定,一个是动态添加(保存JVM内存中)


3.2 RestTemplate 降级
Sentinel支持对RestTemplate 服务调用进行保护,实现流控降级和异常降级

# 开启或关闭 @SentinelRestTemplate
resttemplate:
sentinel:
enabled: true
SentinelFallbackController :通过@SentinelResource配置异常处理
package cn.flowboot.e.commerce.controller;
import cn.flowboot.e.commerce.conf.RestTemplateExceptionHandler;
import cn.flowboot.e.commerce.fallback.MyFallbackHandler;
import cn.flowboot.e.commerce.vo.CommonResponse;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
/**
* <h1>使用Sentinel保护RestTemplate服务调用</h1>
*
* @version 1.0
* @author: Vincent Vic
* @since: 2022/03/18
*/
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/fallback")
public class SentinelFallbackController {
//注入普通
private final RestTemplate restTemplate;
/**
* <h2> remoteConsumer - <h2>
* 容错降级:对于服务不可用时不能生效
* version: 1.0 - 2022/3/18
* @return {@link Map< String, Object> }
*/
@GetMapping("/remote/consumer")
@SentinelResource(
value = "remoteConsumerFallback",
fallback = "remoteConsumerFallback",
fallbackClass = { MyFallbackHandler.class }
)
public CommonResponse remoteConsumer(){
String requestUrL = "http://localhost:8500/sentinel-client/rest/remote/producer";
log.info("RestTemplate request url [{}] ",requestUrL);
return restTemplate.getForObject(requestUrL, CommonResponse.class);
}
/**
* <h2>让 Sentinel 忽略一些异常</h2>
* */
@GetMapping("/ignore-exception")
@SentinelResource(
value = "ignoreException",
fallback = "ignoreExceptionFallback",
fallbackClass = { MyFallbackHandler.class },
exceptionsToIgnore = { NullPointerException.class }
)
public CommonResponse ignoreException(@RequestParam(defaultValue = "1") Integer code) {
if (code % 2 == 0) {
throw new NullPointerException("yout input code is: " + code);
} else if ( code % 3 == 0){
throw new RuntimeException("yout input code is: " + code);
}
return CommonResponse.success();
}
}
MyFallbackHandler
package cn.flowboot.e.commerce.fallback;
import cn.flowboot.e.commerce.vo.CommonResponse;
import lombok.extern.slf4j.Slf4j;
/**
* <h1></h1>
*
* @version 1.0
* @author: Vincent Vic
* @since: 2022/03/18
*/
@Slf4j
public class MyFallbackHandler {
/**
* <h2>remoteConsumer 方法的 fallback</h2>
* */
public static CommonResponse remoteConsumerFallback() {
log.error("remote consumer service fallback");
return CommonResponse.fail("fallback");
}
/**
* <h2>ignoreException 方法的 fallback</h2>
* */
public static CommonResponse ignoreExceptionFallback(Integer code) {
log.error("ignore exception input code: [{}] has trigger exception", code);
return CommonResponse.fail("ignoreExceptionFallback");
}
}
配置统一处理异常
RestTemplateExceptionHandler
package cn.flowboot.e.commerce.conf;
import cn.flowboot.e.commerce.vo.CommonResponse;
import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
/**
* <h1>RestTemplate 在限流或异常的兜底方法</h1>
*
* @version 1.0
* @author: Vincent Vic
* @since: 2022/03/18
*/
@Slf4j
public class RestTemplateExceptionHandler {
/**
* <h2> handleBlock - 限流处理方法<h2>
* version: 1.0 - 2022/3/18
* @param request 请求
* @param body 数据
* @param execution 请求链路
* @param ex 限流处理方法
* @return {@link SentinelClientHttpResponse }
*/
public static SentinelClientHttpResponse handleBlock(HttpRequest request, byte[] body, ClientHttpRequestExecution execution,BlockException ex){
log.error("handler restTemplate block exception : [{}], [{}]", request.getURI().getPath(),request.getClass().getCanonicalName());
return new SentinelClientHttpResponse(
JSON.toJSONString(CommonResponse.fail("服务限流",request.getClass().getCanonicalName()))
);
}
/**
* <h2> handleFallback - 异常处理方法<h2>
* version: 1.0 - 2022/3/18
* @param request 请求
* @param body 数据
* @param execution 请求链路
* @param ex 限流处理方法
* @return {@link SentinelClientHttpResponse }
*/
public static SentinelClientHttpResponse handleFallback(HttpRequest request, byte[] body, ClientHttpRequestExecution execution,BlockException ex){
log.error("handler restTemplate block exception : [{}], [{}]", request.getURI().getPath(),request.getClass().getCanonicalName());
return new SentinelClientHttpResponse(
JSON.toJSONString(CommonResponse.fail("服务异常",request.getClass().getCanonicalName()))
);
}
}
SentinelConfig
package cn.flowboot.e.commerce.conf;
import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* <h1>开启服务间的调用保护,需要给RestTemplate做一些包装</h1>
*
* @version 1.0
* @author: Vincent Vic
* @since: 2022/03/18
*/
@Configuration
public class SentinelConfig {
/**
* <h2> restTemplate - 包装RestTemplate<h2>
* version: 1.0 - 2022/3/18
* @param
* @return {@link RestTemplate }
*/
@Bean
@SentinelRestTemplate(
fallback = "handleFallback",fallbackClass = RestTemplateExceptionHandler.class,
blockHandler = "handleBlock",blockHandlerClass = RestTemplateExceptionHandler.class
)
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
SentinelRestTemplateController 使用统一配置的RestTemplate
package cn.flowboot.e.commerce.controller;
import cn.flowboot.e.commerce.block.handler.MyBlockHandler;
import cn.flowboot.e.commerce.dto.SearchGoodByIdsDto;
import cn.flowboot.e.commerce.vo.CommonResponse;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
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.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <h1>使用Sentinel保护RestTemplate服务调用</h1>
*
* @version 1.0
* @author: Vincent Vic
* @since: 2022/03/18
*/
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/rest")
public class SentinelRestTemplateController {
private final RestTemplate restTemplate;
/**
* <h2> remoteConsumer - <h2>
* 容错降级:对于服务不可用时不能生效
* version: 1.0 - 2022/3/18
* @return {@link Map< String, Object> }
*/
@GetMapping("/remote/consumer")
public CommonResponse remoteConsumer(){
String requestUrL = "http://localhost:8500/sentinel-client/rest/remote/producer";
log.info("RestTemplate request url [{}] ",requestUrL);
return restTemplate.getForObject(requestUrL, CommonResponse.class);
}
/**
* <h2>在 dashboard 中“流控规则”中按照资源名称新增流控规则</h2>
* @return
*/
@GetMapping("/remote/producer")
public CommonResponse<String> producer(){
log.info("coming in rate limit controller by resource");
return CommonResponse.success("producer");
}
}
3.3 Feign 降级
@SentinelResource 中fallback、fallbackClass指定异常降级的类和方法Sentinel还对 Feign 实现了适配,支持Feign的容错降级
feign:
sentinel:
enabled: true
SentinelFeignController
package cn.flowboot.e.commerce.controller;
import cn.flowboot.e.commerce.fallback.MyFallbackHandler;
import cn.flowboot.e.commerce.feign.SentinelFeignClient;
import cn.flowboot.e.commerce.vo.CommonResponse;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
/**
* <h1>使用Sentinel保护RestTemplate服务调用</h1>
*
* @version 1.0
* @author: Vincent Vic
* @since: 2022/03/18
*/
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/feign")
public class SentinelFeignController {
//注入普通
private final SentinelFeignClient sentinelFeignClient;
/**
* <h2> remoteConsumer - <h2>
* 容错降级:对于服务不可用时不能生效
* version: 1.0 - 2022/3/18
* @return {@link Map< String, Object> }
*/
@GetMapping("/remote/consumer")
public CommonResponse remoteConsumer(){
log.info("Sentinel feign client request ");
return sentinelFeignClient.producer();
}
}
SentinelFeignClient 其中微服务定义不存在会抛出异常
package cn.flowboot.e.commerce.feign;
import cn.flowboot.e.commerce.feign.fallback.SentinelFeignClientFallback;
import cn.flowboot.e.commerce.vo.CommonResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* <h1>通过 Sentinel 对 OpenFeign 实现熔断降级</h1>
*
* @version 1.0
* @author: Vincent Vic
* @since: 2022/03/18
*/
@FeignClient(
value = "e-commerce-sentinel-client-1",
fallback = SentinelFeignClientFallback.class
)
public interface SentinelFeignClient {
/**
* <h2>在 dashboard 中“流控规则”中按照资源名称新增流控规则</h2>
* @return
*/
@GetMapping("/sentinel-client/rest/remote/producer")
CommonResponse<String> producer();
}
SentinelFeignClientFallback Sentinel 对 OpenFeign 接口的降级策略
package cn.flowboot.e.commerce.feign.fallback;
import cn.flowboot.e.commerce.feign.SentinelFeignClient;
import cn.flowboot.e.commerce.vo.CommonResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* <h1>Sentinel 对 OpenFeign 接口的降级策略</h1>
*
* @version 1.0
* @author: Vincent Vic
* @since: 2022/03/18
*/
@Slf4j
@Component
public class SentinelFeignClientFallback implements SentinelFeignClient {
/**
* <h2>在 dashboard 中“流控规则”中按照资源名称新增流控规则</h2>
*
* @return
*/
@Override
public CommonResponse<String> producer() {
return CommonResponse.fail("服务错误");
}
}
4 Sentinel 存储
4.1 SpringCloud Alibaba Sentinel 结合 Nacos
- Sentinel Dashboard将规则保存在内存中,重启之后就会丢失,所以,考虑使用外部持久化方案
- 在Nacos中创建规则,Nacos会推送到客户端
- Sentinel Dashboard也会从Nacos 去获取配置信息
- Sentinel存储在 Nacos 中的限流数据结构

需要添加依赖(上述新建项目依赖包含)
<!-- Sentinel 使用 Nacos 存储规则 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
配置
spring:
application:
name: e-commerce-sentinel-client
cloud:
nacos:
#服务发现
discovery:
enabled: true
server-addr: 127.0.0.1:8848
namespace: e-commerce-nacos-server
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
sentinel:
# 配置 sentinel dashboard 地址
transport:
dashboard: 127.0.0.1:7777
port: 8719 # 会在应用对应的机器上启动一个 Http Server, 该 Server 会与 Sentinel 控制台做交互
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
# 服务启动直接建立心跳连接
eager: true
# 开启或关闭 @SentinelRestTemplate
resttemplate:
sentinel:
enabled: true
feign:
sentinel:
enabled: true
nacos 中添加配置 e-commerce-sentinel-client-sentinel

[
{
"resource": "byResource",
"limitApp": "default",
"grade": 1,
"count": 5,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
5 Gateway 结合 Sentinel
5.1 添加依赖
<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>
5.2 硬编码配置
package cn.flowboot.e.commerce.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.RequiredArgsConstructor;
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>
*
* @version 1.0
* @author: Vincent Vic
* @since: 2022/03/18
*/
@Slf4j
@RequiredArgsConstructor
@Configuration
public class SentinelGatewayConfiguration {
/** 视图解析器 */
private final List<ViewResolver> viewResolvers;
/** HTTP 请求和响应数据的编解码配置 */
private final 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();
}
/**
* <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(3);
rule.setCount(1);
rules.add(rule);
// 加载到网关中
GatewayRuleManager.loadRules(rules);
}
/**
* <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);
}
}
SentinelGatewayConfiguration 修改为api分组
public class SentinelGatewayConfiguration {
// 相同代码略...
/**
* <h2>硬编码网关限流规则</h2>
* */
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
// GatewayFlowRule rule = new GatewayFlowRule();
// ... 注释之前单个规则
// 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>
* 1. 最大限制 - 演示
* 2. 具体的分组
* */
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
// nacos-client-api 组, 最大的限制
ApiDefinition api = new ApiDefinition("nacos-client-api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
// 模糊匹配 /ecommerce-nacos-client/ 及其子路径的所有请求
add(new ApiPathPredicateItem()
.setPattern("/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()
// 精确匹配 /n/service/instance/e-commerce-nacos-client
.setPattern("/s/nacos-client/search/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("/ecommerce-nacos-client" +
"/nacos-client/project-config"));
}});
definitions.add(api1);
definitions.add(api2);
// 加载限流分组
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}
5.3 JSON文件配置方式
注释掉SentinelGatewayConfiguration中的@PostConstruct注解
在项目资源文件新建如下两个文件:
gateway-flow-rule-api-sentinel.json
[
{
"apiName": "nacos-client-api",
"predicateItems": [
{
"pattern": "/s/nacos-client/search/service/instance"
},
{
"pattern": "/n/service/instance/**",
"matchStrategy": 1
}
]
}
]
gateway-flow-rule-sentinel.json
[
{
"resource": "e-commerce-nacos-client",
"resourceMode": 0,
"count": 3,
"intervalSec": 60
},
{
"resource": "nacos-client-api",
"resourceMode": 1,
"count": 1,
"intervalSec": 60
}
]
注意:maven clean 当前网关子项目,防止文件在target不存在
通过本地文件方式 配置
spring:
cloud:
sentinel:
eager: true
transport:
port: 8720
dashboard: 127.0.0.1:7777
datasource:
# 通过本地文件方式, 基于服务级别的配置
dsl.file:
file: classpath:gateway-flow-rule-sentinel.json
# 代表服务级别的限流, 一步步点进去看, 文件类型
ruleType: gw-flow
# 通过本地文件方式, 细粒度对指定 api 进行配置
ds2.file:
file: classpath:gateway-flow-rule-api-sentinel.json
# 代表 API 分组, 一步步点进去看, 文件类型
ruleType: gw-api-group
5.3 Nacos存储配置方式
spring:
cloud:
sentinel:
eager: true
transport:
port: 8720
dashboard: 127.0.0.1:7777
datasource:
# 集成 Nacos
ds1:
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: ${spring.cloud.nacos.discovery.namespace}
# 测试时, 看看 Nacos 中修改是否能让 dashboard 生效, 就把第二个 count 也修改为 3
data-id: gateway-flow-rule-sentinel
group-id: DEFAULT_GROUP
data-type: json
rule-type: gw-flow
ds2:
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: ${spring.cloud.nacos.discovery.namespace}
data-id: gateway-flow-rule-sentinel
group-id: DEFAULT_GROUP
data-type: json
rule-type: gw-api-group
Nacos 配置添加
gateway-flow-rule-sentinel
[
{
"resource": "e-commerce-nacos-client",
"resourceMode": 0,
"count": 3,
"intervalSec": 60
},
{
"resource": "nacos-client-api",
"resourceMode": 1,
"count": 1,
"intervalSec": 60
}
]
gateway-flow-rule-api-sentinel
[
{
"apiName": "nacos-client-api",
"predicateItems": [
{
"pattern": "/s/nacos-client/search/service/instance"
},
{
"pattern": "/n/service/instance/**",
"matchStrategy": 1
}
]
}
]
均为json,上述三种需要分开测试,还有其他存储方式
Spring Cloud Alibaba 学习笔记项目:Github,学习笔记,仅为组件学习,并没有完整案例项目
最后
以上就是爱笑冰淇淋最近收集整理的关于Spring Cloud Alibaba】Sentinel 流量控制1 搭建SpringCloud Alibaba Sentinel控制台2 项目集成3 降级4 Sentinel 存储5 Gateway 结合 Sentinel的全部内容,更多相关Spring内容请搜索靠谱客的其他文章。
发表评论 取消回复