在開發高并發系統時有三把利器用來保護系統:緩存、降級和限流。緩存的目的是提升系統通路速度和增大系統能處理的容量,可謂是抗高并發流量的銀彈;而降級是當服務出問題或者影響到核心流程的性能則需要暫時屏蔽掉,待高峰或者問題解決後再打開;而有些場景并不能用緩存和降級來解決,比如稀缺資源(秒殺、搶購)、寫服務(如評論、下單)、頻繁的複雜查詢(評論的最後幾頁),是以需有一種手段來限制這些場景的并發/請求量,即限流。
限流的目的是通過對并發通路/請求進行限速或者一個時間視窗内的的請求進行限速來保護系統,一旦達到限制速率則可以拒絕服務(定向到錯誤頁或告知資源沒有了)、排隊或等待(比如秒殺、評論、下單)、降級(傳回兜底資料或預設資料,如商品詳情頁庫存預設有貨)。
一般開發高并發系統常見的限流有:限制總并發數(比如資料庫連接配接池、線程池)、限制瞬時并發數(如nginx的limit_conn子產品,用來限制瞬時并發連接配接數)、限制時間視窗内的平均速率(如Guava的RateLimiter、nginx的limit_req子產品,限制每秒的平均速率);其他還有如限制遠端接口調用速率、限制MQ的消費速率。另外還可以根據網絡連接配接數、網絡流量、CPU或記憶體負載等來限流。
單應用限流
在單應用中,後端服務的限流實作起來很簡單,比如使用信号量來限制線程數量。根據線上壓測結果,QPS峰值*平均響應時間得到最大并發數。
單例的信号量執行個體
import java.util.concurrent.Semaphore;
/**
* @ClassName SemaphoreUtil
* @Author yjclsx
* @Description 信号量并發通路控制
* @Date 2019/1/30 11:22
* @Version 1.0
*/
public class SemaphoreUtil {
/**
* 線上API壓測的峰值QPS為100,平均響應時間近1s,是以接口的最大并發量為100*1=100
*/
public static final Semaphore semaphore = new Semaphore(100);
private SemaphoreUtil(){}
}
在接口的攔截器中增加并發控制,當超過最大線程數時,直接拒絕通路
if(SemaphoreUtil.semaphore.tryAcquire()){
try {
...
}finally {
SemaphoreUtil.semaphore.release();
}
}else{
throw new ServiceException("系統繁忙,請稍後再試!");
}
分布式系統限流
在分布式系統中,後端服務的限流可以用redis實作,比如通過redis實作限制每個接口的每秒請求數量。
在redis裡放置計數器,key用時間戳(精确到秒)+apiPath,value是該API接口在該秒内累計的請求次數,在你的代碼中增加統一的限流攔截器,每次請求都将此計數器加一,當超過預設的門檻值時就拒絕通路(比如傳回自定義http錯誤碼5xx)。當時間視窗流轉到下一秒時,由于key變化了,是以會自動使用新的計數器,之前的計數器可以設定一個逾時時間來自動過期。
當然實作限流的方案還有很多種,大家都來說說自己系統的限流方案吧!