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 以流量爲切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。

 

每一篇博客都是一種經歷,程序猿生涯的痕跡,知識改變命運,命運要由本身掌控,願你遊歷半生,歸來還是少年。

欲速則不達,欲達則欲速!

更多精彩內容,首發公衆號【素小暖】,歡迎關注。

相關文章
相關標籤/搜索