小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Spring Cloud Alibaba | Sentinel: 服務(wù)限流高級篇

 怡紅公子0526 2021-08-08

Springboot: 2.1.6.RELEASE

SpringCloud: Greenwich.SR1

如無特殊說明,本系列文章全采用以上版本

上一篇《Spring Cloud Alibaba | Sentinel: 服務(wù)限流基礎(chǔ)篇》我們介紹了資源和規(guī)則,幾種主流框架的默認(rèn)適配,我們接著聊一下熔斷降級和幾種其他的限流方式。

1. 熔斷降級

除了流量控制以外,對調(diào)用鏈路中不穩(wěn)定的資源進(jìn)行熔斷降級也是保障高可用的重要措施之一。由于調(diào)用關(guān)系的復(fù)雜性,如果調(diào)用鏈路中的某個資源不穩(wěn)定,最終會導(dǎo)致請求發(fā)生堆積。Sentinel 熔斷降級會在調(diào)用鏈路中某個資源出現(xiàn)不穩(wěn)定狀態(tài)時(例如調(diào)用超時或異常比例升高),對這個資源的調(diào)用進(jìn)行限制,讓請求快速失敗,避免影響到其它的資源而導(dǎo)致級聯(lián)錯誤。當(dāng)資源被降級后,在接下來的降級時間窗口之內(nèi),對該資源的調(diào)用都自動熔斷(默認(rèn)行為是拋出 DegradeException)。

1.1 降級策略

我們通常用以下幾種方式來衡量資源是否處于穩(wěn)定的狀態(tài):

  • 平均響應(yīng)時間 (DEGRADE_GRADE_RT):當(dāng) 1s 內(nèi)持續(xù)進(jìn)入 5 個請求,對應(yīng)時刻的平均響應(yīng)時間(秒級)均超過閾值(count,以 ms 為單位),那么在接下的時間窗口(DegradeRule 中的 timeWindow,以 s 為單位)之內(nèi),對這個方法的調(diào)用都會自動地熔斷(拋出 DegradeException)。注意 Sentinel 默認(rèn)統(tǒng)計的 RT 上限是 4900 ms,超出此閾值的都會算作 4900 ms,若需要變更此上限可以通過啟動配置項 -Dcsp.sentinel.statistic.max.rt=xxx 來配置。

  • 異常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):當(dāng)資源的每秒請求量 >= 5,并且每秒異??倲?shù)占通過量的比值超過閾值(DegradeRule 中的 count)之后,資源進(jìn)入降級狀態(tài),即在接下的時間窗口(DegradeRule 中的 timeWindow,以 s 為單位)之內(nèi),對這個方法的調(diào)用都會自動地返回。異常比率的閾值范圍是 [0.0, 1.0],代表 0% - 100%。

  • 異常數(shù) (DEGRADE_GRADE_EXCEPTION_COUNT):當(dāng)資源近 1 分鐘的異常數(shù)目超過閾值之后會進(jìn)行熔斷。注意由于統(tǒng)計時間窗口是分鐘級別的,若 timeWindow 小于 60s,則結(jié)束熔斷狀態(tài)后仍可能再進(jìn)入熔斷狀態(tài)。

注意:異常降級僅針對業(yè)務(wù)異常,對 Sentinel 限流降級本身的異常(BlockException)不生效。為了統(tǒng)計異常比例或異常數(shù),需要通過 Tracer.trace(ex) 記錄業(yè)務(wù)異常。示例:

Entry entry = null;
try {
  entry = SphU.entry(key, EntryType.IN, key);

  // Write your biz code here.
  // <<BIZ CODE>>
} catch (Throwable t) {
  if (!BlockException.isBlockException(t)) {
    Tracer.trace(t);
  }
} finally {
  if (entry != null) {
    entry.exit();
  }
}

開源整合模塊,如 Sentinel Dubbo Adapter, Sentinel Web Servlet Filter 或 @SentinelResource 注解會自動統(tǒng)計業(yè)務(wù)異常,無需手動調(diào)用。

2. 熱點參數(shù)限流

