微服务接口限流的设计与思考(附GitHub框架源码)
http://www.infoq.com/cn/articles/microservice-interface-rate-limit?useSponsorshipSuggestions=true&utm_source=articles_about_architecture-design&utm_medium=link&utm_campaign=architecture-design
微服務(wù)拆分之后,系統(tǒng)之間的調(diào)用關(guān)系錯(cuò)綜復(fù)雜,平臺(tái)的整體復(fù)雜熵升高,出錯(cuò)的概率、debug 問題的難度都高了好幾個(gè)數(shù)量級(jí)。所以,服務(wù)治理便成了微服務(wù)的一個(gè)技術(shù)重點(diǎn)。服務(wù)治理本身的概念比較大,包括鑒權(quán)、限流、降級(jí)、熔斷、監(jiān)控告警等等,本文聚焦于限流,根據(jù)筆者的實(shí)戰(zhàn)經(jīng)驗(yàn),分享一些對(duì)微服務(wù)接口限流的思考。
本文試圖講清楚以下問題,如果您對(duì)限流也有類似的疑問或?qū)δ骋辉掝}感興趣,歡迎閱讀本文。
文章的最后,還順帶介紹了筆者開源的限流框架: ratelimiter4j,歡迎大家交流使用。
微服務(wù)接口限流的背景
在應(yīng)對(duì)秒殺、大促、雙 11、618 等高性能壓力的場(chǎng)景時(shí),限流已經(jīng)成為了標(biāo)配技術(shù)解決方案,為保證系統(tǒng)的平穩(wěn)運(yùn)行起到了關(guān)鍵性的作用。不管應(yīng)用場(chǎng)景是哪種,限流無非就是針對(duì)超過預(yù)期的流量,通過預(yù)先設(shè)定的限流規(guī)則選擇性的對(duì)某些請(qǐng)求進(jìn)行限流“熔斷”。限于篇幅和作者的經(jīng)驗(yàn)?zāi)芰?#xff0c;本文主要講微服務(wù)架構(gòu)下,服務(wù)接口的限流。
對(duì)于微服務(wù)來說,特別是一些中臺(tái)微服務(wù),其接口請(qǐng)求可能來自很多系統(tǒng),例如用戶服務(wù)的接口會(huì)被很多內(nèi)部系統(tǒng)調(diào)用,比如 CRM, 促銷系統(tǒng)等。對(duì)于服務(wù)于眾多調(diào)用系統(tǒng)和應(yīng)對(duì)海量接口請(qǐng)求的微服務(wù)來說,接口限流除了應(yīng)對(duì)上面提到的一些大促秒殺場(chǎng)景之外,在下面一些場(chǎng)景中也發(fā)揮著很大的作用。
作為提供接口服務(wù)的微服務(wù)系統(tǒng),我們是無法限制調(diào)用方如何來使用我們的接口的,我們?cè)?jīng)就遇到過有一些調(diào)用方多線程并發(fā)跑 job 來請(qǐng)求我們的接口,也遇到到一些因?yàn)檎{(diào)用方的代碼 bug 或者業(yè)務(wù)上面的突發(fā)流量,導(dǎo)致來自這個(gè)調(diào)用方的接口請(qǐng)求數(shù)量突增,過度爭(zhēng)用服務(wù)線程資源,而來自其他調(diào)用方的接口請(qǐng)求因此來不及響應(yīng)而排隊(duì)等待,微服務(wù)整體的請(qǐng)求響應(yīng)時(shí)間變長(zhǎng)甚至超時(shí)。所以為了防止接口被過度調(diào)用,需要對(duì)每個(gè)調(diào)用方進(jìn)行細(xì)粒度的訪問限流。
除了對(duì)調(diào)用者的訪問頻率進(jìn)行限制外,我們有的時(shí)候還需要對(duì)某些接口的訪問頻率做限制。比如一些慢接口,可能因?yàn)檫壿嫃?fù)雜,處理時(shí)間會(huì)比較長(zhǎng),如果對(duì)慢接口的訪問頻率不加限制,過多的慢接口請(qǐng)求會(huì)一直占用服務(wù)的線程資源不釋放,導(dǎo)致無法響應(yīng)其他接口請(qǐng)求,影響微服務(wù)系統(tǒng)整體的吞吐量和接口響應(yīng)時(shí)間,甚至引起大量的接口超時(shí)。除了慢接口,有些核心接口,因?yàn)橐坏┊惓TL問對(duì)業(yè)務(wù)的影響比較大,除了做調(diào)用鑒權(quán)之外,還需要做非預(yù)期異常流量的限流。
綜上所述,我們不僅僅需要針對(duì)大促秒殺場(chǎng)景的粗粒度的微服務(wù)接口限流功能:比如限制微服務(wù)集群?jiǎn)闻_(tái)機(jī)器每秒請(qǐng)求次數(shù),我們還需要針對(duì)不同調(diào)用方甚至不同接口進(jìn)行更加細(xì)粒度限流:比如限制 A 調(diào)用方對(duì)某個(gè)服務(wù)的某個(gè)的接口的每秒最大請(qǐng)求次數(shù)。
關(guān)于接口限流中“流”的定義
限流中的“流”字該如何解讀呢?要限制的指標(biāo)到底是什么?不同的場(chǎng)景對(duì)“流”的定義也是不同的,可以是網(wǎng)絡(luò)流量,帶寬,每秒處理的事務(wù)數(shù) (TPS),每秒請(qǐng)求數(shù) (hits per second),并發(fā)請(qǐng)求數(shù),甚至還可能是業(yè)務(wù)上的某個(gè)指標(biāo),比如用戶在某段時(shí)間內(nèi)允許的最多請(qǐng)求短信驗(yàn)證碼次數(shù)。
從保證系統(tǒng)穩(wěn)定可用的角度考量,對(duì)于微服務(wù)系統(tǒng)來說,最好的一個(gè)限流指標(biāo)是:并發(fā)請(qǐng)求數(shù)。通過限制并發(fā)處理的請(qǐng)求數(shù)目,可以限制任何時(shí)刻都不會(huì)有過多的請(qǐng)求在消耗資源,比如:我們通過配置 web 容器中 servlet worker 線程數(shù)目為 200,則任何時(shí)刻最多都只有 200 個(gè)請(qǐng)求在處理,超過的請(qǐng)求都會(huì)被阻塞排隊(duì)。
上一節(jié)講到,我們?yōu)榱私鉀Q調(diào)用方對(duì)服務(wù)資源的過度爭(zhēng)用問題,還需要針對(duì)不同調(diào)用方甚至不同接口做細(xì)粒度限流,所以,我們除了需要對(duì)系統(tǒng)整體的并發(fā)請(qǐng)求數(shù)做限制之外,還需要對(duì)每個(gè)調(diào)用方甚至不同接口的并發(fā)請(qǐng)求數(shù)做限制。但是,要想合理的設(shè)置某個(gè)調(diào)用方的最大允許并發(fā)數(shù)是比較困難的,這個(gè)值很難通過監(jiān)控統(tǒng)計(jì)來獲取,太小容易誤殺,太大又起不了作用。所以我們還需要其他限流指標(biāo)。
對(duì)比 TPS 和 hits per second 的兩個(gè)指標(biāo),我們選擇使用 hits per second 作為限流指標(biāo)。因?yàn)?#xff0c;對(duì) TPS 的限流實(shí)際上是無法做的,TPS 表示每秒處理事務(wù)數(shù),事務(wù)的開始是接收到接口請(qǐng)求,事務(wù)的結(jié)束是處理完成返回,所以有一定的時(shí)間跨度,如果事務(wù)開始限流計(jì)數(shù)器加一,事務(wù)結(jié)束限流計(jì)數(shù)器減一,則就等同于并發(fā)限流。而如果把事務(wù)請(qǐng)求接收作為計(jì)數(shù)時(shí)間點(diǎn),則就退化為按照 hits per second 來做限流,而如果把事務(wù)結(jié)束作為計(jì)數(shù)時(shí)間點(diǎn),則計(jì)數(shù)器的數(shù)值并不能代表系統(tǒng)當(dāng)下以及接下來的系統(tǒng)訪問壓力。
對(duì) hits per second 的限流是否是一個(gè)有效的限流指標(biāo)呢?答案是肯定的,這個(gè)值是可觀察可統(tǒng)計(jì)的,所以方便配置限流規(guī)則,而且這個(gè)值在一定程度上反應(yīng)系統(tǒng)當(dāng)前和接下來的性能壓力,對(duì)于這一指標(biāo)的限流確實(shí)也可以達(dá)到限制對(duì)系統(tǒng)資源的使用。
有了流的定義之后,我們接下來看幾種常用的限流算法:固定時(shí)間窗口,滑動(dòng)時(shí)間窗口,令牌桶算法,漏桶算法以及他們的改進(jìn)版本。
固定、滑動(dòng)時(shí)間窗口限流算法
基于固定時(shí)間窗口的限流算法是非常簡(jiǎn)單的。首先需要選定一個(gè)時(shí)間起點(diǎn),之后每次接口請(qǐng)求到來都累加計(jì)數(shù)器,如果在當(dāng)前時(shí)間窗口內(nèi),根據(jù)限流規(guī)則(比如每秒鐘最大允許 100 次接口請(qǐng)求),累加訪問次數(shù)超過限流值,則限流熔斷拒絕接口請(qǐng)求。當(dāng)進(jìn)入下一個(gè)時(shí)間窗口之后,計(jì)數(shù)器清零重新計(jì)數(shù)。
這種基于固定時(shí)間窗口的限流算法的缺點(diǎn)在于:限流策略過于粗略,無法應(yīng)對(duì)兩個(gè)時(shí)間窗口臨界時(shí)間內(nèi)的突發(fā)流量。我們舉一個(gè)例子:假設(shè)我們限流規(guī)則為每秒鐘不超過 100 次接口請(qǐng)求,第一個(gè) 1s 時(shí)間窗口內(nèi),100 次接口請(qǐng)求都集中在最后的 10ms 內(nèi),在第二個(gè) 1s 的時(shí)間窗口內(nèi),100 次接口請(qǐng)求都集中在最開始的 10ms 內(nèi),雖然兩個(gè)時(shí)間窗口內(nèi)流量都符合限流要求 (<=100 個(gè)請(qǐng)求),但在兩個(gè)時(shí)間窗口臨界的 20ms 內(nèi)會(huì)集中有 200 次接口請(qǐng)求,如果不做限流,集中在這 20ms 內(nèi)的 200 次請(qǐng)求就有可能壓垮系統(tǒng),如圖 -1:
滑動(dòng)時(shí)間窗口算法是對(duì)固定時(shí)間窗口算法的一種改進(jìn),流量經(jīng)過滑動(dòng)時(shí)間窗口算法整形之后,可以保證任意時(shí)間窗口內(nèi),都不會(huì)超過最大允許的限流值,從流量曲線上來看會(huì)更加平滑,可以部分解決上面提到的臨界突發(fā)流量問題。對(duì)比固定時(shí)間窗口限流算法,滑動(dòng)時(shí)間窗口限流算法的時(shí)間窗口是持續(xù)滑動(dòng)的,并且除了需要一個(gè)計(jì)數(shù)器來記錄時(shí)間窗口內(nèi)接口請(qǐng)求次數(shù)之外,還需要記錄在時(shí)間窗口內(nèi)每個(gè)接口請(qǐng)求到達(dá)的時(shí)間點(diǎn),對(duì)內(nèi)存的占用會(huì)比較多。滑動(dòng)時(shí)間窗口的算法模型如下:
滑動(dòng)窗口記錄的時(shí)間點(diǎn) list = (t_1, t_2, …t_k),時(shí)間窗口大小為 1 秒,起點(diǎn)是 list 中最小的時(shí)間點(diǎn)。當(dāng) t_m 時(shí)刻新的請(qǐng)求到來時(shí),我們通過以下步驟來更新滑動(dòng)時(shí)間窗口并判斷是否限流熔斷:
STEP 1: 檢查接口請(qǐng)求的時(shí)間 t_m 是否在當(dāng)前的時(shí)間窗口 [t_start, t_start+1 秒) 內(nèi)。如果是,則跳轉(zhuǎn)到 STEP 3,否則跳轉(zhuǎn)到 STEP 2.
STEP 2: 向后滑動(dòng)時(shí)間窗口,將時(shí)間窗口的起點(diǎn) t_start 更新為 list 中的第二小時(shí)間點(diǎn),并將最小的時(shí)間點(diǎn)從 list 中刪除。然后,跳轉(zhuǎn)到 STEP 1。
STEP 3: 判斷當(dāng)前時(shí)間窗口內(nèi)的接口請(qǐng)求數(shù)是否小于最大允許的接口請(qǐng)求限流值,即判斷: list.size < max_hits_limit,如果小于,則說明沒有超過限流值,允許接口請(qǐng)求,并將此接口請(qǐng)求的訪問時(shí)間放入到時(shí)間窗口內(nèi),否則直接執(zhí)行限流熔斷。
滑動(dòng)時(shí)間窗口限流算法可以部分解決固定時(shí)間窗口的臨界問題,上面的例子通過滑動(dòng)時(shí)間窗口算法整形之后,第一個(gè) 1 秒的時(shí)間窗口的 100 次請(qǐng)求都會(huì)通過,第二個(gè)時(shí)間窗口最開始 10ms 內(nèi)的 100 個(gè)請(qǐng)求會(huì)被限流熔斷。
即便滑動(dòng)時(shí)間窗口限流算法可以保證任意時(shí)間窗口內(nèi)接口請(qǐng)求次數(shù)都不會(huì)超過最大限流值,但是仍然不能防止在細(xì)時(shí)間粒度上面訪問過于集中的問題,比如上面舉的例子,第一個(gè) 1s 的時(shí)間窗口內(nèi) 100 次請(qǐng)求都集中在最后 10ms 中。也就是說,基于時(shí)間窗口的限流算法,不管是固定時(shí)間窗口還是滑動(dòng)時(shí)間窗口,只能在選定的時(shí)間粒度上限流,對(duì)選定時(shí)間粒度內(nèi)的更加細(xì)粒度的訪問頻率不做限制。
為了應(yīng)對(duì)上面的問題,對(duì)于時(shí)間窗口限流算法,還有很多改進(jìn)版本,比如:
多層次限流,我們可以對(duì)同一個(gè)接口設(shè)置多條限流規(guī)則,除了 1 秒不超過 100 次之外,我們還可以設(shè)置 100ms 不超過 20 次 (這里需要設(shè)置的比 10 次大一些),兩條規(guī)則同時(shí)限制,流量會(huì)更加平滑。除此之外,還有針對(duì)滑動(dòng)時(shí)間窗口限流算法空間復(fù)雜度大的改進(jìn)算法,限于篇幅,這里就不展開詳說了。
令牌桶、漏桶限流算法
上面我們講了兩種基于時(shí)間窗口的限流算法:固定時(shí)間窗口和滑動(dòng)時(shí)間窗口算法,兩種限流算法都無法應(yīng)對(duì)細(xì)時(shí)間粒度的突發(fā)流量,對(duì)流量的整形效果在細(xì)時(shí)間粒度上不夠平滑。本節(jié)介紹兩種更加平滑的限流算法:令牌桶算法和漏桶算法,在某些場(chǎng)景下,這兩種算法會(huì)優(yōu)于時(shí)間窗口算法成為首選。實(shí)際上令牌桶和漏桶算法的算法思想大體類似,可以把漏桶算法作為令牌桶限流算法的改進(jìn)版本,所以我們以介紹令牌桶算法為主。
我們先來看下最基礎(chǔ)未經(jīng)過改進(jìn)的令牌桶算法:
令牌桶算法看似比較復(fù)雜,每間隔固定時(shí)間都要放 token 到桶中,但并不需要專門起一個(gè)線程來做這件事情。每次在取 token 之前,根據(jù)上次放入 token 的時(shí)間戳和現(xiàn)在的時(shí)間戳,計(jì)算出這段時(shí)間需要放多少 token 進(jìn)去,一次性放進(jìn)去,所以在實(shí)現(xiàn)上面也并沒有太大難度。
漏桶算法稍微不同與令牌桶算法的一點(diǎn)是:對(duì)于取令牌的頻率也有限制,要按照 t/n 固定的速度來取令牌,所以可以看出漏桶算法對(duì)流量的整形效果更加好,流量更加平滑,任何突發(fā)流量都會(huì)被限流。因?yàn)榱钆仆按笮?b,所以是可以應(yīng)對(duì)突發(fā)流量的。當(dāng)然,對(duì)于令牌桶算法,還有很多其他改進(jìn)算法,比如:
對(duì)比基于時(shí)間窗口的限流算法,令牌桶和漏桶算法對(duì)流量整形效果比時(shí)間窗口算法要好很多,但是并不是整形效果越好就越合適,對(duì)于沒有提前預(yù)熱的令牌桶,如果做否決式限流,會(huì)導(dǎo)致誤殺很多請(qǐng)求。上述算法中當(dāng) n 比較小時(shí),比如 50,間隔 20ms 才會(huì)向桶中放入一個(gè)令牌,而接口的訪問在 1s 內(nèi)可能隨機(jī)性很強(qiáng),這就會(huì)出現(xiàn):盡管從曲線上看對(duì)最大訪問頻率的限制很有效,流量在細(xì)時(shí)間粒度上面都很平滑,但是誤殺了很多本不應(yīng)該拒絕的接口請(qǐng)求。
所以令牌桶和漏桶算法比較適合阻塞式限流,比如一些后臺(tái) job 類的限流,超過了最大訪問頻率之后,請(qǐng)求并不會(huì)被拒絕,而是會(huì)被阻塞到有令牌后再繼續(xù)執(zhí)行。對(duì)于像微服務(wù)接口這種對(duì)響應(yīng)時(shí)間比較敏感的限流場(chǎng)景,會(huì)比較適合選擇基于時(shí)間窗口的否決式限流算法,其中滑動(dòng)時(shí)間窗口限流算法空間復(fù)雜度較高,內(nèi)存占用會(huì)比較多,所以對(duì)比來看,盡管固定時(shí)間窗口算法處理臨界突發(fā)流量的能力較差,但實(shí)現(xiàn)簡(jiǎn)單,而簡(jiǎn)單帶來了好的性能和不容易出錯(cuò),所以固定時(shí)間窗口算法也不失是一個(gè)好的微服務(wù)接口限流算法。
限流算法分布式改造: 分布式限流算法
相對(duì)于單機(jī)限流算法,分布式限流算法的是指: 算法可以分布式部署在多臺(tái)機(jī)器上面,多臺(tái)機(jī)器協(xié)同提供限流功能,可以對(duì)同一接口或者服務(wù)做限流。分布式限流算法相較于單機(jī)的限流算法,最大的區(qū)別就是接口請(qǐng)求計(jì)數(shù)器需要中心化存儲(chǔ),比如我們開源限流項(xiàng)目 ratelimiter4j 就是基于 Redis 中心計(jì)數(shù)器來實(shí)現(xiàn)分布式限流算法。
分布式限流算法在引入 Redis 中心計(jì)數(shù)器這個(gè)獨(dú)立的系統(tǒng)之后,系統(tǒng)的復(fù)雜度一下子高了很多,因?yàn)橐鉀Q一些分布式系統(tǒng)的共性技術(shù)問題:
1. 數(shù)據(jù)一致性問題
接口限流過程包含三步操作:
Step 1:“讀”當(dāng)前的接口訪問計(jì)數(shù) n;
Step 2:”判斷”是否限流;
Step 3:“寫”接口計(jì)數(shù) n+1, if 接口限流驗(yàn)證通過
在并發(fā)情況下,這 3 步 CAS 操作 (compare and swap) 存在 race condition。在多線程環(huán)境下,可以通過線程的加鎖或者 concurrent 開發(fā)包中的 Atomic 原子對(duì)象來實(shí)現(xiàn)。在分布式情況下,思路也是類似的,可以通過分布式鎖,來保證同一時(shí)間段只有一個(gè)進(jìn)程在訪問,但是引入分布式鎖需要引入新的系統(tǒng)和維護(hù)鎖的代碼,代價(jià)較大,為了簡(jiǎn)單,我們選擇另一種思路:借助 Redis 單線程工作模式 +Lua 腳本完美的支持了上述操作的原子性。限于篇幅,不展開代碼討論,詳細(xì)可以參看開源項(xiàng)目 ratelimiter4j.
2. 超時(shí)問題
對(duì)于 Redis 的各種異常情況,我們處理起來并不是很難,catch 住,封裝為統(tǒng)一的 exception,向上拋,或者吞掉。但是如果 Redis 訪問超時(shí),會(huì)嚴(yán)重影響接口的響應(yīng)時(shí)間甚至導(dǎo)致接口響應(yīng)超時(shí),這個(gè)副作用是不能接受的。所以在我們?cè)L問 Redis 時(shí)需要設(shè)置合理的超時(shí)時(shí)間,一旦超時(shí),判定為限流失效,繼續(xù)執(zhí)行接口邏輯。Redis 訪問超時(shí)時(shí)間的設(shè)置既不能太大也不能太小,太大可能會(huì)影響到接口的響應(yīng)時(shí)間,太小可能會(huì)導(dǎo)致太多的限流失效。我們可以通過壓測(cè)或者線上監(jiān)控,獲取到 Redis 訪問時(shí)間分布情況,再結(jié)合服務(wù)接口可以容忍的限流延遲時(shí)間,權(quán)衡設(shè)置一個(gè)較合理的超時(shí)時(shí)間。
3. 性能問題
分布式限流算法的性能瓶頸主要在中心計(jì)數(shù)器 Redis,從我們開源的 ratelimiter4j 壓測(cè)數(shù)據(jù)來看,在沒有做 Redis sharding 的情況下,基于單實(shí)例 Redis 的分布式限流算法的性能要遠(yuǎn)遠(yuǎn)低于基于內(nèi)存的單機(jī)限流算法,基于我們的壓測(cè)環(huán)境,單機(jī)限流算法可以達(dá)到 200 萬 TPS,而分布式限流算法只能做到 5 萬 TPS。所以,在應(yīng)用分布式限流算法時(shí),一定要考量限流算法的性能是否滿足應(yīng)用場(chǎng)景,如果微服務(wù)接口的 TPS 已經(jīng)超過了限流框架本身的 TPS,則限流功能會(huì)成為性能瓶頸影響接口本身的性能。
除了 TPS 之外,網(wǎng)絡(luò)延遲也是一個(gè)需要特別考慮的問題,特別是如果中心計(jì)數(shù)器與限流服務(wù)跨機(jī)房跨城市部署,之間的網(wǎng)絡(luò)延遲將會(huì)非常大,嚴(yán)重影響微服務(wù)接口的響應(yīng)時(shí)間。
如何選擇單機(jī)限流還是分布式限流
首先需要說明一下:這里所說的單機(jī)限流和分布式限流與之前提到的單機(jī)限流算法和分布式限流算法并不是一個(gè)概念!為了提高服務(wù)的性能和可用性,微服務(wù)都會(huì)多實(shí)例集群部署,所謂單機(jī)限流是指:獨(dú)立的對(duì)集群中的每臺(tái)實(shí)例進(jìn)行接口限流,比如限制每臺(tái)實(shí)例接口訪問的頻率為最大 1000 次 / 秒,單機(jī)限流一般使用單機(jī)限流算法;所謂的分布式限流是指:提供服務(wù)級(jí)的限流,限制對(duì)微服務(wù)集群的訪問頻率,比如限制 A 調(diào)用方每分鐘最多請(qǐng)求 1 萬次“用戶服務(wù)”,分布式限流既可以使用單機(jī)限流算法也可以使用分布式限流算法。
單機(jī)限流的初衷是防止突發(fā)流量壓垮服務(wù)器,所以比較適合針對(duì)并發(fā)做限制。分布式限流適合做細(xì)粒度限流或者訪問配額,不同的調(diào)用方對(duì)不同的接口執(zhí)行不同的限流規(guī)則,所以比較適合針對(duì) hits per second 限流。從保證系統(tǒng)可用性的角度來說,單機(jī)限流更具優(yōu)勢(shì),從防止某調(diào)用方過度競(jìng)爭(zhēng)服務(wù)資源來說,分布式限流更加適合。
分布式限流與微服務(wù)之間常見的部署架構(gòu)有以下幾種:
1. 在接入層(api-gateway)集成限流功能
這種集成方式是在微服務(wù)架構(gòu)下,有 api-gateway 的前提下,最合理的架構(gòu)模式。如果 api-gateway 是單實(shí)例部署,使用單機(jī)限流算法即可。如果 api-gateway 是多實(shí)例部署,為了做到服務(wù)級(jí)別的限流就必須使用分布式限流算法。
2. 限流功能封裝為 RPC 服務(wù)
當(dāng)微服務(wù)接收到接口請(qǐng)求之后,會(huì)先通過限流服務(wù)暴露的 RPC 接口來查詢接口請(qǐng)求是否超過限流閾值。這種架構(gòu)模式,需要部署一個(gè)限流服務(wù),增加了運(yùn)維成本。這種部署架構(gòu),性能瓶頸會(huì)出現(xiàn)在微服務(wù)與限流服務(wù)之間的 RPC 通信上,即便單機(jī)限流算法可以做到 200 萬 TPS,但經(jīng)過 RPC 框架之后,做到 10 萬 TPS 的請(qǐng)求限流就已經(jīng)不錯(cuò)了。
3. 限流功能集成在微服務(wù)系統(tǒng)內(nèi)
這種架構(gòu)模式不需要再獨(dú)立部署服務(wù),減少了運(yùn)維成本,但限流代碼會(huì)跟業(yè)務(wù)代碼有一些耦合,不過,可以將限流功能集成在切面層,盡量跟業(yè)務(wù)代碼解耦。如果做服務(wù)級(jí)的分布式限流,必須使用分布式限流算法,如果是針對(duì)每臺(tái)微服務(wù)實(shí)例進(jìn)行單機(jī)限流,使用單機(jī)限流算法就可以。
針對(duì)不同業(yè)務(wù)使用不同限流熔斷策略
這里所講的熔斷策略,就是當(dāng)接口達(dá)到限流上限之后,如何來處理接口請(qǐng)求的問題。前面也有提到過一些限流熔斷策略了,所謂否決式限流就是超過最大允許訪問頻率之后就拒絕請(qǐng)求,比如返回 HTTP status code 429 等,所謂阻塞式限流就是超過最大允許訪問頻率之后就排隊(duì)請(qǐng)求。除此之外,還有其他一些限流熔斷策略,比如:記錄日志,發(fā)送告警,服務(wù)降級(jí)等等。
同一個(gè)系統(tǒng)對(duì)于不同的調(diào)用方也有可能有不同的限流熔斷策略,比如對(duì)響應(yīng)時(shí)間敏感的調(diào)用方,我們可能采用直接拒絕的熔斷策略,對(duì)于像后臺(tái) job 這樣對(duì)響應(yīng)時(shí)間不敏感的調(diào)用方,我們可能采用阻塞排隊(duì)處理的熔斷策略。
我們?cè)賮砜聪缕渌蹟嗖呗缘囊恍?yīng)用場(chǎng)景:比如限流功能剛剛上線,為了驗(yàn)證限流算法的有效性及其限流規(guī)則的合理性,確保不誤殺請(qǐng)求,可以先采用日志記錄 + 告警的限流熔斷策略,通過分析日志判定限流功能正常工作后,再進(jìn)一步升級(jí)為其他限流熔斷策略。
不同的熔斷策略對(duì)于選擇限流算法也是有影響的,比如令牌桶和漏桶算法就比較適合阻塞式限流熔斷場(chǎng)景,如果是否決式的限流熔斷場(chǎng)景就比較適合選擇基于時(shí)間窗口的限流算法。
如何配置合理的限流規(guī)則
限流規(guī)則包含三個(gè)部分:時(shí)間粒度,接口粒度,最大限流值。限流規(guī)則設(shè)置是否合理直接影響到限流是否合理有效。
對(duì)于限流時(shí)間粒度的選擇,我們既可以選擇 1 秒鐘不超過 1000 次,也可以選擇 10 毫秒不超過 10 次,還可以選擇 1 分鐘不超過 6 萬次,雖然看起這幾種限流規(guī)則都是等價(jià)的,但過大的時(shí)間粒度會(huì)達(dá)不到限流的效果,比如限制 1 分鐘不超過 6 萬次,就有可能 6 萬次請(qǐng)求都集中在某一秒內(nèi);相反,過小的時(shí)間粒度會(huì)削足適履導(dǎo)致誤殺很多本不應(yīng)該限流的請(qǐng)求,因?yàn)榻涌谠L問在細(xì)時(shí)間粒度上隨機(jī)性很大。所以,盡管越細(xì)的時(shí)間粒度限流整形效果越好,流量曲線越平滑,但也并不是越細(xì)越合適。
對(duì)于訪問量巨大的接口限流,比如秒殺,雙十一,這些場(chǎng)景下流量可能都集中在幾秒內(nèi),TPS 會(huì)非常大,幾萬甚至幾十萬,需要選擇相對(duì)小的限流時(shí)間粒度。相反,如果接口 TPS 很小,建議使用大一點(diǎn)的時(shí)間粒度,比如限制 1 分鐘內(nèi)接口的調(diào)用次數(shù)不超過 1000 次,如果換算成:一秒鐘不超過 16 次,這樣的限制就有點(diǎn)不合理,即便一秒內(nèi)超過 16 次,也并沒有理由就拒絕接口請(qǐng)求,因?yàn)閷?duì)于我們系統(tǒng)的處理能力來說,16 次 / 秒的請(qǐng)求頻率太微不足道了。即便 1000 次請(qǐng)求都集中在 1 分鐘內(nèi)的某一秒內(nèi),也并不會(huì)影響到系統(tǒng)的穩(wěn)定性,所以 1 秒鐘 16 次的限制意義不大。
除了時(shí)間粒度之外,還需要根據(jù)不同的限流需求選擇不同接口粒度,比如:
1)限制微服務(wù)每個(gè)實(shí)例接口調(diào)用頻率
2)限制微服務(wù)集群整體的訪問頻率
2)限制某個(gè)調(diào)用方對(duì)某個(gè)服務(wù)的調(diào)用頻率
3)限制某個(gè)調(diào)用方對(duì)某個(gè)服務(wù)的某個(gè)接口的訪問頻率
4)限制某服務(wù)的某個(gè)接口的訪問頻率
5)限制某服務(wù)的某類接口的訪問頻率
對(duì)于最大允許訪問頻率的設(shè)置,需要結(jié)合性能壓測(cè)數(shù)據(jù)、業(yè)務(wù)預(yù)期流量、線上監(jiān)控?cái)?shù)據(jù)來綜合設(shè)置,最大允許訪問頻率不大于壓測(cè) TPS,不小于業(yè)務(wù)預(yù)期流量,并且參考線上監(jiān)控?cái)?shù)據(jù)。
如何評(píng)判限流功能是否正確有效
這里所說的有效性包含兩個(gè)方面:限流算法的有效性和限流規(guī)則的有效性。在大促,秒殺,或者其他異常流量到來之前,我們需要事先通過實(shí)驗(yàn)來驗(yàn)證限流功能的有效性,用數(shù)據(jù)來證明限流功能確實(shí)能夠攔截非預(yù)期的異常流量。否則,就有可能會(huì)因?yàn)橄蘖魉惴ǖ倪x擇不夠合適或者限流規(guī)則設(shè)置不合理,導(dǎo)致真正超預(yù)期流量到來的時(shí)候,限流不能起到保護(hù)服務(wù)的作用,超出預(yù)期的異常流量壓垮系統(tǒng)。
如何測(cè)試限流功能正確有效呢?盡管可以通過模擬流量或者線上流量回放等手段來測(cè)試,但是最有效的測(cè)試方法還是:通過導(dǎo)流的方式將流量集中到一小組機(jī)器上做真實(shí)場(chǎng)景的測(cè)試。對(duì)于測(cè)試結(jié)果,我們至少需要記錄每個(gè)請(qǐng)求的如下信息:對(duì)應(yīng)接口,請(qǐng)求時(shí)間點(diǎn),限流結(jié)果 (通過還是熔斷),然后根據(jù)記錄的數(shù)據(jù)繪制成如下圖表:
從圖表中,我們可以一目了然的了解限流前與限流后的流量情況,可以清晰的看到限流規(guī)則和算法對(duì)流量的整形是否合理有效。
除了事先驗(yàn)證之外,我們還需要時(shí)刻監(jiān)控限流的工作情況,實(shí)時(shí)了解限流功能是否運(yùn)行正常。一旦發(fā)生限流異常,能夠在不重啟服務(wù)的情況下,做到熱更新限流配置:包括開啟關(guān)閉限流功能,調(diào)整限流規(guī)則,更換限流算法等等。
高容錯(cuò)高性能開源限流框架:ratelimiter4j
ratelimiter4j 是一個(gè)高性能高容錯(cuò)易集成的限流框架, 從功能的角度來看限流功能的實(shí)現(xiàn)并不復(fù)雜,而非功能性的需求是系統(tǒng)開發(fā)的技術(shù)難點(diǎn):
1)低延遲:不能或者較小的影響接口本身的響應(yīng)時(shí)間
每個(gè)微服務(wù)接口請(qǐng)求都需要檢查是否超過了限定的訪問頻率,無疑會(huì)增加接口的響應(yīng)時(shí)間,而響應(yīng)時(shí)間對(duì)于微服務(wù)接口來說,是一個(gè)非常關(guān)注的性能指標(biāo),所以讓限流延遲盡可能小,是我們?cè)陂_發(fā) ratelimiter4j 限流框架時(shí)特別考慮的。
2)高度容錯(cuò):限流框架的異常不影響微服務(wù)的可用性
接入限流本身是為了提供系統(tǒng)的可用性穩(wěn)定性,不能因?yàn)橄蘖鞅旧淼漠惓7催^來影響到微服務(wù)的可用性,這個(gè)副作用是不能接受的。比如分布式限流算法依賴的 Redis 掛掉了,限流操作無法進(jìn)行,這個(gè)時(shí)候業(yè)務(wù)接口也要能繼續(xù)正常服務(wù)。
3)高 TPS:限流框架的 TPS 至少要大于微服務(wù)本身的接口 TPS
對(duì)于大規(guī)模服務(wù)來說,接口訪問頻率比較高,幾萬甚至幾十萬的 TPS,限流框架支持的 TPS 至少要高于服務(wù)本身的 TPS,否則就會(huì)因?yàn)橄蘖鞅旧淼男阅軉栴}反過來拖垮服務(wù)。
目前 ratelimiter4j 框架將限流規(guī)則組織成 trie ?tree 數(shù)據(jù)結(jié)構(gòu),可以實(shí)現(xiàn)快速查詢請(qǐng)求對(duì)應(yīng)的接口限流規(guī)則,實(shí)驗(yàn)證明 trie tree 這種數(shù)據(jù)結(jié)構(gòu)非常適合像 url 這種具有分級(jí)目錄且目錄重復(fù)度高的接口格式。
針對(duì)分布式限流,目前 ratelimiter4j 壓測(cè)得到的結(jié)果在響應(yīng)時(shí)間可以接受的范圍內(nèi)最大支持 5 萬 TPS,高并發(fā)對(duì) TPS 的影響并不敏感,瓶頸主要在 Redis 中心計(jì)數(shù)器,接下來會(huì)通過改進(jìn)算法及其中心計(jì)數(shù)器支持 sharding 的方式來優(yōu)化性能。
ratelimiter4j GitHub 地址:https://github.com/wangzheng0822/ratelimiter4j
最后,總結(jié)
限流在很多微服務(wù)及其服務(wù)治理相關(guān)的技術(shù)文章中都有提到,限于篇幅和主題,可能會(huì)講的不夠深入,本文結(jié)合具體的實(shí)踐經(jīng)驗(yàn),聚焦剖析微服務(wù)接口限流,希望讀者在讀完本文之后對(duì)微服務(wù)接口限流有個(gè)更加深刻的認(rèn)識(shí)。
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/articles/9415110.html
總結(jié)
以上是生活随笔為你收集整理的微服务接口限流的设计与思考(附GitHub框架源码)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Great Power, Great R
- 下一篇: CR常见代码问题