Sentinel提供了一个轻量级的开源控制台,它支持机器发现,以及健康情况管理、监控(单机和集群)、规则管理和推送的功能。
Sentinel Dashboard的按照步骤如下。
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
其中,启动参数的含义如下:
需要微服务接入Dashboard,同时需要访问微服务的任意端点(接口)才能触发监控,在Dashboard上看到微服务信息;如果只是接入Dashboard没有触发任意端点是无法在Dashboard上看到微服务信息的。
使用Sentinel的核心库来实现限流,主要分以下几个步骤。
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-coreartifactId>
<version>1.7.1version>
dependency>
private static void doSomething() {
try (Entry entry = SphU.entry("doSomething")) {
// 业务逻辑处理
System.out.println("hello world " + System.currentTimeMillis());
} catch (BlockException e) {
// 处理流控的逻辑
System.out.println("block exception, trigger traffic limit");
}
}
在doSomething方法中,通过使用Sentinel中的SphU.entry(“doSomething”)定义一个资源来实现流控的逻辑,它表示当请求进入doSomething方法时,需要进行限流判断。如果抛出BlockException异常,则表示触发了限流。
private static void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("doSomething");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(5);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
针对资源doSomething,通过initFlowRules设置限流规则,其中参数的含义如下。
public static void main(String[] args) {
initFlowRules();
while(true){
doSomething();
}
}
运行了main方法之后,可以在${USER_HOME}\logs\csp${包名-类名}-metrics.logs.date文件中看到如下日志:
1660630594000|2022-08-16 14:16:34|doSomething|5|8152|5|0|0|0|0|0
1660630595000|2022-08-16 14:16:35|doSomething|5|8964|5|0|0|0|0|0
1660630596000|2022-08-16 14:16:36|doSomething|5|8956|5|0|0|0|0|0
上述日志中对应字段的具体含义是:
timestamp|yyyy-MM-dd HH:mm:ss|resource|passQps|blockQps|successQps|exceptionQps|rt|occupiedPassQps|concurrency|classification
passQps:代表通过的请求。
blockQps:代表被阻止的请求。
successQps:代表成功执行完成的请求个数。
exceptionQps:代表用户自定义的异常。
rt:代表平均响应时长。
occupiedPassQps:代表优先通过的请求。
concurrency:代表并发量。
classification:代表资源类型。
从日志可以看出,这个程序喵喵文档输出(doSomething) 5次,和规则中预先设定的阈值时一样的,而被拒绝的请求每秒最高达8万多次。
在上面,我们通过抛出异常的方式来定义了一个资源,也就是当资源被限流之后,会抛出一个BlockException异常。这时我们需要捕获该异常进行限流后的逻辑处理。
try (Entry entry = SphU.entry("resourceName")) {
// 被保护的业务逻辑
System.out.println("hello world " + System.currentTimeMillis());
} catch (BlockException e) {
// 被限流的逻辑
System.out.println("block exception, trigger traffic limit");
}
其中,resourceName可以定义方法名称、接口名称或者其他的唯一标识。
private static void doSomething1() {
if (SphO.entry("resourceName")) {
try {
// 被保护的业务逻辑
} finally {
SphO.exit();
}
} else {
// 资源访问被现在
}
}
在这种方式中,需要注意资源使用完之后要调用SphO.exit(),否则会导致调用链记录异常,抛出ErrorEntryFreeException异常。
@SentinelResource(value = "resourceName", entryType = EntryType.OUT, blockHandler = "exceptionHandler", fallback = "helloFallback")
public String test() {
double random = Math.random();
System.out.println(random);
System.out.println(1 / 0);
return "success";
}
public String exceptionHandler(BlockException ex) {
System.out.println("exception");
return "exception";
}
public String helloFallback() {
return "hello fall back";
}
需要注意的是,blockHandler所配置的值exceptionHandler会在触发限流后调用,这个方法的定义必须和原始方法test的返回值、参数保持一致,而且需要增加BlockException参数。
Sentinel资源的定义还有更多的方式,这里就不再一一强调了,感兴趣的小伙伴可以去官网看看。
Sentinel支持多种保护规则:流量控制规则、熔断降级规则、系统保护规则、来源访问控制规则、热点参数规则。
限流规则在前面的案例中简单使用过,先通过FlowRule来定义限流规则,然后通过FlowRuleManager.loadRules来加载规则列表。完整的限流规则设置代码如下:
public void init() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setCount(20);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setLimitApp("default");
rule.setStrategy(RuleConstant.STRATEGY_CHAIN);
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
rule.setClusterMode(false);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
其中,FlowRule部分属性的含义说明如下:
Sentinel流量控制统计有两种类型,通过grade属性来控制:
并发线程数限流用来保护线程不被耗尽。比如,A服务调用B服务,而B服务因为某种原因导致服务不稳定或者响应延迟,那么对于A服务来说,它的吞吐量会下降,也意味着占用更多的线程(线程阻塞之后一直未释放),极端情况下会造成线程池耗尽。
针对这种问题,一个常见的解决方案是通过不同业务逻辑使用不同的线程池来隔离业务自身的资源争抢问题,但是这个方案同样会造成线程数量过多带来的上下文切换问题。
Sentinel并发线程数限流就是统计当前请求的上下文线程数量,如果超出阈值,新的请求就会被拒绝。
QPS(Queries Per Second)表示每秒的查询数,也就是一台服务器每秒能够响应的查询次数。当QPS达到限流的阈值时,就会触发限流策略。
当QPS超过阈值时,就会触发流量控制行为,这种行为是通过controlBehavior来设置的,它包含:
直接拒绝是默认的流量控制方式,也就是请求流量超出阈值时,直接抛出一个FlowException。
Warm Up是一种热预热模式。当流量突然增大时,也就意味着系统从空闲状态突然切换到繁忙状态,有可能瞬间把系统压垮。当我们希望请求处理的数量逐步递增,并在一个预期时间之后到达允许处理请求的最大值时,Warm Up就可以达到这个目的。
如图所示,当前系统所能够处理的最大并发数是480,首先,在最下面标记的位置,系统一直处于空闲状态,接着请求量突然直线升高。这个时候系统并不是直接将QPS拉到最大值,而是在一定时间内逐步增加阈值,而中间这段时间就是一个系统逐步预热的过程。
匀速排队的方式会严格控制请求通过的间隔时间,也就是让请求以均匀的速度通过,其实相当于前面讲的漏桶限流算法。
如图所示,当QPS=2时,意味着每隔500ms才允许通过下一个请求,这种方式的好处是可以处理间隔性突发流量。
调用关系包括调用方和被调用方,一个方法又可能会调用其他方法,形成一个调用链。所谓的调用关系流量策略,就是根据不同的调用维度来触发流量控制。
所谓调用方限流,就是根据请求来源进行流量控制,我们需要设置limitApp属性来设置来源信息,它有三个选项。
一个被限流保护的方法,可能来自不同的调用链路。比如针对资源nodeA,入口Entrance1和入口Entrance2都调用了资源nodeA,那么Sentinel允许只根据某个入口来进行流量统计。比如我们针对nodeA资源,设置针对Entrance1入口的调用才会统计请求次数。它在一定程度上有点类似于调用方限流。
链路模式:只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。
例如有两条请求链路:
@SentinelResource("goods")
public void queryGoods() {
System.err.println("查询商品");
}
# 关闭context整合
spring.cloud.sentinel.web-contenxt-unify: false
当两个资源之间存在依赖关系或者资源争抢时,我们就说这两个资源存在关联。这两个存在依赖关系的资源在执行时可能会因为某一个资源执行操作过于频繁而影响另外一个资源的执行效率,所以关联流量控制(流控)就是限制其中一个资源的执行流量。
满足下面条件可以使用关联模式
Sentinel实现服务熔断操作的配置和限流类似,不同之处在于限流采用的是FlowRule,而熔断中采用的是DegradeRule,配置代码如下:
public void initDegradeRule(){
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource("KEY");
rule.setCount(10);
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
rule.setTimeWindow(10);
rule.setMinRequestAmount(5);
rule.setRtSlowRequestAmount(5);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
其中,有几个属性说明如下:
Sentinel提供三种熔断策略,对于不同策略,参数的含义也不相同。
如果1s内持续进入5个请求,对应的平均响应时间都超过了阈值(count:单位ms),那么在接下来的时间窗口(timeWindow,单位s)内,对这个方法的调用都会自动熔断,抛出DegradeException。
Sentinel默认统计的RT上限时4900ms,如果超出此阈值都会算作4900ms,如果需要修改,则通过启动参数 -Dcsp.sentinel.statistic.max.rt=xxx来配置
如果每秒资源数>=minRequestAmount(默认5),并且每秒的异常总数占通过量的比例超过阈值count(count的取值范围是[0.0,1.0]),代表0%~100%),则资源将进入降级状态。同样,在接下来的timeWindow之内,对这个方法的调用都会自动触发熔断。
当资源最近一分钟的异常数目超过阈值之后,会触发熔断。需要注意的是,如果timeWindow小于60s,则结束熔断状态后仍然可能进入熔断状态。