使用Guava RateLimterr如何解决高并发场景下的限流问题的,假设有一个线程池,每秒只能处理两个任务

那么就需要限流器上场了,接下面是一个关于限流器的基本使用的代码

public static void main(String[] args) {

//限流器流速:2个请求/秒

RateLimiter limiter = RateLimiter.create(2.0);

//执行任务的线程池

ExecutorService es = Executors.newFixedThreadPool(1);

//记录上一次执行时间

prev = System.nanoTime();

//测试执行20次

for (int i=0; i<20; i++){

//限流器限流

limiter.acquire();

//提交任务异步执行

es.execute(()->{

long cur=System.nanoTime();

//打印时间间隔:毫秒

System.out.println((cur-prev)/1000_000);

prev = cur;

});

}

/* 输出结果:

500

499

499

500

499*/

}

我们创建了一个限流器,让其每秒可以处理两个请求,然后在真正执行任务之前,调用acquire()方法可以起到限流的作用,而且线程的时间间隔基本在500毫秒之间

那么关于限流器的实现,使用了令牌桶算法,这个算法的核心在于,通过限流器,需要拿到限流器提供的令牌,这个限流器的核心思想就在于令牌的发放速度,如果直接使用一个定时线程池,那么会有一个问题,就是在服务器高压情况下,定时任务可能出现不准确的问题

假设令牌以每500毫秒添加到令牌桶中,

如果令牌桶容量为a,那么如果再往其中添加的令牌都会被丢弃

请求能够通过限流器的前提条件是拿到了令牌

如何去实现令牌桶算法,很简单的一个算法思路,就是去计算下个令牌发放的事件,如果容量为1,速率为1秒一个令牌,那么发放的方式就是

如果发放的时间是第三秒,而在第二秒有一个请求到来,那么他会预定第三秒的令牌,如果同时有第二个请求到来,那么他会被推迟到第四秒获取令牌,且将那个令牌预定下

图片

最后一个空闲的令牌的出现时间被推迟到了第五秒

只要利用一个动态的计算下一个令牌产生的事件,就能完成这个限流的功能,接下来就是这个算法的简单实现

class SimpleLimiter {

//下一令牌产生时间

long next = System.nanoTime();

//发放令牌间隔:纳秒

long interval = 1000_000_000;

//预占令牌,返回能够获取令牌的时间

synchronized long reserve(long now) {

//请求时间在下一令牌产生时间之后

//重新计算下一令牌产生时间

if (now > next) {

//将下一令牌产生时间重置为当前时间

next = now;

}

//能够获取令牌的时间

long at = next;

//设置下一令牌产生时间

next += interval;

//返回线程需要等待的时间

return Math.max(at, 0L);

}

//申请令牌

void acquire() {

//申请令牌时的时间

long now = System.nanoTime();

//预占令牌

long at = reserve(now);

long waitTime = max(at – now, 0);

//按照条件等待

if (waitTime > 0) {

try {

TimeUnit.NANOSECONDS.sleep(waitTime);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

再往复杂了说,如果桶的容量大于1,那么如何处理,很简单,只是增加一个计算桶中令牌数量的方法,在请求令牌的时候,进行一个查询数量,如果可以直接获取到,那么就无须增加一个interval了

class SimpleLimiter {

//当前令牌桶中的令牌数量

long storedPermits = 0;

//令牌桶的容量

long maxPermits = 3;

//下一令牌产生时间

long next = System.nanoTime();

//发放令牌间隔:纳秒

long interval = 1000_000_000;

//请求时间在下一令牌产生时间之后,则

// 1.重新计算令牌桶中的令牌数

// 2.将下一个令牌发放时间重置为当前时间

void resync(long now) {

if (now > next) {

//新产生的令牌数

long newPermits = (now – next) / interval;

//新令牌增加到令牌桶

storedPermits = min(maxPermits, storedPermits + newPermits);

//将下一个令牌发放时间重置为当前时间

next = now;

}

}

//预占令牌,返回能够获取令牌的时间

synchronized long reserve(long now) {

resync(now);

//能够获取令牌的时间

long at = next;

//令牌桶中能提供的令牌

long fb = min(1, storedPermits);

//令牌净需求:首先减掉令牌桶中的令牌

long nr = 1 – fb;

//重新计算下一令牌产生时间

next = next + nr * interval;

//重新计算令牌桶中的令牌

this.storedPermits -= fb;

return at;

}

//申请令牌

void acquire() {

//申请令牌时的时间

long now = System.nanoTime();

//预占令牌

long at = reserve(now);

long waitTime = max(at – now, 0);

//按照条件等待

if (waitTime > 0) {

try {

TimeUnit.NANOSECONDS

.sleep(waitTime);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

经典的限流算法,可以分为令牌桶算法和漏桶算法,这就是一个硬币的正反面,令牌桶算法要求必须拿到令牌才能通过限流器,而漏桶算法这是将请求归为一个桶中,桶中按照一定的速率来处理请求,只有桶中有空余,才能通过限流器

发表评论

邮箱地址不会被公开。 必填项已用*标注