Sentinel的官方标题是:分布式系统的流量防卫兵。从名字上来看,很容易就能猜到它是用来作服务稳定性保障的。对于服务稳定性保障组件,如果熟悉Spring Cloud的用户,第一反应应该就是Hystrix。但是比较可惜的是Netflix已经宣布对Hystrix停止更新。那么,在未来我们还有什么更好的选择呢?除了Spring Cloud官方推荐的resilience4j之外,目前Spring Cloud Alibaba下整合的Sentinel也是用户可以重点考察和选型的目标。
如果某一个服务不可用或者宕机了,就会出现线程池里所有线程都因等待响应而被阻塞, 从而造成服务雪崩。解决雪崩问题的常见方式,也是微服务常见的治理策略:
Sentinel 基本概念:
资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
流量控制
Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状。
流量控制有以下几个角度:
熔断降级
通过并发线程数进行限制
Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
通过响应时间对资源进行降级
除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
Sentinel 的主要工作机制如下:
Sentinel的使用分为两部分:
(1)启动sentinel-dashboard
下载地址:sentinel-dashboard-1.6.0.jar
其他版本:Sentinel/releases
通过命令启动:
java -jar sentinel-dashboard-1.6.0.jar
默认情况下,sentinel-dashboard以8080端口启动。由于sentinel-dashboard是一个标准的spring boot应用,所以如果要自定义端口号等内容的话,可以通过在启动命令中增加参数来调整,比如:
java -jar sentinel-dashboard-1.6.0.jar --server.port=8888
所以可以通过访问:localhost:8888 来验证是否已经启动成功,如果一切顺利的话,可以看到登录页面(默认用户名和密码都是sentinel)。
(2)整合Sentinel
依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置:
spring.application.name=alibaba-sentinel-rate-limiting
server.port=8002
# sentinel dashboard
spring.cloud.sentinel.transport.dashboard=localhost:8888
主类:
package com.example.demospringboot;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@Slf4j
public class DemospringbootApplication {
public static void main(String[] args) {
SpringApplication.run(DemospringbootApplication.class, args);
}
@Slf4j
@RestController
static class TestController {
@GetMapping("/hello")
public String hello() {
return "Hello World";
}
}
}
启动应用,并执行curl命令:
此时,在上面启动的Sentinel Dashboard中就可以当前我们启动的alibaba-sentinel-rate-limiting这个服务以及接口调用的实时监控了。具体如下图所示:
在完成了上面的两节之后,我们在alibaba-sentinel-rate-limiting服务下,点击簇点链路菜单,可以看到如下界面:
通过点击流控按钮,来为该接口设置限流规则,比如:
流控模式:
流控效果:
快速失效:达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。
warm up:预热模式,对超出阈值的请求同样是拒绝,并抛出异常。但这种模式阈值会动态变化,从一个较小的值逐渐增加到最大值。请求阈值初始值是 threshold/coldFactor(默认值是3),持续指定时长后,逐渐提高到threshould值。例如,我设置QPS的threshold的为10,预热时间为5秒,那么初始阈值就是10/3,也就是3,然后在5秒后逐渐增长到10.
排队等待:让所有请求按照先后次序进入一个队列中排队执行,两个请求的间隔不能小于指定时长。例如:QPS = 5,意味着每200ms处理一个队列中的请求;timeout =2000,意味着预期等待超过2000ms的请求会被拒绝并抛出异常。
参考案例
其实就是左侧菜单中流控规则的界面,这里可以看到当前设置的所有限流策略。在完成了上面所有内容之后,我们可以尝试一下快速的调用这个接口,看看是否会触发限流控制,比如:
可以看到,快速的调用/hello接口之后,调用被限流了。
在 sentinel-dashboard界面,也可以看到对应统计:
Dashboard中设置的限流规则在应用重启之后就丢失了。Sentinel自身就支持了多种不同的数据源来持久化规则配置,目前包括以下几种方式:
本文我们使用Spring Cloud Alibaba的中整合的配置中心Nacos存储限流规则。前置条件,启动Nacos,详见 Spring Cloud Alibaba实践 --Nacos
默认配置下启动后,它们的访问地址为:
(1)在Spring Cloud应用的pom.xml中引入Spring Cloud Alibaba的Sentinel模块和Nacos存储扩展:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.5.2</version>
</dependency>
(2)在Spring Cloud应用中添加配置信息
spring.application.name=alibaba-sentinel-rate-limiting
server.port=8002
# sentinel dashboard
spring.cloud.sentinel.transport.dashboard=localhost:8888
# ds-sentinel-nacos-datasource
spring.cloud.sentinel.datasource.ds.nacos.server-addr=localhost:8848
spring.cloud.sentinel.datasource.ds.nacos.dataId=${spring.application.name}-sentinel
spring.cloud.sentinel.datasource.ds.nacos.groupId=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds.nacos.rule-type=flow
(3)Nacos中创建限流规则的配置
注:Data ID、Group就是上面第二步中配置的内容。配置格式选择JSON,并在配置内容中填入下面的内容:
[
{
"resource": "/hello",
"limitApp": "default",
"grade": 1,
"count": 5,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
配置规则是一个数组类型,数组中的每个对象是针对每一个保护资源的配置对象,每个对象中的属性解释如下:
(4)启动应用,curl localhost:8002/hello
测试
如果一些顺利,可以看到类似下面的日志,代表已经成功从Nacos加载了一条限流规则:
2023-12-09 19:00:04.887 INFO 13800 --- [ main] o.s.c.a.s.c.SentinelDataSourceHandler : [Sentinel Starter] DataSource ds-sentinel-nacos-datasource load 1 FlowRule
在Sentinel Dashboard中就可以看到当前我们启动的alibaba-sentinel-rate-limiting
服务。点击左侧菜单中的流控规则,可以看到已经存在一条记录了,具体如下:
注:在完成了上面的整合之后,对于接口流控规则的修改就存在两个地方了:Sentinel控制台、Nacos控制台。
这个时候,需要注意当前版本的Sentinel控制台不具备同步修改Nacos配置的能力,而Nacos由于可以通过在客户端中使用Listener来实现自动更新。所以,在整合了Nacos做规则存储之后,需要知道在下面两个地方修改存在不同的效果:
在实际应用过程中,我们可能需要限流的层面不仅限于接口,而是对于某个方法的调用限流,对于某个外部资源的调用限流等都希望做到控制。这个时候我们就不得不手工定义需要限流的资源点,并配置相关的限流策略等内容了。使用@SentinelResource
注解可以灵活的定义控制资源以及如何配置控制策略。
主启动类:
在应用主类中增加注解支持的配置Bean
package com.example.demospringboot;
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@Slf4j
public class DemospringbootApplication {
public static void main(String[] args) {
SpringApplication.run(DemospringbootApplication.class, args);
}
// 注解支持的配置Bean
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
Service:
在需要通过Sentinel来控制流量的地方使用@SentinelResource
注解,自定义资源点;
并通过@SentinelResource
注解的blockHandler
属性实现限流的异常处理
package com.example.demospringboot;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class TestService {
@SentinelResource(value = "doSomeThing", blockHandler = "exceptionHandler")
public void doSomeThing(String str) {
log.info(str);
}
// 限流与阻塞处理
public void exceptionHandler(String str, BlockException ex) {
log.error( "blockHandler-> " + str, ex);
}
}
Controller:
在Web层调用这个被保护的方法
package com.example.demospringboot;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
public class TestController {
@Autowired
private TestService testService;
@GetMapping("/hello")
public String hello() {
testService.doSomeThing("hello " + new Date());
return "Hello World";
}
}
启动测试应用,启动Sentinel-Dashboard。 curl localhost:8002/hello
发一个请求到/hello接口上,使得Sentinel-Dashboard上可以看到如下图所示的几个控制点:
可以看到,多了一个doSomeThing资源点。可以通过界面为这个资源点设置限流规则,比如将其QPS设置为2。由于/hello资源不设置限流规则,所以只要请求/hello接口,就可以直接模拟调用doSomeThing资源,来观察限流规则是否生效。只要QPS超过2,那么就会出现如下的错误返回,代表限流策略生效了。
2023-12-09 12:45:58.015 INFO 11368 --- [nio-8002-exec-7] com.example.demospringboot.TestService : hello Sat Dec 09 12:45:58 CST 2023
2023-12-09 12:45:58.378 TRACE 11368 --- [nio-8002-exec-8] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.demospringboot.TestController#hello()
2023-12-09 12:45:58.401 ERROR 11368 --- [nio-8002-exec-8] com.example.demospringboot.TestService : blockHandler-> hello Sat Dec 09 12:45:58 CST 2023
com.alibaba.csp.sentinel.slots.block.flow.FlowException: null
@SentinelResource
注解除了可以用来做限流控制之外,还能实现与Hystrix类似的熔断降级策略。
这里只需对TestService类改造,让doSomeThing方法持续抛出异常,触发fallback属性指定的具体方法名进行降级处理。
package com.example.demospringboot;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class TestService {
@SentinelResource(value = "doSomeThing", fallback = "fallbackHandler")
public void doSomeThing(String str) {
log.info(str);
throw new RuntimeException("发生异常");
}
public void fallbackHandler(String str) {
log.error("fallbackHandler-> " + str);
}
}
在Sentinel-Dashboard名为doSomeThing的资源点上,点击”降级“按钮,为该资源设置降级规则。这里使用异常比例策略,比例设置为0.1(即:10%的异常率),时间窗口设置为2(秒):
验证熔断降级,根据上面的降级策略配置,当doSomeThing方法的调用QPS >= 5时,如果异常率超过10%,那么后续2秒内的调用将直接触发熔断降级:
2023-12-09 13:30:21.332 TRACE 4248 --- [io-8002-exec-10] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.demospringboot.TestController#hello()
2023-12-09 13:30:21.333 ERROR 4248 --- [io-8002-exec-10] com.example.demospringboot.TestService : fallbackHandler-> hello Sat Dec 09 13:30:21 CST 2023
2023-12-09 13:30:21.678 TRACE 4248 --- [nio-8002-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.demospringboot.TestController#hello()
2023-12-09 13:30:21.678 ERROR 4248 --- [nio-8002-exec-2] com.example.demospringboot.TestService : fallbackHandler-> hello Sat Dec 09 13:30:21 CST 2023
参考:
https://www.didispace.com/spring-cloud/spring-cloud-alibaba-sentinel-2-5.html
https://zhuanlan.zhihu.com/p/565074363?utm_id=0