聊聊高併發系統之限流特技
在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。快取的目的是提升系統訪問速度和增大系統能處理的容量,可謂是抗高併發流量的銀彈;而降級是當服務出問題或者影響到核心流程的效能則需要暫時遮蔽掉,待高峰或者問題解決後再開啟;而有些場景並不能用快取和降級來解決,比如稀缺資源(秒殺、搶購)、寫服務(如評論、下單)、頻繁的複雜查詢(評論的最後幾頁),因此需有一種手段來限制這些場景的併發/請求量,即限流。
限流的目的是通過對併發訪問/請求進行限速或者一個時間視窗內的的請求進行限速來保護系統,一旦達到限制速率則可以拒絕服務(定向到錯誤頁或告知資源沒有了)、排隊或等待(比如秒殺、評論、下單)、降級(返回兜底資料或預設資料,如商品詳情頁庫存預設有貨)。
一般開發高併發系統常見的限流有:限制總併發數(比如資料庫連線池、執行緒池)、限制瞬時併發數(如nginx的limit_conn模組,用來限制瞬時併發連線數)、限制時間視窗內的平均速率(如Guava的RateLimiter、nginx的limit_req模組,限制每秒的平均速率);其他還有如限制遠端介面呼叫速率、限制MQ的消費速率。另外還可以根據網路連線數、網路流量、CPU或記憶體負載等來限流。
先有快取這個銀彈,後有限流來應對618、雙十一高併發流量,在處理高併發問題上可以說是如虎添翼,不用擔心瞬間流量導致系統掛掉或雪崩,最終做到有損服務而不是不服務;限流需要評估好,不可亂用,否則會正常流量出現一些奇怪的問題而導致使用者抱怨。
在實際應用時也不要太糾結演算法問題,因為一些限流演算法實現是一樣的只是描述不一樣;具體使用哪種限流技術還是要根據實際場景來選擇,不要一味去找最佳模式,白貓黑貓能解決問題的就是好貓。
因在實際工作中遇到過許多人來問如何進行限流,因此本文會詳細介紹各種限流手段。那麼接下來我們從限流演算法、應用級限流、分散式限流、接入層限流來詳細學習下限流技術手段。
限流演算法
常見的限流演算法有:令牌桶、漏桶。計數器也可以進行粗暴限流實現。
令牌桶演算法
令牌桶演算法是一個存放固定容量令牌的桶,按照固定速率往桶裡新增令牌。令牌桶演算法的描述如下:
-
假設限制
-
桶中最多存放b個令牌,當桶滿時,新新增的令牌被丟棄或拒絕;
-
當一個n個位元組大小的資料包到達,將從桶中刪除n個令牌,接著資料包被髮送到網路上;
-
如果桶中的令牌不足n個,則不會刪除令牌,且該資料包將被限流(要麼丟棄,要麼緩衝區等待)。
漏桶演算法
漏桶作為計量工具(The Leaky Bucket Algorithm as a Meter)時,可以用於流量整形(Traffic Shaping)和流量控制(TrafficPolicing),漏桶演算法的描述如下:
-
一個固定容量的漏桶,按照常量固定速率流出水滴;
-
如果桶是空的,則不需流出水滴;
-
可以以任意速率流入水滴到漏桶;
-
如果流入水滴超出了桶的容量,則流入的水滴溢位了(被丟棄),而漏桶容量是不變的。
令牌桶和漏桶對比:
-
令牌桶是按照固定速率往桶中新增令牌,請求是否被處理需要看桶中令牌是否足夠,當令牌數減為零時則拒絕新的請求;
-
漏桶則是按照常量固定速率流出請求,流入請求速率任意,當流入的請求數累積到漏桶容量時,則新流入的請求被拒絕;
-
令牌桶限制的是平均流入速率(允許突發請求,只要有令牌就可以處理,支援一次拿3個令牌,4個令牌),並允許一定程度突發流量;
-
漏桶限制的是常量流出速率(即流出速率是一個固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),從而平滑突發流入速率;
-
令牌桶允許一定程度的突發,而漏桶主要目的是平滑流入速率;
-
兩個演算法實現可以一樣,但是方向是相反的,對於相同的引數得到的限流效果是一樣的。
另外有時候我們還使用計數器來進行限流,主要用來限制總併發數,比如資料庫連線池、執行緒池、秒殺的併發數;只要全域性總請求數或者一定時間段的總請求數設定的閥值則進行限流,是簡單粗暴的總數量限流,而不是平均速率限流。
到此基本的演算法就介紹完了,接下來我們首先看看應用級限流。
應用級限流
限流總併發/連線/請求數
對於一個應用系統來說一定會有極限併發/請求數,即總有一個TPS/QPS閥值,如果超了閥值則系統就會不響應使用者請求或響應的非常慢,因此我們最好進行過載保護,防止大量請求湧入擊垮系統。
如果你使用過Tomcat,其Connector 其中一種配置有如下幾個引數:
acceptCount:如果Tomcat的執行緒都忙於響應,新來的連線會進入佇列排隊,如果超出排隊大小,則拒絕連線;
maxConnections: 瞬時最大連線數,超出的會排隊等待;
maxThreads:Tomcat能啟動用來處理請求的最大執行緒數,如果請求處理量一直遠遠大於最大執行緒數則可能會僵死。
詳細的配置請參考官方文件。另外如Mysql(如max_connections)、Redis(如tcp-backlog)都會有類似的限制連線數的配置。
限流總資源數
如果有的資源是稀缺資源(如資料庫連線、執行緒),而且可能有多個系統都會去使用它,那麼需要限制應用;可以使用池化技術來限制總資源數:連線池、執行緒池。比如分配給每個應用的資料庫連線是100,那麼本應用最多可以使用100個資源,超出了可以等待或者拋異常。
限流某個介面的總併發/請求數
如果介面可能會有突發訪問情況,但又擔心訪問量太大造成崩潰,如搶購業務;這個時候就需要限制這個介面的總併發/請求數總請求數了;因為粒度比較細,可以為每個介面都設定相應的閥值。可以使用Java中的AtomicLong進行限流:
try {
if(atomic.incrementAndGet() > 限流數) {
//拒絕請求
}
//處理請求
} finally {
atomic.decrementAndGet();
}
適合對業務無損的服務或者需要過載保護的服務進行限流,如搶購業務,超出了大小要麼讓使用者排隊,要麼告訴使用者沒貨了,對使用者來說是可以接受的。而一些開放平臺也會限制使用者呼叫某個介面的試用請求量,也可以用這種計數器方式實現。這種方式也是簡單粗暴的限流,沒有平滑處理,需要根據實際情況選擇使用;
限流某個介面的時間窗請求數
即一個時間視窗內的請求數,如想限制某個介面/服務每秒/每分鐘/每天的請求數/呼叫量。如一些基礎服務會被很多其他系統呼叫,比如商品詳情頁服務會呼叫基礎商品服務呼叫,但是怕因為更新量比較大將基礎服務打掛,這時我們要對每秒/每分鐘的呼叫量進行限速;一種實現方式如下所示:
LoadingCache<Long, AtomicLong> counter =
CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.build(new CacheLoader<Long, AtomicLong>() {
@Override
public AtomicLong load(Long seconds) throws Exception {
return new AtomicLong(0);
}
});
long limit = 1000;
while(true) {
//得到當前秒
long currentSeconds = System.currentTimeMillis() / 1000;
if(counter.get(currentSeconds).incrementAndGet() > limit) {
System.out.println("限流了:" + currentSeconds);
continue;
}
//業務處理
}
我們使用Guava的Cache來儲存計數器,過期時間設定為2秒(保證1秒內的計數器是有的),然後我們獲取當前時間戳然後取秒數來作為KEY進行計數統計和限流,這種方式也是簡單粗暴,剛才說的場景夠用了。
平滑限流某個介面的請求數
之前的限流方式都不能很好地應對突發請求,即瞬間請求可能都被允許從而導致一些問題;因此在一些場景中需要對突發請求進行整形,整形為平均速率請求處理(比如5r/s,則每隔200毫秒處理一個請求,平滑了速率)。這個時候有兩種演算法滿足我們的場景:令牌桶和漏桶演算法。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
1、RateLimiter.create(5) 表示桶容量為5且每秒新增5個令牌,即每隔200毫秒新增一個令牌;
2、limiter.acquire()表示消費一個令牌,如果當前桶中有足夠令牌則成功(返回值為0),如果桶中沒有令牌則暫停一段時間,比如發令牌間隔是200毫秒,則等待200毫秒後再去消費令牌(如上測試用例返回的為0.198239,差不多等待了200毫秒桶中才有令牌可用),這種實現將突發請求速率平均為了固定請求速率。
再看一個突發示例:
RateLimiter limiter = RateLimiter.create(5);System.out.println(limiter.acquire(5));
System.out.println(limiter.acquire(1));
System.out.println(limiter.acquire(1));
將得到類似如下的輸出:
0.0
0.98745
0.183553
0.199909
limiter.acquire(5)表示桶的容量為5且每秒新增5個令牌,令牌桶演算法允許一定程度的突發,所以可以一次性消費5個令牌,但接下來的limiter.acquire(1)將等待差不多1秒桶中才能有令牌,且接下來的請求也整形為固定速率了。
RateLimiter limiter = RateLimiter.create(5);System.out.println(limiter.acquire(10));
System.out.println(limiter.acquire(1));
System.out.println(limiter.acquire(1));
將得到類似如下的輸出:
0.0
1.997428
0.192273
0.200616
同上邊的例子類似,第一秒突發了10個請求,令牌桶演算法也允許了這種突發(允許消費未來的令牌),但接下來的limiter.acquire(1)將等待差不多2秒桶中才能有令牌,且接下來的請求也整形為固定速率了。
接下來再看一個突發的例子:
System.out.println(limiter.acquire());
Thread.sleep(2000L);
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.0
0.0
0.0
0.499876
0.495799
1、建立了一個桶容量為2且每秒新增2個令牌;
2、首先呼叫limiter.acquire()消費一個令牌,此時令牌桶可以滿足(返回值為0);
3、然後執行緒暫停2秒,接下來的兩個limiter.acquire()都能消費到令牌,第三個limiter.acquire()也同樣消費到了令牌,到第四個時就需要等待500毫秒了。
此處可以看到我們設定的桶容量為2(即允許的突發量),這是因為SmoothBursty中有一個引數:最大突發秒數(maxBurstSeconds)預設值是1s,突發量/桶容量=速率*maxBurstSeconds,所以本示例桶容量/突發量為2,例子中前兩個是消費了之前積攢的突發量,而第三個開始就是正常計算的了。令牌桶演算法允許將一段時間內沒有消費的令牌暫存到令牌桶中,留待未來使用,並允許未來請求的這種突發。
SmoothBursty通過平均速率和最後一次新增令牌的時間計算出下次新增令牌的時間的,另外需要一個桶暫存一段時間內沒有使用的令牌(即可以突發的令牌數)。另外RateLimiter還提供了tryAcquire方法來進行無阻塞或可超時的令牌消費。
因為SmoothBursty允許一定程度的突發,會有人擔心如果允許這種突發,假設突然間來了很大的流量,那麼系統很可能扛不住這種突發。因此需要一種平滑速率的限流工具,從而系統冷啟動後慢慢的趨於平均固定速率(即剛開始速率小一些,然後慢慢趨於我們設定的固定速率)。Guava也提供了SmoothWarmingUp來實現這種需求,其可以認為是漏桶演算法,但是在某些特殊場景又不太一樣。
SmoothWarmingUp建立方式:RateLimiter.create(doublepermitsPerSecond, long warmupPeriod, TimeUnit unit)
permitsPerSecond表示每秒新增的令牌數,warmupPeriod表示在從冷啟動速率過渡到平均速率的時間間隔。
示例如下:
RateLimiter limiter = RateLimiter.create(5, 1000, TimeUnit.MILLISECONDS);for(int i = 1; i < 5;i++) {
System.out.println(limiter.acquire());
}
Thread.sleep(1000L);
for(int i = 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
速率是梯形上升速率的,也就是說冷啟動時會以一個比較大的速率慢慢到平均速率;然後趨於平均速率(梯形下降到平均速率)。可以通過調節warmupPeriod引數實現一開始就是平滑固定速率。
到此應用級限流的一些方法就介紹完了。假設將應用部署到多臺機器,應用級限流方式只是單應用內的請求限流,不能進行全侷限流。因此我們需要分散式限流和接入層限流來解決這個問題。
分散式限流
分散式限流最關鍵的是要將限流服務做成原子化,而解決方案可以使使用redis+lua或者nginx+lua技術進行實現,通過這兩種技術可以實現的高併發和高效能。
首先我們來使用redis+lua實現時間窗內某個介面的請求數限流,實現了該功能後可以改造為限流總併發/請求數和限制總資源數。Lua本身就是一種程式語言,也可以使用它實現複雜的令牌桶或漏桶演算法。
redis+lua實現中的lua指令碼:
local key = KEYS[1] --限流KEY(一秒一個)local limit = tonumber(ARGV[1]) --限流大小
local current = tonumber(redis.call("INCRBY", key, "1")) --請求數+1
if current > limit then --如果超出限流大小
return 0
elseif current == 1 then --只有第一次訪問需要設定2秒的過期時間
redis.call("expire", key,"2")
end
return 1
如上操作因是在一個lua指令碼中,又因Redis是單執行緒模型,因此是執行緒安全的。如上方式有一個缺點就是當達到限流大小後還是會遞增的,可以改造成如下方式實現:
local key = KEYS[1] --限流KEY(一秒一個)local limit = tonumber(ARGV[1]) --限流大小
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then --如果超出限流大小
return 0
else --請求數+1,並設定2秒過期
redis.call("INCRBY", key,"1")
redis.call("expire", key,"2")
return 1
end
如下是Java中判斷是否需要限流的程式碼:
public static boolean acquire() throws Exception {String luaScript = Files.toString(new File("limit.lua"), Charset.defaultCharset());
Jedis jedis = new Jedis("192.168.147.52", 6379);
String key = "ip:" + System.currentTimeMillis()/ 1000; //此處將當前時間戳取秒數
Stringlimit = "3"; //限流大小
return (Long)jedis.eval(luaScript,Lists.newArrayList(key), Lists.newArrayList(limit)) == 1;
}
因為Redis的限制(Lua中有寫操作不能使用帶隨機性質的讀操作,如TIME)不能在Redis
Lua中使用TIME
上一篇《聊聊高併發系統限流特技-1》講了限流演算法、應用級限流、分散式限流;本篇將介紹接入層限流實現。
接入層限流
接入層通常指請求流量的入口,該層的主要目的有:負載均衡、非法請求過濾、請求聚合、快取、降級、限流、 A/B 測試、服務質量監控
在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。快取的目的是提升系統訪問速度和增大系統能處理的容量,可謂是抗高併發流量的銀彈;而降級是當服務出問題或者影響到核心流程的效能則需要暫時遮蔽掉,待高峰或者問題解決後再開啟;而有些場景並不能用快取和降級來解決,比如
在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。快取的目的是提升系統訪問速度和增大系統能處理的容量,可謂是抗高併發流量的銀彈;而降級是當服務出問題或者影響到核心流程的效能則需要暫時遮蔽掉,待高峰或者問題解決後再開啟;而有些場景並不能用快取和降級來解決,比如稀缺資源(秒殺、搶購)、寫服務(如評論、
轉載 ------ 2016-06-24 張開濤相關文章
在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。快取的目的是提升系統訪問速度和增大系統能處理的容量,可謂是抗高併發流量的銀彈;而降級是當服務出問題或者影響到核心流程的效能則需要暫時遮蔽掉,待高峰或者問 有一種 mic jedis .net cep 防止 方法 框架 ise 在開發高並發系統時有三把利器用來保護系統:緩存、降級和限流。緩存的目的是提升系統訪問速度和增大系統能處理的容量,可謂是抗高並發流量的銀彈;而降級是當服務出問題或者影響到核心流程的性能則需要暫時屏蔽掉,待
在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。之前已經有一些文章介紹過快取和限流了。本文將詳細聊聊降級。當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的效能時,仍然需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵資料進行自動降級,也
簡介
最近遇到很多人來諮詢我關於瀏覽器快取的一些問題,而這些問題都是類似的,因此總結本文來解答以後遇到類似問題的朋友。
因本文主要以瀏覽器快取場景介紹,所以非瀏覽器場景下的一些用法本文不會介紹,而且本文以chrome為測試瀏覽器。
瀏覽器快取是指當我們使用瀏覽器訪問一些網站頁面或者http服務時,根
佇列在資料結構中是一種線性表,從一端插入資料,然後從另一端刪除資料。本文目的不是講解各種佇列演算法,而是在應用層面講述使用佇列能解決哪些場景問題。
在我開發過的系統中,不是所有的業務都必須實時處理、不是所有的請求都必須實時反饋結果給使用者、不是所有的請求/處理都必須100%處理成功、不知道誰依賴“ 概要在大資料量高併發訪問時,經常會出現服務或介面面對暴漲的請求而不可用的情況,甚至引發連鎖反映導致整個系統崩潰。此時你需要使用的技術手段之一就是限流,當請求達到一定的併發數或速率,就進行等待、排隊、降級、拒絕服務等。在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。快取快取比較好理解,在大型高併發系
限流實踐
高併發系統三把利器:快取、降級和限流
限流大家多少都有所瞭解,就是限制當前請求流量、數量,避免系統被蜂擁而至的流量瞬間擊垮,剛好最近手上業務系統做了限流,順便整理出來。
我們的業務場景
雲變數和雲列表,使用者線上使用的程式變數和陣列,一些作品執行人數過多或者作
開濤大神在部落格中說過:在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。本文結合作者的一些經驗介紹限流的相關概念、演算法和常規的實現方式。快取快取比較好理解,在大型高併發系統中,如果沒有快取資料庫將分分鐘被爆,系統也會瞬間癱瘓。使用快取不單單能夠提升系統訪問速度、
幾種呼叫方式
同步阻塞呼叫
即序列呼叫,響應時間為所有服務的響應時間總和;
半非同步(非同步Future)
執行緒池,非同步Future,使用場景:併發請求多服務,總耗時為最長響應時間;提升總響應時間,但是阻塞主請求執行緒,高併發時依然會造成執行緒數過多,CPU上下文切換;
全非同步(Cal 在做電商系統時,流量入口如首頁、活動頁、商品詳情頁等系統承載了網站的大部分流量,而這些系統的主要職責包括聚合資料拼裝模板、熱點統計、快取、下游功能降級開關、託底資料等等。其中聚合資料需要呼叫其它多個系統服務獲取資料、拼裝資料/模板然後返回給前端,聚合資料來源主要有依賴系統/服務、快取、資料庫等;而系統之間
在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。之前已經有一些文章介紹過快取和限流了。本文將詳細聊聊降級。當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的效能時,仍然需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵資料
在做電商系統時,流量入口如首頁、活動頁、商品詳情頁等系統承載了網站的大部分流量,而這些系統的主要職責包括聚合資料拼裝模板、熱點統計、快取、下游功能降級開關、託底資料等等。其中聚合資料需要呼叫其它多個系統服務獲取資料、拼裝資料/模板然後返回給前端,聚合資料來源主要有依賴系統
在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。之前已經有一些文章介紹過快取和限流了。本文將詳細聊聊降級。當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的效能時,仍然需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵 最近又遇到了一次慢查把db(mariadb10)幾乎打掛的案例,作為一個核心支付系統的技術負責人,真是每日如履薄冰。因為之前支付系統經常出問題,現在各個BG對支付系統都盯得很緊。這次要不是我及時讓DB給暴力清理資料,沒準又提一個P2故障;
抱怨歸抱怨,事後覆盤,一絲都不能馬虎。首先,描述一下故障的全過程。起
一、服務等級協議
我們常說的N個9,就是對SLA的一個描述。
SLA全稱是ServiceLevel Agreement,翻譯為服務水平協議,也稱服務等級協議,它表明了公有云提供服務的等級以及質量。
例如阿里雲對外承諾的就是一個服務週期內叢集服務可用性不低於99.99%,如
轉自:https://mp.weixin.qq.com/s/Jgp4KCRmuqVp6W6nd3Bxtw
前言
俗話說的好,冰凍三尺非一日之寒,滴水穿石非一日之功,羅馬也不是一天就建成的。兩週前秒殺案例初步成型,分享到了中國最大的同性交友網站-碼雲。同時也收到了不
概述
高併發系統時有三把利器用來保護系統:快取、降級和限流,快取的目的是提升系統訪問速度和增大系統能處理的容量,降級是當服務出問題或者影響到核心流程的效能則需要暫時遮蔽掉,待高峰或者問題解決後再開啟,而有些場景並不能用快取和降級來解決,比如 相關推薦
聊聊高併發系統之限流特技(二)(轉)
聊聊高併發系統之限流特技
高併發系統之限流特技
高併發系統之限流特技:有了它,京東6.18如虎添翼!
高並發系統之限流特技
聊聊高併發系統之降級特技(轉)
聊聊高併發系統之HTTP快取
聊聊高併發系統之佇列術
Java高併發系統的限流策略
高併發利器之限流
談談高併發系統的限流
聊聊java高併發系統之非同步非阻塞
【轉】聊聊java高併發系統之非同步非阻塞
高併發系統之降級特技
java高併發系統之非同步非阻塞
高併發系統之降級
高併發系統之大忌-慢查詢
高可用架構之限流降級
從構建分散式秒殺系統聊聊限流特技 侵立刪
高併發系統限流設計