當前位置:
首頁 > 科技 > 程序員如何應對雙十一購物的大流量衝擊?

程序員如何應對雙十一購物的大流量衝擊?

作者 | LLLSQ

責編| 胡巍巍

雙十一就要來了!在雙十一這樣的大流量場景中,搶購、下單量會非常大,如果業務應用系統的負載能力有限,非預期的請求,就會給系統帶來很大壓力,從而拖垮業務應用系統。

那麼,在面對大流量時,該如何進行流量控制?

服務介面的流量控制策略:分流、降級、限流等。本文討論下限流策略,雖然降低了服務介面的訪問頻率和並發量,卻換取服務介面和業務應用系統的高可用。

限流演算法

常用的限流演算法有漏桶演算法、令牌桶演算法。

1、漏桶演算法

漏桶(Leaky Bucket)演算法思路很簡單,水(請求)先進入到漏桶里,漏桶以一定的速度出水(介面有響應速率),當水流入速度過大會直接溢出(訪問頻率超過介面響應速率),然後就拒絕請求,可以看出漏桶演算法能強行限制數據的傳輸速率。示意圖如下:

可見這裡有兩個變數,一個是桶的大小,支持流量突發增多時可以存多少的水(burst),另一個是水桶漏洞的大小(rate)。

因為漏桶的漏出速率是固定的參數,所以,即使網路中不存在資源衝突(沒有發生擁塞),漏桶演算法也不能使流突發(burst)到埠速率。因此,漏桶演算法對於存在突發特性的流量來說缺乏效率。

2、令牌桶演算法

令牌桶演算法(Token Bucket)和 Leaky Bucket 效果一樣但方向相反的演算法,更加容易理解。

隨著時間流逝,系統會按恆定1/QPS時間間隔(如果QPS=100,則間隔是10ms)往桶里加入Token(想像和漏洞漏水相反,有個水龍頭在不斷的加水),如果桶已經滿了就不再加了。新請求來臨時,會各自拿走一個Token,如果沒有Token可拿了就阻塞或者拒絕服務。

令牌桶的另外一個好處是可以方便的改變速度。一旦需要提高速率,則按需提高放入桶中的令牌的速率。一般會定時(比如100毫秒)往桶中增加一定數量的令牌, 有些變種演算法則實時的計算應該增加的令牌的數量。

ReteLimiter完成限流、搶購場景實現

RateLimiter是Guava提供的基於令牌桶演算法的實現類,API使用簡單,並且根據系統的實際情況來調整生成Token的速率。

maven引入:

com.google.guava

guava

18.0

代碼:

packagecom.lsq.einterview;

importcom.google.common.util.concurrent.RateLimiter;

publicclassRateLimiterTest{

/*

簡單使用Ratelimiter 實現限流功能,

例:限制2秒鐘只能有一個任務通過

*/

publicstaticvoidmain(String[] args){

//每秒通過0.5,所以2秒只能有一個通過

RateLimiter rateLimiter = RateLimiter.create(0.5);

for(inti =; i

doubleacquire = rateLimiter.acquire();

System.out.println("任務執行,等待時間:"+acquire);

}

}

}

結果:

rateLimiter.acquire()返回的是本次執行等待的時間,我們可以清楚地看到,第一次無需等待,後邊都需要等待兩秒才可以執行到,

rateLimiter.acquire()該方法會阻塞線程,直到令牌桶中能取到令牌為止才繼續向下執行。

搶購實現1

我們模擬搶購商品,總共有35件商品,模擬我們伺服器只能承受住20個並發,超過20個伺服器會掛掉,我們用RateLimiter來進行限流,

我們沒有傳參數,當然實際場景複雜很多。

packagecom.lsq.einterview.controller;

importcom.google.common.util.concurrent.RateLimiter;

importcom.lsq.einterview.domain.APIResponse;

importorg.springframework.web.bind.annotation.RequestMapping;

importorg.springframework.web.bind.annotation.RestController;

@RestController()

@RequestMapping("/api")

publicclassRushBuyController{

privatestaticfinalRateLimiter rateLimiter = RateLimiter.create(20);

privatestatic int productCount=35;

privatestaticfinalObject o=new Object();

/**

* 我們模擬搶購商品,模擬我們伺服器只能承受住20個並發,超過10個伺服器會掛掉,

* 我們用rateLimiter來進行限流,

* 我們也沒有傳參數,當然實際場景複雜很多。

*@return響應體

*/

@RequestMapping(value ="/rushBuy2Product", produces ="application/json;charset=utf-8")

publicAPIResponse rushBuy2Product() {

System.out.println("進入搶購:等待時間為:"+ rateLimiter.acquire());

boolean b = productStock();

if(b){

System.out.println("搶購成功");

}else{

System.out.println("搶購失敗");

}

returnnew APIResponse().success();

}

/**

* 搶購商品的庫存

*@return

*/

privateboolean productStock(){

if(productCount==){

returnfalse;

}

synchronized (o){

if(productCount>){

--productCount;

System.out.println("剩餘庫存為:"+productCount);

returntrue;

}else{

returnfalse;

}

}

}

}

