SpringCloudAlibaba之Sentinel入门笔记

  • Post author:
  • Post category:其他


一、Sentinelg介绍

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

1、Sentinel的历史

2012年,Sentinel诞生,主要功能为入口流量控制。

2013-2017年,Sentinel在阿里巴巴集团内部迅速发展,成为基础技术模块,覆盖了所有的核心场景。Sentinel也因此积累了大量的流量归整场景以及生产实践。

2018年,Sentinel开源,并持续演进。

2019年,Sentinel朝着多语言扩展的方向不断探索,推出 C++ 原生版本,同时针对Service Mesh场景也推出了 Envoy 集群流量控制支持,以解决Service Mesh架构下多语言限流的问题。

2020年,推出 Sentinel Go 版本,继续朝着云原生方向演进。

2、Sentinel是什么?

Sentinel的官方标题是:从名字上来看,很容易就能猜到它是用来作服务稳定性保障的。对于服务稳定性保障组分布式系统的流量防卫兵件,如果熟悉Spring Cloud的用户,第一反应应该就是Hystrix。但是比较可惜的是Netflix已经宣布对Hystrix停止更新。那么,在未来我们还有什么更好的选择呢?除了Spring Cloud官方推荐的resilience4j之外,目前Spring Cloud Alibaba下整合的Sentinel也是用户可以重点考察和选型的目标。

3、Sentinel基本概念

3.1、资源

资源是Sentinel 的关键概念。它可以是Java应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。

只要通过Sentinel API定义的代码,就是资源,能够被Sentinel保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

3.2、规则

围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。

4、Sentinel 具有以下特征:

丰富的应用场景:Sentinel承接了阿里巴巴近10年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。

完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至500台以下规模的集群的汇总运行情况。

广泛的开源生态:Sentinel提供开箱即用的与其它开源框架/库的整合模块,例如与Spring Cloud、Dubbo、gRPC的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。

完善的SPI扩展点:Sentinel提供简单易用、完善的SPI扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。

5、Sentinel与Hystrix、resilience4j[rɪˈzɪliəns]的对比:

Sentinel官方文档地址:

https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

关于每个功能使用里面都有详细介绍。

二、使用Sentinel实现接口限流

Sentinel的使用分为两部分:

sentinel-dashboard:与hystrix-dashboard类似,但是它更为强大一些。除了与hystrix-dashboard一样提供实时监控之外,还提供了流控规则、熔断规则的在线维护等功能。

客户端整合:每个微服务客户端都需要整合sentinel的客户端封装与配置,才能将监控信息上报给dashboard展示以及实时的更改限流或熔断规则等。

下面我们就分两部分来看看,如何使用Sentienl来实现接口限流。

1、Sentinel Dashboard

下载:sentinel-dashboard-1.7.1.jar

其他版本:https://github.com/alibaba/Sentinel/releases

通过命令启动:

java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

sentinel-dashboard不像Nacos的服务端那样提供了外置的配置文件,比较容易修改参数。不过不要紧,由于sentinel-dashboard是一个标准的spring boot应用,所以如果要自定义端口号等内容的话,可以通过在启动命令中增加参数来调整,比如:-Dserver.port=8888。

默认情况下,sentinel-dashboard以8080端口启动,所以可以通过访问:localhost:8080来验证是否已经启动成功,如果一切顺利的话,可以看到登录页面。

注意:只有1.6.0及以上版本,才有这个简单的登录页面。默认用户名和密码都是sentinel。对于用户登录的相关配置可以在启动命令中增加下面的参数来进行配置:

-Dsentinel.dashboard.auth.username=sentinel: 用于指定控制台的登录用户名为sentinel。

-Dsentinel.dashboard.auth.password=123456: 用于指定控制台的登录密码为123456;如果省略这两个参数,默认用户和密码均为sentinel。

-Dserver.servlet.session.timeout=7200: 用于指定Spring Boot服务端session的过期时间,如7200表7200 秒;60m表示60分钟,默认为30分钟。

输入账户密码登录后,可以看到如下页面:

2、Sentinel的客户端

整合Sentinel新建模块:springcloudalibaba-sentinel

2.1、添加pom依赖:

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>com.alibaba.cloud</groupId>

<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>

<version>2.1.2.RELEASE</version>

</dependency>

</dependencies>

2.2、添加配置

在Spring Cloud应用中通过spring.cloud.sentinel.transport.dashboard参数配置sentinel dashboard的访问地址,比如:application.yaml配置

server:

port: 8002

spring:

application:

name: springcloudalibaba-sentinel

cloud:

nacos:

discovery:

server-addr: localhost:8848

sentinel:

transport:

