RateLimiter概述

RateLimiter是Guava提供的的限流器。它基於令牌桶算法實現,預先設定一個速率,然後按照這個速率生成令牌,每次請求消耗一個令牌。限流是保護高併發系統的三把利器之一,另外兩個是緩存和降級,在秒殺搶購等場景中用來限制併發和請求量,保護自身系統和下游系統不被巨型流量沖垮。

核心原理

RateLimiter的核心是"令牌桶算法"。想象一個桶,這個桶以固定速率往裏面放入小令牌。每次程序想要執行一個操作(比如發起請求)時,需要從桶裏取出一個令牌。如果桶裏有令牌,就可以立即執行;如果沒有令牌,就需要等待直到有新的令牌產生。

Demo示例

基礎限流示例(對應1.0,2.0,3.0,5.0,10.0,100.0,1000.0,10000.0)

package com.cqsym.lmdw1.testguava;

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

import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

public class RateLimiterBasicDemo {
    public static void main(String[] args) {
        // 創建一個限流器,每秒生成2個令牌
        RateLimiter limiter = RateLimiter.create(1.0);

        System.out.println("開始測試基礎限流...");

        for (int i = 1; i <= 10; i++) {
            // 阻塞式獲取令牌
            double waitTime = limiter.acquire();
            System.out.println(String.format("第%d個請求,等待時間:%.2f秒,當前時間:%s", i, waitTime, getCurrentTime()));
        }
    }

    private static String getCurrentTime() {
        return LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS"));
    }
}

Guava之RateLimiter_限流

Guava之RateLimiter_java_02

Guava之RateLimiter_限流_03

Guava之RateLimiter_限流_04

Guava之RateLimiter_System_05

Guava之RateLimiter_java_06

Guava之RateLimiter_限流_07

Guava之RateLimiter_System_08


非阻塞式限流示例

package com.cqsym.lmdw1.testguava;

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

import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;

public class RateLimiterNonBlockingDemo {
    public static void main(String[] args) {
        // 創建限流器,每秒1個令牌
        RateLimiter limiter = RateLimiter.create(1.0);

        System.out.println("開始測試非阻塞限流...");

        for (int i = 1; i <= 5; i++) {
            // 嘗試獲取令牌,最多等待100毫秒
            boolean acquired = limiter.tryAcquire(100, TimeUnit.MILLISECONDS);

            if (acquired) {
                System.out.println("請求" + i + ":獲取到令牌,執行業務邏輯");
                // 模擬業務處理
                processRequest(i);
            } else {
                System.out.println("請求" + i + ":未獲取到令牌,請求被拒絕");
            }

            // 模擬請求間隔
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    private static void processRequest(int requestId) {
        System.out.println("  -> 處理請求" + requestId + "中..." + "    時間:" + getCurrentTime());
        try {
            Thread.sleep(500); // 模擬處理時間
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("  -> 請求" + requestId + "處理完成" + "    時間:" + getCurrentTime());
    }

    private static String getCurrentTime() {
        return LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS"));
    }
}

Guava之RateLimiter_java_09

因為在處理任務中消耗了500毫秒,所以理論上1秒鐘只能處理2個,但是RateLimiter是1秒產生1個令牌,但是在獲取令牌這裏設置最多等待100毫秒,所以第二個就直接失敗。


批量獲取令牌示例

package com.cqsym.lmdw1.testguava;

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

public class RateLimiterBatchDemo {
    public static void main(String[] args) {
        // 創建限流器,每秒5個令牌
        RateLimiter limiter = RateLimiter.create(5.0);

        System.out.println("開始測試批量獲取令牌...");

        // 批量獲取不同數量的令牌
        int[] batchSizes = {1, 3, 2, 4, 1};

        for (int i = 0; i < batchSizes.length; i++) {
            int permits = batchSizes[i];
            double waitTime = limiter.acquire(permits);

            System.out.println(String.format("批次%d:獲取%d個令牌,等待時間:%.2f秒,時間:%s",
                    i + 1, permits, waitTime, getCurrentTime()));
        }
    }

    private static String getCurrentTime() {
        return java.time.LocalTime.now().format(
                java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss.SSS"));
    }
}

Guava之RateLimiter_System_10


SpringBoot集成示例

import com.google.common.util.concurrent.RateLimiter;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Service
public class RateLimiterService {
    
    // 每秒允許10個請求
    private final RateLimiter rateLimiter = RateLimiter.create(10.0);
    
    public boolean tryAcquire() {
        // 嘗試獲取令牌,最多等待100毫秒
        return rateLimiter.tryAcquire(100, java.util.concurrent.TimeUnit.MILLISECONDS);
    }
    
    public void acquire() {
        rateLimiter.acquire();
    }
}

@RestController
public class ApiController {
    
    private final RateLimiterService rateLimiterService;
    
    public ApiController(RateLimiterService rateLimiterService) {
        this.rateLimiterService = rateLimiterService;
    }
    
    @GetMapping("/api/limited")
    public String limitedApi() {
        if (!rateLimiterService.tryAcquire()) {
            return "請求過於頻繁,請稍後再試";
        }
        
        // 執行業務邏輯
        return "請求處理成功,時間:" + java.time.LocalDateTime.now();
    }
    
    @GetMapping("/api/blocking")
    public String blockingApi() {
        // 阻塞式獲取令牌
        rateLimiterService.acquire();
        return "阻塞式請求處理成功,時間:" + java.time.LocalDateTime.now();
    }
}


核心API詳解

創建RateLimiter

通過 RateLimiter.create(1) 創建一個限流器,參數代表每秒生成的令牌數 :

// 每秒生成2個令牌
RateLimiter limiter = RateLimiter.create(2.0);

// 創建時指定預熱期(平滑啓動)
RateLimiter limiter = RateLimiter.create(2.0, 1, TimeUnit.SECONDS);

獲取令牌的方式

  1. 阻塞式獲取limiter.acquire(i) 以阻塞的方式獲取令牌
  2. 非阻塞式獲取tryAcquire() 方法可以設置超時時間 
// 阻塞式獲取1個令牌
double waitTime = limiter.acquire();

// 阻塞式獲取多個令牌
double waitTime = limiter.acquire(3);



// 非阻塞式獲取,立即返回
boolean success = limiter.tryAcquire();

// 非阻塞式獲取,最多等待指定時間
boolean success = limiter.tryAcquire(100, TimeUnit.MILLISECONDS);

// 非阻塞式獲取,最多等待指定時間
boolean success = limiter.tryAcquire(3, 100, TimeUnit.MILLISECONDS);