JAVA应用层限流

限流就是对请求或并发数进行限制;通过对一个时间窗口内的请求量进行限制来保障系统的正常运行。如果我们的服务资源有限、处理能力有限,就需要对调用我们服务的上游请求进行限制,以防止自身服务由于资源耗尽而停止服务。
如:公交车满载拒载、地铁站限流排队等

限流中提到的阈值拒绝策略两个概念
阈值:在一个单位时间内允许的请求量。如 QPS 限制为10,说明 1 秒内最多接受 10 次请求。
拒绝策略:超过阈值的请求的拒绝策略,常见的拒绝策略有直接拒绝、排队等待等。

1. 计数器算法

一种简单方便的限流算法。通过一个支持原子操作的计数器来累计 1 秒内的请求次数,当 1 秒内计数达到限流阈值时触发拒绝策略。每过 1 秒,计数器重置为 0 开始重新计数。

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 计数限流器
 */
public class CountLimiter {

    // 阈值
    private Integer qps;
    // 时间窗口(毫秒)
    private long timeWindows = 1000;
    // 计数器
    private AtomicInteger requestCount = new AtomicInteger();

    private long startTime = System.currentTimeMillis();

    private CountLimiter(){}

    public CountLimiter(Integer qps) {
        this.qps = qps;
    }

    public synchronized boolean tryAcquire() {
        if ((System.currentTimeMillis() - startTime) > timeWindows) {
            requestCount.set(0);
            startTime = System.currentTimeMillis();
        }
        return requestCount.incrementAndGet() <= qps;
    }

}

测试

public class RateLimiterTest {

    public static void main(String[] args) throws InterruptedException {
        CountLimiter countLimiter = new CountLimiter(2);
        for (int i = 0; i < 10; i++) {
            Thread.sleep(200);
            LocalTime now = LocalTime.now();
            if (!countLimiter.tryAcquire()) {
                System.out.println(now + ": 限流 ");
            } else {
                System.out.println(now + ": 通行 ");
            }
        }
    }
}

2. 滑动计数器算法

滑动计数器算法为解决计数器算法遇到时间窗口的临界突变时,会发生qps突增,如 1s 中的后 500 ms 和第 2s 的前 500ms 时,虽然是加起来是 1s 时间,却可以被请求 2 次以上。

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 滑动计数器限流
 */
public class SlidingCountLimiter  {

    /**
     * 阈值
     */
    private int qps = 2;

    /**
     * 时间窗口大小(毫秒)
     */
    private long windowSize = 1000;

    /**
     * 子窗口数量
     */
    private Integer windowCount = 10;

    /**
     * 窗口列表
     */
    private WindowInfo[] windowArray = new WindowInfo[windowCount];

    private SlidingCountLimiter() {}

    public SlidingCountLimiter(int qps) {
        this.qps = qps;
        long currentTimeMillis = System.currentTimeMillis();
        for (int i = 0; i < windowArray.length; i++) {
            windowArray[i] = new WindowInfo(currentTimeMillis, new AtomicInteger(0));
        }
    }

    /**
     * 获取权限
     * @return
     */
    public synchronized boolean tryAcquire() {
        long currentTimeMillis = System.currentTimeMillis();
        // 当前时间窗口 = 当前时间 % 时间窗口大小 / (时间窗口大小 / 子窗口数量)
        int currentIndex = (int)(currentTimeMillis % windowSize / (windowSize / windowCount));
        // 更新当前窗口计数 & 重置过期窗口计数
        int sum = 0;
        for (int i = 0; i < windowArray.length; i++) {
            WindowInfo windowInfo = windowArray[i];
            if ((currentTimeMillis - windowInfo.getTime()) > windowSize) {
                windowInfo.getNumber().set(0);
                windowInfo.setTime(currentTimeMillis);
            }
            if (currentIndex == i && windowInfo.getNumber().get() < qps) {
                windowInfo.getNumber().incrementAndGet();
            }
            sum = sum + windowInfo.getNumber().get();
        }
        // 当前 QPS 是否超过限制
        return sum <= qps;
    }

    private class WindowInfo {
        // 窗口开始时间
        private Long time;
        // 计数器
        private AtomicInteger number;

        public WindowInfo(long time, AtomicInteger number) {
            this.time = time;
            this.number = number;
        }

        public Long getTime() {
            return time;
        }

        public void setTime(Long time) {
            this.time = time;
        }

