Java高并发系统的限流策略

本文介绍了三种常见的限流算法:计数器、信号量和Guava的RateLimiter。计数器算法通过AtomicInteger简单限制并发数,但可能过于严格;信号量Semaphore允许任务排队,缓解瞬时高并发;RateLimiter基于令牌桶,允许短期超出,平滑流量。Guava的RateLimiter提供了动态调整速率的能力,适用于流量削峰场景。
摘要由CSDN通过智能技术生成

限流算法

令牌桶(Token Bucket)、漏桶(leaky bucket)和计数器算法是最常用的三种限流的算法。

计数器限流算法也是比较常用的,主要用来限制总并发数,比如数据库连接池大小、线程池大小、程序访问并发数等都是使用计数器算法。也是最简单粗暴的算法。

使用计数器限流示例1:

public class CountRateLimiterDemo {  
   
    private static AtomicInteger count = new AtomicInteger(0);  
   
    public static void exec() {  
        if (count.get() >= 5) {  
            System.out.println("请求用户过多,请稍后在试!"+System.currentTimeMillis()/1000);  
        } else {  
            count.incrementAndGet();  
            try {  
                //处理核心逻辑  
                TimeUnit.SECONDS.sleep(1);  
                System.out.println("--"+System.currentTimeMillis()/1000);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            } finally {  
                count.decrementAndGet();  
            }  
        }  
    }  
}  

使用AomicInteger来进行统计当前正在并发执行的次数,如果超过域值就简单粗暴的直接响应给用户,说明系统繁忙,请稍后再试或其它跟业务相关的信息。

弊端:使用 AomicInteger 简单粗暴超过域值就拒绝请求,可能只是瞬时的请求量高,也会拒绝请求。

使用计数器限流示例2

public class CountRateLimiterDemo {  
   
    private static Semaphore semphore = new Semaphore(50);  
   
    public static void exec() {  
        if(semphore.getQueueLength()>100){  
            System.out.println("当前等待排队的任务数大于100,请稍候再试...");  
        }  
        try {  
            semphore.acquire();  
            // 处理核心逻辑  
            TimeUnit.SECONDS.sleep(1);  
            System.out.println("--" + System.currentTimeMillis() / 1000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        } finally {  
            semphore.release();  
        }  
    }  
} 

使用Semaphore信号量来控制并发执行的次数,如果超过域值信号量,则进入阻塞队列中排队等待获取信号量进行执行。如果阻塞队列中排队的请求过多超出系统处理能力,则可以在拒绝请求。
相对Atomic优点:如果是瞬时的高并发,可以使请求在阻塞队列中排队,而不是马上拒绝请求,从而达到一个流量削峰的目的。

使用guava的RateLimiter限流示例3

Guava中开源工具类RateLimiter是基于令牌桶算法的实现类,可以简单的实现限流的工作,并根据系统实际情况调整生成token速率。RateLimiter 对简单的令牌桶算法做了一些工程上的优化,具体的实现是 SmoothBursty。需要注意的是,RateLimiter 的另一个实现 SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。也许是出于简单起见,RateLimiter 中的时间窗口能且仅能为 1s,如果想搞其他时间单位的限流,只能另外造轮子。

RateLimiter 有一个有趣的特性是「前人挖坑后人跳」,也就是说 RateLimiter 允许某次请求拿走超出剩余令牌数的令牌,但是下一次请求将为此付出代价,一直等到令牌亏空补上,并且桶中有足够本次请求使用的令牌为止。这里面就涉及到一个权衡,是让前一次请求干等到令牌够用才走掉呢,还是让它先走掉后面的请求等一等呢?Guava 的设计者选择的是后者,先把眼前的活干了,后面的事后面再说。

RateLimiter rateLimiter = RateLimiter.create(2);

System.out.println(rateLimiter.acquire(5));

System.out.println(rateLimiter.acquire(2));

System.out.println(rateLimiter.acquire(1));

输出

0.0

2.496889

0.992149

 

可以看出,令牌桶每秒只能产生2个令牌,我们可以第一次取出5个,但是第二个再去取令牌的时候,需要等2.5s,也就是第一次令牌取完后,需要等2.5s才能取到令牌。同样的,第三次取1个令牌的时候,也需要等待第二次的1s的时间。也就是,取的速率可以超过令牌产生的速率,但是下一次再次去取的时候,需要阻塞等待。

当然也可以使用tryAcquire来非阻塞的获取,可以实时返回结果。另外tryAcquire也可以传入参数,也就是等待的时间,超时直接返回false。这点等同于常见的lock,tryLock。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值