任何應用都有一個設計指標,當應用的壓力超過了他設計所能承載的能力時,就好比一座只允許行人通過的獨木橋,是無法承載一輛坦克的重量的,這個時候,為了讓機器能夠繼續運行,在不宕機的情況下盡其所能的對一部分用戶提供服務,保證整個流程能夠繼續走下去,這個時候,就必須對應用進行流控,丟棄一部分用戶的請無法避免。
流控可以從多個維度來進行,比如針對QPS,並發線程數,黑白名單,加權分級等等,最典型最直接的便是針對QPS和並發線程數的流控。當然,要進行流控,首先等有一個流控的閥值,這個閥值不是說拍拍腦袋就能夠想出來,不同類型的應用,所面臨的情況不一樣,也沒有一個統一的衡量標准,必須經過多輪的壓力測試,才能夠得出一個比較靠譜的數值。
一、簡單的流控
1、使用Semphore進行並發流控
模擬代碼如下所示:
Semaphore semphore = new Semaphore(10); if(semphore.getQueueLength() > 10){ //等待隊列閥值為10時 return; } try { semphore.acquire(); //干活 } catch (InterruptedException e) { e.printStackTrace(); }finally{ semphore.release();//釋放 }
也可以參見:http://ifeve.com/concurrency-practice-1/
2、使用樂觀鎖加上下文切換進行流控
public void enter(Object obj){ boolean isUpdate = false; int countValue = count.get(); if(countValue > 0){ isUpdate = count.compareAndSet(countValue, countValue -1); if(isUpdate)return; } concurQueue.add(obj); try { obj.wait(); } catch (InterruptedException e) { logger.error("flowcontrol thread was interrupted .......",e); } return ; } public void release(){ synchronized(count){ if(count.get() < VALVE){ count.set(count.get() + 1); } } Object obj = concurQueue.remove(); if(obj != null){ synchronized (obj) { obj.notify(); } } System.out.println("notify ..............."); return ; }
具體采用信號量還是使用上下文切換形式,需要根據臨界代碼段執行的時間而定
當請求進來時,調用配置的concurrentlock的enter方法,判斷是否達到閥值,如果沒有達到閥值,則進入,進行處理, 處理完后計數器加1,如果已經達到閥值則放入等待隊列,因為等待隊列是消耗內存的,因此等待隊列也必須有閥值,如果隊列超過閥值,請求直接丟棄
二、漏斗算法和桶令牌算法
利用現存的算法,比如:漏斗算法和桶令牌算法進行流量的控制。
參考:http://www.inter12.org/archives/962
https://blog.jamespan.me/2015/10/19/traffic-shaping-with-token-bucket/
1、漏桶算法(Leaky bucket)
漏桶算法強制一個常量的輸出速率而不管輸入數據流的突發性,當輸入空閑時,該算法不執行任何動作.就像用一個底部開了個洞的漏桶接水一樣,水進入到漏桶里,桶里的水通過下面的孔以固定的速率流出,當水流入速度過大會直接溢出,可以看出漏桶算法能強行限制數據的傳輸速率.如下圖所示:
2、令牌桶(Token bucket)
令牌桶算法的基本過程如下:
每秒會有 r 個令牌放入桶中,或者說,每過 1/r 秒桶中增加一個令牌
- 桶中最多存放 b 個令牌,如果桶滿了,新放入的令牌會被丟棄
- 當一個 n 字節的數據包到達時,消耗 n 個令牌,然后發送該數據包
- 如果桶中可用令牌小於 n,則該數據包將被緩存或丟棄
漏桶和令牌桶比較
“漏桶算法”能夠強行限制數據的傳輸速率,而“令牌桶算法”在能夠限制數據的平均傳輸數據外,還允許某種程度的突發傳輸。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允許突發地傳輸數據直到達到用戶配置的上限,因此它適合於具有突發特性的流量。