限流算法用于限制系统的请求流量,防止系统因超载而崩溃。它在高并发场景下广泛应用于微服务、API 网关、缓存等场景。以下是常见的几种限流算法及其详解:
计数器法是最简单的限流算法。它基于一个固定时间窗口,在窗口内统计请求的数量。如果请求次数超过预设的上限,后续请求将在窗口结束前被拒绝。到达下一个时间窗口时,计数器会重置为 0,重新开始统计。
假设设定一个时间窗口为 1 分钟,允许每分钟最多处理 100 个请求。如果在 1 分钟内收到 101 个请求,第 101 个请求将被拒绝或等待到下一个时间窗口。
import java.util.concurrent.atomic.AtomicInteger;
public class FixedWindowRateLimiter {
private final int limit;
private final long windowSize;
private long windowStart;
private final AtomicInteger requestCount;
public FixedWindowRateLimiter(int limit, long windowSize) {
this.limit = limit;
this.windowSize = windowSize;
this.windowStart = System.currentTimeMillis();
this.requestCount = new AtomicInteger(0);
}
public synchronized boolean allowRequest() {
long currentTime = System.currentTimeMillis();
if (currentTime - windowStart > windowSize) {
windowStart = currentTime;
requestCount.set(0); // Reset count
}
if (requestCount.get() < limit) {
requestCount.incrementAndGet(); // Increment count
return true; // Allow request
}
return false; // Reject request
}
}
package main
import (
"sync"
"time"
)
type FixedWindowRateLimiter struct {
limit int
windowSize time.Duration
windowStart time.Time
requestCount int
mu sync.Mutex
}
func NewFixedWindowRateLimiter(limit int, windowSize time.Duration) *FixedWindowRateLimiter {
return &FixedWindowRateLimiter{
limit: limit,
windowSize: windowSize,
windowStart: time.Now(),
}
}
func (rl *FixedWindowRateLimiter) AllowRequest() bool {
rl.mu.Lock()
defer rl.mu.Unlock()
if time.Since(rl.windowStart) > rl.windowSize {
rl.windowStart = time.Now()
rl.requestCount = 0 // Reset count
}
if rl.requestCount < rl.limit {
rl.requestCount++ // Increment count
return true // Allow request
}
return false // Reject request
}
滑动窗口法通过不断移动时间窗口来限制请求,避免了固定窗口带来的突发效应。它将一个大时间窗口分成多个小的时间片,并实时计算多个时间片内的请求总和。
在过去 1 分钟内最多允许 100 个请求,将 1 分钟划分为 6 个 10 秒的小窗口。如果在过去的 6 个窗口内总请求数未超过 100 个,新的请求将通过,否则拒绝。
import java.util.LinkedList;
import java.util.Queue;
public class SlidingWindowRateLimiter {
private final int limit;
private final long windowSize;
private final Queue<Long> requestTimes;
public SlidingWindowRateLimiter(int limit, long windowSize) {
this.limit = limit;
this.windowSize = windowSize;
this.requestTimes = new LinkedList<>();
}
public synchronized boolean allowRequest() {
long currentTime = System.currentTimeMillis();
while (!requestTimes.isEmpty() && currentTime - requestTimes.peek() > windowSize) {
requestTimes.poll(); // Remove expired requests
}
if (requestTimes.size() < limit) {
requestTimes.offer(currentTime); // Add new request
return true; // Allow request
}
return false; // Reject request
}
}
package main
import (
"container/list"
"sync"
"time"
)
type SlidingWindowRateLimiter struct {
limit int
windowSize time.Duration
requestTimes *list.List
mu sync.Mutex
}
func NewSlidingWindowRateLimiter(limit int, windowSize time.Duration) *SlidingWindowRateLimiter {
return &SlidingWindowRateLimiter{
limit: limit,
windowSize: windowSize,
requestTimes: list.New(),
}
}
func (rl *SlidingWindowRateLimiter) AllowRequest() bool {
rl.mu.Lock()
defer rl.mu.Unlock()
currentTime := time.Now()
for rl.requestTimes.Len() > 0 && currentTime.Sub(rl.requestTimes.Front().Value.(time.Time)) > rl.windowSize {
rl.requestTimes.Remove(rl.requestTimes.Front()) // Remove expired requests
}
if rl.requestTimes.Len() < rl.limit {
rl.requestTimes.PushBack(currentTime) // Add new request
return true // Allow request
}
return false // Reject request
}
漏桶算法将请求流量视为向桶中注水,桶以固定速率“漏水”(处理请求)。如果水(请求)进入的速度超过漏水的速度,桶会溢出,多余的水(请求)将被丢弃。因此,它通过固定的处理速率来平滑流量。
假设漏桶的容量为 10,处理速率为每秒 2 个请求。如果每秒收到 5 个请求,多余的请求将被丢弃,只能处理 2 个请求。
import java.util.concurrent.TimeUnit;
public class LeakyBucketRateLimiter {
private final int capacity;
private final int leakRate;
private int currentWater;
private long lastLeakTime;
public LeakyBucketRateLimiter(int capacity, int leakRate) {
this.capacity = capacity;
this.leakRate = leakRate;
this.currentWater = 0;
this.lastLeakTime = System.currentTimeMillis();
}
public synchronized boolean allowRequest() {
long currentTime = System.currentTimeMillis();
long deltaTime = currentTime - lastLeakTime;
int leakedWater = (int) (deltaTime / 1000) * leakRate;
currentWater = Math.max(0, currentWater - leakedWater);
lastLeakTime = currentTime;
if (currentWater < capacity) {
currentWater++; // Add request to bucket
return true; // Allow request
}
return false; // Reject request
}
}
package main
import (
"sync"
"time"
)
type LeakyBucketRateLimiter struct {
capacity int
leakRate int
currentWater int
lastLeakTime time.Time
mu sync.Mutex
}
func NewLeakyBucketRateLimiter(capacity int, leakRate int) *LeakyBucketRateLimiter {
return &LeakyBucketRateLimiter{
capacity: capacity,
leakRate: leakRate,
currentWater: 0,
lastLeakTime: time.Now(),
}
}
func (rl *LeakyBucketRateLimiter) AllowRequest() bool {
rl.mu.Lock()
defer rl.mu.Unlock()
currentTime := time.Now()
deltaTime := currentTime.Sub(rl.lastLeakTime).Seconds()
leakedWater := int(deltaTime) * rl.leakRate
rl.currentWater -= leakedWater
if rl.currentWater < 0 {
rl.currentWater = 0
}
rl.lastLeakTime = currentTime
if rl.currentWater < rl.capacity {
rl.currentWater++ // Add request to bucket
return true // Allow request
}
return false // Reject request
}
令牌桶算法与漏桶算法类似,但它允许突发流量。系统以固定速率生成令牌,放入桶中。每个请求必须消耗一个令牌才能被处理,如果没有令牌,请求将被拒绝或等待。桶的大小决定了系统可以承受的突发流量上限。
假设每秒生成 5 个令牌,最多可以存储 10 个令牌。如果一秒内收到 8 个请求,5 个请求被处理,剩下的 3 个请求会等待令牌。
import java.util.concurrent.atomic.AtomicInteger;
public class TokenBucketRateLimiter {
private final int capacity;
//每秒生成的令牌数量,通常以“令牌/秒”为单位
private final int rate;
//生成一个令牌所需的时间间隔,通常以秒为单位
private final long interval;
private int tokens;
private long lastRefillTime;
public TokenBucketRateLimiter(int capacity, int rate, long interval) {
this.capacity = capacity;
this.rate = rate;
this.interval = interval;
this.tokens = 0;
this.lastRefillTime = System.currentTimeMillis();
}
public synchronized boolean allowRequest() {
long currentTime = System.currentTimeMillis();
refillTokens(currentTime);
if (tokens > 0) {
tokens--; // Consume a token
return true; // Allow request
}
return false; // Reject request
}
private void refillTokens(long currentTime) {
long timeElapsed = currentTime - lastRefillTime;
int tokensToAdd = (int) (timeElapsed / interval) * rate;
tokens = Math.min(capacity, tokens + tokensToAdd);
lastRefillTime = currentTime;
}
}
package main
import (
"sync"
"time"
)
type TokenBucketRateLimiter struct {
capacity int
rate int
interval time.Duration
tokens int
lastRefillTime time.Time
mu sync.Mutex
}
func NewTokenBucketRateLimiter(capacity int, rate int, interval time.Duration) *TokenBucketRateLimiter {
return &TokenBucketRateLimiter{
capacity: capacity,
rate: rate,
interval: interval,
tokens: 0,
lastRefillTime: time.Now(),
}
}
func (rl *TokenBucketRateLimiter) AllowRequest() bool {
rl.mu.Lock()
defer rl.mu.Unlock()
currentTime := time.Now()
rl.refillTokens(currentTime)
if rl.tokens > 0 {
rl.tokens-- // Consume a token
return true // Allow request
}
return false // Reject request
}
func (rl *TokenBucketRateLimiter) refillTokens(currentTime time.Time) {
timeElapsed := currentTime.Sub(rl.lastRefillTime)
tokensToAdd := int(timeElapsed / rl.interval) * rl.rate
rl.tokens += tokensToAdd
if rl.tokens > rl.capacity {
rl.tokens = rl.capacity // Limit to capacity
}
rl.lastRefillTime = currentTime
}
熔断器并不是严格意义上的限流算法,它是用于保护系统的机制。当系统检测到自身的负载过大或某些功能出现问题时,熔断器会切断请求的继续处理,以防止系统崩溃或过载。
如果系统的错误率超过 50%,熔断器开启,所有新请求被直接拒绝,避免进一步的压力导致系统崩溃。
public class CircuitBreaker {
private final int failureThreshold;
private final long timeout;
private int failureCount;
private long lastFailureTime;
private boolean isOpen;
public CircuitBreaker(int failureThreshold, long timeout) {
this.failureThreshold = failureThreshold;
this.timeout = timeout;
this.failureCount = 0;
this.lastFailureTime = 0;
this.isOpen = false;
}
public synchronized boolean allowRequest() {
if (isOpen){
if (System.currentTimeMillis() - lastFailureTime > timeout) {
isOpen = false; // Reset the circuit breaker after timeout
failureCount = 0; // Reset failure count
}
return !isOpen; // Allow request if the circuit is closed
}
return true // Allow request if circuit is closed
}
public synchronized void recordFailure() {
failureCount++;
lastFailureTime = System.currentTimeMillis();
if (failureCount >= failureThreshold) {
isOpen = true; // Open the circuit if failure threshold is reached
}
}
public synchronized void recordSuccess() {
failureCount = 0; // Reset on success
}
}
package main
import (
"sync"
"time"
)
type CircuitBreaker struct {
failureThreshold int
timeout time.Duration
failureCount int
lastFailureTime time.Time
isOpen bool
mu sync.Mutex
}
func NewCircuitBreaker(failureThreshold int, timeout time.Duration) *CircuitBreaker {
return &CircuitBreaker{
failureThreshold: failureThreshold,
timeout: timeout,
}
}
func (cb *CircuitBreaker) AllowRequest() bool {
cb.mu.Lock()
defer cb.mu.Unlock()
if cb.isOpen {
if time.Since(cb.lastFailureTime) > cb.timeout {
cb.isOpen = false // Reset the circuit after timeout
cb.failureCount = 0 // Reset failure count
} else {
return false // Reject request if circuit is open
}
}
return true // Allow request if circuit is closed
}
func (cb *CircuitBreaker) RecordFailure() {
cb.mu.Lock()
defer cb.mu.Unlock()
cb.failureCount++
cb.lastFailureTime = time.Now()
if cb.failureCount >= cb.failureThreshold {
cb.isOpen = true // Open the circuit if failure threshold is reached
}
}
func (cb *CircuitBreaker) RecordSuccess() {
cb.mu.Lock()
defer cb.mu.Unlock()
cb.failureCount = 0 // Reset on success
}
随机丢弃是一种队列管理策略,当系统的请求队列接近满负荷时,随机丢弃部分请求,以防止系统超载。这种机制会在队列未满时就开始丢弃请求,从而在系统达到高负载前缓解压力。
当请求队列的长度达到 80%,系统开始随机丢弃请求,丢弃的概率随着队列长度的增加而增加。在队列满的时候,所有新的请求都将被丢弃。
import java.util.Random;
public class RandomEarlyDropRateLimiter {
private final int limit;
private final Random random;
public RandomEarlyDropRateLimiter(int limit) {
this.limit = limit;
this.random = new Random();
}
public synchronized boolean allowRequest(int currentCount) {
if (currentCount < limit) {
return true; // Allow request
}
// Randomly drop requests
return random.nextInt(currentCount + 1) > limit; // Drop with a probability
}
}
package main
import (
"math/rand"
"sync"
)
type RandomEarlyDropRateLimiter struct {
limit int
mu sync.Mutex
}
func NewRandomEarlyDropRateLimiter(limit int) *RandomEarlyDropRateLimiter {
return &RandomEarlyDropRateLimiter{limit: limit}
}
func (rl *RandomEarlyDropRateLimiter) AllowRequest(currentCount int) bool {
rl.mu.Lock()
defer rl.mu.Unlock()
if currentCount < rl.limit {
return true // Allow request
}
// Randomly drop requests
return rand.Intn(currentCount+1) > rl.limit // Drop with a probability
}
基于优先级的限流算法考虑请求的优先级,根据优先级决定请求的处理顺序。高优先级的请求可以优先处理,而低优先级的请求在系统负载高时可能会被延迟处理或拒绝。
在一场促销活动中,VIP 用户的请求被设定为高优先级,系统优先处理 VIP 请求,而普通用户请求可能需要等待或在高负载时被拒绝。
import java.util.PriorityQueue;
public class PriorityRateLimiter {
private final PriorityQueue<PriorityRequest> requestQueue;
private final int limit;
public PriorityRateLimiter(int limit) {
this.limit = limit;
this.requestQueue = new PriorityQueue<>((a, b) -> b.priority - a.priority);
}
public synchronized boolean allowRequest(PriorityRequest request) {
if (requestQueue.size() < limit) {
requestQueue.offer(request);
return true; // Allow request
}
return false; // Reject request
}
public synchronized PriorityRequest processRequest() {
if (!requestQueue.isEmpty()) {
return requestQueue.poll(); // Process highest priority request
}
return null; // No requests to process
}
}
class PriorityRequest {
public final int priority;
public PriorityRequest(int priority) {
this.priority = priority;
}
}
package main
import (
"container/heap"
"sync"
)
type PriorityRequest struct {
Priority int
}
type PriorityQueue []*PriorityRequest
func (pq PriorityQueue) Len() int { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool {
return pq[i].Priority > pq[j].Priority // Higher priority first
}
func (pq PriorityQueue) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
}
func (pq *PriorityQueue) Push(x interface{}) {
*pq = append(*pq, x.(*PriorityRequest))
}
func (pq *PriorityQueue) Pop() interface{} {
old := *pq
n := len(old)
x := old[n-1]
*pq = old[0 : n-1]
return x
}
type PriorityRateLimiter struct {
Queue *PriorityQueue
Limit int
mu sync.Mutex
}
func NewPriorityRateLimiter(limit int) *PriorityRateLimiter {
pq := make(PriorityQueue, 0)
heap.Init(&pq)
return &PriorityRateLimiter{
Queue: &pq,
Limit: limit,
}
}
func (rl *PriorityRateLimiter) AllowRequest(request *PriorityRequest) bool {
rl.mu.Lock()
defer rl.mu.Unlock()
if len(*rl.Queue) < rl.Limit {
heap.Push(rl.Queue, request)
return true // Allow request
}
return false // Reject request
}
func (rl *PriorityRateLimiter) ProcessRequest() *PriorityRequest {
rl.mu.Lock()
defer rl.mu.Unlock()
if len(*rl.Queue) > 0 {
return heap.Pop(rl.Queue).(*PriorityRequest) // Process highest priority request
}
return nil // No requests to process
}
限流算法的选择需要根据业务需求和场景来进行权衡: