首頁 > 軟體

Java RateLimiter的限流詳解

2022-03-28 13:01:03

限流背景

在早期的計算機領域,限流技術(time limiting)被用做控制網路介面收發通訊資料的速率。可以用來優化效能,減少延遲和提高頻寬等。現在在網際網路領域,也借鑑了這個概念,用來為服務控制請求的速率,如雙十一的限流,12306的搶票等。即使在細粒度的軟體架構中,也有類似的概念。

系統在使用下游資源時,需要考慮下游對資源受限,處理能力,在下游資源無法或者短時間內無法提升處理效能的情況下,可以使用限流器或者類似保護機制,避免下游服務崩潰造成整體服務的不可用。

限流相關概念

在介紹限流之前先介紹幾個容易混淆的概念,包括服務熔斷,服務降級,服務隔離。

服務熔斷

理解熔斷之前先了解另一個概念:微服務的雪崩效應。因為熔斷機制通常是作為應對雪崩效應的一種微服務鏈路保護機制。

在微服務架構中,一個微服務通常是完成一個單一業務功能的獨立應用。這樣做的好處是各個業務功能之間最大可能的解耦,每個微服務可以獨立演進。通常一個應用可能會有很多個微服務組成,服務間通過RPC互相呼叫。假如有如下服務呼叫鏈路:

A,B依賴C去呼叫E,F。如果E服務不能正常提供服務了,C的超時重試機制將會執行。同時新的呼叫不斷產生,會導致C對E服務的呼叫大量的積壓,產生大量的呼叫等待和重試呼叫,慢慢會耗盡C的資源,比如記憶體或CPU,同時影響C調F,最終整個應用不可用。本例中由於鏈路上E的故障,對微服務A,B的呼叫就會佔用越來越多的系統資源,進而引起系統崩潰,即所謂的“雪崩效應”。

熔斷機制是應對雪崩效應的一種微服務鏈路保護機制,生活中有很多熔斷的例子。比如電路中某個地方的電壓過高,熔斷器就會熔斷,對電路進行過載保護。股市裡邊,如果股票指數漲跌幅過高,觸及設定的熔斷點後,隨後的一段時間內將暫停交易。在微服務架構中的熔斷機制作用類似。當呼叫鏈路的某個微服務不可用,或者響應時間太長,或者錯誤次數達到某個閾值,會進行服務熔斷,即快速返回響應資訊。當檢測到該節點微服務呼叫響應正常後,逐步恢復正常的呼叫鏈路。

服務降級

服務降級主要是指在伺服器壓力陡增的情況下,根據某種策略對一些非核心服務或者頁面不做請求處理或者簡單處理,或者限流某個服務,從而釋放伺服器資源以保證核心業務正常運作或高效運作。比如京東618活動時,把無關交易的服務降級,比如關閉某個服務,或者檢視歷史訂單,商品歷史評論等非交易核心業務,只顯示最近100條等。

服務隔離

隔離是指服務或者資源隔離開。服務隔離能夠在服務發生故障時限定其影響範圍,保證其他服務還是可用的。資源隔離一般是指通過隔離來減少服務間資源競爭。資源隔離的粒度有很多種,比如執行緒隔離,程序隔離,機房隔離等。執行緒隔離即隔離執行緒池資源,不同服務的執行使用不同的執行緒池。這樣做的好處是即使其中一個服務執行緒池滿了,也不會影響到其他的服務。比如下圖中Tomcat處理請求,對每個微服務,都分配一個執行緒池。

服務限流

服務限流是限制請求的數量,即某個時間視窗內的請求速率。一旦達到限制速率則可以拒絕服務(定向到錯誤頁或告知系統忙),排隊等待(比如秒殺,使用者評論,下單),降級(返回兜底資料或預設資料)。

比較

服務熔斷,服務降級都是從系統的可用性角度考慮,防止系統響應延遲甚至崩潰而採用的技術性的系統保護手段。服務熔斷一般是由某個下游服務故障引起,而服務降級一般是從整體業務的負載情況考慮,目的是為了降低系統負載。限流則是對單位時間內請求次數的限制。三者都是通過某種手段保證流量過載時系統的可用性。服務隔離則是讓不同的業務使用各自獨立的執行緒池資源,避免服務之間資源競爭的影響。

常見的限流方法

常見的限流手段有如下這些。限制總的並行數(比如資料庫連線池,執行緒池),限制瞬時並行數(如nginx的limit_conn模組,用來限制瞬時並行連線數),限制某個時間視窗內的平均速率(RateLimiter,nginx的limit_req模組);此外還有限制RPC呼叫頻率,限制MQ的消費速率等。

限流工具類RateLimiter

google開源工具包guava提供了限流工具類RateLimiter,該類基於“令牌桶演演算法”,非常方便使用。

RateLimiter 使用Demo:

import com.google.common.util.concurrent.RateLimiter;
public class RateLimiterDemo {
    public static void main(String[] args) {
//        testNoRateLimiter();
        testWithRateLimiter();
    }
    public static void testNoRateLimiter() {
        Long start = System.currentTimeMillis();
        for (int i = 0; i < 10; i++) {
            System.out.println("call execute.." + i);
        }
        Long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
    public static void testWithRateLimiter() {
        Long start = System.currentTimeMillis();
        RateLimiter limiter = RateLimiter.create(10.0); // 每秒不超過10個任務被提交
        for (int i = 0; i < 20; i++) {
            limiter.acquire(); // 請求RateLimiter, 超過permits會被阻塞
            System.out.println("call execute.." + i);
        }
        Long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

Guava版本

<dependency>
     <groupId>com.google.guava</groupId>
     <artifactId>guava</artifactId>
     <version>18.0</version>
</dependency>

總結

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!  


IT145.com E-mail:sddin#qq.com