業務學習 -- 高併發系統保護之限流和降級熔斷

簡介

目前在分佈式系統開發中隨着業務的增長,業務與業務之間的隔離關係越來越明確,限流目前也是在分佈式系統中常見的需求。
限流的出發點可能基於以下考慮:
(1)簡單來說就是控制流程,防止因爲請求流量過大導致服務崩潰。
(2)通過限流設計可以限制不同的接口及服務的優先級關係,在資源一定時優先提供重要的接口及服務。
(3)流量整理,針對流程進行削峯填谷整理。

限流方式

目前常見的限流方式有如下幾種:
(1)請求入口限流算法:線程池及等待隊列(Tomcat)、漏桶算法、令牌桶算法和計數器算法
(2)接口調用限流實現方式:線程池和信號量
(3)MQ 流量削峯填谷

限流算法:
1、令牌桶算法
在這裏插入圖片描述
算法思想是:

  • 令牌以可調控速率產生,並緩存到令牌桶中;
  • 令牌桶放滿時,多餘的令牌被丟棄;
  • 請求要消耗等比例的令牌才能被處理;
  • 令牌不夠時,請求被緩存。

2、漏桶算法
在這裏插入圖片描述
算法思想是:

  • 水(請求)從上方倒入水桶,從水桶下方流出(被處理);
  • 來不及流出的水存在水桶中(緩衝),以固定速率流出;
  • 水桶滿後水溢出(丟棄)。
  • 這個算法的核心是:緩存請求、勻速處理、多餘的請求直接丟棄。
  • 相比漏桶算法,令牌桶算法不同之處在於它不但有一隻“桶”,還有個隊列,這個桶是用來存放令牌的,隊列纔是用來存放請求的。

3、計數器算法

計數器算法是限流算法裏最簡單也是最容易實現的一種算法。比如我們規定,對於A接口來說,我們1分鐘的訪問次數不能超過100個。那麼我們可以這麼做:在一開 始的時候,我們可以設置一個計數器counter,每當一個請求過來的時候,counter就加1,如果counter的值大於100並且該請求與第一個 請求的間隔時間還在1分鐘之內,那麼說明請求數過多;如果該請求與第一個請求的間隔時間大於1分鐘,且counter的值還在限流範圍內,那麼就重置 counter,具體算法的示意圖如下:
在這裏插入圖片描述

限流實現

Nginx 限流

Nginx主要有兩種限流方式:
(1)按連接數限流(ngx_http_limit_conn_module)(令牌算法實現)
(2)按請求速率限流(ngx_http_limit_req_module)(漏桶算法實現)

ngx_http_limit_req_module

模塊提供限制請求處理速率能力,使用了漏桶算法(leaky bucket)。下面例子使用 nginx limit_req_zone 和 limit_req 兩個指令,限制單個IP的請求處理速率。在 nginx.conf http 中添加限流配置:

格式:limit_req_zone key zone rate
http {
limit_req_zone $binary_remote_addr zone=myRateLimit:10m rate=10r/s;
}

配置 server,使用 limit_req 指令應用限流。

server {
location / {
limit_req zone=myRateLimit;
proxy_pass http://my_upstream;
}
}

key :定義限流對象,binary_remote_addr 是一種key,表示基於 remote_addr(客戶端IP) 來做限流,binary_ 的目的是壓縮內存佔用量。
zone:定義共享內存區來存儲訪問信息, myRateLimit:10m 表示一個大小爲10M,名字爲myRateLimit的內存區域。1M能存儲16000 IP地址的訪問信息,10M可以存儲16W IP地址訪問信息。
rate 用於設置最大訪問速率,rate=10r/s 表示每秒最多處理10個請求。Nginx 實際上以毫秒爲粒度來跟蹤請求信息,因此 10r/s 實際上是限制:每100毫秒處理一個請求。這意味着,自上一個請求處理完後,若後續100毫秒內又有請求到達,將拒絕處理該請求。

burst
上面例子限制 10r/s,如果有時正常流量突然增大,超出的請求將被拒絕,無法處理突發流量,可以結合 burst 參數使用來解決該問題。

server {
location / {
limit_req zone=myRateLimit burst=20;
proxy_pass http://my_upstream;
}
}

burst 譯爲突發、爆發,表示在超過設定的處理速率後能額外處理的請求數。當 rate=10r/s 時,將1s拆成10份,即每100ms可處理1個請求。

此處,burst=20 ,若同時有21個請求到達,Nginx 會處理第一個請求,剩餘20個請求將放入隊列,然後每隔100ms從隊列中獲取一個請求進行處理。若請求數大於21,將拒絕處理多餘的請求,直接返回503.

不過,單獨使用 burst 參數並不實用。假設 burst=50 ,rate依然爲10r/s,排隊中的50個請求雖然每100ms會處理一個,但第50個請求卻需要等待 50 * 100ms即 5s,這麼長的處理時間自然難以接受。

因此,burst 往往結合 nodelay 一起使用。

server {
location / {
limit_req zone=myRateLimit burst=20 nodelay;
proxy_pass http://my_upstream;
}
}

