国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

限流降級神器,帶你解讀阿里巴巴開源 Sentinel 實現原理

新聞 前端
Sentinel 是阿里中間件團隊開源的,面向分布式服務架構的輕量級高可用流量控制組件,主要以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度來幫助用戶保護服務的穩定性。

Sentinel 是阿里中間件團隊開源的,面向分布式服務架構的輕量級高可用流量控制組件,主要以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度來幫助用戶保護服務的穩定性。

大家可能會問:Sentinel 和之前常用的熔斷降級庫 Netflix Hystrix 有什么異同呢?Sentinel官網有一個對比的文章,這里摘抄一個總結的表格,具體的對比可以點此 鏈接 查看。

限流降級神器,帶你解讀阿里巴巴開源 Sentinel 實現原理

從對比的表格可以看到,Sentinel比Hystrix在功能性上還要強大一些,本文讓我們一起來了解下Sentinel的源碼,揭開Sentinel的神秘面紗。

項目結構

將Sentinel的源碼fork到自己的github庫中,接著把源碼clone到本地,然后開始源碼閱讀之旅吧。

首先我們看一下Sentinel項目的整個結構:

限流降級神器,帶你解讀阿里巴巴開源 Sentinel 實現原理
  • sentinel-core 核心模塊,限流、降級、系統保護等都在這里實現
  • sentinel-dashboard 控制臺模塊,可以對連接上的sentinel客戶端實現可視化的管理
  • sentinel-transport 傳輸模塊,提供了基本的監控服務端和客戶端的API接口,以及一些基于不同庫的實現
  • sentinel-extension 擴展模塊,主要對DataSource進行了部分擴展實現
  • sentinel-adapter 適配器模塊,主要實現了對一些常見框架的適配
  • sentinel-demo 樣例模塊,可參考怎么使用sentinel進行限流、降級等
  • sentinel-benchmark 基準測試模塊,對核心代碼的精確性提供基準測試

運行樣例

基本上每個框架都會帶有樣例模塊,有的叫example,有的叫demo,sentinel也不例外。

那我們從sentinel的demo中找一個例子運行下看看大致的情況吧,上面說過了sentinel主要的核心功能是做限流、降級和系統保護,那我們就從“限流”開始看sentinel的實現原理吧。

限流降級神器,帶你解讀阿里巴巴開源 Sentinel 實現原理

