Java互聯網架構-分散式高併發限流系統應用設計
概述
高併發系統時有三把利器用來保護系統:緩存、降級和限流。
緩存
緩存比較好理解,在大型高併發系統中,如果沒有緩存資料庫將分分鐘被爆,系統也會瞬間癱瘓。使用緩存不單單能夠提升系統訪問速度、提高併發訪問量,也是保護資料庫、保護系統的有效方式。大型網站一般主要是「讀」,緩存的使用很容易被想到。在大型「寫」系統中,緩存也常常扮演者非常重要的角色。比如累積一些數據批量寫入,內存裡面的緩存隊列(生產消費),以及HBase寫數據的機制等等也都是通過緩存提升系統的吞吐量或者實現系統的保護措施。甚至消息中間件,你也可以認為是一種分散式的數據緩存。
降級
服務降級是當伺服器壓力劇增的情況下,根據當前業務情況及流量對一些服務和頁面有策略的降級,以此釋放伺服器資源以保證核心任務的正常運行。降級往往會指定不同的級別,面臨不同的異常等級執行不同的處理。根據服務方式:可以拒接服務,可以延遲服務,也有時候可以隨機服務。根據服務範圍:可以砍掉某個功能,也可以砍掉某些模塊。總之服務降級需要根據不同的業務需求採用不同的降級策略。主要的目的就是服務雖然有損但是總比沒有好。
Advertisements
限流
限流可以認為服務降級的一種,限流就是限制系統的輸入和輸出流量已達到保護系統的目的。一般來說系統的吞吐量是可以被測算的,為了保證系統的穩定運行,一旦達到的需要限制的閾值,就需要限制流量並採取一些措施以完成限制流量的目的。比如:延遲處理,拒絕處理,或者部分拒絕處理等等。
一丶限流方式
限制總併發數(比如資料庫連接池、線程池)
限制瞬時併發數(如nginx的limit_conn模塊,用來限制瞬時併發連接數)
限制時間窗口內的平均速率(如Guava的RateLimiter、nginx的limit_req模塊,限制每秒的平均速率)
限制遠程介面調用速率
限制MQ的消費速率。
可以根據網路連接數、網路流量、CPU或內存負載等來限流
Advertisements
限流演算法
令牌桶
漏桶
計數器
有時候我們還使用計數器來進行限流,主要用來限制總併發數,比如資料庫連接池、線程池、秒殺的併發數;只要全局總請求數或者一定時間段的總請求數設定的閥值則進行限流,是簡單粗暴的總數量限流,而不是平均速率限流
令牌桶vs漏桶
令牌桶限制的是平均流入速率,允許突發請求,並允許一定程度突發流量
漏桶限制的是常量流出速率,從而平滑突發流入速率
二丶應用級別限流
限流總併發/連接/請求數
如果你使用過Tomcat,其Connector其中一種配置有如下幾個參數:
acceptCount:如果Tomcat的線程都忙於響應,新來的連接會進入隊列排隊,如果超出排隊大小,則拒絕連接
maxConnections:瞬時最大連接數,超出的會排隊等待
maxThreads:Tomcat能啟動用來處理請求的最大線程數,如果請求處理量一直遠遠大於最大線程數則可能會僵死
限流總資源數
可以使用池化技術來限制總資源數:連接池、線程池。比如分配給每個應用的資料庫連接是100,那麼本應用最多可以使用100個資源,超出了可以等待或者拋異常
限流某個介面的總併發/請求數
可以使用Java中的AtomicLong,示意代碼:
try{
if(atomic.incrementAndGet() > 限流數) {
//拒絕請求
}
//處理請求
}finally{
atomic.decrementAndGet();
}
限流某個介面的時間窗請求數
使用Guava的Cache,示意代碼:
LoadingCache counter =
CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.build(newCacheLoader() {
@Override
publicAtomicLong load(Long seconds)throwsException {
return newAtomicLong(0);
}
});
longlimit =1000;
while(true) {
//得到當前秒longcurrentSeconds = System.currentTimeMillis() /1000;
if(counter.get(currentSeconds).incrementAndGet() > limit) {
System.out.println("限流了:"+ currentSeconds);
continue;
}
//業務處理
}
平滑限流某個介面的請求數
之前的限流方式都不能很好地應對突發請求,即瞬間請求可能都被允許從而導致一些問題;因此在一些場景中需要對突發請求進行整形,整形為平均速率請求處理
Guava框架提供了令牌桶演算法實現
Guava RateLimiter提供了令牌桶演算法實現:平滑突發限流(SmoothBursty)和平滑預熱限流(SmoothWarmingUp)實現
平滑突發限流(SmoothBursty)
RateLimiter limiter = RateLimiter.create(5);
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
將得到類似如下的輸出:
0.0
0.198239
0.196083
0.200609
0.199599
0.19961
limiter.acquire(5)表示桶的容量為5且每秒新增5個令牌
平滑預熱限流(SmoothWarmingUp)
RateLimiter limiter = RateLimiter.create(5,1000, TimeUnit.MILLISECONDS);
for(inti =1; i <5;i++) {
System.out.println(limiter.acquire());
}
Thread.sleep(1000L);
for(inti =1; i <5;i++) {
System.out.println(limiter.acquire());
}
將得到類似如下的輸出:
0.0
0.51767
0.357814
0.219992
0.199984
0.0
0.360826
0.220166
0.199723
0.199555
SmoothWarmingUp創建方式:RateLimiter.create(doublepermitsPerSecond, long warmupPeriod, TimeUnit unit)
permitsPerSecond表示每秒新增的令牌數,warmupPeriod表示在從冷啟動速率過渡到平均速率的時間間隔。
速率是梯形上升速率的,也就是說冷啟動時會以一個比較大的速率慢慢到平均速率;然後趨於平均速率(梯形下降到平均速率)。可以通過調節warmupPeriod參數實現一開始就是平滑固定速率。
三丶分散式限流
分散式限流最關鍵的是要將限流服務做成原子化,而解決方案可以使使用redis+lua或者nginx+lua技術進行實現
四丶接入層限流
接入層通常指請求流量的入口,該層的主要目的有:負載均衡、非法請求過濾、請求聚合、緩存、降級、限流、A/B測試、服務質量監控等等
對於Nginx接入層限流可以使用Nginx自帶了兩個模塊:連接數限流模塊ngx_http_limit_conn_module和漏桶演算法實現的請求限流模塊ngx_http_limit_req_module。還可以使用OpenResty提供的Lua限流模塊lua-resty-limit-traffic進行更複雜的限流場景。
limit_conn用來對某個KEY對應的總的網路連接數進行限流,可以按照如IP、域名維度進行限流。
limit_req用來對某個KEY對應的請求的平均速率進行限流,並有兩種用法:平滑模式(delay)和允許突發模式(nodelay)。
OpenResty提供的Lua限流模塊lua-resty-limit-traffic進行更複雜的限流場景。
總結
到這裡,分散式高併發限流系統應用設計就結束了,,不足之處還望大家多多包涵!!覺得收穫的話可以點個關注收藏轉發一波喔,謝謝大佬們支持。(吹一波,233~~)
下面和大家交流幾點編程的經驗:
1、多寫多敲代碼,好的代碼與紮實的基礎知識一定是實踐出來的
2丶 測試、測試再測試,如果你不徹底測試自己的代碼,那恐怕你開發的就不只是代碼,可能還會聲名狼藉。
3丶 簡化編程,加快速度,代碼風騷,在你完成編碼后,應回頭並且優化它。從長遠來看,這裡或那裡一些的改進,會讓後來的支持人員更加輕鬆。
最後,每一位讀到這裡的網友,感謝你們能耐心地看完。希望在成為一名更優秀的Java程序員的道路上,我們可以一起學習、一起進步。
內部交流群469717771 歡迎各位前來交流和分享, 驗證:(007)