介面限流

weixin_34054866發表於2017-11-13

介面限流

簡述

有時候,介面對外提供服務的時候,需要保護我們的介面,避免併發過大導致系統癱瘓。

限流演算法

常用的限流演算法存在兩種:漏桶演算法和令牌桶演算法。
漏桶演算法:故名思意,就是桶下面有個洞,以恆定的速率處理請求,請求過來放入桶中,請求量大於桶的容量益處,則拒絕服務。

令牌桶演算法:令牌桶演算法是一個存放固定容量令牌的桶,按照固定速率往桶裡新增令牌。令牌桶演算法的描述如下: 假設限制2r/s,則按照500毫秒的固定速率往桶中新增令牌; 桶中最多存放b個令牌,當桶滿時,新新增的令牌被丟棄或拒絕; 當一個n個位元組大小的資料包到達,將從桶中刪除n個令牌,接著資料包被髮送到網路上; 如果桶中的令牌不足n個,則不會刪除令牌,且該資料包將被限流(要麼丟棄,要麼緩衝區等待)

限流工具類RateLimiter

Google開源工具包Guava提供了限流工具類RateLimiter,該類基於令牌桶演算法來完成限流,非常易於使用。

方法摘要

修飾符和型別 方法和描述
double acquire()從RateLimiter獲取一個許可,該方法會被阻塞直到獲取到請求
double acquire(int permits)從RateLimiter獲取指定許可數,該方法會被阻塞直到獲取到請求
static RateLimiter create(double permitsPerSecond)根據指定的穩定吞吐率建立RateLimiter,這裡的吞吐率是指每秒多少許可數(通常是指QPS,每秒多少查詢)
static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)根據指定的穩定吞吐率和預熱期來建立RateLimiter,這裡的吞吐率是指每秒多少許可數(通常是指QPS,每秒多少個請求量),在這段預熱時間內RateLimiter每秒分配的許可數會平穩地增長直到預熱期結束時達到其最大速率。(只要存在足夠請求數來使其飽和)
double getRate()返回RateLimiter 配置中的穩定速率,該速率單位是每秒多少許可數
void setRate(double permitsPerSecond)更新RateLimite的穩定速率,引數permitsPerSecond 由構造RateLimiter的工廠方法提供。
String toString()返回物件的字元表現形式
boolean tryAcquire()從RateLimiter 獲取許可,如果該許可可以在無延遲下的情況下立即獲取得到的話
boolean tryAcquire(int permits)從RateLimiter 獲取許可數,如果該許可數可以在無延遲下的情況下立即獲取得到的話
boolean tryAcquire(int permits, long timeout, TimeUnit unit)從RateLimiter 獲取指定許可數如果該許可數可以在不超過timeout的時間內獲取得到的話,或者如果無法在timeout 過期之前獲取得到許可數的話,那麼立即返回false (無需等待)
boolean tryAcquire(long timeout, TimeUnit unit)從RateLimiter 獲取許可如果該許可可以在不超過timeout的時間內獲取得到的話,或者如果無法在timeout 過期之前獲取得到許可的話,那麼立即返回false(無需等待)

舉例

一個秒殺活動限流例子。每秒限流10個請求

import com.google.common.base.Stopwatch;
import com.google.common.util.concurrent.RateLimiter;

import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author td
 * @date 2017/09/13
 */
public class TokenBucket {
    private AtomicInteger phoneNumbers = new AtomicInteger(0);

    private final static int LIMIT = 100;

    private RateLimiter rateLimiter = RateLimiter.create(10);

    private final  int saleLimit;

    public TokenBucket() {
        this(LIMIT);
    }

    public TokenBucket(int saleLimit) {
        this.saleLimit = saleLimit;
    }

    public int buy() {

        Stopwatch stopwatch = Stopwatch.createStarted();
        boolean success = rateLimiter.tryAcquire(10, TimeUnit.MILLISECONDS);
        if (success) {
            int phoneNum = phoneNumbers.getAndIncrement();
            if (phoneNum>=saleLimit) {
                throw new IllegalStateException("not any phone can be sale,please wait to next time.");
            }
            handleOrder();
            System.out.println(Thread.currentThread()+"user  get the phone "+phoneNum+",ELT:"+stopwatch.stop());

            return phoneNum;
        }else {
            stopwatch.stop();
            throw new RuntimeException("Sorry,occur excepiton when buy phone");

        }
    }

    private void handleOrder() {
        try {
            TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


}

/**
 * @author td
 * @date 2017/09/13
 */
public class TokenBucketExample {
    public static void main(String[] args) {
        final  TokenBucket tokenBucket = new TokenBucket();
        for (int i=0;i< 200;i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            new Thread(tokenBucket::buy).start();
        }
    }
}

相關文章