何為熱點?熱點即經(jīng)常訪問的數(shù)據(jù)。很多時候我們希望統(tǒng)計某個熱點數(shù)據(jù)中訪問頻次最高的 Top K 數(shù)據(jù),并對其訪問進(jìn)行限制。比如:

  • 商品 ID 為參數(shù),統(tǒng)計一段時間內(nèi)最常購買的商品 ID 并進(jìn)行限制

  • 用戶 ID 為參數(shù),針對一段時間內(nèi)頻繁訪問的用戶 ID 進(jìn)行限制

熱點參數(shù)限流會統(tǒng)計傳入?yún)?shù)中的熱點參數(shù),并根據(jù)配置的限流閾值與模式,對包含熱點參數(shù)的資源調(diào)用進(jìn)行限流。熱點參數(shù)限流可以看做是一種特殊的流量控制,僅對包含熱點參數(shù)的資源調(diào)用生效。

Sentinel 利用 LRU 策略統(tǒng)計最近最常訪問的熱點參數(shù),結(jié)合令牌桶算法來進(jìn)行參數(shù)級別的流控。

2.1 項目依賴

<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-parameter-flow-control</artifactId>
  <version>x.y.z</version>
</dependency>

然后為對應(yīng)的資源配置熱點參數(shù)限流規(guī)則,并在 entry 的時候傳入相應(yīng)的參數(shù),即可使熱點參數(shù)限流生效。

注:若自行擴展并注冊了自己實現(xiàn)的 SlotChainBuilder,并希望使用熱點參數(shù)限流功能,則可以在 chain 里面合適的地方插入 ParamFlowSlot。

那么如何傳入對應(yīng)的參數(shù)以便 Sentinel 統(tǒng)計呢?我們可以通過 SphU 類里面幾個 entry 重載方法來傳入:

public static Entry entry(String name, EntryType type, int count, Object... args) throws BlockException

public static Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException

其中最后的一串 args 就是要傳入的參數(shù),有多個就按照次序依次傳入。比如要傳入兩個參數(shù) paramAparamB,則可以:

// paramA in index 0, paramB in index 1.
// 若需要配置例外項或者使用集群維度流控,則傳入的參數(shù)只支持基本類型。
SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB);

注意 :若 entry 的時候傳入了熱點參數(shù),那么 exit 的時候也一定要帶上對應(yīng)的參數(shù)(exit(count, args)),否則可能會有統(tǒng)計錯誤。正確的示例:

Entry entry = null;
try {
    entry = SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB);
    // Your logic here.
} catch (BlockException ex) {
    // Handle request rejection.
} finally {
    if (entry != null) {
        entry.exit(1, paramA, paramB);
    }
}

對于 @SentinelResource 注解方式定義的資源,若注解作用的方法上有參數(shù),Sentinel 會將它們作為參數(shù)傳入 SphU.entry(res, args)。比如以下的方法里面 uidtype 會分別作為第一個和第二個參數(shù)傳入 Sentinel API,從而可以用于熱點規(guī)則判斷:

@SentinelResource("myMethod")
public Result doSomething(String uid, int type) {
  // some logic here...
}

2.2 熱點參數(shù)規(guī)則

熱點參數(shù)規(guī)則(ParamFlowRule)類似于流量控制規(guī)則(FlowRule):

屬性說明默認(rèn)值
resource資源名,必填
count限流閾值,必填
grade限流模式QPS 模式
durationInSec統(tǒng)計窗口時間長度(單位為秒),1.6.0 版本開始支持1s
controlBehavior流控效果(支持快速失敗和勻速排隊模式),1.6.0 版本開始支持快速失敗
maxQueueingTimeMs最大排隊等待時長(僅在勻速排隊模式生效),1.6.0 版本開始支持0ms
paramIdx熱點參數(shù)的索引,必填,對應(yīng) SphU.entry(xxx, args) 中的參數(shù)索引位置
paramFlowItemList參數(shù)例外項,可以針對指定的參數(shù)值單獨設(shè)置限流閾值,不受前面 count 閾值的限制。僅支持基本類型
clusterMode是否是集群參數(shù)流控規(guī)則false
clusterConfig集群流控相關(guān)配置

我們可以通過 ParamFlowRuleManagerloadRules 方法更新熱點參數(shù)規(guī)則,下面是一個示例:

ParamFlowRule rule = new ParamFlowRule(resourceName)
    .setParamIdx(0)
    .setCount(5);
// 針對 int 類型的參數(shù) PARAM_B,單獨設(shè)置限流 QPS 閾值為 10,而不是全局的閾值 5.
ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf(PARAM_B))
    .setClassType(int.class.getName())
    .setCount(10);
rule.setParamFlowItemList(Collections.singletonList(item));

ParamFlowRuleManager.loadRules(Collections.singletonList(rule));

3. 系統(tǒng)自適應(yīng)限流

Sentinel 系統(tǒng)自適應(yīng)限流從整體維度對應(yīng)用入口流量進(jìn)行控制,結(jié)合應(yīng)用的 Load、總體平均 RT、入口 QPS 和線程數(shù)等幾個維度的監(jiān)控指標(biāo),讓系統(tǒng)的入口流量和系統(tǒng)的負(fù)載達(dá)到一個平衡,讓系統(tǒng)盡可能跑在最大吞吐量的同時保證系統(tǒng)整體的穩(wěn)定性。

在開始之前,先回顧一下 Sentinel 做系統(tǒng)自適應(yīng)限流的目的:

  • 保證系統(tǒng)不被拖垮

  • 在系統(tǒng)穩(wěn)定的前提下,保持系統(tǒng)的吞吐量

3.1 背景

長期以來,系統(tǒng)自適應(yīng)保護的思路是根據(jù)硬指標(biāo),即系統(tǒng)的負(fù)載 (load1) 來做系統(tǒng)過載保護。當(dāng)系統(tǒng)負(fù)載高于某個閾值,就禁止或者減少流量的進(jìn)入;當(dāng) load 開始好轉(zhuǎn),則恢復(fù)流量的進(jìn)入。這個思路給我們帶來了不可避免的兩個問題:

  • load 是一個“果”,如果根據(jù) load 的情況來調(diào)節(jié)流量的通過率,那么就始終有延遲性。也就意味著通過率的任何調(diào)整,都會過一段時間才能看到效果。當(dāng)前通過率是使 load 惡化的一個動作,那么也至少要過 1 秒之后才能觀測到;同理,如果當(dāng)前通過率調(diào)整是讓 load 好轉(zhuǎn)的一個動作,也需要 1 秒之后才能繼續(xù)調(diào)整,這樣就浪費了系統(tǒng)的處理能力。所以我們看到的曲線,總是會有抖動。

  • 恢復(fù)慢。想象一下這樣的一個場景(真實),出現(xiàn)了這樣一個問題,下游應(yīng)用不可靠,導(dǎo)致應(yīng)用 RT 很高,從而 load 到了一個很高的點。過了一段時間之后下游應(yīng)用恢復(fù)了,應(yīng)用 RT 也相應(yīng)減少。這個時候,其實應(yīng)該大幅度增大流量的通過率;但是由于這個時候 load 仍然很高,通過率的恢復(fù)仍然不高。

TCP BBR 的思想給了我們一個很大的啟發(fā)。我們應(yīng)該根據(jù)系統(tǒng)能夠處理的請求,和允許進(jìn)來的請求,來做平衡,而不是根據(jù)一個間接的指標(biāo)(系統(tǒng) load)來做限流。最終我們追求的目標(biāo)是 在系統(tǒng)不被拖垮的情況下,提高系統(tǒng)的吞吐率,而不是 load 一定要到低于某個閾值。如果我們還是按照固有的思維,超過特定的 load 就禁止流量進(jìn)入,系統(tǒng) load 恢復(fù)就放開流量,這樣做的結(jié)果是無論我們怎么調(diào)參數(shù),調(diào)比例,都是按照果來調(diào)節(jié)因,都無法取得良好的效果。

Sentinel 在系統(tǒng)自適應(yīng)保護的做法是,用 load1 作為啟動控制流量的值,而允許通過的流量由處理請求的能力,即請求的響應(yīng)時間以及當(dāng)前系統(tǒng)正在處理的請求速率來決定。

3.2 系統(tǒng)規(guī)則

