溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

架構(gòu)師成長(zhǎng)之路之限流漫談

發(fā)布時(shí)間:2020-08-09 01:37:20 來(lái)源:ITPUB博客 閱讀:161 作者:孤獨(dú)鍵客 欄目:編程語(yǔ)言

閱讀本文大概需要 7.3 分鐘。

1. 我們?yōu)槭裁葱枰蘖?

在上一篇架構(gòu)師成長(zhǎng)之路之服務(wù)治理漫談里面,我們已經(jīng)談到了高可用治理的部分。為了“反脆弱”,在微服務(wù)復(fù)雜拓?fù)涞那闆r下,限流是保障服務(wù)彈性和拓?fù)浣训闹刂兄亍?

想一想,如果業(yè)務(wù)推出了一個(gè)秒殺活動(dòng),而你沒(méi)有任何的限流措施;當(dāng)你搭建了一個(gè)賬號(hào)平臺(tái),而完全沒(méi)有對(duì)十幾個(gè)業(yè)務(wù)方設(shè)定流量配額……這些很有可能在特定場(chǎng)合下給你的產(chǎn)品帶來(lái)大量的業(yè)務(wù)損失和口碑影響。

我們通常重點(diǎn)關(guān)注產(chǎn)品業(yè)務(wù)層面正向和逆向功能的完成,而對(duì)于逆向技術(shù)保障,這一點(diǎn)則是企業(yè)發(fā)展過(guò)程中很容易忽視的,所以一旦業(yè)務(wù)快速增長(zhǎng),這將給你的產(chǎn)品帶來(lái)很大的隱患。

當(dāng)然,也不是所有的系統(tǒng)都需要限流,這取決于架構(gòu)師對(duì)于當(dāng)前業(yè)務(wù)發(fā)展的預(yù)判。

2. 我們常見(jiàn)的限流手段

我們來(lái)列舉業(yè)內(nèi)比較常見(jiàn)的一些限流手段。

2.1 信號(hào)量計(jì)數(shù)

信號(hào)量競(jìng)爭(zhēng)是用來(lái)控制并發(fā)的一個(gè)常見(jiàn)手段。比如 C 和 Java 中都有 Semaphore 的實(shí)現(xiàn)可以讓你方便地上手。鼎鼎大名的彈性框架 Hystrix 也默認(rèn)選擇了信號(hào)量來(lái)作為隔離和控制并發(fā)的辦法。它的優(yōu)點(diǎn)即在于簡(jiǎn)單可靠,但是只能在單機(jī)環(huán)境中使用。

2.2 線程池隔離

隔離艙技術(shù)中也大量使用了線程池隔離的方式來(lái)實(shí)現(xiàn),通過(guò)限制使用的線程數(shù)來(lái)對(duì)流量進(jìn)行限制,一般會(huì)用阻塞隊(duì)列配合線程池來(lái)實(shí)現(xiàn)。如果線程池和隊(duì)列都被打滿,可以設(shè)計(jì)對(duì)應(yīng)拒絕策略。需要謹(jǐn)慎調(diào)整其參數(shù)和線程池隔離的個(gè)數(shù),以避免線程過(guò)多導(dǎo)致上下文切換帶來(lái)的高昂成本。也是基于這個(gè)考慮,Hystrix 默認(rèn)采用了信號(hào)量計(jì)數(shù)的方式來(lái)控制并發(fā)。同樣,其也只能在單機(jī)環(huán)境中使用。

2.3 固定窗口計(jì)數(shù)

我們可以以第一次請(qǐng)求訪問(wèn)的時(shí)候開(kāi)始進(jìn)行計(jì)數(shù),而不嚴(yán)格按照自然時(shí)間來(lái)計(jì)數(shù)。比如可以利用 Redis 的 INCR 和 EXPIRE 組合進(jìn)行計(jì)數(shù),如下偽代碼所示:

count = redis.incrby(key)if count == 1    redis.expire(key,3600)if count >= threshold    println("exceed...")