可以看到sentinel-demo模塊中有很多不同的樣例,我們找到basic模塊下的flow包,這個包下面就是對應的限流的樣例,但是限流也有很多種類型的限流,我們就找根據qps限流的類看吧,其他的限流方式原理上都大差不差。

  1. public class FlowQpsDemo { 
  2.  
  3. private static final String KEY = "abc"
  4.  
  5. private static AtomicInteger pass = new AtomicInteger(); 
  6.  
  7. private static AtomicInteger block = new AtomicInteger(); 
  8.  
  9. private static AtomicInteger total = new AtomicInteger(); 
  10.  
  11. private static volatile boolean stop = false
  12.  
  13. private static final int threadCount = 32
  14.  
  15. private static int seconds = 30
  16.  
  17. public static void main(String[] args) throws Exception { 
  18.  
  19. initFlowQpsRule(); 
  20. tick(); 
  21.  
  22. // first make the system run on a very low condition 
  23.  
  24. simulateTraffic(); 
  25.  
  26. System.out.println("===== begin to do flow control"); 
  27.  
  28. System.out.println("only 20 requests per second can pass"); 
  29.  
  30.  
  31. private static void initFlowQpsRule() { 
  32.  
  33. List<FlowRule> rules = new ArrayList<FlowRule>(); 
  34.  
  35. FlowRule rule1 = new FlowRule(); 
  36.  
  37. rule1.setResource(KEY); 
  38.  
  39. // set limit qps to 20 
  40.  
  41. rule1.setCount(20); 
  42.  
  43. // 設置限流類型:根據qps 
  44.  
  45. rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); 
  46.  
  47. rule1.setLimitApp("default"); 
  48.  
  49. rules.add(rule1); 
  50.  
  51. // 加載限流的規則 
  52.  
  53. FlowRuleManager.loadRules(rules); 
  54.  
  55.  
  56. private static void simulateTraffic() { 
  57.  
  58. for (int i = 0; i < threadCount; i++) { 
  59.  
  60. Thread t = new Thread(new RunTask()); 
  61.  
  62. t.setName("simulate-traffic-Task"); 
  63.  
  64. t.start(); 
  65.  
  66.  
  67.  
  68. private static void tick() { 
  69.  
  70. Thread timer = new Thread(new TimerTask()); 
  71.  
  72. timer.setName("sentinel-timer-task"); 
  73.  
  74. timer.start(); 
  75.  
  76.  
  77. static class TimerTask implements Runnable { 
  78.  
  79. @Override 
  80.  
  81. public void run() { 
  82.  
  83. long start = System.currentTimeMillis(); 
  84.  
  85. System.out.println("begin to statistic!!!"); 
  86.  
  87. long oldTotal = 0
  88.  
  89. long oldPass = 0
  90.  
  91. long oldBlock = 0
  92.  
  93. while (!stop) { 
  94.  
  95. try { 
  96.  
  97. TimeUnit.SECONDS.sleep(1); 
  98.  
  99. catch (InterruptedException e) { 
  100.  
  101.  
  102. long globalTotal = total.get(); 
  103.  
  104. long oneSecondTotal = globalTotal - oldTotal; 
  105.  
  106. oldTotal = globalTotal; 
  107.  
  108. long globalPass = pass.get(); 
  109.  
  110. long oneSecondPass = globalPass - oldPass; 
  111.  
  112. oldPass = globalPass; 
  113.  
  114. long globalBlock = block.get(); 
  115.  
  116. long oneSecondBlock = globalBlock - oldBlock; 
  117.  
  118. oldBlock = globalBlock; 
  119.  
  120. System.out.println(seconds + " send qps is: " + oneSecondTotal); 
  121.  
  122. System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal 
  123.  
  124. ", pass:" + oneSecondPass 
  125.  
  126. ", block:" + oneSecondBlock); 
  127.  
  128. if (seconds-- <= 0) { 
  129.  
  130. stop = true
  131.  
  132.  
  133.  
  134. long cost = System.currentTimeMillis() - start; 
  135.  
  136. System.out.println("time cost: " + cost + " ms"); 
  137.  
  138. System.out.println("total:" + total.get() + ", pass:" + pass.get() 
  139.  
  140. ", block:" + block.get()); 
  141.  
  142. System.exit(0); 
  143.  
  144.  
  145.  
  146. static class RunTask implements Runnable { 
  147.  
  148. @Override 
  149.  
  150. public void run() { 
  151.  
  152. while (!stop) { 
  153.  
  154. Entry entry = null
  155.  
  156. try { 
  157.  
  158. entry = SphU.entry(KEY); 
  159.  
  160. // token acquired, means pass 
  161.  
  162. pass.addAndGet(1); 
  163.  
  164. catch (BlockException e1) { 
  165.  
  166. block.incrementAndGet(); 
  167.  
  168. catch (Exception e2) { 
  169.  
  170. // biz exception 
  171.  
  172. finally { 
  173.  
  174. total.incrementAndGet(); 
  175.  
  176. if (entry != null) { 
  177.  
  178. entry.exit(); 
  179.  
  180.  
  181.  
  182. Random random2 = new Random(); 
  183.  
  184. try { 
  185.  
  186. TimeUnit.MILLISECONDS.sleep(random2.nextInt(50)); 
  187.  
  188. catch (InterruptedException e) { 
  189.  
  190. // ignore 
  191.  
  192.  
  193.  
  194.  
  195.  

執行上面的代碼后,打印出如下的結果:

限流降級神器,帶你解讀阿里巴巴開源 Sentinel 實現原理

可以看到,上面的結果中,pass的數量和我們的預期并不相同,我們預期的是每秒允許pass的請求數是20個,但是目前有很多pass的請求數是超過20個的。

原因是,我們這里測試的代碼使用了多線程,注意看 threadCount 的值,一共有32個線程來模擬,而在RunTask的run方法中執行資源保護時,即在 SphU.entry 的內部是沒有加鎖的,所以就會導致在高并發下,pass的數量會高于20。

可以用下面這個模型來描述下,有一個TimeTicker線程在做統計,每1秒鐘做一次。有N個RunTask線程在模擬請求,被訪問的business code被資源key保護著,根據規則,每秒只允許20個請求通過。

由于pass、block、total等計數器是全局共享的,而多個RunTask線程在執行SphU.entry申請獲取entry時,內部沒有鎖保護,所以會存在pass的個數超過設定的閾值。

限流降級神器,帶你解讀阿里巴巴開源 Sentinel 實現原理

那為了證明在單線程下限流的正確性與可靠性,那我們的模型就應該變成了這樣:

限流降級神器,帶你解讀阿里巴巴開源 Sentinel 實現原理

那接下來我把 threadCount 的值改為1,只有一個線程來執行這個方法,看下具體的限流結果,執行上面的代碼后打印的結果如下:

限流降級神器,帶你解讀阿里巴巴開源 Sentinel 實現原理

可以看到pass數基本上維持在20,但是***次統計的pass值還是超過了20。這又是什么原因導致的呢?

其實仔細看下Demo中的代碼可以發現,模擬請求是用的一個線程,統計結果是用的另外一個線程,統計線程每1秒鐘統計一次結果,這兩個線程之間是有時間上的誤差的。從TimeTicker線程打印出來的時間戳可以看出來,雖然每隔一秒進行統計,但是當前打印時的時間和上一次的時間還是有誤差的,不完全是1000ms的間隔。

要真正驗證每秒限制20個請求,保證數據的精準性,需要做基準測試,這個不是本篇文章的重點,有興趣的同學可以去了解下jmh,sentinel中的基準測試也是通過jmh做的。

深入原理

通過一個簡單的示例程序,我們了解了sentinel可以對請求進行限流,除了限流外,還有降級和系統保護等功能。那現在我們就撥開云霧,深入源碼內部去一窺sentinel的實現原理吧。

首先從入口開始: SphU.entry() 。這個方法會去申請一個entry,如果能夠申請成功,則說明沒有被限流,否則會拋出BlockException,表面已經被限流了。

從 SphU.entry() 方法往下執行會進入到 Sph.entry() ,Sph的默認實現類是 CtSph ,在CtSph中最終會執行到 entry(ResourceWrapperresourceWrapper,intcount,Object...args)throwsBlockException 這個方法。

我們來看一下這個方法的具體實現:

  1. public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException { 
  2.  
  3. Context context = ContextUtil.getContext(); 
  4.  
  5. if (context instanceof NullContext) { 
  6.  
  7. // Init the entry only. No rule checking will occur. 
  8.  
  9. return new CtEntry(resourceWrapper, null, context); 
  10.  
  11.  
  12. if (context == null) { 
  13.  
  14. context = MyContextUtil.myEnter(Constants.CONTEXT_DEFAULT_NAME, "", resourceWrapper.getType()); 
  15.  
  16.  
  17. // Global switch is close, no rule checking will do. 
  18.  
  19. if (!Constants.ON) { 
  20.  
  21. return new CtEntry(resourceWrapper, null, context); 
  22.  
  23.  
  24. // 獲取該資源對應的SlotChain 
  25.  
  26. ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper); 
  27.  
  28. /* 
  29.  
  30. * Means processor cache size exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, so no 
  31.  
  32. * rule checking will be done. 
  33.  
  34. */ 
  35.  
  36. if (chain == null) { 
  37.  
  38. return new CtEntry(resourceWrapper, null, context); 
  39.  
  40.  
  41. Entry e = new CtEntry(resourceWrapper, chain, context); 
  42.  
  43. try { 
  44.  
  45. // 執行Slot的entry方法 
  46.  
  47. chain.entry(context, resourceWrapper, null, count, args); 
  48.  
  49. catch (BlockException e1) { 
  50.  
  51. e.exit(count, args); 
  52.  
  53. // 拋出BlockExecption 
  54.  
  55. throw e1; 
  56.  
  57. catch (Throwable e1) { 
  58.  
  59. RecordLog.info("Sentinel unexpected exception", e1); 
  60.  
  61.  
  62. return e; 
  63.  

這個方法可以分為以下幾個部分:

  • 1.對參數和全局配置項做檢測,如果不符合要求就直接返回了一個CtEntry對象,不會再進行后面的限流檢測,否則進入下面的檢測流程。
  • 2.根據包裝過的資源對象獲取對應的SlotChain
  • 3.執行SlotChain的entry方法
  • 3.1.如果SlotChain的entry方法拋出了BlockException,則將該異常繼續向上拋出
  • 3.2.如果SlotChain的entry方法正常執行了,則***會將該entry對象返回
  • 4.如果上層方法捕獲了BlockException,則說明請求被限流了,否則請求能正常執行

其中比較重要的是第2、3兩個步驟,我們來分解一下這兩個步驟。

創建SlotChain

首先看一下lookProcessChain的方法實現:

  1. private ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) { 
  2.  
  3. ProcessorSlotChain chain = chainMap.get(resourceWrapper); 
  4.  
  5. if (chain == null) { 
  6.  
  7. synchronized (LOCK) { 
  8.  
  9. chain = chainMap.get(resourceWrapper); 
  10.  
  11. if (chain == null
  12.  
  13. // Entry size limit. 
  14.  
  15. if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) { 
  16.  
  17. return null
  18.  
  19.  
  20. // 具體構造chain的方法 
  21.  
  22. chain = Env.slotsChainbuilder.build(); 
  23.  
  24. Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(chainMap.size() + 1); 
  25.  
  26. newMap.putAll(chainMap); 
  27.  
  28. newMap.put(resourceWrapper, chain); 
  29.  
  30. chainMap = newMap; 
  31.  
  32.  
  33.  
  34.  
  35. return chain; 
  36.  

該方法使用了一個HashMap做了緩存,key是資源對象。這里加了鎖,并且做了 doublecheck 。具體構造chain的方法是通過: Env.slotsChainbuilder.build() 這句代碼創建的。那就進入這個方法看看吧。

  1. public ProcessorSlotChain build() { 
  2.  
  3. ProcessorSlotChain chain = new DefaultProcessorSlotChain(); 
  4.  
  5. chain.addLast(new NodeSelectorSlot()); 
  6.  
  7. chain.addLast(new ClusterBuilderSlot()); 
  8.  
  9. chain.addLast(new LogSlot()); 
  10.  
  11. chain.addLast(new StatisticSlot()); 
  12.  
  13. chain.addLast(new SystemSlot()); 
  14.  
  15. chain.addLast(new AuthoritySlot()); 
  16.  
  17. chain.addLast(new FlowSlot()); 
  18.  
  19. chain.addLast(new DegradeSlot()); 
  20.  
  21. return chain; 
  22.  

Chain是鏈條的意思,從build的方法可看出,ProcessorSlotChain是一個鏈表,里面添加了很多個Slot。具體的實現需要到DefaultProcessorSlotChain中去看。

  1. public class DefaultProcessorSlotChain extends ProcessorSlotChain { 
  2.  
  3. AbstractLinkedProcessorSlot<?> first = new AbstractLinkedProcessorSlot<Object>() { 
  4.  
  5. @Override 
  6.  
  7. public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, Object... args) 
  8.  
  9. throws Throwable { 
  10.  
  11. super.fireEntry(context, resourceWrapper, t, count, args); 
  12.  
  13.  
  14. @Override 
  15.  
  16. public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { 
  17.  
  18. super.fireExit(context, resourceWrapper, count, args); 
  19.  
  20.  
  21. }; 
  22.  
  23. AbstractLinkedProcessorSlot<?> end = first; @Override public void addFirst(AbstractLinkedProcessorSlot<?> protocolProcessor) { protocolProcessor.setNext(first.getNext()); first.setNext(protocolProcessor); if (end == first) { end = protocolProcessor; } } @Override 
  24. public void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor) 
  25.  
  26. end.setNext(protocolProcessor); 
  27.  
  28. end = protocolProcessor; 
  29.  
  30.  

DefaultProcessorSlotChain中有兩個AbstractLinkedProcessorSlot類型的變量:first和end,這就是鏈表的頭結點和尾節點。

創建DefaultProcessorSlotChain對象時,首先創建了首節點,然后把首節點賦值給了尾節點,可以用下圖表示:

限流降級神器,帶你解讀阿里巴巴開源 Sentinel 實現原理

將***個節點添加到鏈表中后,整個鏈表的結構變成了如下圖這樣:

限流降級神器,帶你解讀阿里巴巴開源 Sentinel 實現原理

將所有的節點都加入到鏈表中后,整個鏈表的結構變成了如下圖所示:

限流降級神器,帶你解讀阿里巴巴開源 Sentinel 實現原理

這樣就將所有的Slot對象添加到了鏈表中去了,每一個Slot都是繼承自AbstractLinkedProcessorSlot。而AbstractLinkedProcessorSlot是一種責任鏈的設計,每個對象中都有一個next屬性,指向的是另一個AbstractLinkedProcessorSlot對象。其實責任鏈模式在很多框架中都有,比如Netty中是通過pipeline來實現的。

知道了SlotChain是如何創建的了,那接下來就要看下是如何執行Slot的entry方法的了。

執行SlotChain的entry方法

lookProcessChain方法獲得的ProcessorSlotChain的實例是DefaultProcessorSlotChain,那么執行chain.entry方法,就會執行DefaultProcessorSlotChain的entry方法,而DefaultProcessorSlotChain的entry方法是這樣的:

  1. @Override 
  2.  
  3. public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, Object... args) 
  4.  
  5. throws Throwable { 
  6.  
  7. first.transformEntry(context, resourceWrapper, t, count, args); 
  8.  

也就是說,DefaultProcessorSlotChain的entry實際是執行的first屬性的transformEntry方法。

而transformEntry方法會執行當前節點的entry方法,在DefaultProcessorSlotChain中first節點重寫了entry方法,具體如下:

  1. @Override 
  2.  
  3. public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, Object... args) 
  4.  
  5. throws Throwable { 
  6.  
  7. super.fireEntry(context, resourceWrapper, t, count, args); 
  8.  

first節點的entry方法,實際又是執行的super的fireEntry方法,那繼續把目光轉移到fireEntry方法,具體如下:

  1. @Override 
  2.  
  3. public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, Object... args) 
  4.  
  5. throws Throwable { 
  6.  
  7. if (next != null) { 
  8.  
  9. next.transformEntry(context, resourceWrapper, obj, count, args); 
  10.  
  11.  

從這里可以看到,從fireEntry方法中就開始傳遞執行entry了,這里會執行當前節點的下一個節點transformEntry方法,上面已經分析過了,transformEntry方法會觸發當前節點的entry,也就是說fireEntry方法實際是觸發了下一個節點的entry方法。具體的流程如下圖所示:

限流降級神器,帶你解讀阿里巴巴開源 Sentinel 實現原理

從圖中可以看出,從最初的調用Chain的entry()方法,轉變成了調用SlotChain中Slot的entry()方法。從上面的分析可以知道,SlotChain中的***個Slot節點是NodeSelectorSlot。

執行Slot的entry方法

現在可以把目光轉移到SlotChain中的***個節點NodeSelectorSlot的entry方法中去了,具體的代碼如下:

  1. @Override 
  2.  
  3. public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, Object... args) 
  4.  
  5. throws Throwable { 
  6.  
  7. DefaultNode node = map.get(context.getName()); 
  8.  
  9. if (node == null) { 
  10.  
  11. synchronized (this) { 
  12.  
  13. node = map.get(context.getName()); 
  14.  
  15. if (node == null) { 
  16.  
  17. node = Env.nodeBuilder.buildTreeNode(resourceWrapper, null); 
  18.  
  19. HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size()); 
  20.  
  21. cacheMap.putAll(map); 
  22.  
  23. cacheMap.put(context.getName(), node); 
  24.  
  25. map = cacheMap; 
  26.  
  27.  
  28. // Build invocation tree 
  29.  
  30. ((DefaultNode)context.getLastNode()).addChild(node); 
  31.  
  32.  
  33.  
  34. context.setCurNode(node); 
  35.  
  36. // 由此觸發下一個節點的entry方法 
  37.  
  38. fireEntry(context, resourceWrapper, node, count, args); 
  39.  

從代碼中可以看到,NodeSelectorSlot節點做了一些自己的業務邏輯處理,具體的大家可以深入源碼繼續追蹤,這里大概的介紹下每種Slot的功能職責:

  • NodeSelectorSlot 負責收集資源的路徑,并將這些資源的調用路徑,以樹狀結構存儲起來,用于根據調用路徑來限流降級;
  • ClusterBuilderSlot 則用于存儲資源的統計信息以及調用者信息,例如該資源的 RT, QPS, thread count 等等,這些信息將用作為多維度限流,降級的依據;
  • StatistcSlot 則用于記錄,統計不同緯度的 runtime 信息;
  • FlowSlot 則用于根據預設的限流規則,以及前面 slot 統計的狀態,來進行限流;
  • AuthorizationSlot 則根據黑白名單,來做黑白名單控制;
  • DegradeSlot 則通過統計信息,以及預設的規則,來做熔斷降級;
  • SystemSlot 則通過系統的狀態,例如 load1 等,來控制總的入口流量;

執行完業務邏輯處理后,調用了fireEntry()方法,由此觸發了下一個節點的entry方法。此時我們就知道了sentinel的責任鏈就是這樣傳遞的:每個Slot節點執行完自己的業務后,會調用fireEntry來觸發下一個節點的entry方法。

所以可以將上面的圖完整了,具體如下:

限流降級神器,帶你解讀阿里巴巴開源 Sentinel 實現原理

至此就通過SlotChain完成了對每個節點的entry()方法的調用,每個節點會根據創建的規則,進行自己的邏輯處理,當統計的結果達到設置的閾值時,就會觸發限流、降級等事件,具體是拋出BlockException異常。

總結

sentinel主要是基于7種不同的Slot形成了一個鏈表,每個Slot都各司其職,自己做完分內的事之后,會把請求傳遞給下一個Slot,直到在某一個Slot中***規則后拋出BlockException而終止。

前三個Slot負責做統計,后面的Slot負責根據統計的結果結合配置的規則進行具體的控制,是Block該請求還是放行。

控制的類型也有很多可選項:根據qps、線程數、冷啟動等等。

然后基于這個核心的方法,衍生出了很多其他的功能:

  • 1、dashboard控制臺,可以可視化的對每個連接過來的sentinel客戶端 (通過發送heartbeat消息)進行控制,dashboard和客戶端之間通過http協議進行通訊。
  • 2、規則的持久化,通過實現DataSource接口,可以通過不同的方式對配置的規則進行持久化,默認規則是在內存中的
  • 3、對主流的框架進行適配,包括servlet,dubbo,rRpc等

Dashboard控制臺

sentinel-dashboard是一個單獨的應用,通過spring-boot進行啟動,主要提供一個輕量級的控制臺,它提供機器發現、單機資源實時監控、集群資源匯總,以及規則管理的功能。

我們只需要對應用進行簡單的配置,就可以使用這些功能。

1 啟動控制臺

1.1 下載代碼并編譯控制臺

  • 下載 控制臺 工程
  • 使用以下命令將代碼打包成一個 fat jar: mvn cleanpackage

1.2 啟動

使用如下命令啟動編譯后的控制臺:

$ java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -jar target/sentinel-dashboard.jar

上述命令中我們指定了一個JVM參數, -Dserver.port=8080 用于指定 Spring Boot 啟動端口為 8080。

2 客戶端接入控制臺

控制臺啟動后,客戶端需要按照以下步驟接入到控制臺。

2.1 引入客戶端jar包

通過 pom.xml 引入 jar 包:

  1. <dependency> 
  2.  
  3. <groupId>com.alibaba.csp</groupId> 
  4.  
  5. <artifactId>sentinel-transport-simple-http</artifactId> 
  6.  
  7. <version>x.y.z</version> 
  8.  
  9. </dependency> 

2.2 配置啟動參數

啟動時加入 JVM 參數 -Dcsp.sentinel.dashboard.server=consoleIp:port 指定控制臺地址和端口。若啟動多個應用,則需要通過 -Dcsp.sentinel.api.port=xxxx 指定客戶端監控 API 的端口(默認是 8719)。

除了修改 JVM 參數,也可以通過配置文件取得同樣的效果。更詳細的信息可以參考 啟動配置項。

2.3 觸發客戶端初始化

確保客戶端有訪問量,Sentinel 會在客戶端***調用的時候進行初始化,開始向控制臺發送心跳包。

sentinel-dashboard是一個獨立的web應用,可以接受客戶端的連接,然后與客戶端之間進行通訊,他們之間使用http協議進行通訊。他們之間的關系如下圖所示:

限流降級神器,帶你解讀阿里巴巴開源 Sentinel 實現原理

dashboard

dashboard啟動后會等待客戶端的連接,具體的做法是在 MachineRegistryController 中有一個 receiveHeartBeat 的方法,客戶端發送心跳消息,就是通過http請求這個方法。

dashboard接收到客戶端的心跳消息后,會把客戶端的傳遞過來的ip、port等信息封裝成一個 MachineInfo對象,然后將該對象通過 MachineDiscovery 接口的 addMachine 方法添加到一個ConcurrentHashMap中保存起來。

這里會有問題,因為客戶端的信息是保存在dashboard的內存中的,所以當dashboard應用重啟后,之前已經發送過來的客戶端信息都會丟失掉。

client

client在啟動時,會通過CommandCenterInitFunc選擇一個,并且只選擇一個CommandCenter進行啟動。

啟動之前會通過spi的方式掃描獲取到所有的CommandHandler的實現類,然后將所有的CommandHandler注冊到一個HashMap中去,待后期使用。

PS:考慮一下,為什么CommandHandler不需要做持久化,而是直接保存在內存中。

注冊完CommandHandler之后,緊接著就啟動CommandCenter了,目前CommandCenter有兩個實現類:

  • SimpleHttpCommandCenter 通過ServerSocket啟動一個服務端,接受socket連接
  • NettyHttpCommandCenter 通過Netty啟動一個服務端,接受channel連接

CommandCenter啟動后,就等待dashboard發送消息過來了,當接收到消息后,會把消息通過具體的CommandHandler進行處理,然后將處理的結果返回給dashboard。

這里需要注意的是,dashboard給client發送消息是通過異步的httpClient進行發送的,在HttpHelper類中。

但是詭異的是,既然通過異步發送了,又通過一個CountDownLatch來等待消息的返回,然后獲取結果,那這樣不就失去了異步的意義的嗎?具體的代碼如下:

  1. private String httpGetContent(String url) { final HttpGet httpGet = new HttpGet(url); final CountDownLatch latch = new CountDownLatch(1); 
  2. final AtomicReference<String> reference = new AtomicReference<>(); 
  3.  
  4. httpclient.execute(httpGet, new FutureCallback<HttpResponse>() { 
  5.  
  6. @Override 
  7.  
  8. public void completed(final HttpResponse response) { 
  9.  
  10. try { 
  11.  
  12. reference.set(getBody(response)); 
  13.  
  14. catch (Exception e) { 
  15.  
  16. logger.info("httpGetContent " + url + " error:", e); 
  17.  
  18. finally { 
  19.  
  20. latch.countDown(); 
  21.  
  22.  
  23.  
  24. @Override 
  25.  
  26. public void failed(final Exception ex) { 
  27.  
  28. latch.countDown(); 
  29.  
  30. logger.info("httpGetContent " + url + " failed:", ex); 
  31.  
  32.  
  33. @Override 
  34.  
  35. public void cancelled() { 
  36.  
  37. latch.countDown(); 
  38.  
  39.  
  40. }); 
  41.  
  42. try { 
  43.  
  44. latch.await(5, TimeUnit.SECONDS); 
  45.  
  46. catch (Exception e) { 
  47.  
  48. logger.info("wait http client error:", e); 
  49.  
  50.  
  51. return reference.get(); 
  52.  

主流框架的適配

sentinel也對一些主流的框架進行了適配,使得在使用主流框架時,也可以享受到sentinel的保護。目前已經支持的適配器包括以下這些:

  • Web Servlet
  • Dubbo
  • Spring Boot / Spring Cloud
  • gRPC
  • Apache RocketMQ

其實做適配就是通過那些主流框架的擴展點,然后在擴展點上加入sentinel限流降級的代碼即可。拿Servlet的適配代碼看一下,具體的代碼是:

  1. public class CommonFilter implements Filter { 
  2.  
  3. @Override 
  4.  
  5. public void init(FilterConfig filterConfig) { 
  6.  
  7.  
  8. @Override 
  9.  
  10. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
  11.  
  12. throws IOException, ServletException 
  13.  
  14. HttpServletRequest sRequest = (HttpServletRequest)request; 
  15.  
  16. Entry entry = null
  17.  
  18. try { 
  19.  
  20. // 根據請求生成的資源 
  21.  
  22. String target = FilterUtil.filterTarget(sRequest); 
  23.  
  24. target = WebCallbackManager.getUrlCleaner().clean(target); 
  25.  
  26. // “申請”該資源 
  27.  
  28. ContextUtil.enter(target); 
  29.  
  30. entry = SphU.entry(target, EntryType.IN); 
  31.  
  32. // 如果能成功“申請”到資源,則說明未被限流 
  33.  
  34. // 則將請求放行 
  35.  
  36. chain.doFilter(request, response); 
  37.  
  38. catch (BlockException e) { 
  39.  
  40. // 否則如果捕獲了BlockException異常,說明請求被限流了 
  41.  
  42. // 則將請求重定向到一個默認的頁面 
  43.  
  44. HttpServletResponse sResponse = (HttpServletResponse)response; 
  45.  
  46. WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse); 
  47.  
  48. catch (IOException e2) { 
  49.  
  50. // 省略部分代碼 
  51.  
  52. finally { 
  53.  
  54. if (entry != null) { 
  55.  
  56. entry.exit(); 
  57.  
  58.  
  59. ContextUtil.exit(); 
  60.  
  61.  
  62.  
  63. @Override 
  64.  
  65. public void destroy() { 
  66.  
  67.  

通過Servlet的Filter進行擴展,實現一個Filter,然后在doFilter方法中對請求進行限流控制,如果請求被限流則將請求重定向到一個默認頁面,否則將請求放行給下一個Filter。

規則持久化,動態化

Sentinel 的理念是開發者只需要關注資源的定義,當資源定義成功,可以動態增加各種流控降級規則。

Sentinel 提供兩種方式修改規則:

  • 通過 API 直接修改 ( loadRules)
  • 通過 DataSource適配不同數據源修改

通過 API 修改比較直觀,可以通過以下三個 API 修改不同的規則:

FlowRuleManager.loadRules(List<FlowRule> rules); // 修改流控規則

DegradeRuleManager.loadRules(List<DegradeRule> rules); // 修改降級規則

SystemRuleManager.loadRules(List<SystemRule> rules); // 修改系統規則

DataSource 擴展

上述 loadRules() 方法只接受內存態的規則對象,但應用重啟后內存中的規則就會丟失,更多的時候規則***能夠存儲在文件、數據庫或者配置中心中。

DataSource 接口給我們提供了對接任意配置源的能力。相比直接通過 API 修改規則,實現 DataSource 接口是更加可靠的做法。

官方推薦通過控制臺設置規則后將規則推送到統一的規則中心,用戶只需要實現 DataSource 接口,來監聽規則中心的規則變化,以實時獲取變更的規則

DataSource 拓展常見的實現方式有:

  • 拉模式:客戶端主動向某個規則管理中心定期輪詢拉取規則,這個規則中心可以是 SQL、文件,甚至是 VCS 等。這樣做的方式是簡單,缺點是無法及時獲取變更;
  • 推模式:規則中心統一推送,客戶端通過注冊監聽器的方式時刻監聽變化,比如使用 Nacos、Zookeeper 等配置中心。這種方式有更好的實時性和一致性保證。

至此,sentinel的基本情況都已經分析了,更加詳細的內容,可以繼續閱讀源碼來研究。

責任編輯:張燕妮 來源: 頭條科技
相關推薦

2024-09-06 13:53:28

2019-01-28 10:10:36

開源技術 趨勢

2023-04-26 09:16:17

2018-12-14 11:00:18

2010-06-28 10:43:47

2022-08-22 08:07:45

DruidMySQL密碼

2021-03-16 08:31:59

微服務Sentinel雪崩效應

2013-08-22 09:41:52

阿里巴巴去IOE王堅

2019-02-01 11:16:55

阿里巴巴Java開源

2025-05-30 06:48:53

2011-12-28 15:26:16

Spring\Dubb

2022-04-06 08:14:49

云原生混部系統開源

2014-03-17 10:24:22

阿里云物聯網美的

2009-02-27 10:46:32

DBA筆試題阿里巴巴

2023-03-29 09:42:32

2013-08-22 09:36:45

阿里巴巴王堅阿里云

2019-08-15 10:25:02

代碼開發工具

2022-03-21 08:30:13

開源模型訓練預測引擎

2020-11-10 09:00:31

阿里巴巴技術開源

2021-05-14 07:45:07

Sentinel 接口限流
點贊
收藏

51CTO技術棧公眾號

国产欧美自拍视频| 希岛爱理一区二区三区| 亚洲欧洲日韩| 国产在线视频福利| 性8sex亚洲区入口| 午夜精品电影在线观看| 日韩一区二区三区精品| 欧洲视频一区| 日韩欧美亚洲一二三区| 99国产在线观看| 最新中文字幕在线观看| 国产97在线播放| 色影视在线观看| 国产精品国产自产拍高清av| 亚洲欧洲一区二区在线观看| 99久久99久久精品国产片桃花| 久久av在线播放| www.综合网.com| 欧美午夜久久久| 激情 小说 亚洲 图片: 伦| 久久超碰97人人做人人爱| 92看片淫黄大片欧美看国产片| aiss精品大尺度系列| 5月丁香婷婷综合| 中文字幕在线视频观看| 亚洲一区二区三区四区在线免费观看 | 色天天综合狠狠色| 夜夜爽99久久国产综合精品女不卡| 日本成人一区| 日韩欧美中文| 欧美日韩国产一中文字不卡 | 日韩免费一级| 欧美性猛交xxxx富婆| 日韩国产欧美精品| 黄色精品视频| 国产欧美精品一区二区色综合| 亚洲伊人久久综合| 欧美爱爱视频| 日韩高清不卡av| 伊人网站在线| 亚洲美女淫视频| 99热亚洲精品| 免费观看30秒视频久久| 国产偷久久久精品专区| 精品一区二区三区在线| 97色在线观看免费视频| 丰满少妇一区| 亚洲欧洲国产伦综合| 成人免费高清观看| 在线91免费看| 午夜影院免费在线| 午夜私人影院久久久久| 黄色福利视频网站| 欧美激情五月| 国产亚洲日本欧美韩国| 黄色动漫在线| 色噜噜狠狠色综合网图区| 美女高潮久久久| 国内精品久久久久久久影视麻豆 | 91色|porny| 久久久久观看| 欧美亚洲国产怡红院影院| av片在线免费观看| 日韩欧美国产精品| 91精品啪在线观看国产爱臀 | 精品一区二区三区日韩| 国产精品欧美一区二区| 日韩精品成人一区二区在线| 国产主播福利在线| 99精品国产高清一区二区| 亚洲第一福利视频在线| 日韩精品免费一区二区三区竹菊| 超碰网在线观看| 欧美性生交大片免费| 欧美办公室脚交xxxx| 最新国产精品拍自在线播放| 午夜精品成人av| 久久riav二区三区| 亚洲欧美视频一区二区三区| 久久综合av免费| 天堂社区在线视频| 一区二区三区四区在线免费观看| 久久久久久五月天久久久久久久久| 色婷婷一区二区三区四区| 污视频网站在线免费| 日韩在线视频观看正片免费网站| 久久av影视| 久久久久成人精品免费播放动漫| 高清不卡一二三区| 中文字字幕在线中文乱码电影| 欧美日韩精品电影| 欧美a级大片在线| 国产欧美一区二区| 久久成人综合网| 色婷婷亚洲十月十月色天| 69久久99精品久久久久婷婷| 一区二区三区久久精品| 亚洲高清自拍| 99热免费观看| 色婷婷av一区二区三区久久| 久久一二三区| 日韩福利视频一区| 日本午夜一区| 精品嫩草影院| 偷拍25位美女撒尿视频在线观看| 91免费黄视频| 蜜桃成人在线| 欧美精品日韩三级| 国产欧美三级| 日韩av片在线看| 中文字幕日韩av资源站| eeuss在线观看| 一区二区三区精密机械公司| 性直播体位视频在线观看| 精品国产自在精品国产浪潮| 午夜在线精品| 9色在线视频网站| 国产在线精品成人一区二区三区| 成人99免费视频| 久久精品99国产精品日本| 久久国产精品久久w女人spa| 浪潮色综合久久天堂| 久久国产精品 国产精品| 色8久久人人97超碰香蕉987| 99成人在线视频| 国产最顶级的黄色片在线免费观看| 91国内在线视频| 亚洲伦理在线精品| 免费看成人哺乳视频网站| 中文字幕在线第一页| 国产精品久久久久久久久男| 国产精品伦一区二区三级视频| 国产精品电影| 国产xxxx振车| 久久精品中文字幕电影| 久久精品一级爱片| 日韩精品一区二区久久| 国产粉嫩一区二区三区在线观看 | 亚洲综合视频网| 美腿丝袜亚洲图片| 91在线超碰| 欧美日韩精品| 在线成人视屏| 免费成人黄色网| av电影一区| 免费电影日韩网站| 黄瓜视频成人app免费| 免费看男女www网站入口在线| 婷婷五月在线视频| av文字幕在线观看| 啊啊啊久久久| 最近2019中文字幕mv免费看| 国产69精品久久777的优势| 欧美bbbbb| 成人av在线一区二区三区| 97久久久精品综合88久久| 91丝袜美腿高跟国产极品老师| 国产精品自拍三区| 久久综合久久综合亚洲| 国产精品天美传媒沈樵| 污片在线观看一区二区| 欧美理论片在线| 日韩精品一区二区视频| 国产传媒日韩欧美成人| 久久久久久久久久久电影| 精品免费在线观看| 欧美成人精品福利| 最新国产精品拍自在线播放| 亚洲午夜女主播在线直播| 久久精彩免费视频| 国外成人免费在线播放| 91网免费观看| 黄色网zhan| 久久精品午夜福利| 色视频在线观看| 黄色日韩网站| 亚洲国产高清一区二区三区| 波多野结衣中文字幕一区 | 日韩免费毛片视频| 一级毛片国产| 日本成人一区二区| 一本色道久久综合亚洲精品不| 国产精品免费视频网站| 日韩一区二区精品葵司在线| 国产精品久久久久久久7电影| 成人淫片在线看| av磁力番号网| 超碰在线公开免费| 欧美91在线|欧美| 激情综合激情| 亚洲欧洲精品天堂一级 | 日本电影全部在线观看网站视频| 美女高潮在线观看| 国模大胆一区二区三区| 日本一区二区三区久久久久久久久不 | mm1313亚洲国产精品美女| 韩国三级成人在线| 国产一区二区不卡在线| 在线欧美小视频| 国产精品免费一区|