概述
【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 Cloud Alibaba】Sentinel 流量控制1 搭建SpringCloud Alibaba Sentinel控制台2 项目集成3 降级4 Sentinel 存储5 Gateway 结合 Sentinel所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复