内容简介:随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。如上图所示,Sentinel中一个资源访问条目被抽象成一个Entry,这个Entry包含几个重要的信息,一个是当前节点(Node)、源节点,一个是资源信息(比如资源名称等)。规则则是对资源访问控制的一种策略。 Node是Sentinel中对应一个资源实时访问指标的抽象,其代表请求进入后的一种状态,每个请求进来,对于关联的资源,采取前面设置的访问策略进行指标
Sentinel介绍
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
- Sentinel历史
- 2012 年,Sentinel 诞生
- 2013-2017 年,作为阿里基础技术模块, Sentinel 承接了阿里巴巴近 几年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等
- 2018 年,Sentinel 开源
Sentinel基本概念
如上图所示,Sentinel中一个资源访问条目被抽象成一个Entry,这个Entry包含几个重要的信息,一个是当前节点(Node)、源节点,一个是资源信息(比如资源名称等)。规则则是对资源访问控制的一种策略。 Node是Sentinel中对应一个资源实时访问指标的抽象,其代表请求进入后的一种状态,每个请求进来,对于关联的资源,采取前面设置的访问策略进行指标统计。
Sentinel基本功能
如何使用Sentinel
第一步当然是引入sentinel依赖了,对于不同框架的集成 采用不同的适配模块,详情参考官方文档。
定义资源
抛出异常的方式定义资源 用这种方式,当资源发生了限流之后会抛出 BlockException。这个时候可以捕捉异常,进行限流之后的逻辑处理。示例代码如下:
Entry entry = null; // 务必保证finally会被执行 try { // 资源名可使用任意有业务语义的字符串 entry = SphU.entry("自定义资源名"); /** * 被保护的业务逻辑 */ } catch (BlockException e1) { // 资源访问阻止,被限流或被降级 // 进行相应的处理操作 } finally { if (entry != null) { entry.exit(); } }
返回布尔值方式定义资源 用这种方式,当资源发生了限流之后会返回 false,这个时候可以根据返回值,进行限流之后的逻辑处理。示例代码如下:
// 资源名可使用任意有业务语义的字符串 if (SphO.entry("自定义资源名")) { // 务必保证finally会被执行 try { /** * 被保护的业务逻辑 */ } finally { SphO.exit(); } } else { // 资源访问阻止,被限流或被降级 // 进行相应的处理操作 }
注解方式定义资源
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-annotation-aspectj</artifactId> <version>x.y.z</version> </dependency>
@SentinelResource 注解 @SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:
value: 资源名称,必需项(不能为空)
entryType: 入口类型,可选项(默认为 EntryType.OUT)
blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。若未配置,则将 BlockException 直接抛出。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
fallback: fallback 函数名称,可选项,仅针对降级功能生效(DegradeException)。fallback 函数的访问范围需要是 public,参数类型和返回类型都需要与原方法相匹配,并且需要和原方法在同一个类中。
若 blockHandler 和 fallback 都进行了配置,则遇到降级的时候首先选择 fallback 函数进行处理。
注意 blockHandler 是处理被 block 的情况(所有类型的 BlockException),而 fallback 仅处理被降级的情况(DegradeException)。其它异常会原样抛出,Sentinel 不会进行处理。
public class TestService { // 对应的 `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 函数,函数签名与原函数一致. 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; } }
异步调用支持 Sentinel 从 0.2.0 版本开始支持异步调用资源的定义。在异步调用中,需要通过 SphU.asyncEntry(xxx) 方法定义资源,并通常需要在异步的回调函数中调用 exit 方法。以下是一个简单的示例:
try { AsyncEntry entry = SphU.asyncEntry(resourceName); // 异步调用. doAsync(userId, result -> { try { // 在此处处理异步调用的结果. } finally { // 在回调结束后 exit. entry.exit(); } }); } catch (BlockException ex) { // Request blocked. // Handle the exception (e.g. retry or fallback). }
关于异步调用的详细用法,请参考官方文档。
主流框架的适配:
因篇幅有限,其它各种框架的适配,请参考官方文档。
定义规则
Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效。同时 Sentinel 也提供相关 API,供您来定制自己的规则策略。
规则的定义 Sentinel 支持以下几种规则:流量控制规则、熔断降级规则、系统保护规则 以及 授权规则。因篇幅问题,对应功能的规则配置请参考官方文档,如下只是一个示例:
private static void initFlowQpsRule() { List<FlowRule> rules = new ArrayList<>(); FlowRule rule1 = new FlowRule(); rule1.setResource(KEY); // set limit qps to 20 rule1.setCount(20); rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setLimitApp("default"); rules.add(rule1); FlowRuleManager.loadRules(rules); }
Sentinel工作原理
Sentinel的工作原理,简单理解可以看上面的脑图
对于需要保护的资源,系统先定义其rule,创建资源、然后创建默认的SoltChain。这个SoltChain,我们可以自行装配,也可以直接默认使用Sentinel提供的DefaultProcessorSlotChain。Sentinel提供了基于SPI的拓展口子,让我们可以自己拓展每一个ProcessorSlot。Sentinel中ProcessorSlot的设计思路和Netty中Handler处理链的设计非常像。一个请求进入某个资源,其接下来的调用链路都会由前面装配好的路劲进行,每个插槽(Solt)的作用参考上图。这其中,无论多少个request进入资源,其全部共享同一个上下文Context,Sentinel内部是按资源名划分的。每个Entry都关联一个Context和一个Chain。这块的设计思路,如果看过Netty的源码,你会感觉非常的熟悉。后面我会谈到Sentinel的核心组件和源码。
Sentinel和Hystrix 对比
Hystrix 的关注点在于以 隔离 和 熔断 为主的容错机制,超时或被熔断的调用将会快速失败,并可以提供 fallback 机制。
Sentinel 的侧重点在于:多样化的流量控制、熔断降级、系统负载保护、实时监控和控制台
Hystrix中每个 Command 创建时都要指定 commandKey 和 groupKey(用于区分资源)以及对应的隔离策略(线程池隔离 or 信号量隔离)
Sentinel 的资源定义与规则配置的耦合度更低
Sentinel 中资源定义和规则配置是分离的。用户先通过 Sentinel API 给对应的业务逻辑定义资源(埋点),然后可以在需要的时候配置规则。
Hystrix在采用线程池隔离时,有个最大的缺点就是,Hystrix 的线程池隔离针对不同的资源分别创建不同的线程池,对于一些线程或者资源敏感的应用,可能会创建太多的线程,造成线程上下文切换的 overhead 比较大。比如使用Tomcat的应用,Tomcat作为web服务器,本身就消耗不少线程资源,如果再使用Hystrix,在采用线程池隔离时上下文切换会有非常大的损耗。
总结如下 :
Sentinel源码浅析
Sentinel核心组件UML图
这个图有点大,超出限制,我截图后压缩后才能正常上传,有同学如果感兴趣可以私下找我要。 Sentinel中比较重要的几个比较重要的抽象
Entry,Context,Node,Sph,ProcessorSlot,Rule,RuleManager,SentinelProperty,PropertyListener,Metric,MetricBucket,MetricWriter,MetricsReader,MetricTimerListener,WindowWrap,LeapArray、transport。具体抽象的其实就是(对应上面)资源、节点、获取资源条目的抽象、资源上下文、处理插槽、规则、规则管理器、配置、配置监听器、度量、度量槽、度量写、度量读、度量信息监听器(定时写入)、时间片、滑动窗口、发送统计数据到指定的sentinel控制台。对于每一个请求,进入某个资源,都会通过资源名称获取一个资源以及其对应的上下文,所有的请求都会经过一个处理链(SoltChain),其中StatisticSlot负责统计度量指标,具体的操作是调用StatisticNode这个统计节点进行操作的,统计节点StatisticNode维持两个滑动窗口,一个窗口周期是1秒,一个是60秒。滑动窗口实现没啥特别之处,每个时间片内部的桶信息计数是通过大名鼎鼎的Doug Lea写的实现的并发累计器Striped64实现的LongAdder。具体思路和算法是将AtomicInteger的内部核心数据value分离成一个数组,每个线程访问时,通过哈希等算法映射到其中一个数字进行计数,而最终的计数结果,则为这个数组的求和累加,其中,热点数据value被分离成多个单元cell,每个cell独自维护内部的值,当前对象的实际值由所有的cell累计合成,这样,热点就进行了有效的分离,提高了并行度,LongAdder正是使用了这种思想,类似ConcurrentHashMap,热点分离。因为传统的CAS实现,在并发量较大的时候,修改失败的概率就很高,在大量修改失败时,这些原子操作就会进行多次循环尝试,因此性能就会受到影响。LongAdder的热点分离分段累加操作,在并发较高时性能相对会好很多。
下面发下请求进入资源后的整个时序图
最后放下个人学习sentinel的一个思维导图,起个抛砖引玉的作用。
总的来说sentinel的模块和代码实现还是非常清晰的,大家有兴趣可以再深入研究下,以上均为个人理解观点,因为个人能力和研究深度有限,观点难免有失偏颇,欢迎各位同学私下斧正交流。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。