關於高併發限流那些事

面試被問到限流算法這塊,這裏總結下關於限流的算法和方案。計數器算法、滑動窗口算法、漏桶算法、令牌桶算法我的極簡筆記。java

爲何須要限流

正常的業務量增加不是瞬時的,能夠採用應用實例或者數據庫實例的垂直或水平伸縮應對,而限流針對場景主要兩種:nginx

  1. 網絡攻擊、爬蟲程序面試

  2. 熱點事件觸發業務(如各平臺營銷活動、微博熱點話題)redis

限流算法

一、計數器算法

原理:請求達到時計數器+1,而後比較當前計數是否達到閾值。算法

具體有兩種實現:數據庫

1.1 判斷系統正在處理的請求數是否達到閾值網絡

請求處理前計數器+1,請求處理完成計數器-1,好比要控制系統同時處理的請求不超過100個。併發

static AtomicInteger runningThread = new AtomicInteger(0);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    if (runningThread.get() > MAX_VALUE) {
    return getVoidMono(exchange, BaseResult.builder().code("SYSTEM_BUSY").message("系統繁忙").build());
    }
    runningThread.getAndIncrement();
    Mono<Void> mono = chain.filter(exchange)
    .then(Mono.fromRunnable(() -> runningThread.getAndDecrement()));
    return mono;
}

1.2  判斷單位時間內請求數是否達到閾值運維

如:每分鐘處理請求不超過100個。ide

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    //redis鍵值自增,key不存在時自動建立,初始值爲0
    Long v = redisTemplate.opsForValue().increment(key, 1L);
    //初始化生效時間
    if (v == 1) {
    redisTemplate.expire(key, 1L, TimeUnit.MINUTES);
    }
    //請求數大於閾值攔截處理
    if (v > MAX_VALUE) {
    return getVoidMono(exchange, BaseResult.builder().code("SYSTEM_BUSY").message("系統繁忙").build());
    }
    return chain.filter(exchange);
}

這種限流方法有明顯的缺陷,以下圖,分別看兩個單位時間內的併發數是被限制到每分鐘100個請求了,可是若是請求若是集中分佈在第一分鐘的最後一秒和第二分鐘的第一秒之間,算出的併發成了每秒100個請求。

二、滑動窗口算法

滑動窗口本質上仍是計數器算法,只是採用了更加細粒度的計數,對滾動區間進行計數。針對上述問題,將1分鐘拆分爲10個格子即每10秒一次滑動,統計當前一分鐘窗口內的總計數。針對上述的問題,把一分鐘拆成60個格子,即每秒一次滑動。00:59秒進入100個請求,01:00的請求則被限流攔截。

so 滑動窗口的本質是對粗粒度限流必定程度的優化,假如一開始就用極細粒度時間間隔作計數統計,也能實現較爲精確的限流,但同時由於頻繁的進行計數器的重置犧牲部分效率。

三、 漏桶算法

原理:請求排隊,系統勻速取出排隊請求進行處理。

漏桶很形象,無論進水速度如何,漏孔滴水的速率是勻速穩定的。

四、令牌桶算法

劃重點,目前應用比較普遍的算法。

原理: 好比每分鐘/秒產生必定數目的令牌到令牌桶中(令牌桶滿則忽略),請求到達時網關獲取令牌成功則處理,失敗則觸發限流邏輯。

Google開源工具包Guava提供了基於令牌桶算法的限流工具類RateLimiter。

詳細用法 :https://cloud.tencent.com/developer/article/1408819

//每秒生成10個令牌,5秒預熱時間。
static RateLimiter rateLimiter = RateLimiter.create(10, 5, TimeUnit.SECONDS);

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    if (!rateLimiter.tryAcquire()) {
    return getVoidMono(exchange, BaseResult.builder().code("SYSTEM_BUSY").message("系統繁忙").build());
    }
    return chain.filter(exchange);
}

總結限流實現方案

一、 網關代碼層限流,自定義限流邏輯,可以使用
Semaphore、AtomicInteger、Redis等計數器
RateLimiter(令牌桶)
RedisRateLimter(可根據IP、用戶、接口路徑等定製限流規則)

二、運維代理層
nginx配置:限制併發鏈接數、根據IP、接口限制併發訪問速率。


本文持續完善中....

相關文章
相關標籤/搜索