這種實(shí)現(xiàn)方式簡(jiǎn)單粗暴,可以解決絕大部分分布式限流的問(wèn)題。但是其存在的問(wèn)題是:

  1. 該計(jì)數(shù)方式并不是準(zhǔn)確計(jì)數(shù),由于時(shí)間窗口一旦過(guò)期,則之前積累的數(shù)據(jù)就失效,這樣可能導(dǎo)致比如本來(lái)希望限制“一分鐘內(nèi)訪問(wèn)不能超過(guò) 100 次”,但實(shí)際上做不到精準(zhǔn)的限制,會(huì)存在誤判放過(guò)本應(yīng)拒絕的流量。

  2. 每次請(qǐng)求都將訪問(wèn)一次 Redis,可能存在大流量并發(fā)時(shí)候?qū)⒕彺娲虮雷罱K拖垮業(yè)務(wù)應(yīng)用的問(wèn)題。這個(gè)在高并發(fā)場(chǎng)景中是非常嚴(yán)重的問(wèn)題。當(dāng)然,你可以選擇按照業(yè)務(wù)進(jìn)行適當(dāng)?shù)木彺婕呵懈顏?lái)緩解這種問(wèn)題,但是這仍然是治標(biāo)不治本。當(dāng)然,如果你選擇單機(jī)限流的實(shí)現(xiàn)方式,則無(wú)需使用 Redis,進(jìn)一步,單機(jī)限流情況下該問(wèn)題不存在。

2.4 自然窗口計(jì)數(shù)

有的場(chǎng)景會(huì)需要以自然窗口為維度進(jìn)行限制,實(shí)現(xiàn)方式即進(jìn)行分桶計(jì)數(shù)。每個(gè) slot 一般以時(shí)間戳為 key salt,以單 slot 時(shí)間長(zhǎng)度內(nèi)的計(jì)數(shù)值為 Value,我們可以根據(jù)實(shí)際的需求對(duì)單 slot 的時(shí)間長(zhǎng)度進(jìn)行限制,比如如果你需要限制一天發(fā)送短信數(shù)不超限,則以 1 個(gè)自然天為 1 個(gè) slot,如果希望限制 QPS,則以 1s 為 1 個(gè) slot。然后起定時(shí)任務(wù)獲取 slot,進(jìn)一步取出實(shí)際的分桶計(jì)算結(jié)果,進(jìn)行判斷是否達(dá)到閾值,如果超過(guò)閾值則執(zhí)行對(duì)應(yīng)的限制操作。

該策略如果應(yīng)用在分布式限流環(huán)境下,則會(huì)碰到若干個(gè)問(wèn)題。這個(gè)后面章節(jié)中會(huì)提到。另外,該策略本質(zhì)上其實(shí)是也是一種特殊的固定窗口計(jì)數(shù)策略,那么固定窗口所存在的弊端,自然窗口計(jì)數(shù)也會(huì)存在。那么我們不禁會(huì)問(wèn),如果希望規(guī)避固定窗口的一大問(wèn)題——“無(wú)法準(zhǔn)確計(jì)數(shù)”的話,要怎么做呢?這時(shí), “滑動(dòng)窗口計(jì)數(shù)” 方式應(yīng)運(yùn)而生。

2.5 滑動(dòng)窗口計(jì)數(shù)

滑動(dòng)窗口的出現(xiàn),可以很好地解決精準(zhǔn)計(jì)數(shù)的問(wèn)題。隨著時(shí)間窗口不斷地滑動(dòng),動(dòng)態(tài)地進(jìn)行計(jì)數(shù)判斷??梢砸?guī)避自然窗口和固定窗口計(jì)數(shù)所存在的計(jì)數(shù)不準(zhǔn)確的問(wèn)題。以下有兩種常見(jiàn)的滑動(dòng)窗口計(jì)數(shù)的實(shí)現(xiàn)類別。

2.5.1 基于共享分布式內(nèi)存

可以采用 Redis ZSet,存儲(chǔ)結(jié)構(gòu)如下圖所示。Key 為功能 ID,Value 為 UUID,Score 也記為同一時(shí)間戳。整個(gè)過(guò)程簡(jiǎn)單概括為“添加記錄、設(shè)置失效時(shí)間、計(jì)數(shù)、刪除過(guò)期記錄”四部分。使用 ZADD、EXPIRE、ZCOUNT 和 zremrangeScore 來(lái)實(shí)現(xiàn),并同時(shí)注意開(kāi)啟 Pipeline 來(lái)盡可能提升性能。

架構(gòu)師成長(zhǎng)之路之限流漫談cdn.xitu.io/2019/5/17/16ac3ae28dd1eb6d?w=938&h=260&f=png&s=10048">

偽代碼如下:

