1、计数器限流,一般用在单一维度的访问频率限制上,比如短信验证码每隔60s 只能发送一次,或者接口调用次数等。
例如系统在指定的时间段内能同时处理 100 个请求,保存一个计数器,处理了一个请求,计数器就加一,一个请求处理完毕之后计数器减一。
计数限流算法策略实现步骤:
- 设定时间窗口: 将时间划分为固定的窗口,例如每秒、每分钟等。
- 维护计数器: 在每个时间窗口内,维护一个请求计数器,记录当前时间窗口内的请求数。
- 限制请求数: 如果请求计数超过设定的阈值,则拒绝多余的请求。
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class CountLimit {
private final int limit; // 限制每秒请求数
private final long windowMillis; // 时间窗口大小(毫秒)
private final AtomicInteger requestCount = new AtomicInteger(0);
private final Lock lock = new ReentrantLock();
private long lastWindowStart;
public CountLimit(int limit, int windowSeconds) {
this.limit = limit;
this.windowMillis = windowSeconds * 1000L;
this.lastWindowStart = System.currentTimeMillis();
}
public boolean tryConsume() {
long currentTime = System.currentTimeMillis();
long currentWindowStart = currentTime - currentTime % windowMillis;
lock.lock();
try {
// 判断是否进入新的时间窗口
if (currentWindowStart > lastWindowStart) {
requestCount.set(1);
lastWindowStart = currentWindowStart;
// 处理请求
return true;
} else {
// 检查请求数是否超过限制
int count = requestCount.incrementAndGet();
if (count <= limit) {
// 处理请求
return true;
} else {
// 请求被拒绝
return false;
}
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
CountLimit countLimit = new CountLimit(5, 1); // 每秒限制5个请求
// 模拟请求
for (int i = 0; i < 10; i++) {
if (countLimit.tryConsume()) {
System.out.println("处理请求:" + i);
} else {
System.out.println("请求被限流:" + i);
}
}
}
}
在示例中,CountLimit
类通过维护一个请求计数器和使用 ReentrantLock
进行线程安全的计数和限流。通过调用 tryConsume
方法,可以判断请求是否被允许处理。
令牌桶限流算法策略:
令牌桶是一种经典的限流算法,它通过维护一个固定容量的令牌桶,按照固定速率往桶中放入令牌。请求需要获取令牌才能执行,如果桶中没有足够的令牌,则请求被阻塞或拒绝。
每个客户端请求进来的时候,必须要从令牌桶获得一个令牌才能访问,否则排队等待。在流量低峰的时候,令牌桶会出现堆积,因此当出现瞬时高峰的时候,有足够多的令牌可以获取,因此令牌桶能够允许瞬时流量的处理。网关层面的限流、或者接口调用的限流,都可以使用令牌桶算法,像 Google 的 Guav和 Redisson 的限流,都用到了令牌桶算法。
步骤如下:
- 维护令牌桶: 维护一个容量固定的令牌桶,以固定的速率往桶中放入令牌。
- 获取令牌: 每个请求在执行前,需要获取一个令牌,如果桶中有足够的令牌,则允许执行;否则,等待或拒绝。
代码如下:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TokenBucket {
private final int capacity; // 令牌桶容量
private final long refillRate; // 令牌补充速率(毫秒/令牌)
private final BlockingQueue<Object> tokens; // 令牌桶队列
private final ScheduledExecutorService scheduler; // 令牌补充调度器
public TokenBucket(int capacity, int tokensPerSecond) {
this.capacity = capacity;
this.refillRate = 1000L / tokensPerSecond; // 转换成毫秒/令牌
this.tokens = new LinkedBlockingQueue<>(capacity);
this.scheduler = new ScheduledThreadPoolExecutor(1);
// 初始化令牌桶
for (int i = 0; i < capacity; i++) {
tokens.offer(new Object());
}
// 定时补充令牌
scheduler.scheduleAtFixedRate(this::refillTokens, refillRate, refillRate, TimeUnit.MILLISECONDS);
}
public boolean tryConsume() {
return tokens.poll() != null;
}
private void refillTokens() {
int missingTokens = capacity - tokens.size();
for (int i = 0; i < missingTokens; i++) {
tokens.offer(new Object());
}
}
public static void main(String[] args) {
TokenBucket tokenBucket = new TokenBucket(5, 2); // 每秒生成2个令牌
// 模拟请求
for (int i = 0; i < 10; i++) {
if (tokenBucket.tryConsume()) {
System.out.println("处理请求:" + i);
} else {
System.out.println("请求被限流:" + i);
}
try {
Thread.sleep(500); // 模拟请求间隔
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在示例中,TokenBucket
类通过使用 LinkedBlockingQueue
维护令牌桶队列,通过调用 tryConsume
方法来判断请求是否被允许处理。令牌桶的补充由定时任务进行。
漏桶限流算法策略:
漏桶算法,它是一种恒定速率的限流算法,不管请求量是多少,服务端的 处理效率是恒定的。基于 MQ 来实现的生产者消费者模型,其实算是一种漏桶限流 算法。
规则如下:
请求来了放入桶中
桶内请求量满了拒绝请求
服务定速从桶内拿请求处理
漏桶算法的原理如下:
- 首先,将请求流量比作水流,将漏桶的容量设置为允许的最大流量。
- 然后,以恒定的速度将请求流量放入漏桶中。
- 最后,如果漏桶已满,则将请求流量拒绝。
import java.util.concurrent.atomic.AtomicInteger;
public class LeakyBucket {
private final int capacity;
private final AtomicInteger counter;
public LeakyBucket(int capacity) {
this.capacity = capacity;
this.counter = new AtomicInteger(0);
}
public boolean accept() {
int current = counter.get();
if (current < capacity) {
counter.incrementAndGet();
return true;
} else {
return false;
}
}
}
该代码定义了一个 LeakyBucket
类,用于实现漏桶算法。该类有两个属性:
capacity
:漏桶的容量counter
:漏桶中的水量
该类提供了一个 accept()
方法,用于判断是否允许请求通过。该方法首先获取漏桶中的水量,如果水量小于容量,则将水量加 1,并返回 true
,否则返回 false
。
以下是使用该代码进行限流的示例:
public class Main {
public static void main(String[] args) throws InterruptedException {
LeakyBucket bucket = new LeakyBucket(100);
// 模拟 100 个请求
for (int i = 0; i < 100; i++) {
if (bucket.accept()) {
// 处理请求
System.out.println("处理请求 " + i);
} else {
// 拒绝请求
System.out.println("拒绝请求 " + i);
}
Thread.sleep(10);
}
}
}
该示例将漏桶的容量设置为 100,并模拟 100 个请求。当请求流量小于容量时,所有请求都将被处理。当请求流量超过容量时,部分请求将被拒绝。
以下是该示例的运行结果:
处理请求 0
处理请求 1
处理请求 2
...
处理请求 98
拒绝请求 99
拒绝请求 100
限流窗口算法策略:
滑动窗口限流,本质上也是一种计数器,只是通过以时间为维度的可滑动 窗口设计,来减少了临界值带来的并发超过阈值的问题。 每次进行数据统计的时候,只需要统计这个窗口内每个时间刻度的访问量就可以了。 Spring Cloud里面的熔断框架Hystrix ,以及Spring Cloud Alibaba里面的Sentinel 都采用了滑动窗口来做数据统计。
滑动窗口限流算法的原理如下:
- 首先,将时间划分为多个窗口,每个窗口的大小为固定时间段。
- 然后,统计每个窗口内的请求数量。
- 如果某个窗口内的请求数量超过阈值,则拒绝该窗口内的请求。
以下是用 Java 后端服务进行滑动窗口限流的代码示例:
import java.util.concurrent.atomic.AtomicInteger;
public class SlidingWindow {
private final int windowSize;
private final AtomicInteger counter;
public SlidingWindow(int windowSize) {
this.windowSize = windowSize;
this.counter = new AtomicInteger(0);
}
public boolean accept() {
int current = counter.get();
if (current < windowSize) {
counter.incrementAndGet();
return true;
} else {
return false;
}
}
}
该代码定义了一个 SlidingWindow
类,用于实现滑动窗口限流算法。该类有两个属性:
windowSize
:窗口的大小counter
:窗口内的请求数量
该类提供了一个 accept()
方法,用于判断是否允许请求通过。该方法首先获取窗口内的请求数量,如果请求数量小于窗口大小,则将请求数量加 1,并返回 true
,否则返回 false
。
以下是使用该代码进行限流的示例:
public class Main {
public static void main(String[] args) throws InterruptedException {
SlidingWindow window = new SlidingWindow(100);
// 模拟 100 个请求
for (int i = 0; i < 100; i++) {
if (window.accept()) {
// 处理请求
System.out.println("处理请求 " + i);
} else {
// 拒绝请求
System.out.println("拒绝请求 " + i);
}
Thread.sleep(10);
}
}
}
该示例将窗口的大小设置为 100,并模拟 100 个请求。当请求流量小于窗口大小时,所有请求都将被处理。当请求流量超过窗口大小时,部分请求将被拒绝。
以下是该示例的运行结果:
处理请求 0
处理请求 1
处理请求 2
...
处理请求 98
拒绝请求 99
拒绝请求 100
滑动窗口限流算法的优缺点
滑动窗口限流算法具有以下优点:
- 可以有效控制突发流量
滑动窗口限流算法也具有以下缺点:
- 需要维护窗口内的请求数量,可能会增加系统的开销