系統(tǒng)保護規(guī)則是從應(yīng)用級別的入口流量進(jìn)行控制,從單臺機器的總體 Load、RT、入口 QPS 和線程數(shù)四個維度監(jiān)控應(yīng)用數(shù)據(jù),讓系統(tǒng)盡可能跑在最大吞吐量的同時保證系統(tǒng)整體的穩(wěn)定性。

系統(tǒng)保護規(guī)則是應(yīng)用整體維度的,而不是資源維度的,并且僅對入口流量生效。入口流量指的是進(jìn)入應(yīng)用的流量(EntryType.IN),比如 Web 服務(wù)或 Dubbo 服務(wù)端接收的請求,都屬于入口流量。

系統(tǒng)規(guī)則支持四種閾值類型:

  • Load(僅對 Linux/Unix-like 機器生效):當(dāng)系統(tǒng) load1 超過閾值,且系統(tǒng)當(dāng)前的并發(fā)線程數(shù)超過系統(tǒng)容量時才會觸發(fā)系統(tǒng)保護。系統(tǒng)容量由系統(tǒng)的 maxQps * minRt 計算得出。設(shè)定參考值一般是 CPU cores * 2.5。

  • RT:當(dāng)單臺機器上所有入口流量的平均 RT 達(dá)到閾值即觸發(fā)系統(tǒng)保護,單位是毫秒。

  • 線程數(shù):當(dāng)單臺機器上所有入口流量的并發(fā)線程數(shù)達(dá)到閾值即觸發(fā)系統(tǒng)保護。

  • 入口 QPS:當(dāng)單臺機器上所有入口流量的 QPS 達(dá)到閾值即觸發(fā)系統(tǒng)保護。

3.3 原理

先用經(jīng)典圖來鎮(zhèn)樓:

我們把系統(tǒng)處理請求的過程想象為一個水管,到來的請求是往這個水管灌水,當(dāng)系統(tǒng)處理順暢的時候,請求不需要排隊,直接從水管中穿過,這個請求的RT是最短的;反之,當(dāng)請求堆積的時候,那么處理請求的時間則會變?yōu)椋号抨爼r間 + 最短處理時間。

  • 推論一: 如果我們能夠保證水管里的水量,能夠讓水順暢的流動,則不會增加排隊的請求;也就是說,這個時候的系統(tǒng)負(fù)載不會進(jìn)一步惡化。

我們用 T 來表示(水管內(nèi)部的水量),用RT來表示請求的處理時間,用P來表示進(jìn)來的請求數(shù),那么一個請求從進(jìn)入水管道到從水管出來,這個水管會存在 P * RT 個請求。換一句話來說,當(dāng) T ≈ QPS * Avg(RT) 的時候,我們可以認(rèn)為系統(tǒng)的處理能力和允許進(jìn)入的請求個數(shù)達(dá)到了平衡,系統(tǒng)的負(fù)載不會進(jìn)一步惡化。

接下來的問題是,水管的水位是可以達(dá)到了一個平衡點,但是這個平衡點只能保證水管的水位不再繼續(xù)增高,但是還面臨一個問題,就是在達(dá)到平衡點之前,這個水管里已經(jīng)堆積了多少水。如果之前水管的水已經(jīng)在一個量級了,那么這個時候系統(tǒng)允許通過的水量可能只能緩慢通過,RT會大,之前堆積在水管里的水會滯留;反之,如果之前的水管水位偏低,那么又會浪費了系統(tǒng)的處理能力。

  • 推論二: 當(dāng)保持入口的流量是水管出來的流量的最大的值的時候,可以最大利用水管的處理能力。

然而,和 TCP BBR 的不一樣的地方在于,還需要用一個系統(tǒng)負(fù)載的值(load1)來激發(fā)這套機制啟動。

注:這種系統(tǒng)自適應(yīng)算法對于低 load 的請求,它的效果是一個“兜底”的角色。對于不是應(yīng)用本身造成的 load 高的情況(如其它進(jìn)程導(dǎo)致的不穩(wěn)定的情況),效果不明顯。

3.4 示例

public class SystemGuardDemo {

    private static AtomicInteger pass = new AtomicInteger();
    private static AtomicInteger block = new AtomicInteger();
    private static AtomicInteger total = new AtomicInteger();

