本文將從以下幾個(gè)方面分析限流策略:
- 什么是限流
- 限流算法
- 限流算法的應(yīng)用
什么是限流
在開發(fā)高并發(fā)系統(tǒng)時(shí)纹笼,有很多手段來防止系統(tǒng)過載:緩存宾肺、降級文黎、限流。緩存的目的是提升系統(tǒng)訪問速度和增大系統(tǒng)的吞吐量痛倚,降級和限流的目的如下:
降級
降級是當(dāng)服務(wù)出問題或者影響到核心流程的性能時(shí)需要暫時(shí)屏蔽掉某些功能规婆,等高峰或者問題解決后再打開。降級一般有幾種實(shí)現(xiàn)手段蝉稳,自動(dòng)降級和人工降級:
1抒蚜、通過配置降級開關(guān),實(shí)現(xiàn)對流程的控制
2耘戚、前置化降級開關(guān)嗡髓, 基于 OpenResty+配置中心實(shí)現(xiàn)降級
3、業(yè)務(wù)降低收津,比如在大促的時(shí)候饿这,會優(yōu)先保證核心業(yè)務(wù)的流程可用
限流
限流是對資源訪問做控制浊伙,防止惡意請求流量、惡意攻擊蛹稍、或者防止流量超過系統(tǒng)峰值吧黄。它有兩個(gè)核心概念:
資源:被流量控制的對象,比如接口
策略:由限流算法和可調(diào)節(jié)的參數(shù)兩部分組成
限流算法
漏桶算法
桶本身具有一個(gè)恒定的速率往下漏水唆姐,而上方時(shí)快時(shí)慢的會有水進(jìn)入桶內(nèi)拗慨。當(dāng)桶還未滿時(shí),上方的水可以加入奉芦。一旦水滿赵抢,上方的水就無法加入。桶滿正是算法中的一個(gè)關(guān)鍵的觸發(fā)條件(即流量異常判斷成立的條件)声功。而此條件下如何處理上方流下來的水烦却,有兩種方式:
1、暫時(shí)攔截住上方水的向下流動(dòng)先巴,等待桶中的一部分水漏走后其爵,再放行上方水
2、溢出的上方水直接拋棄
令牌桶算法(能夠解決突發(fā)流量)
令牌桶算法是網(wǎng)絡(luò)流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一種算法伸蚯。典型情況下摩渺,令牌桶算法用來控制發(fā)送到網(wǎng)絡(luò)上的數(shù)據(jù)的數(shù)目,并允許突發(fā)數(shù)據(jù)的發(fā)送剂邮。
令牌桶是一個(gè)存放固定容量令牌(token)的桶摇幻,按照固定速率往桶里添加令牌; 令牌桶算法實(shí)際上由三部分組成:兩個(gè)流和一個(gè)桶,分別是令牌流挥萌、數(shù)據(jù)流和令牌桶
限流算法的應(yīng)用
Guava 的 RateLimiter 實(shí)現(xiàn)
在 Guava 中 RateLimiter 的實(shí)現(xiàn)有兩種: Bursty 和 WarmUp
bursty
bursty 基于令牌桶的算法實(shí)現(xiàn)绰姻。
RateLimiter rateLimiter=RateLimiter.create(permitPerSecond); //創(chuàng)建一個(gè) bursty實(shí)例。
rateLimiter.acquire(); //獲取 1 個(gè) permit引瀑,當(dāng)令牌數(shù)量不夠時(shí)會阻塞直到獲取為止
WarmingUp
WarmingUp基于漏桶的算法實(shí)現(xiàn)狂芋,QPS 是固定的,使用于需要預(yù)熱時(shí)間的使用場景憨栽。
RateLimiter rateLimiter =RateLimiter.create(permitsPerSecond,warmupPeriod,timeUnit);//warmupPeriod 是指預(yù)熱的時(shí)間
rateLimiter.acquire();//獲取 1 個(gè) permit
public class TokenDemo {
private int qps;
private int countOfReq;
private RateLimiter rateLimiter;
public TokenDemo(int qps, int countOfReq) {
this.qps = qps;
this.countOfReq = countOfReq;
}
public TokenDemo processWithTokenBucket(){
rateLimiter=RateLimiter.create(qps);
return this;
}
public TokenDemo processWithLeakyBucket(){
rateLimiter=RateLimiter.create(qps,00,TimeUnit.MILLISECONDS);
return this;
}
private void processRequest(){
System.out.println("RateLimiter:"+rateLimiter.getClass());
long start=System.currentTimeMillis();
for(int i=0;i<countOfReq;i++){
rateLimiter.acquire();
}
long end=System.currentTimeMillis()-start;
System.out.println("處理請求數(shù)量:"+countOfReq+"," +
"耗時(shí):"+end+"," +
"qps:"+rateLimiter.getRate()+"," +
"實(shí)際 qps:"+Math.ceil(countOfReq / (end / 1000.00)));
}
public void doProcess() throws InterruptedException {
for(int i=0;i<20;i=i+5){
TimeUnit.SECONDS.sleep(i);
processRequest();
}
}
public static void main(String[] args) throws InterruptedException
{
new TokenDemo(50,100).processWithTokenBucket().doProcess();
new TokenDemo(50,100).processWithLeakyBucket().doProcess();
}
}