使用Jmeter測試開啟100個線程進行測試:

統計下商品搶購成功的數據,正好是35個搶購成功。

我們看到在開始進入的請求不需要等待,直接執行,後面進來的需要等待的時間慢慢變長,因為拿不到令牌。

但是這種方法有個很明顯的缺陷,在實際並不適合使用,因為用戶請求進來,拿不到令牌就需要等待執行,體驗十分差。所以下面我們介紹另一種方式。

搶購實現2

由於RateLimiter是屬於單位時間內生成多少個令牌的方式,譬如0.1秒生成1個,那搶購就要看運氣了,你剛好是在剛生成1個時進來了。

那麼你就能搶到,在這0.1秒內其他的請求就算白瞎了,只能寄希望於下一個0.1秒,而從用戶體驗上來說,不能讓他在那一直阻塞等待。

所以就需要迅速判斷,該用戶在某段時間內,還有沒有機會得到令牌,這裡就需要使用TryAcquire(long timeout、TimeUnit、Unit)方法,指定一個超時時間,一旦判斷出在timeout時間內還無法取得令牌,就返回false。

注意,這裡並不是真正的等待了timeout時間,而是被判斷為即便過了timeout時間,也無法取得令牌。這個是不需要等待的。

packagecom.lsq.einterview.controller;

importcom.google.common.util.concurrent.RateLimiter;

importcom.lsq.einterview.domain.APIResponse;

importorg.springframework.web.bind.annotation.RequestMapping;

importorg.springframework.web.bind.annotation.RestController;

importjava.util.concurrent.TimeUnit;

@RestController()

@RequestMapping("/api")

publicclassRushBuyController{

privatestaticfinalRateLimiter rateLimiter = RateLimiter.create(20);

privatestatic int productCount=35;

privatestaticfinalObject o=new Object();

/**

* 我們模擬搶購商品,模擬我們伺服器只能承受住20個並發,超過10個伺服器會掛掉,

* 我們用rateLimiter來進行限流,

* 我們也沒有傳參數,當然實際場景複雜很多。

*@return響應體

*/

@RequestMapping(value ="/rushBuy2Product", produces ="application/json;charset=utf-8")

publicAPIResponse rushBuy2Product() {

boolean isAcquire = rateLimiter.tryAcquire(1, TimeUnit.SECONDS);

if(!isAcquire){

System.out.println("進入搶購:在1s內無法拿到令牌,直接返回");

returnnew APIResponse().fail("搶購失敗");

}

boolean b = productStock();

if(b){

System.out.println("搶購成功");

}else{

System.out.println("搶購失敗");

}

returnnew APIResponse().success();

}

/**

* 搶購商品的庫存

*@return

*/

privateboolean productStock(){

if(productCount==){

returnfalse;

}

synchronized (o){

if(productCount>){

--productCount;

System.out.println("剩餘庫存為:"+productCount);

returntrue;

}else{

returnfalse;

}

}

}

}

結果:

在我們的所有日誌文件中統計:

搶購成功:35;

搶購失敗:23;

無法拿到令牌,未參與搶購:42;

總數為我們100個線程。

總結

搶購實現的方式有很多種,在搶購2案例中,按照固定的單位時間進行分割,每個單位時間產生一個令牌,可供購買。

我們可以想到平常參與搶購秒殺時,有時候你網速很快,但總是搶不到,現在明白了吧。

真正的搶購不是這麼簡單,難度也複雜得多。所以不只是在代碼中限流能實現的。

瞬間的流量洪峰會衝垮伺服器的負載,當幾十萬人搶購幾件商品時,連介面都請求不進來,更別提介面里的令牌分配了。

作者:LLLSQ,一隻有著悲慘故事的北漂程序員,為讀者提供熱點技術文章和IT實時熱點新聞、架構、面試信息等最新訊息。

聲明:本文為作者投稿,版權歸其個人所有。

-- END --

微信改版了,

想快速看到CSDN的熱乎文章,

趕快把CSDN公眾號設為星標吧,

打開公眾號,點擊「設為星標」就可以啦!


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 CSDN 的精彩文章:

BAT 把持的小程序領地,現在入場的今日頭條還有救嗎?
微軟曾經的二號人物永遠地離開了

TAG:CSDN |