Java架構(gòu)師:高并發(fā)下的流量控制
這個(gè)時(shí)候如果不做任何保護(hù)措施,服務(wù)器就會(huì)承受很大的處理壓力,請(qǐng)求量很高,服務(wù)器負(fù)載也很高,并且當(dāng)請(qǐng)求超過(guò)服務(wù)器承載極限的時(shí)候,系統(tǒng)就會(huì)崩潰,導(dǎo)致所有人都不能訪問(wèn)。
為了應(yīng)用服務(wù)的高可用,一個(gè)常用的辦法是對(duì)大流量的請(qǐng)求(秒殺/搶購(gòu))進(jìn)行限流,攔截掉大部分請(qǐng)求,只允許一部分請(qǐng)求真正進(jìn)入后端服務(wù)器,這樣就可以防止大量請(qǐng)求造成系統(tǒng)壓力過(guò)大導(dǎo)致的系統(tǒng)崩潰,從而保護(hù)服務(wù)正常可用。
令牌桶(Token Bucket) 、 漏桶(leaky bucket) 和 計(jì)數(shù)器 算法是最常用的三種限流的算法。
限流算法
計(jì)數(shù)器
計(jì)數(shù)器限流算法也是比較常用的,主要用來(lái)限制總并發(fā)數(shù)。比如限流 qps 為 100,算法的實(shí)現(xiàn)思路就是從第一個(gè)請(qǐng)求進(jìn)來(lái)開(kāi)始計(jì)時(shí),在接下去的 1s 內(nèi),每來(lái)一個(gè)請(qǐng)求,就把計(jì)數(shù)加 1 ,如果累加的數(shù)字達(dá)到了 100 ,那么后續(xù)的請(qǐng)求就會(huì)被全部拒絕。等到 1s 結(jié)束后,把計(jì)數(shù)恢復(fù)成 0 ,重新開(kāi)始計(jì)數(shù)。
這種實(shí)現(xiàn)方式有一個(gè)弊端:如果我在單位時(shí)間 1s 內(nèi)的前 10ms ,已經(jīng)通過(guò)了 100個(gè)請(qǐng)求,那后面的 990ms ,只能眼巴巴的把請(qǐng)求拒絕,這種現(xiàn)象稱為 突刺現(xiàn)象。
漏桶
為了消除 突刺現(xiàn)象,可以采用漏桶算法實(shí)現(xiàn)限流,漏桶算法這個(gè)名字就很形象,算法內(nèi)部有一個(gè)容器,類似生活用到的漏斗,當(dāng)請(qǐng)求進(jìn)來(lái)時(shí),相當(dāng)于水倒入漏斗,然后從下端小口慢慢勻速的流出。不管上面流量多大,下面流出的速度始終保持不變。
不管服務(wù)調(diào)用方多么不穩(wěn)定,通過(guò)漏桶算法進(jìn)行限流,每 10 毫秒處理一次請(qǐng)求。因?yàn)樘幚淼乃俣仁枪潭ǖ模?qǐng)求進(jìn)來(lái)的速度是未知的,可能突然進(jìn)來(lái)很多請(qǐng)求,沒(méi)來(lái)得及處理的請(qǐng)求就先放在桶里,既然是個(gè)桶,肯定是有容量上限,如果桶滿了,那么新進(jìn)來(lái)的請(qǐng)求就丟棄。

