Spring Cloud Alibaba:Sentinel实现熔断与限流

1、什么是Sentineljava

Sentinel,中文翻译为哨兵,是为微服务提供流量控制、熔断降级的功能,它和Hystrix提供的功能同样,能够有效的解决微服务调用产生的“雪崩效应”,为微服务系统提供了稳定性的解决方案。随着Hystrix进入了维护期,再也不提供新功能,Sentinel是一个不错的替代方案。一般状况下,Hystrix采用线程池对服务的调用进行隔离,Sentinel采用了用户线程对接口进行隔离,两者相比,Hystrix是服务级别的隔离,Sentinel提供了接口级别的隔离,Sentinel隔离级别更加精细,另外Sentinel直接使用用户线程提供限制,相比Hystrix的线程池隔离,减小了线程切换的开销。另外Sentinel的DashBoard提供了在线更改限流规则的配置,也更加的优化。node

2、开源生态spring

Sentinel和Hystrix数组

功能 Sentinel Hystrix
隔离策略 信号量隔离(并发线程数限流) 线程池隔离/信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于RxJava)
动态规则配置 支持多种数据源 支持多种数据源
扩展性 多个扩展点 插件形式
基于注解的支持 支持 支持
限流 基于QPS,支持基于调用关系的限流 有限的支持
流量整形 支持预热模式、匀速器模式、预热排队模式 不支持
系统自适应保护 支持 不支持
控制台 可配置规则、查看秒级监控、机器发现等 简单的监控查看

3、Sentinel特性浏览器

一、丰富的应用场景缓存

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

二、完备的实时监控app

Sentinel同时提供实时的监控功能。咱们能够在控制台中看到接入应用的单台机器秒级数据,甚至500台如下规模的集群的汇总进行状况。框架

三、普遍的开源生态async

Sentinel提供开箱即用的与其它开源框架的整合模块,例如与spring cloud、dubbo、grpc的整合。咱们只须要引入响应的依赖并进行简单的配置便可快速的接入Sentinel。

四、完美的SPI扩展点

Sentinel提供简单易用、完善的SPI扩展点。咱们能够经过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。

4、资源和规则

资源是Sentinel的关键概念。它能够是java应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至能够是一段代码。只是经过Sentinel API定义的代码,就是资源,可以被Sentinel保护起来,大部分状况下,可使用方法签名,URL,甚至服务名称做为资源名来表示资源。

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

Sentinel中调用SphU或者SphO的entry方法获取限流资源,不一样的是前者获取限流资源失败时会跑BlockException异常,后者返回false,两者的实现都是基于CtSph类完成的。

5、核心概念

一、Resource

resource是Sentinel中最重要的一个概念,Sentinel经过资源来保护具体的业务代码或其它后方服务。Sentinel把复杂的逻辑给屏蔽了,用户只须要为受保护的代码或服务定义一个资源,而后定义规则就能够了,剩下的统统交给Sentinel来处理。而且资源和规则是解耦的,规则甚至能够在运行时动态修改。定义完资源后,就能够经过在程序中埋点来保护你本身的服务,埋点的方式有两种:

(1)try-catch方式(经过SphU.entry(...)),当catch到BlockException时执行异常处理或fallback。

(2)if-else方式(经过SphO.entry(...)),当返回false时执行异常处理或fallback。

以上两种方式都是经过硬编码的形式定义资源而后进行资源埋点的,对业务代码的侵入太大,从0.1.1版本开始,Sentinel加入了注解的支持,能够经过注解来定义资源,具体的注解为:SentinelResource。经过注解除了能够定义资源外,还能够指定blockHandler和fallback方法。

在Sentinel中具体表示资源的类:ResourceWrapper,它是一个抽象的包装类,包装了资源的Name和EntryType。他有两个实现类,分别是:StringResourceWrapper和MethodResourceWrapper。顾名思义,StringResourceWrapper是经过对一串字符进行包装,是一个通用的资源包装类,MethodResourceWrapper是对方法调用的包装。

二、Context

Context是对资源操做时的上下文环境,每一个资源操做(针对resource的entry和exit)必须属于一个Context,若是程序中未指定Context,会建立name为“Sentinel_default_context”的默认Context。一个Context生命周期内可能有多个资源操做,Context生命周期内的最后一个资源exit时会清理该Context,这也预示着整个Context生命周期的结束。Context主要属性以下:

