<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    少年阿賓

    那些青春的歲月

      BlogJava :: 首頁(yè) :: 聯(lián)系 :: 聚合  :: 管理
      500 Posts :: 0 Stories :: 135 Comments :: 0 Trackbacks

    在開(kāi)發(fā)高并發(fā)系統(tǒng)時(shí)有三把利器用來(lái)保護(hù)系統(tǒng):緩存、降級(jí)和限流。緩存的目的是提升系統(tǒng)訪問(wèn)速度和增大系統(tǒng)能處理的容量,可謂是抗高并發(fā)流量的銀彈;而降級(jí)是當(dāng)服務(wù)出問(wèn)題或者影響到核心流程的性能則需要暫時(shí)屏蔽掉,待高峰或者問(wèn)題解決后再打開(kāi);而有些場(chǎng)景并不能用緩存和降級(jí)來(lái)解決,比如稀缺資源(秒殺、搶購(gòu))、寫(xiě)服務(wù)(如評(píng)論、下單)、頻繁的復(fù)雜查詢(評(píng)論的最后幾頁(yè)),因此需有一種手段來(lái)限制這些場(chǎng)景的并發(fā)/請(qǐng)求量,即限流。

     

    限流的目的是通過(guò)對(duì)并發(fā)訪問(wèn)/請(qǐng)求進(jìn)行限速或者一個(gè)時(shí)間窗口內(nèi)的的請(qǐng)求進(jìn)行限速來(lái)保護(hù)系統(tǒng),一旦達(dá)到限制速率則可以拒絕服務(wù)(定向到錯(cuò)誤頁(yè)或告知資源沒(méi)有了)、排隊(duì)或等待(比如秒殺、評(píng)論、下單)、降級(jí)(返回兜底數(shù)據(jù)或默認(rèn)數(shù)據(jù),如商品詳情頁(yè)庫(kù)存默認(rèn)有貨)。

     

    一般開(kāi)發(fā)高并發(fā)系統(tǒng)常見(jiàn)的限流有:限制總并發(fā)數(shù)(比如數(shù)據(jù)庫(kù)連接池、線程池)、限制瞬時(shí)并發(fā)數(shù)(如nginxlimit_conn模塊,用來(lái)限制瞬時(shí)并發(fā)連接數(shù))、限制時(shí)間窗口內(nèi)的平均速率(如GuavaRateLimiternginxlimit_req模塊,限制每秒的平均速率);其他還有如限制遠(yuǎn)程接口調(diào)用速率、限制MQ的消費(fèi)速率。另外還可以根據(jù)網(wǎng)絡(luò)連接數(shù)、網(wǎng)絡(luò)流量、CPU或內(nèi)存負(fù)載等來(lái)限流。

     

    先有緩存這個(gè)銀彈,后有限流來(lái)應(yīng)對(duì)618、雙十一高并發(fā)流量,在處理高并發(fā)問(wèn)題上可以說(shuō)是如虎添翼,不用擔(dān)心瞬間流量導(dǎo)致系統(tǒng)掛掉或雪崩,最終做到有損服務(wù)而不是不服務(wù);限流需要評(píng)估好,不可亂用,否則會(huì)正常流量出現(xiàn)一些奇怪的問(wèn)題而導(dǎo)致用戶抱怨。


    在實(shí)際應(yīng)用時(shí)也不要太糾結(jié)算法問(wèn)題,因?yàn)橐恍┫蘖魉惴▽?shí)現(xiàn)是一樣的只是描述不一樣;具體使用哪種限流技術(shù)還是要根據(jù)實(shí)際場(chǎng)景來(lái)選擇,不要一味去找最佳模式,白貓黑貓能解決問(wèn)題的就是好貓。

     

    因在實(shí)際工作中遇到過(guò)許多人來(lái)問(wèn)如何進(jìn)行限流,因此本文會(huì)詳細(xì)介紹各種限流手段。那么接下來(lái)我們從限流算法、應(yīng)用級(jí)限流、分布式限流、接入層限流來(lái)詳細(xì)學(xué)習(xí)下限流技術(shù)手段。

     

    限流算法

    常見(jiàn)的限流算法有:令牌桶、漏桶。計(jì)數(shù)器也可以進(jìn)行粗暴限流實(shí)現(xiàn)。

     

     

    令牌桶算法

    令牌桶算法是一個(gè)存放固定容量令牌的桶,按照固定速率往桶里添加令牌。令牌桶算法的描述如下:

    • 假設(shè)限制2r/s,則按照500毫秒的固定速率往桶中添加令牌;

    • 桶中最多存放b個(gè)令牌,當(dāng)桶滿時(shí),新添加的令牌被丟棄或拒絕;

    • 當(dāng)一個(gè)n個(gè)字節(jié)大小的數(shù)據(jù)包到達(dá),將從桶中刪除n個(gè)令牌,接著數(shù)據(jù)包被發(fā)送到網(wǎng)絡(luò)上;

    • 如果桶中的令牌不足n個(gè),則不會(huì)刪除令牌,且該數(shù)據(jù)包將被限流(要么丟棄,要么緩沖區(qū)等待)。



    漏桶算法

    漏桶作為計(jì)量工具(The Leaky Bucket Algorithm as a Meter)時(shí),可以用于流量整形(Traffic Shaping)和流量控制(TrafficPolicing),漏桶算法的描述如下:

    • 一個(gè)固定容量的漏桶,按照常量固定速率流出水滴;

    • 如果桶是空的,則不需流出水滴;

    • 可以以任意速率流入水滴到漏桶;

    • 如果流入水滴超出了桶的容量,則流入的水滴溢出了(被丟棄),而漏桶容量是不變的。


    令牌桶和漏桶對(duì)比:

    • 令牌桶是按照固定速率往桶中添加令牌,請(qǐng)求是否被處理需要看桶中令牌是否足夠,當(dāng)令牌數(shù)減為零時(shí)則拒絕新的請(qǐng)求;

    • 漏桶則是按照常量固定速率流出請(qǐng)求,流入請(qǐng)求速率任意,當(dāng)流入的請(qǐng)求數(shù)累積到漏桶容量時(shí),則新流入的請(qǐng)求被拒絕;

    • 令牌桶限制的是平均流入速率(允許突發(fā)請(qǐng)求,只要有令牌就可以處理,支持一次拿3個(gè)令牌,4個(gè)令牌),并允許一定程度突發(fā)流量;

    • 漏桶限制的是常量流出速率(即流出速率是一個(gè)固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),從而平滑突發(fā)流入速率;

    • 令牌桶允許一定程度的突發(fā),而漏桶主要目的是平滑流入速率;

    • 兩個(gè)算法實(shí)現(xiàn)可以一樣,但是方向是相反的,對(duì)于相同的參數(shù)得到的限流效果是一樣的。

     

    另外有時(shí)候我們還使用計(jì)數(shù)器來(lái)進(jìn)行限流,主要用來(lái)限制總并發(fā)數(shù),比如數(shù)據(jù)庫(kù)連接池、線程池、秒殺的并發(fā)數(shù);只要全局總請(qǐng)求數(shù)或者一定時(shí)間段的總請(qǐng)求數(shù)設(shè)定的閥值則進(jìn)行限流,是簡(jiǎn)單粗暴的總數(shù)量限流,而不是平均速率限流。

     

    到此基本的算法就介紹完了,接下來(lái)我們首先看看應(yīng)用級(jí)限流。


    應(yīng)用級(jí)限流


    限流總并發(fā)/連接/請(qǐng)求數(shù)

    對(duì)于一個(gè)應(yīng)用系統(tǒng)來(lái)說(shuō)一定會(huì)有極限并發(fā)/請(qǐng)求數(shù),即總有一個(gè)TPS/QPS閥值,如果超了閥值則系統(tǒng)就會(huì)不響應(yīng)用戶請(qǐng)求或響應(yīng)的非常慢,因此我們最好進(jìn)行過(guò)載保護(hù),防止大量請(qǐng)求涌入擊垮系統(tǒng)。

    如果你使用過(guò)Tomcat,其Connector 其中一種配置有如下幾個(gè)參數(shù):

    acceptCount:如果Tomcat的線程都忙于響應(yīng),新來(lái)的連接會(huì)進(jìn)入隊(duì)列排隊(duì),如果超出排隊(duì)大小,則拒絕連接;

    maxConnections 瞬時(shí)最大連接數(shù),超出的會(huì)排隊(duì)等待;

    maxThreadsTomcat能啟動(dòng)用來(lái)處理請(qǐng)求的最大線程數(shù),如果請(qǐng)求處理量一直遠(yuǎn)遠(yuǎn)大于最大線程數(shù)則可能會(huì)僵死。

    詳細(xì)的配置請(qǐng)參考官方文檔。另外如Mysql(如max_connections)、Redis(如tcp-backlog)都會(huì)有類似的限制連接數(shù)的配置。

     

    限流總資源數(shù)

    如果有的資源是稀缺資源(如數(shù)據(jù)庫(kù)連接、線程),而且可能有多個(gè)系統(tǒng)都會(huì)去使用它,那么需要限制應(yīng)用;可以使用池化技術(shù)來(lái)限制總資源數(shù):連接池、線程池。比如分配給每個(gè)應(yīng)用的數(shù)據(jù)庫(kù)連接是100,那么本應(yīng)用最多可以使用100個(gè)資源,超出了可以等待或者拋異常。

     

    限流某個(gè)接口的總并發(fā)/請(qǐng)求數(shù)

    如果接口可能會(huì)有突發(fā)訪問(wèn)情況,但又擔(dān)心訪問(wèn)量太大造成崩潰,如搶購(gòu)業(yè)務(wù);這個(gè)時(shí)候就需要限制這個(gè)接口的總并發(fā)/請(qǐng)求數(shù)總請(qǐng)求數(shù)了;因?yàn)榱6缺容^細(xì),可以為每個(gè)接口都設(shè)置相應(yīng)的閥值。可以使用Java中的AtomicLong進(jìn)行限流:

    =================================

    try {
    if(atomic.incrementAndGet() > 限流數(shù)) {
    //拒絕請(qǐng)求
       }
    //處理請(qǐng)求
    } finally {
    atomic.decrementAndGet();
    }

    =================================

    適合對(duì)業(yè)務(wù)無(wú)損的服務(wù)或者需要過(guò)載保護(hù)的服務(wù)進(jìn)行限流,如搶購(gòu)業(yè)務(wù),超出了大小要么讓用戶排隊(duì),要么告訴用戶沒(méi)貨了,對(duì)用戶來(lái)說(shuō)是可以接受的。而一些開(kāi)放平臺(tái)也會(huì)限制用戶調(diào)用某個(gè)接口的試用請(qǐng)求量,也可以用這種計(jì)數(shù)器方式實(shí)現(xiàn)。這種方式也是簡(jiǎn)單粗暴的限流,沒(méi)有平滑處理,需要根據(jù)實(shí)際情況選擇使用;

     

    限流某個(gè)接口的時(shí)間窗請(qǐng)求數(shù)

    即一個(gè)時(shí)間窗口內(nèi)的請(qǐng)求數(shù),如想限制某個(gè)接口/服務(wù)每秒/每分鐘/每天的請(qǐng)求數(shù)/調(diào)用量。如一些基礎(chǔ)服務(wù)會(huì)被很多其他系統(tǒng)調(diào)用,比如商品詳情頁(yè)服務(wù)會(huì)調(diào)用基礎(chǔ)商品服務(wù)調(diào)用,但是怕因?yàn)楦铝勘容^大將基礎(chǔ)服務(wù)打掛,這時(shí)我們要對(duì)每秒/每分鐘的調(diào)用量進(jìn)行限速;一種實(shí)現(xiàn)方式如下所示:

    =================================

    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) {
       
    //得到當(dāng)前秒
       
    long currentSeconds = System.currentTimeMillis() / 1000;
       
    if(counter.get(currentSeconds).incrementAndGet() > limit) {
            System.
    out.println("限流了:" + currentSeconds);
            
    continue;
        }
       
    //業(yè)務(wù)處理
    }

    =================================

    我們使用GuavaCache來(lái)存儲(chǔ)計(jì)數(shù)器,過(guò)期時(shí)間設(shè)置為2秒(保證1秒內(nèi)的計(jì)數(shù)器是有的),然后我們獲取當(dāng)前時(shí)間戳然后取秒數(shù)來(lái)作為KEY進(jìn)行計(jì)數(shù)統(tǒng)計(jì)和限流,這種方式也是簡(jiǎn)單粗暴,剛才說(shuō)的場(chǎng)景夠用了。


    平滑限流某個(gè)接口的請(qǐng)求數(shù)

    之前的限流方式都不能很好地應(yīng)對(duì)突發(fā)請(qǐng)求,即瞬間請(qǐng)求可能都被允許從而導(dǎo)致一些問(wèn)題;因此在一些場(chǎng)景中需要對(duì)突發(fā)請(qǐng)求進(jìn)行整形,整形為平均速率請(qǐng)求處理(比如5r/s,則每隔200毫秒處理一個(gè)請(qǐng)求,平滑了速率)。這個(gè)時(shí)候有兩種算法滿足我們的場(chǎng)景:令牌桶和漏桶算法。Guava框架提供了令牌桶算法實(shí)現(xiàn),可直接拿來(lái)使用。

    Guava RateLimiter提供了令牌桶算法實(shí)現(xiàn):平滑突發(fā)限流(SmoothBursty)和平滑預(yù)熱限流(SmoothWarmingUp)實(shí)現(xiàn)。

     

    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

    =================================

    1RateLimiter.create(5) 表示桶容量為5且每秒新增5個(gè)令牌,即每隔200毫秒新增一個(gè)令牌;

    2limiter.acquire()表示消費(fèi)一個(gè)令牌,如果當(dāng)前桶中有足夠令牌則成功(返回值為0),如果桶中沒(méi)有令牌則暫停一段時(shí)間,比如發(fā)令牌間隔是200毫秒,則等待200毫秒后再去消費(fèi)令牌(如上測(cè)試用例返回的為0.198239,差不多等待了200毫秒桶中才有令牌可用),這種實(shí)現(xiàn)將突發(fā)請(qǐng)求速率平均為了固定請(qǐng)求速率。

     

    再看一個(gè)突發(fā)示例:

    =================================

    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個(gè)令牌,令牌桶算法允許一定程度的突發(fā),所以可以一次性消費(fèi)5個(gè)令牌,但接下來(lái)的limiter.acquire(1)將等待差不多1秒桶中才能有令牌,且接下來(lái)的請(qǐng)求也整形為固定速率了。


    =================================

    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

    =================================

    同上邊的例子類似,第一秒突發(fā)了10個(gè)請(qǐng)求,令牌桶算法也允許了這種突發(fā)(允許消費(fèi)未來(lái)的令牌),但接下來(lái)的limiter.acquire(1)將等待差不多2秒桶中才能有令牌,且接下來(lái)的請(qǐng)求也整形為固定速率了。


    接下來(lái)再看一個(gè)突發(fā)的例子:

    =================================

    RateLimiter limiter = RateLimiter.create(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、創(chuàng)建了一個(gè)桶容量為2且每秒新增2個(gè)令牌;

    2、首先調(diào)用limiter.acquire()消費(fèi)一個(gè)令牌,此時(shí)令牌桶可以滿足(返回值為0);

    3、然后線程暫停2秒,接下來(lái)的兩個(gè)limiter.acquire()都能消費(fèi)到令牌,第三個(gè)limiter.acquire()也同樣消費(fèi)到了令牌,到第四個(gè)時(shí)就需要等待500毫秒了。

    此處可以看到我們?cè)O(shè)置的桶容量為2(即允許的突發(fā)量),這是因?yàn)?/span>SmoothBursty中有一個(gè)參數(shù):最大突發(fā)秒數(shù)(maxBurstSeconds)默認(rèn)值是1s,突發(fā)量/桶容量=速率*maxBurstSeconds,所以本示例桶容量/突發(fā)量為2,例子中前兩個(gè)是消費(fèi)了之前積攢的突發(fā)量,而第三個(gè)開(kāi)始就是正常計(jì)算的了。令牌桶算法允許將一段時(shí)間內(nèi)沒(méi)有消費(fèi)的令牌暫存到令牌桶中,留待未來(lái)使用,并允許未來(lái)請(qǐng)求的這種突發(fā)。

     

    SmoothBursty通過(guò)平均速率和最后一次新增令牌的時(shí)間計(jì)算出下次新增令牌的時(shí)間的,另外需要一個(gè)桶暫存一段時(shí)間內(nèi)沒(méi)有使用的令牌(即可以突發(fā)的令牌數(shù))。另外RateLimiter還提供了tryAcquire方法來(lái)進(jìn)行無(wú)阻塞或可超時(shí)的令牌消費(fèi)。

     

    因?yàn)?/span>SmoothBursty允許一定程度的突發(fā),會(huì)有人擔(dān)心如果允許這種突發(fā),假設(shè)突然間來(lái)了很大的流量,那么系統(tǒng)很可能扛不住這種突發(fā)。因此需要一種平滑速率的限流工具,從而系統(tǒng)冷啟動(dòng)后慢慢的趨于平均固定速率(即剛開(kāi)始速率小一些,然后慢慢趨于我們?cè)O(shè)置的固定速率)。Guava也提供了SmoothWarmingUp來(lái)實(shí)現(xiàn)這種需求,其可以認(rèn)為是漏桶算法,但是在某些特殊場(chǎng)景又不太一樣。

     

    SmoothWarmingUp創(chuàng)建方式:RateLimiter.create(doublepermitsPerSecond, long warmupPeriod, TimeUnit unit)

    permitsPerSecond表示每秒新增的令牌數(shù),warmupPeriod表示在從冷啟動(dòng)速率過(guò)渡到平均速率的時(shí)間間隔。


    示例如下:

    =================================

    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

    =================================

    速率是梯形上升速率的,也就是說(shuō)冷啟動(dòng)時(shí)會(huì)以一個(gè)比較大的速率慢慢到平均速率;然后趨于平均速率(梯形下降到平均速率)。可以通過(guò)調(diào)節(jié)warmupPeriod參數(shù)實(shí)現(xiàn)一開(kāi)始就是平滑固定速率。

     

    到此應(yīng)用級(jí)限流的一些方法就介紹完了。假設(shè)將應(yīng)用部署到多臺(tái)機(jī)器,應(yīng)用級(jí)限流方式只是單應(yīng)用內(nèi)的請(qǐng)求限流,不能進(jìn)行全局限流。因此我們需要分布式限流和接入層限流來(lái)解決這個(gè)問(wèn)題。


    分布式限流

    分布式限流最關(guān)鍵的是要將限流服務(wù)做成原子化,而解決方案可以使使用redis+lua或者nginx+lua技術(shù)進(jìn)行實(shí)現(xiàn),通過(guò)這兩種技術(shù)可以實(shí)現(xiàn)的高并發(fā)和高性能。

    首先我們來(lái)使用redis+lua實(shí)現(xiàn)時(shí)間窗內(nèi)某個(gè)接口的請(qǐng)求數(shù)限流,實(shí)現(xiàn)了該功能后可以改造為限流總并發(fā)/請(qǐng)求數(shù)和限制總資源數(shù)。Lua本身就是一種編程語(yǔ)言,也可以使用它實(shí)現(xiàn)復(fù)雜的令牌桶或漏桶算法。


    redis+lua實(shí)現(xiàn)中的lua腳本:

    =================================

    local key = KEYS[1] --限流KEY(一秒一個(gè))
    local limit = tonumber(ARGV[1])        --限流大小
    local current = tonumber(redis.call("INCRBY", key, "1")) --請(qǐng)求數(shù)+1
    if current > limit then --如果超出限流大小
       return 0
    elseif current == 1 then  --只有第一次訪問(wèn)需要設(shè)置2秒的過(guò)期時(shí)間
       redis.call("expire", key,"2")
    end
    return 1

    =================================

    如上操作因是在一個(gè)lua腳本中,又因Redis是單線程模型,因此是線程安全的。如上方式有一個(gè)缺點(diǎn)就是當(dāng)達(dá)到限流大小后還是會(huì)遞增的,可以改造成如下方式實(shí)現(xiàn):

    =================================

    local key = KEYS[1] --限流KEY(一秒一個(gè))
    local limit = tonumber(ARGV[1])        --限流大小
    local current = tonumber(redis.call('get', key) or "0")
    if current + 1 > limit then --如果超出限流大小
       return 0
    else  --請(qǐng)求數(shù)+1,并設(shè)置2秒過(guò)期
       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; //此處將當(dāng)前時(shí)間戳取秒數(shù)
       
    Stringlimit = "3"; //限流大小
       
    return (Long)jedis.eval(luaScript,Lists.newArrayList(key), Lists.newArrayList(limit)) == 1;
    }

    =================================

    因?yàn)?/span>Redis的限制(Lua中有寫(xiě)操作不能使用帶隨機(jī)性質(zhì)的讀操作,如TIME)不能在Redis Lua中使用TIME獲取時(shí)間戳,因此只好從應(yīng)用獲取然后傳入,在某些極端情況下(機(jī)器時(shí)鐘不準(zhǔn)的情況下),限流會(huì)存在一些小問(wèn)題。


    使用Nginx+Lua實(shí)現(xiàn)的Lua腳本:

    =================================

    local locks = require "resty.lock"

    local function acquire()
        local lock =locks:new("locks")
        local elapsed, err =lock:lock("limit_key") --
    互斥鎖
        local limit_counter =ngx.shared.limit_counter --計(jì)數(shù)器

        local key = "ip:" ..os.time()
        local limit = 5 --限流大小
        local current =limit_counter:get(key)

        if current ~= nil and current + 1> limit then --如果超出限流大小
           lock:unlock()
           return 0
        end
        if current == nil then
           limit_counter:set(key, 1, 1) --第一次需要設(shè)置過(guò)期時(shí)間,設(shè)置key的值為1,過(guò)期時(shí)間為1秒
        else
            limit_counter:incr(key, 1) --第二次開(kāi)始加1即可
        end
        lock:unlock()
        return 1
    end
    ngx.print(acquire())

    =================================

    實(shí)現(xiàn)中我們需要使用lua-resty-lock互斥鎖模塊來(lái)解決原子性問(wèn)題(在實(shí)際工程中使用時(shí)請(qǐng)考慮獲取鎖的超時(shí)問(wèn)題),并使用ngx.shared.DICT共享字典來(lái)實(shí)現(xiàn)計(jì)數(shù)器。如果需要限流則返回0,否則返回1。使用時(shí)需要先定義兩個(gè)共享字典(分別用來(lái)存放鎖和計(jì)數(shù)器數(shù)據(jù)):

    =================================

    http {

        ……

        lua_shared_dict locks 10m;

        lua_shared_dict limit_counter 10m;

    }

    =================================

    有人會(huì)糾結(jié)如果應(yīng)用并發(fā)量非常大那么redis或者nginx是不是能抗得住;不過(guò)這個(gè)問(wèn)題要從多方面考慮:你的流量是不是真的有這么大,是不是可以通過(guò)一致性哈希將分布式限流進(jìn)行分片,是不是可以當(dāng)并發(fā)量太大降級(jí)為應(yīng)用級(jí)限流;對(duì)策非常多,可以根據(jù)實(shí)際情況調(diào)節(jié);像在京東使用Redis+Lua來(lái)限流搶購(gòu)流量,一般流量是沒(méi)有問(wèn)題的。

     

    對(duì)于分布式限流目前遇到的場(chǎng)景是業(yè)務(wù)上的限流,而不是流量入口的限流;流量入口限流應(yīng)該在接入層完成,而接入層筆者一般使用Nginx


    參考資料

    https://en.wikipedia.org/wiki/Token_bucket

    https://en.wikipedia.org/wiki/Leaky_bucket

    http://redis.io/commands/incr

    http://nginx.org/en/docs/http/ngx_http_limit_req_module.html

    http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html

    https://github.com/openresty/lua-resty-limit-traffic

    http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate



    http://mp.weixin.qq.com/s?__biz=MzIwODA4NjMwNA==&mid=2652897781&idx=1&sn=ae121ce4c3c37b7158bc9f067fa024c0&scene=22&srcid=0612HUDZ8kkp1INrr4fceNoK#rd
    posted on 2016-06-14 13:38 abin 閱讀(2706) 評(píng)論(1)  編輯  收藏 所屬分類: concurrent

    Feedback

    # re: 聊聊高并發(fā)系統(tǒng)之限流特技-1 2016-08-19 14:01 垃圾
    抄別人的都不知道寫(xiě)一下轉(zhuǎn)載????  回復(fù)  更多評(píng)論
      

    主站蜘蛛池模板: 日本XXX黄区免费看| 免费人成又黄又爽的视频在线电影| 国产日韩久久免费影院 | 久久亚洲精品无码AV红樱桃| 中文字幕成人免费高清在线| 亚洲欧洲久久av| 120秒男女动态视频免费| 亚洲va中文字幕无码久久| 热99RE久久精品这里都是精品免费 | 中文亚洲AV片在线观看不卡| 一区免费在线观看| 亚洲自偷自偷在线制服| 国产一级婬片A视频免费观看| 国产精品久久久亚洲| 免费视频一区二区| 亚洲性无码av在线| 欧美a级在线现免费观看| 亚洲AV无码专区在线亚| 成人性生免费视频| 色视频在线观看免费| 久久精品国产亚洲Aⅴ香蕉| 免费人成在线观看视频高潮| 久久精品蜜芽亚洲国产AV| 免费可以在线看A∨网站| 国产一区二区三区亚洲综合| 国产精品久久香蕉免费播放| kk4kk免费视频毛片| 亚洲国语精品自产拍在线观看| 国产精品怡红院永久免费| 亚洲а∨精品天堂在线| 亚洲成人影院在线观看| 99在线视频免费观看| 亚洲国产亚洲综合在线尤物| 国产视频精品免费| a毛片在线免费观看| 亚洲AV无码专区在线亚| 亚洲国产婷婷香蕉久久久久久| 99蜜桃在线观看免费视频网站| 一本天堂ⅴ无码亚洲道久久| 国产亚洲大尺度无码无码专线 | 精品免费人成视频app|