// 開(kāi)啟pipepipeline = redis.pielined()// 增加一條請(qǐng)求pipeline.zadd(key, getUUID(), now)// 重新設(shè)置失效時(shí)間pipeline.expire(key, 3600)// 統(tǒng)計(jì)在滑動(dòng)窗口內(nèi),有多少次的請(qǐng)求count = pipeline.zcount(key, expireTimeStamp, now)// 刪除過(guò)期記錄pipeline.zremrangeByScore(key, 0, expireTimeStamp - 1)pipeline.sync()if count >= threshold    println("exceed")

但是該方法,有一個(gè)比較突出的問(wèn)題。就是這是一個(gè)重操作,將引發(fā)高 QPS 下 Redis 的性能瓶頸,也將消耗較多的資源和時(shí)間。一般我們可以付出秒級(jí)的時(shí)延,對(duì)其做多階段異步化的處理。比如將計(jì)數(shù)、刪除過(guò)期數(shù)據(jù)和新增記錄分為三部分去進(jìn)行異步處理。此處就不進(jìn)一步展開(kāi)了。

2.5.2 基于本地內(nèi)存

第一個(gè)方案中,分布式滑動(dòng)窗口的難度在于,不得不進(jìn)行內(nèi)存共享來(lái)達(dá)到窗口計(jì)數(shù)準(zhǔn)確的目的。如果考慮分發(fā)時(shí)進(jìn)行 Key Based Routing 是不是能解決這個(gè)問(wèn)題?在付出非冪等、復(fù)雜度抬升等一定代價(jià)的情況下,引入基于本地內(nèi)存的分布式限流實(shí)現(xiàn)方式。

實(shí)現(xiàn)方式有如下兩種:

  1. 如果可以接受準(zhǔn)實(shí)時(shí)計(jì)算的話,可以采用 Storm,使用 filedsGroup,指定 Key 到對(duì)應(yīng)的 Bolt 去處理;

  2. 如果需要實(shí)時(shí)計(jì)算的話,那么就采用 RPC 框架的 LB 策略為指定 Key 的一致性 Hash。然后路由到對(duì)應(yīng)的服務(wù)實(shí)例去處理。

以上兩個(gè)實(shí)現(xiàn)方式,當(dāng)?shù)竭_(dá) Bolt 或者服務(wù)實(shí)例后,即可基于本地內(nèi)存進(jìn)行處理,處理方式也有三種。

  1. 采用 Esper,用 DSL 語(yǔ)句即可簡(jiǎn)單實(shí)現(xiàn)滑動(dòng)窗口。

  2. Storm 1.0 之后提供了滑動(dòng)窗口的實(shí)現(xiàn)。

  3. 如果希望自實(shí)現(xiàn)滑動(dòng)窗口(不推薦),實(shí)現(xiàn)思路也比較簡(jiǎn)單即:循環(huán)隊(duì)列+自然窗口滑動(dòng)計(jì)數(shù)。

循環(huán)隊(duì)列來(lái)解決無(wú)限后延的時(shí)間里,計(jì)數(shù)空間重復(fù)利用的問(wèn)題。而此處,我們看到了一個(gè)熟悉的名詞——“自然窗口計(jì)數(shù)”。沒(méi)錯(cuò),底層仍然采用自然窗口計(jì)數(shù),但是區(qū)別在于,我們會(huì)對(duì)自然窗口切分更細(xì)的粒度,每次批量超前獲取多個(gè)分桶,來(lái)進(jìn)行加和計(jì)算。這樣就可以實(shí)現(xiàn)滑動(dòng)窗口的效果,你可以認(rèn)為,當(dāng)分桶被細(xì)化到 10s、5s 甚至越來(lái)越細(xì)的時(shí)候,計(jì)數(shù)將趨近于更加準(zhǔn)確。

2.6 令牌桶和漏桶算法計(jì)數(shù)

令牌桶的示意圖如下:

架構(gòu)師成長(zhǎng)之路之限流漫談

而漏桶的示意圖如下:

架構(gòu)師成長(zhǎng)之路之限流漫談

這個(gè)在業(yè)內(nèi)也是鼎鼎大名?;菊勂鹣蘖魉惴ǎ@兩個(gè)算法必然會(huì)被提起,令牌桶可以有流量應(yīng)對(duì)突發(fā)流量,漏桶則強(qiáng)調(diào)對(duì)流量的整型。二者的模型是相反的。令牌桶和漏桶算法在單機(jī)限流中較為常見(jiàn),而在分布式限流中罕見(jiàn)蹤跡。

