Java限流方案详解及示例代码

1. 为什么要限流?

限流是一种流行的系统设计策略,其目的是保护系统免受过高的请求流量或恶意攻击,确保系统的可用性和稳定性。在高并发的环境下,没有限流措施的系统可能会遇到以下问题:

  1. 系统过载:系统无法处理过多的请求,导致响应时间延长,甚至请求失败,给用户带来不好的体验。
  2. 资源耗尽:当大量请求同时到达系统时,系统资源如CPU、内存、I/O等容易被过度消耗,导致系统瘫痪或宕机。
  3. 数据库压力:系统并发访问数据库时,数据库的连接数和查询负载可能会超过其处理能力,导致数据库崩溃或性能下降。
  4. 防止恶意攻击:恶意用户或者攻击者可能会利用系统的漏洞或者薄弱环节进行恶意请求,如暴力破解、DDoS攻击等,限流可以防止这些攻击造成的危害。

基于以上原因,限流成为了现代软件架构中不可或缺的一环。

1.2 在业务中如何做限流

下面我们用最简单的代码描述常见的限流算法,实际场景中可能要换成redis等工具在分布式集群环境下限流,下面代码仅作演示方便理解。 本篇讲方案理论,您可以访问下一篇文章查看Springboot+AOP+Redis实现限流的教程 http://refblogs.com/article/481

2. 常见的限流方案

2.1 固定时间窗口计数器

固定时间窗口计数器是一种简单有效的限流方案,其原理是设定一个固定的时间窗口大小,将请求按时间片划分。在每个时间窗口内,统计请求的数量,然后与设定的阈值进行比较。如果超过阈值,则拒绝新的请求。

示例代码:

// 使用AtomicInteger记录请求数量
AtomicInteger counter = new AtomicInteger(0);
// 定义时间窗口大小(毫秒)
int windowSize = 1000;
// 定义请求数量阈值
int threshold = 100;

boolean allowRequest() {
    long currentTime = System.currentTimeMillis();
    long lastWindowTime = currentTime - windowSize;
    
    // 如果时间窗口内的请求数量超过阈值,拒绝请求
    if (counter.get() >= threshold) {
        return false;
    }
    
    // 如果上一个时间窗口已过期,重置请求数量
    if (lastWindowTime > counter.get()) {
        counter.set(0);
    }
    
    // 增加请求数量
    return counter.incrementAndGet() <= threshold;
}

测试代码:

for (int i = 0; i < 200; i++) {
    System.out.println("Allow request: " + allowRequest());
}

输出结果:

Allow request: true
Allow request: true
...
Allow request: false
Allow request: false
...

2.2 令牌桶算法

令牌桶算法是另一种常用的限流方案,它通过维护一个固定容量的令牌桶,每个令牌代表一个请求的处理能力。系统按照一定速率往令牌桶中添加令牌,当请求到达时,需要消费一个令牌,如果令牌桶中没有足够的令牌,则请求被拒绝。

示例代码:

public class TokenBucket {
    // 令牌桶容量
    private final int capacity;
    // 当前令牌数量
    private AtomicInteger tokens;
    // 添加令牌的速率(个/秒)
    private final int rate;
    // 最后添加令牌的时间戳
    private long lastRefillTime;

    public TokenBucket(int capacity, int rate) {
        this.capacity = capacity;
        this.tokens = new AtomicInteger(capacity);
        this.rate = rate;
        this.lastRefillTime = System.currentTimeMillis();
    }

    public boolean allowRequest() {
        refillTokens();

        // 如果没有足够的令牌,则拒绝请求
        if (tokens.get() <= 0) {
            return false;
        }

        // 消费一个令牌
        tokens.decrementAndGet();
        return true;
    }

    private void refillTokens() {
        long currentTime = System.currentTimeMillis();
        long elapsedTime = currentTime - lastRefillTime;

        // 计算添加的令牌数量
        int refillAmount = (int) (elapsedTime * rate / 1000);
        
        // 添加令牌(不超过容量)
        tokens.addAndGet(Math.min(refillAmount, capacity));
        // 更新最后添加令牌的时间戳
        lastRefillTime = currentTime;
    }
}

测试代码:

TokenBucket tokenBucket = new TokenBucket(100, 10);
for (int i = 0; i < 200; i++) {
    System.out.println("Allow request: " + tokenBucket.allowRequest());
}

输出结果:

Allow request: true
Allow request: true
...
Allow request: false
Allow request: false
...

3. 总结

限流是保护系统稳定性和可用性的重要手段,能够有效避免过载、资源耗尽、数据库压力过大和恶意攻击的问题。在实际开发中,我们可以根据系统的需求选择适合的限流方案,如固定时间窗口计数器、令牌桶算法等。通过合理地设置阈值、窗口大小和速率等参数,可以快速响应用户请求,保持系统的高可用性。

正文到此结束
评论插件初始化中...
Loading...