        public AtomicInteger getNumber() {
            return number;
        }

        public void setNumber(AtomicInteger number) {
            this.number = number;
        }
    }

}

测试

public class RateLimiterTest {

    public static void main(String[] args) throws InterruptedException {
		SlidingCountLimiter slidingCountLimiter = new SlidingCountLimiter(2);
        for (int i = 0; i < 20; i++) {
            Thread.sleep(200);
            if (slidingCountLimiter.tryAcquire()) {
                System.out.print(LocalTime.now() + ": 通行 ");
            } else {
                System.out.println(LocalTime.now() + ": 限流 ");
            }
        }
    }
    
}

3. 令牌桶算法 (推荐)

每个请求都需要去桶中拿取一个令牌,取到令牌则继续执行;如果桶中无令牌可取,就触发拒绝策略,可以是超时等待,也可以是直接拒绝本次请求,由此达到限流目的。

使用Google 的 Java 开发工具包 Guava 中的限流工具类 RateLimiter

<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>28.2-jre</version>
</dependency>
public class RateLimiterTest {

    public static void main(String[] args) throws InterruptedException {
		RateLimiter rateLimiter = RateLimiter.create(2);
        for (int i = 0; i < 10; i++) {
            String time = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME);
            System.out.println(time + ":" + rateLimiter.tryAcquire());
            Thread.sleep(200);
        }
    }
    
}
### Java 中实现请求限流的方法和工具 在 Java 中,可以通过多种方法和工具来实现请求限流功能。以下是常见的几种方式及其特点: #### 1. 使用 Guava 的 `RateLimiter` 类 Guava 提供了一个成熟的限流工具——`RateLimiter`,它通过令牌桶算法实现了速率限制的功能。以下是一个简单的示例代码[^1]: ```java import com.google.common.util.concurrent.RateLimiter; public class TokenBucketDemo { private final RateLimiter rateLimiter = RateLimiter.create(10); // 每秒最多发放10个令牌 public void doRequest() { if (rateLimiter.tryAcquire()) { // 尝试获取一个令牌 System.out.println("正常处理请求"); } else { System.out.println("限流"); } } public static void main(String[] args) throws InterruptedException { TokenBucketDemo tokenBucketDemo = new TokenBucketDemo(); for (int i = 0; i < 20; i++) { Thread.sleep(1000); tokenBucketDemo.doRequest(); } } } ``` 上述代码展示了如何利用 `RateLimiter` 来控制每秒钟的最大请求数量。需要注意的是,该工具适用于单机环境下的限流场景[^3]。 为了引入 Guava 库,可以在项目的 Maven 配置文件中添加如下依赖项[^4]: ```xml <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.0.1-jre</version> </dependency> ``` --- #### 2. 基于 Redis 实现分布式限流 对于分布式系统而言,仅依靠单机版的 `RateLimiter` 是不够的。可以借助 Redis 这样的内存数据库来实现跨节点的限流逻辑。常用的算法有滑动窗口计数器、漏桶算法以及令牌桶算法等。 以下是一段基于 Redis 和令牌桶算法的伪代码示例: ```java import redis.clients.jedis.Jedis; public class DistributedRateLimiter { private Jedis jedis; private String keyPrefix = "ratelimit:"; public boolean tryAcquire(String resource, int maxTokensPerSecond) { String key = keyPrefix + resource; long currentTimeMillis = System.currentTimeMillis(); double tokensAvailable = jedis.get(key); // 获取当前剩余令牌数量 if (tokensAvailable >= 1) { jedis.decrByFloat(key, 1); // 减少一个令牌 return true; } else { return false; } } } ``` 此方案能够很好地支持高并发访问,并且具备良好的扩展性和性能表现[^2]。 --- #### 3. Spring Cloud Gateway 内置限流组件 如果项目采用了微服务架构并集成了 Spring Cloud,则可以直接使用其内置的支持模块完成 API 层面的流量管理任务。例如,在网关层配置 Nacos 或者 Sentinel 插件即可轻松达成目标。 具体操作步骤可参考官方文档说明链接地址待补充。 --- ### 总结 综上所述,针对不同规模的应用程序可以选择合适的策略来进行有效的资源保护措施。如果是小型应用或者内部调用的话,那么采用本地化的解决方案会更加简便高效;而当面对复杂的生产环境下多实例部署的需求时,则应该考虑结合外部存储介质如Redis构建全局一致性的防护机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值