    private static volatile boolean stop = false;
    private static final int threadCount = 100;

    private static int seconds = 60 + 40;

    public static void main(String[] args) throws Exception {

        tick();
        initSystemRule();

        for (int i = 0; i < threadCount; i++) {
            Thread entryThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        Entry entry = null;
                        try {
                            entry = SphU.entry("methodA", EntryType.IN);
                            pass.incrementAndGet();
                            try {
                                TimeUnit.MILLISECONDS.sleep(20);
                            } catch (InterruptedException e) {
                                // ignore
                            }
                        } catch (BlockException e1) {
                            block.incrementAndGet();
                            try {
                                TimeUnit.MILLISECONDS.sleep(20);
                            } catch (InterruptedException e) {
                                // ignore
                            }
                        } catch (Exception e2) {
                            // biz exception
                        } finally {
                            total.incrementAndGet();
                            if (entry != null) {
                                entry.exit();
                            }
                        }
                    }
                }

            });
            entryThread.setName("working-thread");
            entryThread.start();
        }
    }

    private static void initSystemRule() {
        List<SystemRule> rules = new ArrayList<SystemRule>();
        SystemRule rule = new SystemRule();
        // max load is 3
        rule.setHighestSystemLoad(3.0);
        // max cpu usage is 60%
        rule.setHighestCpuUsage(0.6);
        // max avg rt of all request is 10 ms
        rule.setAvgRt(10);
        // max total qps is 20
        rule.setQps(20);
        // max parallel working thread is 10
        rule.setMaxThread(10);

        rules.add(rule);
        SystemRuleManager.loadRules(Collections.singletonList(rule));
    }

    private static void tick() {
        Thread timer = new Thread(new TimerTask());
        timer.setName("sentinel-timer-task");
        timer.start();
    }

    static class TimerTask implements Runnable {
        @Override
        public void run() {
            System.out.println("begin to statistic!!!");
            long oldTotal = 0;
            long oldPass = 0;
            long oldBlock = 0;
            while (!stop) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                }
                long globalTotal = total.get();
                long oneSecondTotal = globalTotal - oldTotal;
                oldTotal = globalTotal;

                long globalPass = pass.get();
                long oneSecondPass = globalPass - oldPass;
                oldPass = globalPass;

                long globalBlock = block.get();
                long oneSecondBlock = globalBlock - oldBlock;
                oldBlock = globalBlock;

                System.out.println(seconds + ", " + TimeUtil.currentTimeMillis() + ", total:"
                    + oneSecondTotal + ", pass:"
                    + oneSecondPass + ", block:" + oneSecondBlock);
                if (seconds-- <= 0) {
                    stop = true;
                }
            }
            System.exit(0);
        }
    }
}

4. 黑白名單控制

很多時候,我們需要根據(jù)調(diào)用方來限制資源是否通過,這時候可以使用 Sentinel 的黑白名單控制的功能。黑白名單根據(jù)資源的請求來源(origin)限制資源是否通過,若配置白名單則只有請求來源位于白名單內(nèi)時才可通過;若配置黑名單則請求來源位于黑名單時不通過,其余的請求通過。

調(diào)用方信息通過 ContextUtil.enter(resourceName, origin) 方法中的 origin 參數(shù)傳入。

4.1 規(guī)則配置

黑白名單規(guī)則(AuthorityRule)非常簡單,主要有以下配置項:

  • resource:資源名,即限流規(guī)則的作用對象

  • limitApp:對應(yīng)的黑名單/白名單,不同 origin 用 , 分隔,如 appA,appB

  • strategy:限制模式,AUTHORITY_WHITE 為白名單模式,AUTHORITY_BLACK 為黑名單模式,默認(rèn)為白名單模式

4.2 示例

比如我們希望控制對資源 test 的訪問設(shè)置白名單,只有來源為 appA 和 appB 的請求才可通過,則可以配置如下白名單規(guī)則:

AuthorityRule rule = new AuthorityRule();
rule.setResource("test");
rule.setStrategy(RuleConstant.AUTHORITY_WHITE);
rule.setLimitApp("appA,appB");
AuthorityRuleManager.loadRules(Collections.singletonList(rule));

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多