一、什么是Sentinel

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

二、开源生态

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

Sentinel和Hystrix

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

三、Sentinel特性

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

1、丰富的应用场景

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

2、完备的实时监控

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

3、广泛的开源生态

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

4、完美的SPI扩展点

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

四、资源和规则

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

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

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

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

五、核心概念

1、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是对方法调用的包装。

2、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清空。

3、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; // 资源信息
}

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

4、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了,上图吧:

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

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;
}

5、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
}

6、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(www.yongshenyul.com));
   chain.addLast(new ClusterBuilderSlot());
   chain.addLast(new LogSlot());
   chain.addLast(new StatisticSlot( www.xingtuylgw.com));
   chain.addLast(new SystemSlot());
   chain.addLast(new AuthoritySlot());
   chain.addLast(new FlowSlot());
   chain.addLast(new DegradeSlot());

   return chain;

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

六、springcloud如何使用sentinel

学习了sentinel核心概念之后,感觉整个人都不好了,真的是晦涩难懂,来个helloworld,轻松一下。

1、pom.xml

<dependency>
    <groupId>org.springframework.cloud<shentuylzc.cn  /groupId>
    <artifactId>spring-www.yasenyulee.cn cloud-starter-alibaba-sentinel< www.sangyulpt.com /artifactId>
</dependency>

2、 controller

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

3、引入dashboard

直接下载sentinel-dashboard的jar包。

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

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

4、application.properties

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

# sentinel dashboard
spring.cloud.sentinel.www.yixingxzc.cn transport.dashboard=www.niujinzc.cn localhost:8080

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

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

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

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

7、测试

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

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

七、总结

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

05-28 12:32