重慶分公司,新征程啟航
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊(cè)、服務(wù)器等服務(wù)
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊(cè)、服務(wù)器等服務(wù)
這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)怎么深入研究阿里sentinel源碼,文章內(nèi)容豐富且以專(zhuān)業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
十多年的鎮(zhèn)賚網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開(kāi)發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。全網(wǎng)營(yíng)銷(xiāo)推廣的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整鎮(zhèn)賚建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無(wú)論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。成都創(chuàng)新互聯(lián)公司從事“鎮(zhèn)賚網(wǎng)站設(shè)計(jì)”,“鎮(zhèn)賚網(wǎng)站推廣”以來(lái),每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
根據(jù)我目前的實(shí)踐,限流和降級(jí)規(guī)則似乎不能一同起效,還不知道原因,下面繼續(xù)探索
首先客戶端而言,我關(guān)注的是我寫(xiě)的代碼SphU.entry
,這明顯是很關(guān)鍵的方法,下圖的內(nèi)容就是這里構(gòu)建的 -Sentinel工作主流程就包含在上面一個(gè)方法里,通過(guò)鏈?zhǔn)秸{(diào)用的方式,經(jīng)過(guò)了建立樹(shù)狀結(jié)構(gòu),保存統(tǒng)計(jì)簇點(diǎn),異常日志記錄,實(shí)時(shí)數(shù)據(jù)統(tǒng)計(jì),負(fù)載保護(hù),權(quán)限認(rèn)證,流量控制,熔斷降級(jí)等Slot
進(jìn)入鏈?zhǔn)椒椒ǖ娜肟跒镃tSph類(lèi),try方法大括號(hào)內(nèi)
Entry e = new CtEntry(resourceWrapper, chain, context); try { chain.entry(context, resourceWrapper, null, count, prioritized, args); } catch (BlockException e1) { e.exit(count, args); throw e1; } catch (Throwable e1) { // This should not happen, unless there are errors existing in Sentinel internal. RecordLog.info("Sentinel unexpected exception", e1); }
看sentinel-transport-simple-http包中的HttpEventTask
類(lèi),它開(kāi)啟了一個(gè)線程,轉(zhuǎn)么用來(lái)做為socket連接,控制臺(tái)通過(guò)socket請(qǐng)求通知客戶端,從而更新客戶端規(guī)則,更改規(guī)則核心代碼如下
// Find the matching command handler. CommandHandler> commandHandler = SimpleHttpCommandCenter.getHandler(commandName); if (commandHandler != null) { CommandResponse> response = commandHandler.handle(request); handleResponse(response, printWriter, outputStream); } else { // No matching command handler. badRequest(printWriter, "Unknown command `" + commandName + '`'); }
通過(guò)命令模式,commandName為setRules時(shí),更新規(guī)則
帶著這個(gè)疑問(wèn),我本想在issues里找下,突然發(fā)現(xiàn)它的源碼中有個(gè)sentinel-transport-netty-http
這個(gè)包和sentinel-transport-simple-http
處于同級(jí),官方的例子用的simple-http,但明顯它也準(zhǔn)備了netty-http,于是我替換成了netty-http,運(yùn)行后效果和原先一樣,至于效率上有沒(méi)有提升,我就不清楚了^_^
該規(guī)則檢查類(lèi)為FlowRuleChecker
,在core核心包中,核心檢查方法如下
private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, boolean prioritized) { Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node); if (selectedNode == null) { return true; } return rule.getRater().canPass(selectedNode, acquireCount, prioritized); }
判斷類(lèi)為DegradeRuleManager
,在core核心包,核心內(nèi)容如下,再深入就是它判斷的算法了,感興趣的自己去看如下的passCheck
public static void checkDegrade(ResourceWrapper resource, Context context, DefaultNode node, int count) throws BlockException { Setrules = degradeRules.get(resource.getName()); if (rules == null) { return; } for (DegradeRule rule : rules) { if (!rule.passCheck(context, node, count)) { throw new DegradeException(rule.getLimitApp(), rule); } } }
核心類(lèi)為DefaultSlotChainBuilder
,構(gòu)建了如下的slot
public class DefaultSlotChainBuilder implements SlotChainBuilder { @Override 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; } }
發(fā)現(xiàn)類(lèi)SlotChainProvider
中的構(gòu)建方法如下
private static void resolveSlotChainBuilder() { Listlist = new ArrayList (); boolean hasOther = false; for (SlotChainBuilder builder : LOADER) { if (builder.getClass() != DefaultSlotChainBuilder.class) { hasOther = true; list.add(builder); } } if (hasOther) { builder = list.get(0); } else { // No custom builder, using default. builder = new DefaultSlotChainBuilder(); } RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: " + builder.getClass().getCanonicalName()); }
也就是說(shuō),我們?nèi)绻?code>LOADER中加入了其他的非默認(rèn)實(shí)現(xiàn)就可以替代原來(lái)的DefaultSlotChainBuilder
,那LOADER
怎么來(lái)的?看代碼,如下的全局變量,也就是需要自定義實(shí)現(xiàn)SlotChainBuilder
接口的實(shí)現(xiàn)類(lèi)
private static final ServiceLoaderLOADER = ServiceLoader.load(SlotChainBuilder.class);
這里要注意的是它使用了ServiceLoader
,也就是SPI
,全稱(chēng)Service Provider Interface
,加載它需要特定的配合,比如我自定義實(shí)現(xiàn)一個(gè)Slot
/** * @author laoliangliang * @date 2019/7/25 14:13 */ public class MySlotChainBuilder implements SlotChainBuilder { @Override 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()); //自定義的 chain.addLast(new CarerSlot()); return chain; } }
/** * @author laoliangliang * @date 2019/7/25 14:15 */ @Slf4j public class CarerSlot extends AbstractLinkedProcessorSlot{ @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { log.info(JSON.toJSONString(resourceWrapper)); fireEntry(context, resourceWrapper, node, count, prioritized, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } }
這里我自定義了CarerSlot
,那是否能被加載到呢?事實(shí)上還不夠,需要在META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder
建這樣一個(gè)文件,內(nèi)容如下
好了,這樣配置過(guò)后,它就能讀到我們自定義的實(shí)現(xiàn)類(lèi)代替它原先的類(lèi)了
用過(guò)sentinel的都會(huì)感受到,只有當(dāng)有第一個(gè)sentinel監(jiān)控的請(qǐng)求過(guò)來(lái)時(shí),sentinel客戶端才會(huì)正式初始化,這樣看來(lái),這個(gè)初始化步驟應(yīng)該在哪呢?
我通過(guò)不斷反向跟蹤上述的命令模式最初的初始化,找到了最初初始化的地方如下
public class Env { public static final Sph sph = new CtSph(); static { // If init fails, the process will exit. InitExecutor.doInit(); } }
有沒(méi)有覺(jué)得很熟悉?doInit就是很多初始化的起點(diǎn),當(dāng)Env被調(diào)用時(shí)會(huì)運(yùn)行static代碼塊,那么只有可能是sph被調(diào)用時(shí)
只要你debug過(guò)我上述第一條SphU.entry
的源碼,就會(huì)發(fā)現(xiàn),如下,該方法一進(jìn)入不就是先獲取Env的sph,再調(diào)用的entry嗎,所以初始化的地方也就找到了,第一次調(diào)用SphU.entry
的地方,或者你不用這個(gè),使用的注解,里面同樣有這個(gè)方法
public static Entry entry(String name) throws BlockException { return Env.sph.entry(name, EntryType.OUT, 1, OBJECTS0); }
這個(gè)其實(shí)是比較容易理解的,既然通過(guò)SphU.entry
包裹可以實(shí)現(xiàn)熔斷降級(jí),通過(guò)注解的形式包裹代碼方法應(yīng)該是比較容易的,那么在哪里實(shí)現(xiàn)和配置的呢
看過(guò)我前一篇文章的應(yīng)該看到了,有存在如下配置
@Bean public SentinelResourceAspect sentinelResourceAspect() { pushlish(); return new SentinelResourceAspect(); }
很明顯的注解切面,通過(guò)spring注解的形式注入,我覺(jué)得這還是比較優(yōu)雅的注入方式了,點(diǎn)進(jìn)入就可以看到如下
@Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)") public void sentinelResourceAnnotationPointcut() { }
對(duì)@SentinelResource
注解進(jìn)行了處理
這個(gè)很好理解,qps超過(guò)設(shè)置的值,直接失敗
這個(gè)似乎看字面意思很好理解,但是一旦你點(diǎn)了這個(gè)選項(xiàng),下面還有個(gè)參數(shù)的
所以這個(gè)排隊(duì)等待是有超時(shí)時(shí)間的,達(dá)到峰值后勻速通過(guò),采用的漏桶算法,流控圖
以下是核心算法,Warm Up
模式不看算法細(xì)節(jié),看它的中文說(shuō)明應(yīng)該就能理解是怎么回事了吧;所謂慢啟動(dòng)模式,要求系統(tǒng)的QPS請(qǐng)求增速不能超過(guò)一定的速率,否則會(huì)被壓制超過(guò)部分請(qǐng)求失敗,應(yīng)該是為了避免一啟動(dòng)就有大流量的請(qǐng)求進(jìn)入導(dǎo)致系統(tǒng)一下子就宕機(jī)卡主或直接進(jìn)入了熔斷
@Override public boolean canPass(Node node, int acquireCount, boolean prioritized) { long passQps = (long) node.passQps(); long previousQps = (long) node.previousPassQps(); syncToken(previousQps); // 開(kāi)始計(jì)算它的斜率 // 如果進(jìn)入了警戒線,開(kāi)始調(diào)整他的qps long restToken = storedTokens.get(); if (restToken >= warningToken) { long aboveToken = restToken - warningToken; // 消耗的速度要比warning快,但是要比慢 // current interval = restToken*slope+1/count double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count)); if (passQps + acquireCount <= warningQps) { return true; } } else { if (passQps + acquireCount <= count) { return true; } } return false; }
配置如下時(shí),測(cè)試流控
流控圖
你會(huì)發(fā)現(xiàn)直接失敗和排隊(duì)等待的區(qū)別在流控圖上并不明顯,那差別在哪呢?我重慶給個(gè)請(qǐng)求參數(shù),5秒內(nèi)模擬100個(gè)人輪流請(qǐng)求10次
sentinel控制臺(tái)設(shè)置
流控圖
總結(jié):我設(shè)置了超時(shí)時(shí)間是5秒,而100個(gè)線程10次輪詢也就是1000個(gè)請(qǐng)求,可以看出,它并不是一定要在5秒內(nèi)解決這些請(qǐng)求,有了延時(shí)后,代表只要響應(yīng)時(shí)間在5秒以內(nèi),不管多少請(qǐng)求都不會(huì)拒絕;
幾個(gè)模式有利有弊,默認(rèn)的快速失敗使我們可以最大程度的控制系統(tǒng)的QPS,避免造成系統(tǒng)壓力過(guò)大,但同時(shí)可能造成用于的體驗(yàn)效果變差
慢啟動(dòng)上面說(shuō)過(guò)了
排隊(duì)等待在設(shè)置合理的超時(shí)時(shí)間后可以最大程度的避免求情的失敗,但同時(shí)可能造成線程壓力過(guò)大
綜上,在我看來(lái)排隊(duì)等待模式是比較適合線上運(yùn)行的,只是需要設(shè)置合理的超時(shí)時(shí)間,大公司機(jī)器不愁那就設(shè)小點(diǎn),業(yè)界一般標(biāo)準(zhǔn)是200ms用戶無(wú)感知,中小型可以設(shè)500ms甚至更大,看機(jī)器情況動(dòng)態(tài)調(diào)整了
像我是用apollo來(lái)持久化規(guī)則的,你也可以用nacos,redis,zookeeper等,當(dāng)控制臺(tái)未啟動(dòng)時(shí),你啟動(dòng)客戶端規(guī)則也會(huì)生效,只是沒(méi)了控制臺(tái)實(shí)時(shí)監(jiān)控?cái)?shù)據(jù)。
上述就是小編為大家分享的怎么深入研究阿里sentinel源碼了,如果剛好有類(lèi)似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。