dashboard: localhost:8090

eager: true

2.3、创建应用主类,并提供一个rest接口,比如

@RestController

public class TestController {


@GetMapping(value = “/hello”)

@SentinelResource(“hello”) //注解用来标识资源是否被限流、降级

public String hello() {


return “Hello Sentinel”;

}

}

@SentinelResource 注解用来标识资源是否被限流、降级。上述例子上该注解的属性 Hello 表示资源名。

@SentinelResource 还提供了其它额外的属性如 blockHandler,blockHandlerClass,fallback 用于表示限流或降级的操作(注意有方法签名要求),更多内容可以参考Sentinel注解支持文档(https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81)。

若不配置 blockHandler、fallback 等函数,则被流控降级时方法会直接抛出对应的 BlockException;若方法未定义 throws BlockException 则会被JVM包装一层 UndeclaredThrowableException。

注:一般推荐将 @SentinelResource 注解加到服务实现上,而在Web层直接使用Spring Cloud Alibaba自带的Web埋点适配。Sentinel Web适配同样支持配置自定义流控处理逻辑。

2.4、启动应用

访问hello接口:http://localhost:8090/sentinel/hello

3、基于QPS/并发数的流量控制

3.1、配置限流规则—QPS流量控制

当QPS超过某个阈值的时候,则采取措施进行流量控制。流量控制的效果包括以下几种:

直接拒绝、Warm Up、匀速排队。对应 FlowRule 中的 controlBehavior 字段。

1)直接拒绝

直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。

2)Warm Up

Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过”冷启动”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

3)匀速排队

匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。

注意:若使用除了直接拒绝之外的流量控制效果,则调用关系限流策略(strategy)会被忽略

在完成了上面的sentinel控制台与客户端环境搭建之后,我们在springcloudalibaba-sentinel服务下,点击簇点链路菜单,可以看到如下界面:

————————————————

其中/hello接口,就是我们上一节中实现并调用过的接口。通过点击流控按钮,来为该接口设置限流规则,比如:

这里做一个最简单的配置:阈值类型选择:QPS、单机阈值:2

综合起来的配置效果就是,该接口的限流策略是每秒最多允许2个请求进入。

点击新增按钮之后,可以看到如下界面:

验证限流规则:

在完成了上面所有内容之后,我们可以尝试一下快速的调用这个接口,看看是否会触发限流控制,比如:

curl http://192.168.41.241:8002/sentinel/hello

Hello Sentinel

curl http://192.168.41.241:8002/sentinel/hello

Hello Sentinel

curl http://192.168.41.241:8002/sentinel/hello

Blocked by Sentinel (flow limiting)

可以看到,快速的调用两次/sentinel/hello接口之后,第三次调用被限流了。

3.2、并发线程数流量控制

并发线程数限流用于保护业务线程数不被耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。

为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的overhead比较大,特别是对低延时的调用有比较大的影响。Sentinel并发线程数限流不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目,如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。

3.3、流控模式说明

1)直接:直接比较简单单个接口流控模式配置

2)关联:

比如我们下订单接口与库存接口,如果下订单数量不能超过库存数量,因此当库存达到极限以后就不能再下订单了。

Postman里面设置hello2接口没400毫秒请求一次,这样大于我们配置的每秒两次访问量,正常情况hello接口应该不能访问。

然后访问hello接口:果然被限流了

3)链路:

目前版本官方有bug,后续更新。

4、基于调用关系的流量控制

调用关系包括调用方、被调用方;一个方法又可能会调用其它方法,形成一个调用链路的层次关系。Sentinel 通过 NodeSelectorSlot 建立不同资源间的调用的关系,并且通过 ClusterNodeBuilderSlot 记录每个资源的实时统计信息。

有了调用链路的统计信息,我们可以衍生出多种流量控制手段。

4.1、根据调用方限流

ContextUtil.enter(resourceName, origin) 方法中的origin参数标明了调用方身份。这些信息会在 ClusterBuilderSlot 中被统计。可通过以下命令来展示不同的调用方对同一个资源的调用数据:

4.2、根据调用链路入口限流:链路限流

NodeSelectorSlot 中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 machine-root 的虚拟节点,调用链的入口都是这个虚节点的子节点。

一棵典型的调用树如下图所示:

machine-root

/         \

Entrance1     Entrance2

/               \

DefaultNode(nodeA)   DefaultNode(nodeA)

上图中来自入口 Entrance1 和 Entrance2 的请求都调用到了资源 NodeA,Sentinel 允许只根据某个入口的统计信息对资源限流。比如我们可以设置 FlowRule.strategy 为 RuleConstant.CHAIN,同时设置 FlowRule.ref_identity 为 Entrance1 来表示只有从入口 Entrance1 的调用才会记录到 NodeA 的限流统计当中,而不关心经 Entrance2 到来的调用。

