目录
在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以通过 RPC 相互调用,在 Spring Cloud 中可以用 RestTemplate + LoadBalanceClient 和 Feign 来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证 100% 可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet 容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的 “雪崩” 效应。
为了解决这个问题,业界提出了熔断器模型。
阿里巴巴开源了 Sentinel 组件,实现了熔断器模式,Spring Cloud 对这一组件进行了整合。在微服务架构中,一个请求需要调用多个服务是非常常见的,如下图:
较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阀值熔断器将会被打开。
熔断器打开后,为了避免连锁故障,通过 fallback 方法可以直接返回一个固定值。
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助您保障微服务的稳定性。
官网:home | Sentinel
功能 | Sentinel | Hystrix |
---|---|---|
隔离策略 | 信号量隔离(并发线程数限流) | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间、异常比率、异常数 | 基于异常比率 |
实时统计实现 | 滑动窗口(LeapArray) | 滑动窗口(基于 RxJava) |
动态规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持预热模式、匀速器模式、预热排队模式(流量规则处可配置) | 不支持 |
系统自适应保护 | 支持 | 不支持 |
控制台 | 提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等 | 简单的监控查看 |
1.添加依赖
-
com.alibaba.cloud -
spring-cloud-starter-alibaba-sentinel
2.添加yml配置
- spring:
- cloud:
- sentinel:
- transport:
- # 默认端口8719端口,假如被占用会自动从8719开始依次+1扫描,直到找到未被占用的端口
- port: 8719
- dashboard: 192.168.0.102:8080
这里的 spring.cloud.sentinel.transport.port 端口配置会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互。比如 Sentinel 控制台添加了 1 个限流规则,会把规则数据 push 给这个 Http Server 接收,Http Server 再将规则注册到 Sentinel 中。
3.创建Controller测试
- @RestController
- @RequestMapping("/v1")
- public class TestController {
-
- @Autowired
- private ConfigurableApplicationContext applicationContext;
-
- @GetMapping(value = "/test")
- public String test() {
- return "port:" +applicationContext.getEnvironment().getProperty("server.port");
- }
- }
4.测试
访问:http://127.0.0.1:8000/v1/test
打开:打开Sentinel Dashboard 如下配置成功
Sentinel 控制台提供一个轻量级的控制台,它提供机器发现、单机资源实时监控、集群资源汇总,以及规则管理的功能。您只需要对应用进行简单的配置,就可以使用这些功能。
注意: 集群资源汇总仅支持 500 台以下的应用集群,有大概 1 - 2 秒的延时。
官方文档:dashboard | Sentinel
1.获取 Sentinel 控制台
2.启动
注意:启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。
Sentinel 控制台是一个标准的 Spring Boot 应用,以 Spring Boot 的方式运行 jar 包即可。
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
如若8080端口冲突,可使用 -Dserver.port=新端口 进行设置。
3. 访问控制台
http://ip:8080/
-
org.springframework.cloud -
spring-cloud-starter-openfeign
通过 @FeignClient("服务名") 注解来指定调用哪个服务。代码如下:
- @FeignClient(value = "eagle-provider",fallback = TestFallback.class)
- public interface TestService {
-
- @GetMapping(value = "/test/{message}")
- String test(@PathVariable("message") String message);
- }
熔断处理方法
- @Component
- public class TestFallback implements TestService{
- @Override
- public String test(String message) {
- return "服务降级---TestFallback";
- }
- }
服务eagle-provider提供的Controller接口如下:
- @RestController
- public class TestController {
-
- @Value("${spring.application.name}")
- private String name;
-
- @Resource
- private ConfigurableApplicationContext applicationContext;
-
- /**
- * 配置的动态更新测试
- */
- @GetMapping(value = "/test/{message}")
- public String test(@PathVariable String message) {
- return name+" port:" +applicationContext.getEnvironment().getProperty("server.port")+" "+message;
- }
- }
- @RestController
- public class TestController {
-
- @Resource
- private TestService testService;
-
- /**
- * openfeign测试
- */
- @GetMapping(value = "/feignTest")
- public String feignTest() {
- return testService.test("我是eagle-consumer");
- }
- }
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
- @GetMapping(value = "/testA")
- public String testA() {
- return "testA port:" +applicationContext.getEnvironment().getProperty("server.port");
- }
-
- @GetMapping(value = "/testB")
- public String testB() {
- return "testB port:" +applicationContext.getEnvironment().getProperty("server.port");
- }
官方文档:Home · alibaba/Sentinel Wiki · GitHub
当调用该api的QPS达到阈值的时候,进行限流
表示调用/v1/testA时,QPS的单机阀值大于1后面的请求直接失败。失败结果如下:
当调用该api的线程数达到阈值的时候,进行限流
表示调用/v1/testA时,线程数的单机阀值大于1后面的请求直接失败。
比如a请求过来,处理很慢,在一直处理,此时b请求又过来了,
此时因为a占用一个线程,此时要处理b请求就只有额外开启一个线,那么就会报错。可以延时模拟此情况Thread.sleep(1000)。
直接失败结果如下:
当关联的资源达到阈值时,就限流自己
表示调用/v1/testB的QPS的单机阀值大于1,之后调用/v1/testA的请求直接失败。
使用postman模拟测试。
应用场景: 比如支付接口达到阈值,就要限流下订单的接口,防止一直有订单
对一条链路的访问进行控制(指定资源从入口资源进来的流量,如果达到阈值,就进行限流) [api级别的针对来源]
比方说,我有一个二叉树
服务A->服务B->服务D, 服务A->服务B->服务E, 服务A->服务C->服务E, 服务A->服务C->服务F均可视作链路。
假设服务A为入口资源,服务D为终点资源,对这条链路进行限制的话,则资源A,B,D均会被限制访问。
系统初始化的阀值为10/ 3约等于3.,即阀值刚开始为3; 然后过了5秒后阀值才慢性升高恢到10
应用场景
如秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流放进来,慢的把阀值增长到设置的阀值。
阀值类型必须设成QPS,否则无效。
表示调用/v1/testA时,QPS的单机阀值大于1,请求以QPS=1匀速通过,QPS的单机阀值大于1的请求排队等候,排队等候的超市时间为2000ms。
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
RT (平均响应时间,秒级):当 1s 内持续进入 N 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。
注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
异常比列(秒级):当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
异常数(分钟级):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
即时间窗口时间配置要大于60秒
- @GetMapping(value = "/testC")
- public String testC() {
- // 模拟异常
- int a = 1/0;
- return "testC port:" +applicationContext.getEnvironment().getProperty("server.port");
- }
-
- @GetMapping(value = "/testD")
- public String testD() {
- // 模拟业务处理时间一份钟
- try {
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return "testD port:" +applicationContext.getEnvironment().getProperty("server.port");
- }
表示调用/v1/testD时,此时的平均响应时间大于200ms则触发熔断,3秒之后恢复正常。
测试:
jmeter以每秒10个线程访问/v1/testD,由于代码里面模拟业务睡眠了一秒,平局响应时间肯定大于降级策略的200ms,触发熔断,3秒之内访问/v1/testD,仍触发熔断,jmeter停止访问,3秒后正常访问。
表示调用/v1/testC时,此时的异常比例大于0.2(即20%)则触发熔断,3秒之后恢复正常。
表示调用/v1/testC时,一分钟内的异常数大于5则触发熔断,65秒之后恢复正常。
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
兜底方法
- 系统默认:限流出问题后,sentine|系统默认的提示:Blocked by Sentinel (flow limiting)
- 用户自定义:使用@SentinelResource直接实现降级方法,它等同Hystrix的@HystrixCommand同理
- @GetMapping("/testHotKey")
- @SentinelResource(value="testHotKey",blockHandler="deal_testHotKey")
- public String testHotKey(@RequestParam(value="p1",required=false) String p1,
- @RequestParam(value="p2",required=false) String p2){
- return "-----deal_testHotKey";
- }
-
- public String deal_testHotKey(String p1,String p2, BlockException exception){
- return "-----------deal_testHotKey----༼ ༎ຶ ෴ ༎ຶ༽"; //系统默认提示:Blocked by Sentinel (flow limiting)
- }
表示访问/v1/testHotKey时,带上只要带了第一个参数并且QPS>1时触发熔断,1秒(统计窗口时长)之后恢复正常。测试结果如下:
普通:指定参数p1的QPS>N(配置)会被限流
特例:指定参数p1的QPS>N(配置)会被限流,但是p1参数是某个特殊值的时候不会被限流
测试p1=“3”,jmeter每秒的qps=11,postman测试触发熔断
注意:
如果我们程序出现异常,是不会走blockHander的降级方法的,因为这个方法只配置了热点规则,没有配置限流规则。
我们这里配置的降级方法是sentinel针对热点规则配置的,只有触发热点规则才会降级。
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则支持以下的模式:
这里只以入口QPS为例:
表示单台机器上的QPS大于1时触发熔断。
测试下面任意接口QPS大于1都会触发熔断。
http://127.0.0.1:8100/v1/testA
http://127.0.0.1:8100/v1/testB
注意:注解方式埋点不支持 private 方法。
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:
注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理。
特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandler、fallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。
示例:
- // 对应的 `handleException` 函数需要位于 `ExceptionUtil` 类中,并且必须为 static 函数.
- @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
- public void test() {
- System.out.println("Test");
- }
-
- // 原函数
- @SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
- public String hello(long s) {
- return String.format("Hello at %d", s);
- }
-
- // Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
- public String helloFallback(long s) {
- return String.format("Halooooo %d", s);
- }
-
- // Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
- public String exceptionHandler(long s, BlockException ex) {
- // Do some log here.
- ex.printStackTrace();
- return "Oops, error occurred at " + s;
- }
从 1.4.0 版本开始,注解方式定义资源支持自动统计业务异常,无需手动调用 Tracer.trace(ex) 来记录业务异常。Sentinel 1.4.0 以前的版本需要自行调用 Tracer.trace(ex) 来记录业务异常。
- @GetMapping("/testF")
- public String testF(){
- return "-----testF正常访问------";
- }
熔断之后为sentinel默认页面
- @GetMapping("/testG")
- @SentinelResource(value ="testG",blockHandler = "testGBlockHandler")
- public String testG(){
- return "-----testG正常访问------";
- }
- public String testGBlockHandler(BlockException exception){
- //系统默认提示:Blocked by Sentinel (flow limiting)
- return "------testGBlockHandler-----触发熔断----༼ ༎ຶ ෴ ༎ຶ༽";
- }
- @GetMapping("/testH")
- @SentinelResource(value ="testH",blockHandlerClass = TestHandler.class,blockHandler = "testHBlockHandler" )
- public String testH(){
- return "-----testH正常访问------";
- }
- public class TestHandler {
-
- public static String testHBlockHandler(BlockException exception){
- return "-----------触发熔断----༼ ༎ຶ ෴ ༎ຶ༽"; //系统默认提示:Blocked by Sentinel (flow limiting)
- }
- }
- @GetMapping("/testG")
- @SentinelResource(value ="testG",blockHandler = "testGBlockHandler",fallback = "testGFallback")
- public String testG(@RequestParam(value="p1",required=false) String p1){
- if (p1.equals("1")){
- throw new RuntimeException("1111");
- }
- return "-----testG正常访问------";
- }
-
- public String testGBlockHandler(String p1,BlockException ex){
- // Do some log here.
- ex.printStackTrace();
- return "------testGBlockHandler-----触发熔断----༼ ༎ຶ ෴ ༎ຶ༽"; //系统默认提示:Blocked by Sentinel (flow limiting)
- }
-
- public String testGFallback(String p1){
- return "-----testGFallback------触发熔断----༼ ༎ຶ ෴ ༎ຶ༽";
- }
fallback是用于管理异常的,当业务方法发生异常,可以降级到指定方法
blockHandler只对sentienl定义的规则降级
同时配置fallback和blockHandler,两者都会生效,触发降级时blockhandler优先生效
exceptionsToIgnore指定一个异常类,
表示如果当前方法抛出的是指定的异常,不降级,直接对用户抛出异常
Sentinel Dashboard中添加的规则是默认存储在内存中的,重启应用或者sentinel规则就会消失。
官方地址:Sentinel · alibaba/spring-cloud-alibaba Wiki · GitHub
1.添加依赖
-
com.alibaba.csp -
sentinel-datasource-nacos
2.修改配置
- spring:
- cloud:
- sentinel:
- datasource:
- ds1:
- nacos:
- server-addr: ${spring.cloud.nacos.discovery.server-addr}
- namespace: ${spring.cloud.nacos.config.namespace}
- data-id: ${spring.application.name}
- group-id: DEFAULT_GROUP
- data-type: json
- rule-type: flow
json中,这些属性的含义:
resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up, 2表示排队等待;
clusterMode:是否集群。