【高并发】限流与熔断:高并发系统的守护神!

Scroll Down

限流与熔断:高并发系统的守护神!

大家好!今天我们来聊聊高并发系统中的两大守护神:限流(Rate Limiting)和熔断(Circuit Breaker)。无论是电商秒杀、社交热点,还是金融交易,高并发场景下系统的稳定性至关重要。限流和熔断是保障系统稳定性的核心手段。本文将通过生动的故事和 Java 实战代码,带你彻底搞懂限流与熔断的原理和应用!


1. 限流与熔断是什么?

1.1 限流(Rate Limiting)

限流是指通过限制系统的请求速率,防止系统因流量过大而崩溃。限流的核心思想是控制流量,确保系统在可承受的范围内运行。

常见限流算法

  • 计数器算法:固定时间窗口内限制请求数量。
  • 滑动窗口算法:动态时间窗口内限制请求数量。
  • 令牌桶算法:以固定速率生成令牌,请求需要消耗令牌。
  • 漏桶算法:以固定速率处理请求,超出速率的请求会被丢弃或排队。

1.2 熔断(Circuit Breaker)

熔断是指当系统出现故障或性能下降时,自动切断对故障服务的调用,避免故障扩散。熔断的核心思想是快速失败,防止系统雪崩。

熔断器的三种状态

  • 关闭状态(Closed):正常调用服务。
  • 打开状态(Open):切断服务调用,直接返回错误。
  • 半开状态(Half-Open):尝试恢复服务调用,根据结果决定是否切换到关闭状态。

2. 限流与熔断的应用场景

2.1 限流的应用场景

  • API 限流:防止 API 被恶意刷量。
  • 秒杀系统:控制秒杀活动的请求速率。
  • 消息队列:限制消息的生产和消费速率。

2.2 熔断的应用场景

  • 微服务调用:防止某个服务的故障导致整个系统崩溃。
  • 数据库访问:防止数据库因高并发而宕机。
  • 第三方服务调用:防止第三方服务不可用影响系统稳定性。

3. Java 实战代码

3.1 限流实战:令牌桶算法

以下是一个基于令牌桶算法的限流实现。

令牌桶限流器

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

public class TokenBucketRateLimiter {
    private final int capacity; // 桶的容量
    private final AtomicInteger tokens; // 当前令牌数量
    private final long rate; // 令牌生成速率(毫秒)
    private AtomicLong lastRefillTime; // 上次令牌生成时间

    public TokenBucketRateLimiter(int capacity, int ratePerSecond) {
        this.capacity = capacity;
        this.tokens = new AtomicInteger(capacity);
        this.rate = 1000 / ratePerSecond;
        this.lastRefillTime = new AtomicLong(System.currentTimeMillis());
    }

    // 尝试获取令牌
    public boolean tryAcquire() {
        refillTokens();
        if (tokens.get() > 0) {
            tokens.decrementAndGet();
            return true;
        }
        return false;
    }

    // 补充令牌
    private void refillTokens() {
        long now = System.currentTimeMillis();
        long elapsedTime = now - lastRefillTime.get();
        int newTokens = (int) (elapsedTime / rate);
        if (newTokens > 0) {
            long newRefillTime = lastRefillTime.get() + newTokens * rate;
            if (lastRefillTime.compareAndSet(lastRefillTime.get(), newRefillTime)) {
                tokens.updateAndGet(current -> Math.min(capacity, current + newTokens));
            }
        }
    }
}

使用示例

public class RateLimiterDemo {
    public static void main(String[] args) throws InterruptedException {
        TokenBucketRateLimiter rateLimiter = new TokenBucketRateLimiter(10, 2); // 每秒生成 2 个令牌

        for (int i = 0; i < 20; i++) {
            if (rateLimiter.tryAcquire()) {
                System.out.println("Request " + i + " allowed.");
            } else {
                System.out.println("Request " + i + " blocked.");
            }
            Thread.sleep(200); // 模拟请求间隔
        }
    }
}

输出结果

Request 0 allowed.
Request 1 allowed.
Request 2 blocked.
Request 3 blocked.
Request 4 allowed.
Request 5 allowed.
Request 6 blocked.
Request 7 blocked.
Request 8 allowed.
Request 9 allowed.
...

3.2 熔断实战:简单熔断器

以下是一个简单的熔断器实现。

熔断器

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 void call(Runnable service) {
        if (isOpen) {
            long now = System.currentTimeMillis();
            if (now - lastFailureTime > timeout) {
                isOpen = false; // 切换到半开状态
                System.out.println("Circuit breaker: Half-Open.");
            } else {
                System.out.println("Circuit breaker: Open. Service call blocked.");
                return;
            }
        }

        try {
            service.run();
            failureCount = 0; // 重置失败次数
        } catch (Exception e) {
            failureCount++;
            lastFailureTime = System.currentTimeMillis();
            if (failureCount >= failureThreshold) {
                isOpen = true; // 切换到打开状态
                System.out.println("Circuit breaker: Open.");
            }
            System.out.println("Service call failed. Failure count: " + failureCount);
        }
    }
}

使用示例

public class CircuitBreakerDemo {
    public static void main(String[] args) throws InterruptedException {
        CircuitBreaker circuitBreaker = new CircuitBreaker(3, 5000); // 失败阈值 3,超时时间 5 秒

        for (int i = 0; i < 10; i++) {
            int finalI = i;
            circuitBreaker.call(() -> {
                if (finalI < 5) {
                    throw new RuntimeException("Service failed.");
                } else {
                    System.out.println("Service call succeeded.");
                }
            });
            Thread.sleep(1000); // 模拟请求间隔
        }
    }
}

输出结果

Service call failed. Failure count: 1
Service call failed. Failure count: 2
Service call failed. Failure count: 3
Circuit breaker: Open.
Circuit breaker: Open. Service call blocked.
Circuit breaker: Open. Service call blocked.
Circuit breaker: Half-Open.
Service call succeeded.
Service call succeeded.
Service call succeeded.

4. 总结

  • 限流:通过控制请求速率,防止系统因流量过大而崩溃。
  • 熔断:通过快速失败,防止故障扩散,保障系统稳定性。

通过本文的讲解和代码示例,相信你已经掌握了限流与熔断的核心原理和实现方法。希望你能在实际项目中灵活运用这些技术,构建高可用、高性能的系统!


互动话题
你在项目中使用过限流和熔断吗?遇到过哪些有趣的问题?欢迎在评论区分享你的经验!