public class Context {
   // context名字,默认名字 "sentinel_default_context"
   private final String name;
   // context入口节点,每一个context必须有一个entranceNode
   private DefaultNode entranceNode;
   // context当前entry,Context生命周期中可能有多个Entry,全部curEntry会有变化
   private Entry curEntry;
   // The origin of this context (usually indicate different invokers, e.g. service consumer name or origin IP).
   private String origin = "";
   private final boolean async;
}

一个Context生命周期内Context只能初始化一次,存到ThreadLocal中,而且只有在非NULL时才会进行初始化。若是想在调用SphU.entry()或SphO.entry()前,自定义一个context,则经过ContextUtil.enter()方法来建立。context保存在ThreadLocal中,每次执行的时候会优先到ThreadLocal中获取,为null时会建立一个context。当Entry执行exit方法时,若是entry的parent节点为null,表示当前context中最外层的entry了,此时将threadLocals中的context清空。

三、Entry

每次执行SphU.entry()或SphO.entry()都会返回一个Entry,Entry表示一次资源操做,内部会保存单签invocation信息。在一个context声明周期中屡次资源操做,也就是对应多个Entry,parent/child结构保存在Entry实例中,Entry类CtEntry结构以下:

class CtEntry extends Entry {
   protected Entry parent = null;
   protected Entry child = null;

   protected ProcessorSlot<Object> chain;
   protected Context context;
}
public abstract class Entry implements AutoCloseable {
   private long createTime;
   private Node curNode;
   /**
    * {@link Node} of the specific origin, Usually the origin is the Service Consumer.
    */
   private Node originNode;
   private Throwable error; // 是否出现异常
   protected ResourceWrapper resourceWrapper; // 资源信息
}

四、DefaultNode

Node默认实现类DefaultNode,该类还有一个子类EntranceNode;context有一个entranceNode属性,Entry中有一个curNode属性。

  • EntranceNode:该类的建立在初始化context时完成的,注意该类是针对context维度的,也就是一个context有且仅有一个EntranceNode。
  • DefaultNode:该类的建立是在NodeSelectorSlot.entry完成的,当不存在context.name对应的DefaultNode时会建立并保存在本地缓存;获取到context.name对应的DefaultNode后将该DefaultNode设置到当前context的curEntry.curNode属性,也就是说,在DefaultSelectorSlot中是一个context有且仅有一个DefaultNode。

看到这里,你是否是有疑问?为何一个context有且仅有一个DefaultNode,咱们的resouece跑哪去了呢,其实,这里的一个context有且仅有一个DefaultNode是在NodeSelectorSlot范围内,NodeSelectorSlot是ProcessorSlotChain中的一环,获取ProcessorSlotChain是根据Resource维度来的。总结为一句话就是:针对同一个Resource,多个context对应多个DefaultNode;针对不一样Resource,(不论是否是同一个context)对应多个不一样DefaultNode。这还没看明白 : ),好吧,我不bb了,上图吧:

DefaultNode结构以下:

public class DefaultNode extends StatisticNode {
   private ResourceWrapper id;
   /**
    * The list of all child nodes.
    * 子节点集合
    */
   private volatile Set<Node> childList = new HashSet<>();
   /**
    * Associated cluster node.
    */
   private ClusterNode clusterNode;
}

一个Resouce只有一个clusterNode,多个defaultNode对应一个clusterNode,若是defaultNode.clusterNode为null,则在ClusterBuilderSlot.entry中会进行初始化。
同一个Resource,对应同一个ProcessorSlotChain,这块处理逻辑在lookProcessChain方法中,以下:

ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
   ProcessorSlotChain chain = chainMap.get(resourceWrapper);
   if (chain == null) {
       synchronized (LOCK) {
           chain = chainMap.get(resourceWrapper);
           if (chain == null) {
               // Entry size limit.
               if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
                   return null;
              }

               chain = SlotChainProvider.newSlotChain();
               Map<ResourceWrapper, ProcessorSlotChain> newMap = newHashMap<ResourceWrapper, ProcessorSlotChain>(
                   chainMap.size() + 1);
               newMap.putAll(chainMap);
               newMap.put(resourceWrapper, chain);
               chainMap = newMap;
          }
      }
  }
   return chain;
}

五、StatisticNode

StatisticNode中保存了资源的实时统计数据(基于滑动时间窗口机制),经过这些统计数据,sentinel才能进行限流、降级等一系列操做。StatisticNode属性以下:

public class StatisticNode implements Node {
   /**
    * 秒级的滑动时间窗口(时间窗口单位500ms)
    */
   private transient volatile Metric rollingCounterInSecond = newArrayMetric(SampleCountProperty.SAMPLE_COUNT,
       IntervalProperty.INTERVAL);
   /**
    * 分钟级的滑动时间窗口(时间窗口单位1s)
    */
   private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000,false);
   /**
    * The counter for thread count. 
* 线程个数用户触发线程数流控
    */
   private LongAdder curThreadNum = new LongAdder();
}
public class ArrayMetric implements Metric {
   private final LeapArray<MetricBucket> data;
}
public class MetricBucket {
// 保存统计值
   private final LongAdder[] counters;
// 最小rt
   private volatile long minRt;
}

其中MetricBucket.counters数组大小为MetricEvent枚举值的个数,每一个枚举对应一个统计项,好比PASS表示经过个数,限流可根据经过的个数和设置的限流规则配置count大小比较,得出是否触发限流操做,全部枚举值以下:

public enum MetricEvent {
   PASS, // Normal pass.
   BLOCK, // Normal block.
   EXCEPTION,
   SUCCESS,
   RT,
   OCCUPIED_PASS
}

六、Slot

Slot是sentinel中很是重要的概念,sentinel的工做流程就是围绕着一个个插槽所组成的插槽链来展开的。须要注意的是每一个插槽都有本身的职责,他们各司其职完美的配合,经过必定的编排顺序,来达到最终的限流降级。默认的各个插槽之间的顺序是固定的,由于有的插槽须要依赖其余的插槽计算出来的结果才能进行工做。

sentinel经过SlotChainBuilder做为SPI接口,使得Slot Chain具有了扩展的能力。咱们能够经过实现SlotChainBuilder接口加入自定义Slot而且定义编排各个slot之间的排序,从而能够给sentinel添加自定义的功能。

那SlotChain是在哪建立的呢?是在 CtSph.lookProcessChain() 方法中建立的,而且该方法会根据当前请求的资源先去一个静态的HashMap中获取,若是获取不到才会建立,建立后会保存到HashMap中。这就意味着,同一个资源会全局共享一个SlotChain。默认生成ProcessorSlotChain为:

// DefaultSlotChainBuilder
public ProcessorSlotChain build() {
   ProcessorSlotChain chain = new DefaultProcessorSlotChain();
   chain.addLast(new NodeSelectorSlot());
   chain.addLast(new ClusterBuilderSlot());
   chain.addLast(new LogSlot());
   chain.addLast(new StatisticSlot());
   chain.addLast(new SystemSlot());
   chain.addLast(new AuthoritySlot());
   chain.addLast(new FlowSlot());
   chain.addLast(new DegradeSlot());

   return chain;

6、springcloud如何使用sentinel

学习了sentinel核心概念以后,感受整我的都很差了,真的是晦涩难懂,来个helloworld,轻松一下。

一、pom.xml

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

二、 controller

@RestController
public class TestController {
    @GetMapping(value = "/hello")
    @SentinelResource("hello")
    public String hello() {
        return "Hello Sentinel";
    }
}

三、引入dashboard

直接下载sentinel-dashboard的jar包。

默认是8080端口,在浏览器输入:localhost:8080,默认帐号密码:sentinel:sentinel,看到控制台界面为部署成功。

四、application.properties

server.port=8088
spring.application.name=spring-cloud-alibaba-sentinel-demo

# sentinel dashboard
spring.cloud.sentinel.transport.dashboard=localhost:8080

五、 启动spring boot 项目,继续访问localhost:8080,会看到以下界面

六、 使用Sentinel实现接口限流(在控制台)

七、测试

经过上面的配置,实现的是/hello接口qps最大是2,若是qps大于2,则快速失败,配置完成,点击保存,咱们快速刷新浏览器,会发现快速失败

7、总结

本文主要介绍了Sentinel的概念、特性、与Hystrix的区别、一些核心概念和与SpringCloud的简单整合。随着微服务的流行,服务和服务之间的稳定性变得愈来愈重要。  Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

 

每一篇博客都是一种经历,程序猿生涯的痕迹,知识改变命运,命运要由本身掌控,愿你游历半生,归来还是少年。

欲速则不达,欲达则欲速!

更多精彩内容,首发公众号【素小暖】,欢迎关注。

相关文章
相关标签/搜索