JAVA应用层限流
Posted l_learning
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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应用层限流的主要内容,如果未能解决你的问题,请参考以下文章