對(duì)于令牌桶來(lái)說(shuō),你可以采用定時(shí)任務(wù)去做投遞令牌的動(dòng)作,也可以采用算法的方式去進(jìn)行簡(jiǎn)單的計(jì)算。Guava Ratelimiter 采用的是后者。

令牌桶的優(yōu)勢(shì)之一,在于可以有部分余量用以應(yīng)對(duì)突發(fā)流量。但是在實(shí)際生產(chǎn)環(huán)境中,這不一定是安全的。如果我們的服務(wù)沒(méi)有做好應(yīng)對(duì)更高突發(fā)流量的準(zhǔn)備,那么很有可能會(huì)引發(fā)服務(wù)雪崩。所以考慮到這一點(diǎn),Guava 采用了令牌桶 + 漏桶結(jié)合的策略來(lái)進(jìn)行限流。對(duì)于默認(rèn)業(yè)務(wù),采用標(biāo)準(zhǔn)令牌桶方式進(jìn)行“可超支”限速,而對(duì)于無(wú)法突然應(yīng)對(duì)高峰流量的業(yè)務(wù),會(huì)采用緩慢提升投放令牌速率(即逐步縮短業(yè)務(wù)請(qǐng)求等待時(shí)間)的方式來(lái)進(jìn)行熱啟動(dòng)控制,具體見(jiàn) Guava Ratelimiter 源碼注釋描述,此處不贅述,其效果如下圖所示:

架構(gòu)師成長(zhǎng)之路之限流漫談

3. 微服務(wù)限流幾個(gè)考慮的點(diǎn)

以上的限流手段,有的能應(yīng)用在單機(jī)環(huán)境,有的能應(yīng)用在分布式環(huán)境。而在高并發(fā)的分布式環(huán)境中,我們需要考慮清楚如下幾個(gè)問(wèn)題如何解決。

3.1 機(jī)器時(shí)鐘不一致或者時(shí)鐘回退問(wèn)題

一旦出現(xiàn)這種問(wèn)題,則可能導(dǎo)致收集的數(shù)據(jù)相互污染而導(dǎo)致判斷出錯(cuò)。所以一方面,在運(yùn)維層面需要確保機(jī)器時(shí)鐘能夠按期同步。另一方面,需要有準(zhǔn)實(shí)時(shí)檢測(cè)的手段,及時(shí)發(fā)現(xiàn)時(shí)鐘偏差太大或者時(shí)鐘回退的機(jī)器,基于一定策略篩選出不合格的數(shù)據(jù)來(lái)源,將其刨除出計(jì)算范圍并發(fā)出警告。

3.2 在 SDK 還是 Server 端做限流邏輯

你需要考慮你的限流策略迭代的頻繁程度,推動(dòng)業(yè)務(wù)方改造的成本,語(yǔ)言/技術(shù)棧異構(gòu)情況,是否有需要進(jìn)行立多系統(tǒng)聯(lián)合限流的場(chǎng)景,以此來(lái)進(jìn)行決策。如果采用 SDK 方式,你需要做好碰到這幾個(gè)棘手問(wèn)題的心理準(zhǔn)備。

而如果采用 Server 方式,你則需要更多考慮高并發(fā)下數(shù)據(jù)堆積,機(jī)器資源消耗,以及對(duì)業(yè)務(wù)方性能的影響問(wèn)題。一般業(yè)內(nèi)采用的是富 SDK 的方式來(lái)做,但是對(duì)于上述的 SDK 會(huì)面臨的幾個(gè)問(wèn)題沒(méi)有很好的解決方案。而 ServiceMesh 領(lǐng)軍人物 Istio 采用了 Mixer 來(lái)實(shí)現(xiàn) Server 端限流的方式,但是碰到了很?chē)?yán)重的性能問(wèn)題。所以這是一個(gè)很困難的選擇。

回顧下架構(gòu)師成長(zhǎng)之路之服務(wù)治理漫談一篇中所講到的服務(wù)治理發(fā)展路徑,是不是有點(diǎn)驚人的相似?是不是也許限流的未來(lái),不在 SDK 也不在 Server,而在于 ServiceMesh?我不確定,但我覺(jué)得這是一個(gè)很好的探索方向。

