关于限流的基本概念
限流从基本上讲就是通过代码控制客户端对接口的访问次数,由于在正式的生产环境中接口的相关服务可能存在被大量访问的情况如果不对接口进行相关限流操作可能导致接口请求过于频繁服务端负载过重导致服务端的崩溃,因此对于后端开发人员有必要去了解和学习关于限流的相关知识。
常见的限流算法
在去学习限流的常见代码和工具前还是有必要去了解了解关限流有关的理论算法,因为所有的工具也都是以限流算法为依据。常见的限流算法有漏桶算法和令牌桶算法。下面依次看看相关算法的具体内容。
漏桶算法
漏桶算法的思路就是把请求放进一个类似于漏桶的缓存中,然后请求像水流一样以一定的速率向外发送,当漏桶满后多余的请求将会被丢弃,通过这种方式服务端可以强制的控制请求的进入速率而不用去关心客户端的发送速率。但是由于传入的速率一致对于某种需要突然改变传入速率的情况下这种算法可能并不那么合适了。
令牌桶算法
为了解决漏桶算法恒定速率问题衍生的算法,该算法的思路就是以恒定的速率往桶中放入一定的令牌,桶中的请求执行时需要先获取相关的令牌,如果令牌不存在相关请求将不再执行。
限流工具类
Ratelimiter
首先介绍Guava提供的限流工具类RateLimiter,该类是对令牌桶算法的一种实现.接下来通过部分代码来具体观察该类的具体使用方式。
我们通过模拟任务的产生并使用ratelimiter产生许可用来观察任务执行时的情况并用来检测ratelimiter的限流作用
public class RateLimiterTest {
// 使用ratelimiter限制任务产生数量
public static void main(String [] args) {
//每秒产生一个许可
RateLimiter rateLimiter = RateLimiter.create(1);
List<Runnable> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new Task(i));
}
ExecutorService threadPool = Executors.newCachedThreadPool();
for (Runnable task : list) {
System.out.println("等待时间" + rateLimiter.acquire());//ratelimiter.acquire()返回单个请求在获取到许可前被阻塞的时间,如果需要查看多个任务可使用rateLimiter.acquire(int num)
threadPool.execute(task);
}
}
private static class Task implements Runnable {
private int id;
public Task(int id) {
this.id = id;
}
@Override
public void run() {
System.out.println("任务" + id + "已执行");
}
}
}
具体执行结果如图,我们设置许可每秒产生一个,每个任务执行后几乎是等待一秒后才执行下一个任务由此可见使用ratelimiter确实起到了相关的限流作用。
接下来我们改变每秒产生许可的数量在执行相同的方法
任务的等待时间变短,可以看到ratelimiter确实起到了限制任务产生速率的作用。
使用信号量进行限流
//我们在代码中设置信号量的许可产生为3个,任务的数量为10个,根据信号量的性质,每个任务在获得相关许可后将进行执行,执行完成后释放许可其他任务获得许可。以此来控制任务的数量,达到限流的目的。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemphareTest {
static Semaphore semaphore = new Semaphore(3);
public static void main(String [] args) {
List<Runnable> list = new ArrayList<>();
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
Task task = new Task(i);
list.add(task);
}
for (Runnable task : list) {
threadPool.execute(task);
}
}
private static class Task implements Runnable{
int id;
public Task(int id) {
this.id = id;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("任务" + id + "执行");
Thread.sleep(1000);
System.out.println(id + "执行完成");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
从图中执行结果来看我们可以看到任务基本上处于执行完任务释放信号量后其他任务才可以执行未获得许可的任务将被阻塞。
总结
关于限流除了以上几种方式外也可以使用flowable等工具进行,在此就不做介绍。关于ratelimiter和semaphare两种方式进行限流其实都是任务通过获取许可的方式进行任务的执行,不同的是信号量产生的许可只有在之前获得许可的任务执行完成后才可以释放许可,而ratelimiter则会定期的向任务队列中加入许可,相对而言灵活性更高。