调用链的入口(上下文)是通过 API 方法 ContextUtil.enter(contextName) 定义的,其中 contextName 即对应调用链路入口名称。详情可以参考 ContextUtil 文档。

4.3、具有关系的资源流量控制:关联流量控制

当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 FlowRule.strategy 为 RuleConstant.RELATE同时设置 FlowRule.ref_identity 为 write_db。这样当写库操作过于频繁时,读数据的请求会被限流。

备注:

sentinel同时满足:Feign支持与RestTemplate 支持,具体使用参考官方文档

https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel

三、其他常用配置规则

1、热的规则配置

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的Top K数据,并对其访问进行限制。比如:

商品ID为参数,统计一段时间内最常购买的商品ID并进行限制

用户ID为参数,针对一段时间内频繁访问的用户ID进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

Sentinel利用LRU策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。

1.1、热点参数规则

1.2、增加热点配置接口

/**

* 热点配置–流控降级配置(注意配置方法的第一个参数)

*/

@GetMapping(“hotlimit”)

@SentinelResource(value = “hotlimit”,blockHandler =”blockHandler”,fallback =”fallbackHandler”, fallbackClass =TestFallback.class )

public String hotconfig(String name,String type) throws InterruptedException {


if (name.equals(“xx”)){ // 如果参数为xx则抛出异常,回调fallbackHandler方法

System.out.println(1/0);

}

return “it’s hotconfig method”;

}

http://localhost:8002/login?name=xxx&type=11

参数索引从0开始,当参数等于aaa的时候,限流规则QPS为2

————————————————


1)当参数为aaa的时候,访问测试:


http://localhost:8003/hotlimit?name=aaa&type=11

当请求QPS超过2以后则被限流,回调blockHandler方法,结果如下:

2)如果参数为xx则抛出异常,回调fallbackHandler方法


http://localhost:8003/hotlimit?name=xx&type=11

3)参数为其他值的时候,走正常的流控规则(即QPS达到配置的4以后被限流)

2、降级规则

2.1、降级策略

我们通常用以下几种方式来衡量资源是否处于稳定的状态:

1)RT–平均响应时间 (DEGRADE_GRADE_RT):

当1s内持续进入N个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以ms为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以s为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意Sentinel默认统计的RT上限是4900 ms,超出此阈值的都会算作4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。

2)异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):

当资源的每秒请求量>= N(可配置),并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以s为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% – 100%。

3)异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):

当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于60s,则结束熔断状态后仍可能再进入熔断状态。

注意:异常降级仅针对业务异常,对Sentinel限流降级本身的异常(BlockException)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex) 记录业务异常。

开源整合模块,如Sentinel Dubbo Adapter, Sentinel Web Servlet Filter或 @SentinelResource 注解会自动统计业务异常,无需手动调用。

2.2、RT配置方式

1)测试代码如下所示,模拟配置让方法睡眠600毫秒,大于RT配置时间

@GetMapping(“login”)

public String login(String name,String type) throws InterruptedException {


Thread.sleep(600);  //  降级规则配置

return “success”;

}

2)配置降级规则,使用RT方式,时间为400毫秒,时间窗口为3秒

如果每次不能访问该接口,且每次时间都大于小于400毫秒的话,该接口将会在3秒后给降级限流

————————————————


3)访问测试:


http://localhost:8002/login?name=xxx&type=11

2.3、异常数配置测试

当参数name值为xx的时候跑出异常

@GetMapping(“exception”)

public String exception(String name) {


if (name.equals(“xx”)){


System.out.println(1/0);

}

return “success”;

}

降级规则我们配置的异常数为每分钟2个异常,超过就降级

2.4、服务降级回调方法配置

/**

* 降级配置–方法回调

*/

@GetMapping(“/limit”)

@SentinelResource(value = “limit”,blockHandler =”blockHandler”,fallback =”fallbackHandler”, fallbackClass =TestFallback.class )

public String limit (String  name,String type) {


if (name.equals(“xx”)){


System.out.println(1/0);

}

return “success”;

}

/**

* 流控触发后的降级方法

*/

public  String blockHandler(String  name,String type, BlockException e){


System.out.println(“——blockHandler——-“);

return “blockHandler”;

}

public static class TestFallback {


public static String fallbackHandler(String  name,String type){


System.out.println(“————-fallbackHandler———-“);

return “fallbackHandler”;

}

}

如果name=xx则会抛出异常,然后触发fallbackHandler方法