JAVA实现简单限流器(上)
JAVA实现简单限流器
什么是限流器
在高并发的场景下,出于对系统的保护会对流量进行限制。
信号量实现限流器
提到限流器的实现方式,很容易可以想到信号量是与之类似的原理,都是允许一定数量的线程访问临界区,具体实现代码如下所示,同一时刻只允许两个线程访问临界区域,其它线程等待实现限流目的。
// 定义信号量对象 指定允许同时访问临界区的线程数
Semaphore semaphore = new Semaphore(2);ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i <20 ; i++) {int finalI = i;executorService.submit(() -> {try {semaphore.acquire();System.out.println(Thread.currentThread().getName() + "===进入===" + finalI);TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName() + "===结束===" + finalI);semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}});
}
但是信号量的实现还需要类似互斥锁的加锁解锁操作,能不能进一步优化呢?
高性能限流器Guava RateLimiter
入门使用
Guava 是谷歌开源的工具类库,它提供了RateLimiter作为限流工具类,使用前需要引入它的依赖如下。
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>31.1-jre</version>
</dependency>
简单使用如下
// 定义限流器流速:2个请求/秒
RateLimiter limiter = RateLimiter.create(2.0);
// 执行任务的线程池
ExecutorService es = Executors.newFixedThreadPool(1);
// 记录上一次执行时间
AtomicLong prev = new AtomicLong(System.nanoTime());
// 测试执行20次
for (int i = 0; i < 20; i++) {//限流器限流limiter.acquire();//提交任务异步执行es.execute(() -> {long cur = System.nanoTime();//打印时间间隔:毫秒System.out.println((cur - prev.get()) / 1000_000);prev.set(cur);});
}
请求结果如下,可以很明显的看到大多数请求维持在500毫秒左右,可以得出第一个结论限流的另外一个体现应该是均速,比如限流器定义流速为2,那么就是每秒两个请求,500毫秒就是一个请求,符合请求结果。
令牌桶算法
Guava 在使用上还是比较简单,那么Guava限流的原理是什么呢?其实就是我们熟知的令牌桶算法,以一定速率向桶中放入令牌,想要通过限流器的线程需要拿到令牌桶中的令牌才让放行,也就是说我们只要限制令牌的放入速度,就能控制流速,达到限流的目的,详细描述如下:
- 令牌以固定速率放入令牌桶中,如果限流的速率是r/秒,那么每1/r秒就会放入一个令牌到桶中。
- 假设令牌桶的大小为b,那么令牌超过b后将不再放入。
- 请求能够通过限流器的前提是令牌桶中有令牌。
生产者消费者模式实现限流器
我们可以采用生产者消费者模式,其中生产者以一定频率向任务队列中添加令牌,而试图通过限流器的线程作为消费者只有从任务队列中取出令牌才能通过限流器,基于这个原理,演示代码如下。
// 自定义限流器
public class CustomRateLimiter {// 任务队列,任务队列的数量就代表限流器允许通过的最大线程数private static BlockingQueue blockingQueue = new ArrayBlockingQueue(3);static {try {blockingQueue.put(new Object());blockingQueue.put(new Object());blockingQueue.put(new Object());} catch (InterruptedException e) {e.printStackTrace();}}// 生产者public void product(){ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);// scheduleAtFixedRate 以固定的频率 上一个任务开始的时间计时,一个period后,检测上一个任务是否执行完毕,// 如果上一个任务执行完毕,则当前任务立即执行,如果上一个任务没有执行完毕,则需要等上一个任务执行完毕后立即执行// scheduleWithFixedDelay 以固定的延时 ,delay(延时)指的是一次执行终止和下一次执行开始之间的延迟ses.scheduleWithFixedDelay(()->{try {blockingQueue.put(new Object());} catch (InterruptedException e) {e.printStackTrace();}},4,4, TimeUnit.SECONDS);}// 限流器public void acquire(){try {// 阻塞获取blockingQueue.take();} catch (InterruptedException e) {e.printStackTrace();}}
}
测试代码如下
class Test3{public static void main(String[] args) {CustomRateLimiter customRateLimiter = new CustomRateLimiter();// 调用生产者customRateLimiter.product();ExecutorService es = Executors.newFixedThreadPool(10);//记录上一次执行时间AtomicLong prev = new AtomicLong(System.nanoTime());// 去除random方法,和static中的初始化逻辑 时间会稳定在2秒左右 匀速for (int i = 0; i <20 ; i++) {random();customRateLimiter.acquire();es.execute(()->{long cur = System.nanoTime();// 打印时间间隔:毫秒System.out.println(Thread.currentThread().getName()+"==="+(cur - prev.get()) / 1000_000);prev.set(cur);});}}public static void random(){Random random = new Random();int i = random.nextInt(5);try {System.out.println(Thread.currentThread().getName()+"==睡眠了=="+i);TimeUnit.SECONDS.sleep(i);} catch (InterruptedException e) {e.printStackTrace();}}
}
去除random方法和static中的初始化逻辑线程池的访问都保持在两秒以内,如下所示效果
当加上random方法和static中的初始化逻辑,就能明显看到限流器的效果,如下所示
用生产者消费者实现都是很完美的写法,那么为什么Guava,不是采用这个实现的呢?原因很简单因为在高并发下机器的CPU基本上是忙碌状态,这时定时任务去占用CPU大概率会抢占不到,就又可能导致定时任务延时。
那Guava又是如何处理的呢?由于篇幅问题下期再聊。
JAVA实现简单限流器(上)相关推荐
- java web简单线上游戏_有什么在线的编程游戏?
<星际争霸2> 适用范围:Python入门及以上. (用星际争霸入门Python也不错,10+行写一个农民采矿的Bot,30+行Rush一个简单难度的电脑,50+行虐虐困难模式的电脑,菜鸡 ...
- github上创建java项目简单操作
github上创建java项目简单操作 参考L: github上创建java项目简单操作 - CSDN博客 http://blog.csdn.net/qq_29392425/article/detai ...
- 阿里云oss简单的上传下载删除(java)
阿里云oss上传和下载. 配置maven <!-- https://mvnrepository.com/artifact/com.aliyun.oss/aliyun-sdk-oss --> ...
- java集合框架史上最详解(list set 以及map)
title: Java集合框架史上最详解(list set 以及map) tags: 集合框架 list set map 文章目录 一.集合框架总体架构 1.1 集合框架在被设计时需满足的目标 1.2 ...
- java实现简单窗体小游戏----球球大作战
java实现简单窗体小游戏----球球大作战 需求分析 1.分析小球的属性: 坐标.大小.颜色.方向.速度 2.抽象类:Ball 设计类:BallMain-创建窗体 BallJPanel- ...
- 我来谈谈PHP和JAVA在web开发上的的区别
这里的标题写的是谈谈PHP和JAVA的区别,其实是委婉的说法,其实别人是想听PHP好还是JAVA好!!! 从而从中找到存在感!!! 因为由于我是从多年的php开发转到java开发的.所以最,不时的有好 ...
- 快速定位java系统的线上问题--转
原文地址:http://m.blog.csdn.net/article/details?id=43376943 前言:我们的场景并没有像BAT等大型互联网公司里的系统那么复杂,但是基本上也有一定的规模 ...
- java语言 文件上传,java中实现文件上传的方法
java中实现文件上传的方法 发布时间:2020-06-19 10:29:11 来源:亿速云 阅读:86 作者:Leah 这篇文章给大家分享的是java中实现文件上传的方法,相信大部分人都还没学会这个 ...
- Java学习之容器上(Collection接口常用方法,Iterator接口,使用foreach循环遍历Collection集合元素,Set集合通用知识(Hashset类,hashcode()与Lin
1.容器API的类图结构如下: JAVA的集合类是一种特别有用的工具类,它可以用于存储数量不等的多个对象,并可以实现常用数据结构,如栈,队列等,除此之外,JAVA集合还可用于保存具有映射关系的关联数组 ...
最新文章
- ORA-01081: cannot start already-running ORACLE - shut it down first
- UVA 122 Trees on the level 二叉树 广搜
- ubuntu常见错误–Could not get lock /var/lib/dpkg/lock解决
- 图解使用Ant构建一个Java项目
- AtCoder AGC007E Shik and Travel (二分、DP、启发式合并)
- a^logbx=x^logba
- Source Insight常用快捷键
- [转载]IIS7报500.23错误的解决方法
- OpenCV开发团队开源计算机视觉标注工具CVAT
- 向量二次规划matlab,MATLAB中使用Opti Toolbox的混合整数二次规划
- zb怎么做渲染图_如何在ZBrush中渲染漫画风格的插画
- Python计算从n个元素中任选i个的组合数C(n,i)
- 一个离线的简单的 JSON 格式化编辑器
- 机器学习算法(十):线性回归之最小二乘法
- 【云流送技术】为手办行业可以带来哪些变革
- 华为防火墙配置SSL+自签CA证书挑战登录
- 【学习番外篇】Firefly ROC-RK3328-CC刷Ubuntu18.04+VNC
- wchar_t 转换 string std::string 转换 wchar_t
- 物联网平台分为几层,你了解吗
- python小练习--GUI基础