在算法實(shí)現(xiàn)方面,可以 準(zhǔn)備一個(gè)隊(duì)列,用來(lái)保存請(qǐng)求,另外通過(guò)一個(gè)線程池定期從隊(duì)列中獲取請(qǐng)求并執(zhí)行,可以一次性獲取多個(gè)并發(fā)執(zhí)行。
這種算法,在使用過(guò)后也存在弊端:無(wú)法應(yīng)對(duì)短時(shí)間的突發(fā)流量,同時(shí)它的優(yōu)點(diǎn)也是可以平滑網(wǎng)絡(luò)上的突發(fā)流量,請(qǐng)求可以被整形成穩(wěn)定的流量。
令牌桶
從某種意義上講,令牌桶算法是對(duì)漏桶算法的一種改進(jìn),桶算法能夠限制請(qǐng)求調(diào)用的速率,而令牌桶算法能夠在限制調(diào)用的平均速率的同時(shí)還允許一定程度的突發(fā)調(diào)用。
在令牌桶算法中,存在一個(gè)桶,用來(lái)存放固定數(shù)量的令牌。算法中存在一種機(jī)制,以一定的速率往桶中放令牌。每次請(qǐng)求調(diào)用需要先獲取令牌,只有拿到令牌,才有機(jī)會(huì)繼續(xù)執(zhí)行,否則選擇選擇等待可用的令牌、或者直接拒絕。
放令牌這個(gè)動(dòng)作是持續(xù)不斷的進(jìn)行,如果桶中令牌數(shù)達(dá)到上限,就丟棄令牌,所以就存在這種情況,桶中一直有大量的可用令牌,這時(shí)進(jìn)來(lái)的請(qǐng)求就可以直接拿到令牌執(zhí)行,比如設(shè)置 qps為 100 ,那么限流器初始化完成一秒后,桶中就已經(jīng)有 100 個(gè)令牌了,這時(shí)服務(wù)還沒(méi)完全啟動(dòng)好,等啟動(dòng)完成對(duì)外提供服務(wù)時(shí),該限流器可以抵擋瞬時(shí)的 100 個(gè)請(qǐng)求。所以,只有桶中沒(méi)有令牌時(shí),請(qǐng)求才會(huì)進(jìn)行等待,最后相當(dāng)于以一定的速率執(zhí)行。

實(shí)現(xiàn)思路:可以 準(zhǔn)備一個(gè)隊(duì)列,用來(lái)保存令牌,另外通過(guò)一個(gè)線程池定期生成令牌放到隊(duì)列中,每來(lái)一個(gè)請(qǐng)求,就從隊(duì)列中獲取一個(gè)令牌,并繼續(xù)執(zhí)行。
漏桶 VS 令牌桶:兩者主要區(qū)別在于“漏桶算法”能夠強(qiáng)行限制數(shù)據(jù)的傳輸速率,而“令牌桶算法”在能夠限制數(shù)據(jù)的平均傳輸速率外,還允許某種程度的突發(fā)傳輸。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允許突發(fā)地傳輸數(shù)據(jù)直到達(dá)到用戶配置的門限,所以它適合于具有突發(fā)特性的流量。
集群限流
Redis 請(qǐng)求窗口
采用redis 的計(jì)時(shí)和計(jì)數(shù)方式,在規(guī)定的時(shí)間窗口期,允許通過(guò)的最大請(qǐng)求數(shù)量
比如為了限制某個(gè)資源被每個(gè)用戶或者商戶的訪問(wèn)次數(shù),5s 只能訪問(wèn) 2 次,或者一天只能調(diào)用 1000 次,這種需求,單機(jī)限流是無(wú)法實(shí)現(xiàn)的,這時(shí)就需要通過(guò)集群限流進(jìn)行實(shí)現(xiàn)。
如何實(shí)現(xiàn)?為了控制訪問(wèn)次數(shù),肯定需要一個(gè)計(jì)數(shù)器,而且這個(gè)計(jì)數(shù)器只能保存在第三方服務(wù),比如redis。
大概思路:每次有相關(guān)操作的時(shí)候,就向 redis 服務(wù)器發(fā)送一個(gè) incr 命令,比如需要限制某個(gè)用戶訪問(wèn) /index 接口的次數(shù),只需要拼接用戶 id 和接口名生成 redis 的 key ,每次該用戶訪問(wèn)此接口時(shí),只需要對(duì)這個(gè) key 執(zhí)行 incr 命令,在這個(gè) key 帶上過(guò)期時(shí)間,就可以實(shí)現(xiàn)指定時(shí)間的訪問(wèn)頻率。
Nginx 限流
Nginx按請(qǐng)求速率限速模塊使用的是漏桶算法,即能夠強(qiáng)行保證請(qǐng)求的實(shí)時(shí)處理速度不會(huì)超過(guò)設(shè)置的閾值。
Nginx官方版本限制IP的連接和并發(fā)分別有兩個(gè)模塊: - limit_req_zone 用來(lái)限制單位時(shí)間內(nèi)的請(qǐng)求數(shù),即速率限制,采用的漏桶算法 “leaky bucket”。 - limit_req_conn 用來(lái)限制同一時(shí)間連接數(shù),即并發(fā)限制。