nodelay 針對的是 burst 參數,burst=20 nodelay 表示這20個請求立馬處理,不能延遲,相當於特事特辦。不過,即使這20個突發請求立馬處理結束,後續來了請求也不會立馬處理。burst=20 相當於緩存隊列中佔了20個坑,即使請求被處理了,這20個位置這隻能按 100ms一個來釋放。

這就達到了速率穩定,但突然流量也能正常處理的效果。

ngx_http_limit_conn_module

ngx_http_limit_conn_module 提供了限制連接數的能力,利用 limit_conn_zone 和 limit_conn 兩個指令即可。下面是 Nginx 官方例子:

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
...
limit_conn perip 10;
limit_conn perserver 100;
}

limit_conn perip 10 作用的key 是 $binary_remote_addr,表示限制單個IP同時最多能持有10個連接。

limit_conn perserver 100 作用的key是 $server_name,表示虛擬主機(server) 同時能處理併發連接的總數。

需要注意的是:只有當 request header 被後端server處理後,這個連接才進行計數。

Tomcat 線程池限流

tomcat 通過以下三個配置參數來進行限流操作:
(1)maxThreads(最大線程數):每一次HTTP請求到達Web服務,tomcat都會創建一個線程來處理該請求,那麼最大線程數決定了Web服務可以同時處理多少個請求,默認200.
(2)accepCount(最大等待數):當調用Web服務的HTTP請求數達到tomcat的最大線程數時,還有新的HTTP請求到來,這時tomcat會將該請求放在等待隊列中,這個acceptCount就是指能夠接受的最大等待數,默認100.如果等待隊列也被放滿了,這個時候再來新的請求就會被tomcat拒絕(connection refused)。
(3)maxConnections(最大連接數):這個參數是指在同一時間,tomcat能夠接受的最大連接數。一般這個值要大於maxThreads+acceptCount。

    <Connector port="8080" protocol="HTTP/1.1" 
               connectionTimeout="20000" 
               redirectPort="8443" acceptCount="500" maxThreads="400" />

Spring Cloud Hystrix

Spring Cloud Hystrix 是 Spring Cloud 全家桶系列中提供的限流及熔斷框架,其提供了基於線程池和信號量的熔斷機制
1、通過配置隔離策略來選擇是使用線程池還是信號量模式,默認線程池方式。

//信號量模式
ExecutionIsolationStrategy.SEMAPHORE
//線程池方式
ExecutionIsolationStrategy.THREAD

2、信號量和線程模式比較:
(1)線程池隔離模式:使用一個線程池來存儲當前請求,線程池對請求作處理,設置任務返回處理超時時間,堆積的請求先入線程池隊列。這種方式要爲每個依賴服務申請線程池,有一定的資源消耗,好處是可以應對突發流量(流量洪峯來臨時,處理不完可將數據存儲到線程池隊裏慢慢處理)
(2)信號量隔離模式:使用一個原子計數器(或信號量)記錄當前有多少個線程在運行,請求來先判斷計數器的數值,若超過設置的最大線程個數則丟棄該類型的新請求,若不超過則執行計數操作請求來計數器+1,請求返回計數器-1。這種方式是嚴格的控制線程且立即返回模式,無法應對突發流量(流量洪峯來臨時,處理的線程超過數量,其他的請求會直接返回,不繼續去請求依賴的服務)
在這裏插入圖片描述
3、基於線程池或信號量對接口進行限流及降級配置示例

@HystrixCommand(fallbackMethod = "getDefaultUserName", threadPoolKey = "query_user",
        threadPoolProperties = {
                @HystrixProperty(name = CORE_SIZE, value = "10"),
                @HystrixProperty(name = MAX_QUEUE_SIZE, value = "10")
        },
        commandProperties = {
                @HystrixProperty(name = CIRCUIT_BREAKER_ENABLED, value = "true"),
                @HystrixProperty(name = CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "1000"),
                @HystrixProperty(name = CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "25"),
				@HystrixProperty(name = "execution.isolation.strategy", value = "thread")
        }
)
static String getUserName(String userID) throws InterruptedException {
  Thread.sleep(-1);
  return userID;
}
public String getDefaultUserName(String userID) {
  return "";
}

Spring Cloud Hystrix 接口調用限流及熔斷流程:
在這裏插入圖片描述

MQ 削峯填谷

目前 MQ(kafka、RocketMQ和RabbitMQ) 常被我們用來服務之間解耦、通知,但是目前比較常用的場景也是對流量進行削峯填谷,首先MQ 會對接收的消息持久化,盡最大程度的處理請求並持久化消息,消費者會根據自己的業務特別基於推或者拉模式來進行消息消費,在一定程度上也是能夠在高併發下對系統進行保護。

基於Redis 實現限流

1、計數算法:
基於redis的incrby 加一和decrby 操作,來控制分佈式系統中計數器。

2、令牌桶算法:

基於 Redis 的 list接口可以實現令牌桶令牌補充和令牌消耗操作。

參考:
https://www.cnblogs.com/biglittleant/p/8979915.html
https://juejin.im/post/5b3a25e46fb9a024fc284de4

https://blog.wangqi.love/articles/Java/基於redis的分佈式限流方案.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章