3.3 限流是不是會(huì)讓你的系統(tǒng)變得不可控

這是一個(gè)很有意思的問(wèn)題,限流本身是為了“反脆弱”而存在的,但是如果你的分布式復(fù)雜拓?fù)渲斜椴枷蘖鞴δ?,那么以后你每個(gè)服務(wù)的擴(kuò)容,新的功能上線,拓?fù)浣Y(jié)構(gòu)的變更,都有可能會(huì)導(dǎo)致局部服務(wù)流量的驟增,進(jìn)一步引發(fā)限流導(dǎo)致業(yè)務(wù)有損問(wèn)題。 這就是“反脆弱”的本身也有可能會(huì)導(dǎo)致“脆弱”的出現(xiàn)。 所以,當(dāng)你進(jìn)行大規(guī)模限流能力擴(kuò)張覆蓋的時(shí)候,需要謹(jǐn)慎審視你的限流能力和成熟度是否能夠支撐起如此大規(guī)模的應(yīng)用。

3.4 拓?fù)涞年P(guān)聯(lián)性能給限流帶來(lái)什么

我們置身于復(fù)雜服務(wù)拓?fù)浜透鞣N調(diào)用鏈路中,這一方面確實(shí)給限流帶來(lái)了很大的麻煩,但另一方面,我們是不是可以思考一下,這些復(fù)雜度,本身是不是可以帶給我們什么樣的利好?比如:底層服務(wù)扛不住,那么是不是可以在更上層的調(diào)用方入口進(jìn)行限流?如此是不是可以給予用戶更友好提示的同時(shí),也可避免鏈路上服務(wù)各自限流后帶來(lái)的系統(tǒng)級(jí)聯(lián)處理壓力?微服務(wù)的本質(zhì)是自治沒(méi)錯(cuò),但是我們是不是可以更好地對(duì)各個(gè)服務(wù)的限流自治能力進(jìn)行編排,以達(dá)到效率、體驗(yàn)、資源利用的優(yōu)化?

相信大家都會(huì)有自己的答案。這件事情本身的難度是在于決策的準(zhǔn)確性,但如果能很好地進(jìn)行落地實(shí)現(xiàn),則意味著我們的限流從自動(dòng)化已經(jīng)逐步轉(zhuǎn)向了智能化。這也將是更高一層次的挑戰(zhàn)和機(jī)遇。

3.5 準(zhǔn)確性和實(shí)時(shí)性的權(quán)衡

在高并發(fā)限流場(chǎng)景下,準(zhǔn)確性和實(shí)時(shí)性理論上不可兼得。在特定的場(chǎng)景中,你需要作出你的選擇,比如前文介紹的基于 Redis ZSet 實(shí)現(xiàn)的滑動(dòng)窗口實(shí)時(shí)計(jì)算方式可以滿足實(shí)時(shí)性和準(zhǔn)確性,但其會(huì)帶來(lái)很明顯的性能問(wèn)題。所以我們需要作出我們的權(quán)衡,比如犧牲準(zhǔn)確性將滑動(dòng)窗口退化為固定窗口來(lái)保障性能;或者犧牲實(shí)時(shí)性,對(duì)滑動(dòng)窗口多階段去做異步化,分析和決策兩階段分離,來(lái)保障性能。這取決于你的判斷。

4. 總結(jié)

限流是高可用治理中核心的一環(huán),實(shí)現(xiàn)方式也五花八門(mén),每種方式也都有各自的問(wèn)題,本文只是做了一個(gè)簡(jiǎn)單的回顧。希望隨著 ServiceMesh、AIOps 等理論的興起,我們對(duì)于限流是什么,能做什么,怎么實(shí)現(xiàn),能夠釋放出更大的空間去想象。

首發(fā):https://cloud.tencent.com/developer/article/1408380


往期精彩回顧

推薦 11 個(gè) GitHub 上比較熱門(mén)的 Java 項(xiàng)目

分庫(kù)分表?如何做到永不遷移數(shù)據(jù)和避免熱點(diǎn)?

Linux運(yùn)維寶典:最常用的150個(gè)命令匯總

工作發(fā)狂:Mybatis 中$和#千萬(wàn)不要亂用!

分享一些好用的 Chrome 擴(kuò)展

我爸的電腦中了勒索病毒……

P7 黑客是如何發(fā)現(xiàn)女朋友出軌的,痛心的經(jīng)歷!


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI