Alibaba Sentinel限流功能

前言

上周經(jīng)歷了合作方未按照約定在客戶端進(jìn)行緩存,以高QPS調(diào)用我這邊某個(gè)接口的問題,當(dāng)時(shí)帶來(lái)的影響是接口RT變高,當(dāng)時(shí)如果QPS繼續(xù)增加,將會(huì)導(dǎo)致整個(gè)應(yīng)用級(jí)別的服務(wù)不可用。那么有沒有辦法,來(lái)限制系統(tǒng)的某個(gè)服務(wù)被調(diào)用的QPS,以保護(hù)系統(tǒng)不會(huì)過載呢?Alibaba Sentinel就是這樣的一個(gè)產(chǎn)品。本文只介紹限流的功能,Sentinel本身是極其強(qiáng)大的,支持流量控制、熔斷降級(jí)、系統(tǒng)負(fù)載保護(hù),詳見官方文檔。https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

Sentinel如何使用

引入maven依賴

<dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-annotation-aspectj</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-transport-simple-http</artifactId>
        </dependency>
  1. 若希望在代碼塊級(jí)別限流,使用SphU#entry和Entry#exit將代碼塊包住即可,這跟很多打點(diǎn)的工具是一樣的。
Entry entry = SphU.entry(resourceName);
businessCode();
entry.exit();

初始化流控規(guī)則配置見下,流控規(guī)則里面的resourceName和Entry初始化的時(shí)候一致即可。(Sentinel也支持控制臺(tái)的方式來(lái)配置限流規(guī)則)

private static void initFlowQpsRule() {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule1 = new FlowRule();
        rule1.setResource(resourceName);
        // set limit qps to 20
        rule1.setCount(20);
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
    }

這種在代碼塊級(jí)別硬編碼的方式并不是我們的主要場(chǎng)景,更多的時(shí)候,我們希望在某個(gè)服務(wù),或者某個(gè)接口級(jí)別進(jìn)行限流,Sentinel支持注解的方式來(lái)配置限流。

@GetMapping("/hello")
    @SentinelResource("resourceName")
    public String hello() {
        return "Hello";
    }

只需要一個(gè)注解,即可添加限流功能。

Sentinel執(zhí)行過程

使用注解來(lái)引入限流,其實(shí)就是使用了一個(gè)aop切面自動(dòng)幫你初始化一個(gè)Entry,執(zhí)行完畢之后exit。
詳見SentinelResourceAspect

@Around("sentinelResourceAnnotationPointcut()")
    public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
        Method originMethod = resolveMethod(pjp);

        SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
        if (annotation == null) {
            // Should not go through here.
            throw new IllegalStateException("Wrong state for SentinelResource annotation");
        }
        String resourceName = getResourceName(annotation.value(), originMethod);
        EntryType entryType = annotation.entryType();
        int resourceType = annotation.resourceType();
        Entry entry = null;
        try {
            entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
            Object result = pjp.proceed();
            return result;
        } catch (BlockException ex) {
            return handleBlockException(pjp, annotation, ex);
        }finally {
            if (entry != null) {
                entry.exit(1, pjp.getArgs());
            }
        }

整個(gè)限流的核心就在SphU#entry方法,如果被限流攔截,就拋出異常BlockException,不再執(zhí)行業(yè)務(wù)代碼。
如果讓我們自己實(shí)現(xiàn)一套針對(duì)服務(wù)的限流邏輯,會(huì)有兩個(gè)關(guān)鍵點(diǎn)需要考慮,一個(gè)點(diǎn)是,請(qǐng)求進(jìn)來(lái)了,需要去檢查當(dāng)前服務(wù)qps,判斷是否需要進(jìn)行攔截;另一個(gè)點(diǎn)是統(tǒng)計(jì)當(dāng)前服務(wù)的QPS,每處理一個(gè)請(qǐng)求,去更新當(dāng)前服務(wù)QPS值。
SphU#entry方法主要就是做這兩個(gè)事情。Sentinel對(duì)每一個(gè)限流的Resouce維護(hù)了一個(gè)基于滑動(dòng)窗口的計(jì)數(shù)器rollingCounterInSecond。

public class StatisticNode implements Node {
    private transient volatile Metric rollingCounterInSecond;
}

請(qǐng)求進(jìn)來(lái)之后,先進(jìn)行canPassCheck,判斷是否攔截,判斷的邏輯
curCount 為當(dāng)前qps,通過滑動(dòng)窗口計(jì)數(shù)器rollingCounterInSecond計(jì)算得出
acquireCount 為請(qǐng)求個(gè)數(shù),這個(gè)數(shù)寫死的是1
this.count為限流配置
如果curCount+1>this.count則返回false,進(jìn)行攔截,然后拋出FlowException。
否則通過,去更新計(jì)數(shù)器rollingCounterInSecond

public boolean canPass(Node node, int acquireCount, boolean prioritized) {
        int curCount = this.avgUsedTokens(node);
        if ((double)(curCount + acquireCount) > this.count) {
            if (prioritized && this.grade == 1) {
                long currentTime = TimeUtil.currentTimeMillis();
                long waitInMs = node.tryOccupyNext(currentTime, acquireCount, this.count);
                if (waitInMs < (long)OccupyTimeoutProperty.getOccupyTimeout()) {
                    node.addWaitingRequest(currentTime + waitInMs, acquireCount);
                    node.addOccupiedPass(acquireCount);
                    this.sleep(waitInMs);
                    throw new PriorityWaitException(waitInMs);
                }
            }

            return false;
        } else {
            return true;
        }
    }

總結(jié)

Sentinel限流的本質(zhì)是為每個(gè)resource(限流單元)維護(hù)一個(gè)基于滑動(dòng)窗口的計(jì)數(shù)器,當(dāng)請(qǐng)求進(jìn)來(lái),先檢查計(jì)數(shù)器,校驗(yàn)是否需要攔截,通過后,更新這個(gè)計(jì)數(shù)器。限流功能可以保證我們的接口在一個(gè)可控的負(fù)載范圍內(nèi)。不至于因?yàn)槟骋粋€(gè)接口的過載導(dǎo)致整個(gè)應(yīng)用級(jí)別的不可用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容