Sentinel 以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性。
同hystrix相比,除了服务熔断,还可以进行服务降级、服务流控等。
sentinel有多种使用方式,常见的有单独使用dashboard、同Gateway结合使用、同Spring Cloud/Alibaba结合使用、同RPC组件结合使用等。建议结合项目选择。开源项目Ruoyi-Cloud选择结合Gateway使用。
在服务端用maven引入jar包;
控制面板dashboard可以从https://github.com/alibaba/Sentinel/releases
下载sentinel-dashboard-$version.jar
包。
流控规则
降级规则
监控应用中资源调用请求,达到阈值时自动触发熔断降级。
热点规则
系统规则
系统自适应限流
授权规则
黑白名单控制
本文以Spring Cloud Alibaba中引入Sentinel为例,其他方式引入请参考官方wiki。
在服务中引入Sentinel,然后在dashboard中配置规则。
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
在配置文件中开启Sentinel
spring.cloud.sentinel.enabled=true
# 指定sentinel dashboard web地址
spring.cloud.sentinel.transport.dashboard= #dashboard的ip:端口
# 指定snetinel组件和sentinel dashboard组件通信地址(用tcp更高效的通信,端口默认为8719)
spring.cloud.sentinel.transport.port=8719
使用java -jar
命令启动dashboard控制台:
java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.api.port=8719 -jar D:\sentinel\sentinel-dashboard-1.8.0.jar
Sentinel提供了一个可视化的操作平台,安装好之后,在浏览器中输入(http://localhost:8718 (opens new window))就可以访问了,默认的用户名和密码都是sentinel
dashboard在有一次调用发生后,才能在web界面看到服务(这算懒加载么?)
调用后sentinel才会产生资源调用日志,有日志生成后才会dashboard出现服务,多调用几次才会出现qps等信息。
用来避免服务雪崩。触发熔断后对该服务的调用不可用。
在dashboard中选择“降级规则”
触发熔断(断路器打开)的三种策略
RT 根据请求响应时间熔断
异常比例
抛出的异常的比例。如设置为0.1,则比例超过10%时触发熔断
异常数
时间窗口(单位:秒)结束后,断路器关闭。
也成为热点参数限流,对携带了指定参数的资源进行限流。
如https://editor.csdn.net/md/?articleId=131038775
,参数为articleId
只能使用QPS模式。
不能使用资源路径的方式配置,只能使用资源别名。
资源别名:在处理请求的方法上使用@SentinelResource(value="别名")
注解指定别名。mvc模式的话就放在controller层方法上。
在[统计窗口时长]内,对带有第[参数索引]个参数的请求,超过阈值时进行限流。
参数索引从0开始计数,代表请求中的第几个参数。
@SentinelResource(value=“别名”, fallback=“”, blcokHandler=“限流处理method名”)
fallback处理业务异常,blockHandler处理限流。指定方法名。
限流处理method例子:
controller层方法带Integer id参数:
private String blockHandler(Integer id, BlockException e){
if(e instanceof FlowException){
return "当前请求过于火爆,已被降级";
}
if(e instanceof DegradeException){
return "当前请求过于火爆,已被降级";
}
if(e instanceof ParaFlowException){
return "当前请求过于火爆,已被参数热点限流";
}
return "服务器过于火爆,请稍后再试捏~(づ ̄3 ̄)づ╭❤~kira";
}
限流处理method可以通过这种方式拿到请求发来的参数。(要保持一致?)
原理是监控应用流量的 QPS 或 并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被顺势的流量高峰冲垮,从而保障应用的高可用性;
高级选项-流控模式
:
配置资源:在编辑规则时设置的资源
关联资源:和配置资源相关联的资源
当对配置资源的请求超过阈值时,对配置资源的请求的处理(三种处理方式在本文下一小节)
当所关联的资源的请求超过阈值后,对当前资源进行的处理
如在以下例子中,当关联的"/aa"资源访问次数超过阈值,则"/demo"资源要受到流控处理
对链路上的资源的处理
注意:只适用于QPS限流
FlowExcrption
预热时长
)内将系统逐渐拉至高水位在Gateway配置类
中添加限流规则
import javax.annotation.PostConstruct;
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 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;
/**
* 网关限流配置
*
*/
@Configuration
public class GatewayConfig
{
@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter()
{
return new SentinelGatewayFilter();
}
@PostConstruct
public void doInit()
{
// 加载网关限流规则
initGatewayRules();
}
/**
* 网关限流规则
*/
private void initGatewayRules()
{
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("ruoyi-system")
.setCount(3) // 限流阈值
.setIntervalSec(60)); // 统计时间窗口,单位是秒,默认是 1 秒
// 加载网关限流规则
GatewayRuleManager.loadRules(rules);
}
}
import java.util.HashSet;
import java.util.Set;
import javax.annotation.PostConstruct;
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 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.ruoyi.gateway.handler.SentinelFallbackHandler;
/**
* 网关限流配置
*
* @author ruoyi
*/
@Configuration
public class GatewayConfig
{
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelFallbackHandler sentinelGatewayExceptionHandler()
{
return new SentinelFallbackHandler();
}
@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter()
{
return new SentinelGatewayFilter();
}
@PostConstruct
public void doInit()
{
// 加载网关限流规则
initGatewayRules();
}
/**
* 网关限流规则
*/
private void initGatewayRules()
{
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("system-api")
.setCount(3) // 限流阈值
.setIntervalSec(60)); // 统计时间窗口,单位是秒,默认是 1 秒
rules.add(new GatewayFlowRule("code-api")
.setCount(5) // 限流阈值
.setIntervalSec(60));
// 加载网关限流规则
GatewayRuleManager.loadRules(rules);
// 加载限流分组
initCustomizedApis();
}
/**
* 限流分组
*/
private void initCustomizedApis()
{
Set<ApiDefinition> definitions = new HashSet<>();
// ruoyi-system 组
ApiDefinition api1 = new ApiDefinition("system-api").setPredicateItems(new HashSet<ApiPredicateItem>()
{
private static final long serialVersionUID = 1L;
{
// 匹配 /user 以及其子路径的所有请求
add(new ApiPathPredicateItem().setPattern("/system/user/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}
});
// ruoyi-gen 组
ApiDefinition api2 = new ApiDefinition("code-api").setPredicateItems(new HashSet<ApiPredicateItem>()
{
private static final long serialVersionUID = 1L;
{
// 只匹配 /job/list
add(new ApiPathPredicateItem().setPattern("/code/gen/list"));
}
});
definitions.add(api1);
definitions.add(api2);
// 加载限流分组
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}
为了展示更加友好的限流提示, Sentinel支持自定义异常处理。
方案一:yml
配置
# Spring
spring:
cloud:
sentinel:
scg:
fallback:
mode: response
response-body: '{"code":403,"msg":"请求超过最大数,请稍后再试"}'
方案二:GatewayConfig
注入Bean
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelFallbackHandler sentinelGatewayExceptionHandler()
{
return new SentinelFallbackHandler();
}
SentinelFallbackHandler.java
import java.nio.charset.StandardCharsets;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;
/**
* 自定义限流异常处理
*
*/
public class SentinelFallbackHandler implements WebExceptionHandler
{
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange)
{
ServerHttpResponse serverHttpResponse = exchange.getResponse();
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
byte[] datas = "{\"code\":429,\"msg\":\"请求超过最大数,请稍后再试\"}".getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
return serverHttpResponse.writeWith(Mono.just(buffer));
}
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex)
{
if (exchange.getResponse().isCommitted())
{
return Mono.error(ex);
}
if (!BlockException.isBlockException(ex))
{
return Mono.error(ex);
}
return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange));
}
private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable)
{
return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
}
}
在 Sentinel 里面,所有的资源都对应一个资源名称以及一个 Entry。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 API 显式创建;每一个 Entry 创建的时候,同时也会创建一系列功能插槽(slot chain)
这些功能插槽有些类似游戏或是组装电脑里“升级插槽”的概念,在原有基础上添加功能。可以自定义插槽
这个 slot 主要负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级。
资源确实是树状结构的,就像树形目录。结构上资源和url很像,也应当是树形结构
熔断降级设计理念
在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。
Hystrix 通过线程池的方式,来对依赖(在我们的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。
缺点:
核心类解析
Sentinel 对这个问题采取了两种手段:
通过并发线程数进行限制
和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
通过响应时间对资源进行降级
除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
系统负载保护
Sentinel 同时对系统的维度提供保护。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。
针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
SPI:SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议,比如AT91RM9200。
官方文档:https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5