java并发系列 - 第29天:高并发中常见的限流方式
这是java高并发系列第29篇。
环境:jdk1.8。
本文内容
- 介绍常见的限流算法
- 通过控制最大并发数来进行限流
- 通过漏桶算法来进行限流
- 通过令牌桶算法来进行限流
- 限流工具类RateLimiter
常见的限流的场景
- 秒杀活动,数量有限,访问量巨大,为了防止系统宕机,需要做限流处理
- 国庆期间,一般的旅游景点人口太多,采用排队方式做限流处理
- 医院看病通过发放排队号的方式来做限流处理。
常见的限流算法
- 通过控制最大并发数来进行限流
- 使用漏桶算法来进行限流
- 使用令牌桶算法来进行限流
通过控制最大并发数来进行限流
以秒杀业务为例,10个iphone,100万人抢购,100万人同时发起请求,最终能够抢到的人也就是前面几个人,后面的基本上都没有希望了,那么我们可以通过控制并发数来实现,比如并发数控制在10个,其他超过并发数的请求全部拒绝,提示:秒杀失败,请稍后重试。
并发控制的,通俗解释:一大波人去商场购物,必须经过一个门口,门口有个门卫,兜里面有指定数量的门禁卡,来的人先去门卫那边拿取门禁卡,拿到卡的人才可以刷卡进入商场,拿不到的可以继续等待。进去的人出来之后会把卡归还给门卫,门卫可以把归还来的卡继续发放给其他排队的顾客使用。
JUC中提供了这样的工具类:Semaphore,示例代码:
package com.itsoku.chat29;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 跟着阿里p7学并发,微信公众号:javacode2018
*/
public class Demo1 {
static Semaphore semaphore = new Semaphore(5);
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
boolean flag = false;
try {
flag = semaphore.tryAcquire(100, TimeUnit.MICROSECONDS);
if (flag) {
//休眠2秒,模拟下单操作
System.out.println(Thread.currentThread() + ",尝试下单中。。。。。");
TimeUnit.SECONDS.sleep(2);
} else {
System.out.println(Thread.currentThread() + ",秒杀失败,请稍微重试!");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (flag) {
semaphore.release();
}
}
}).start();
}
}
}
输出:
Thread[Thread-10,5,main],尝试下单中。。。。。
Thread[Thread-8,5,main],尝试下单中。。。。。
Thread[Thread-9,5,main],尝试下单中。。。。。
Thread[Thread-12,5,main],尝试下单中。。。。。
Thread[Thread-11,5,main],尝试下单中。。。。。
Thread[Thread-2,5,main],秒杀失败,请稍微重试!
Thread[Thread-1,5,main],秒杀失败,请稍微重试!
Thread[Thread-18,5,main],秒杀失败,请稍微重试!
Thread[Thread-16,5,main],秒杀失败,请稍微重试!
Thread[Thread-0,5,main],秒杀失败,请稍微重试!
Thread[Thread-3,5,main],秒杀失败,请稍微重试!
Thread[Thread-14,5,main],秒杀失败,请稍微重试!
Thread[Thread-6,5,main],秒杀失败,请稍微重试!
Thread[Thread-13,5,main],秒杀失败,请稍微重试!
Thread[Thread-17,5,main],秒杀失败,请稍微重试!
Thread[Thread-7,5,main],秒杀失败,请稍微重试!
Thread[Thread-19,5,main],秒杀失败,请稍微重试!
Thread[Thread-15,5,main],秒杀失败,请稍微重试!
Thread[Thread-4,5,main],秒杀失败,请稍微重试!
Thread[Thread-5,5,main],秒杀失败,请稍微重试!
关于Semaphore的使用,可以移步:JUC中的Semaphore(信号量)
使用漏桶算法来进行限流
国庆期间比较火爆的景点,人流量巨大,一般入口处会有限流的弯道,让游客进去进行排队,排在前面的人,每隔一段时间会放一拨进入景区。排队人数超过了指定的限制,后面再来的人会被告知今天已经游客量已经达到峰值,会被拒绝排队,让其明天或者以后再来,这种玩法采用漏桶限流的方式。
漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
漏桶算法示意图:

简陋版的实现,代码如下:
package com.itsoku.chat29;
import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
/**
* 跟着阿里p7学并发,微信公众号:javacode2018
*/
public class Demo2 {
public static class BucketLimit {
static AtomicInteger threadNum = new AtomicInteger(1);
//容量
private int capcity;
//流速
private int flowRate;
//流速时间单位
private TimeUnit flowRateUnit;
private BlockingQueue<Node> queue;
//漏桶流出的任务时间间隔(纳秒)
private long flowRateNanosTime;
public BucketLimit(int capcity, int flowRate, TimeUnit flowRateUnit) {
this.capcity = capcity;
this.flowRate = flowRate;
this.flowRateUnit = flowRateUnit;
this.bucketThreadWork();
}
//漏桶线程
public void bucketThreadWork() {
this.queue = new ArrayBlockingQueue<Node>(capcity);
//漏桶流出的任务时间间隔(纳秒)
this.flowRateNanosTime = flowRateUnit.toNanos(1) / flowRate;
Thread thread = new Thread(this::bucketWork);
thread.setName("漏桶线程-" + threadNum.getAndIncrement());
thread.start();
}
//漏桶线程开始工作
public void bucketWork() {
while (true) {
Node node = this.queue.poll();
if (Objects.nonNull(node)) {
//唤醒任务线程
LockSupport.unpark(node.thread);
}
//休眠flowRateNanosTime
LockSupport.parkNanos(this.flowRateNanosTime);
}
}
//返回一个漏桶
public static BucketLimit build(int capcity, int flowRate, TimeUnit flowRateUnit) {
if (capcity < 0 || flowRate < 0) {
throw new IllegalArgumentException("capcity、flowRate必须大于0!");
}
return new BucketLimit(capcity, flowRate, flowRateUnit);
}
//当前线程加入漏桶,返回false,表示漏桶已满;true:表示被漏桶限流成功,可以继续处理任务
public boolean acquire() {
Thread thread = Thread.currentThread();
Node node = new Node(thread);
if (this.queue.offer(node)) {
LockSupport.park();
return true;
}
return false;
}
//漏桶中存放的元素
class Node {
private Thread thread;
public Node(Thread thread) {
this.thread = thread;
}
}
}
public static void main(String[] args) {
BucketLimit bucketLimit = BucketLimit.build(10, 60, TimeUnit.MINUTES);
for (int i = 0; i < 15; i++) {
new Thread(() -> {
boolean acquire = bucketLimit.acquire();
System.out.println(System.currentTimeMillis() + " " + acquire);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
代码中BucketLimit.build(10, 60, TimeUnit.MINUTES);创建了一个容量为10,流水为60/分钟的漏桶。
代码中用到的技术有:
使用令牌桶算法来进行限流
令牌桶算法的原理是系统以恒定的速率产生令牌,然后把令牌放到令牌桶中,令牌桶有一个容量,当令牌桶满了的时候,再向其中放令牌,那么多余的令牌会被丢弃;当想要处理一个请求的时候,需要从令牌桶中取出一个令牌,如果此时令牌桶中没有令牌,那么则拒绝该请求。从原理上看,令牌桶算法和漏桶算法是相反的,一个“进水”,一个是“漏水”。这种算法可以应对突发程度的请求,因此比漏桶算法好。
令牌桶算法示意图:

有兴趣的可以自己去实现一个。
限流工具类RateLimiter
Google开源工具包Guava提供了限流工具类RateLimiter,可以非常方便的控制系统每秒吞吐量,示例代码如下:
package com.itsoku.chat29;
import com.google.common.util.concurrent.RateLimiter;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
/**
* 跟着阿里p7学并发,微信公众号:javacode2018
*/
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
RateLimiter rateLimiter = RateLimiter.create(5);//设置QPS为5
for (int i = 0; i < 10; i++) {
rateLimiter.acquire();
System.out.println(System.currentTimeMillis());
}
System.out.println("----------");
//可以随时调整速率,我们将qps调整为10
rateLimiter.setRate(10);
for (int i = 0; i < 10; i++) {
rateLimiter.acquire();
System.out.println(System.currentTimeMillis());
}
}
}
输出:
1566284028725
1566284028922
1566284029121
1566284029322
1566284029522
1566284029721
1566284029921
1566284030122
1566284030322
1566284030522
----------
1566284030722
1566284030822
1566284030921
1566284031022
1566284031121
1566284031221
1566284031321
1566284031422
1566284031522
1566284031622
代码中RateLimiter.create(5)创建QPS为5的限流对象,后面又调用rateLimiter.setRate(10);将速率设为10,输出中分2段,第一段每次输出相隔200毫秒,第二段每次输出相隔100毫秒,可以非常精准的控制系统的QPS。
上面介绍的这些,业务中可能会用到,也可以用来应对面试。
java高并发系列目录
- 第1天:必须知道的几个概念
- 第2天:并发级别
- 第3天:有关并行的两个重要定律
- 第4天:JMM相关的一些概念
- 第5天:深入理解进程和线程
- 第6天:线程的基本操作
- 第7天:volatile与Java内存模型
- 第8天:线程组
- 第9天:用户线程和守护线程
- 第10天:线程安全和synchronized关键字
- 第11天:线程中断的几种方式
- 第12天JUC:ReentrantLock重入锁
- 第13天:JUC中的Condition对象
- 第14天:JUC中的LockSupport工具类,必备技能
- 第15天:JUC中的Semaphore(信号量)
- 第16天:JUC中等待多线程完成的工具类CountDownLatch,必备技能
- 第17天:JUC中的循环栅栏CyclicBarrier的6种使用场景
- 第18天:JAVA线程池,这一篇就够了
- 第19天:JUC中的Executor框架详解1
- 第20天:JUC中的Executor框架详解2
- 第21天:java中的CAS,你需要知道的东西
- 第22天:JUC底层工具类Unsafe,高手必须要了解
- 第23天:JUC中原子类,一篇就够了
- 第24天:ThreadLocal、InheritableThreadLocal(通俗易懂)
- 第25天:掌握JUC中的阻塞队列
- 第26篇:学会使用JUC中常见的集合,常看看!
- 第27天:实战篇,接口性能提升几倍原来这么简单
- 第28天:实战篇,微服务日志的伤痛,一并帮你解决掉
java高并发系列连载中,总计估计会有四五十篇文章。
阿里p7一起学并发,公众号:路人甲java,每天获取最新文章!

java并发系列 - 第29天:高并发中常见的限流方式的更多相关文章
- java高并发系列 - 第16天:JUC中等待多线程完成的工具类CountDownLatch,必备技能
这是java高并发系列第16篇文章. 本篇内容 介绍CountDownLatch及使用场景 提供几个示例介绍CountDownLatch的使用 手写一个并行处理任务的工具类 假如有这样一个需求,当我们 ...
- java高并发系列 - 第17天:JUC中的循环栅栏CyclicBarrier常见的6种使用场景及代码示例
这是java高并发系列第17篇. 本文主要内容: 介绍CyclicBarrier 6个示例介绍CyclicBarrier的使用 对比CyclicBarrier和CountDownLatch Cycli ...
- java高并发系列 - 第20天:JUC中的Executor框架详解2之ExecutorCompletionService
这是java高并发系列第20篇文章. 本文内容 ExecutorCompletionService出现的背景 介绍CompletionService接口及常用的方法 介绍ExecutorComplet ...
- 深入理解Java虚拟机-如何利用VisualVM对高并发项目进行性能分析
前面在学习JVM的知识的时候,一般都需要利用相关参数进行分析,而分析一般都需要用到一些分析的工具,因为一般使用IDEA,而VisualVM对于IDEA也不错,所以就选择VisualVM来分析JVM性能 ...
- java高并发编程(四)高并发的一些容器
摘抄自马士兵java并发视频课程: 一.需求背景: 有N张火车票,每张票都有一个编号,同时有10个窗口对外售票, 请写一个模拟程序. 分析下面的程序可能会产生哪些问题?重复销售?超量销售? /** * ...
- java高并发系列 - 第14天:JUC中的LockSupport工具类,必备技能
这是java高并发系列第14篇文章. 本文主要内容: 讲解3种让线程等待和唤醒的方法,每种方法配合具体的示例 介绍LockSupport主要用法 对比3种方式,了解他们之间的区别 LockSuppor ...
- java高并发系列 - 第15天:JUC中的Semaphore,最简单的限流工具类,必备技能
这是java高并发系列第15篇文章 Semaphore(信号量)为多线程协作提供了更为强大的控制方法,前面的文章中我们学了synchronized和重入锁ReentrantLock,这2种锁一次都只能 ...
- java架构师负载均衡、高并发、nginx优化、tomcat集群、异步性能优化、Dubbo分布式、Redis持久化、ActiveMQ中间件、Netty互联网、spring大型分布式项目实战视频教程百度网盘
15套Java架构师详情 * { font-family: "Microsoft YaHei" !important } h1 { background-color: #006; ...
- Java互联网架构-直播互动平台高并发分布式架构应用设计
概述 网页HTML 静态化: 其实大家都知道网页静态化,效率最高,消耗最小的就是纯静态化的 html 页面,所以我们尽可能使我们的网站上的页面采用静态页面来实现,这个最简单的方法其实也是最有效的方法, ...
随机推荐
- ThreadLocal的使用场景:Web容器、Spring容器、日志打印
一.对于HTTP事务的理解 一次HTTP请求,就是一个事务.事务者,必须完整的执行其中的所有步骤,不能中断. 二.HTTP事务的隔离 每次HTTP请求对应一个HTTP事务,而每个请求都对应一个线程,线 ...
- Spark学习之第一个程序 WordCount
WordCount程序 求下列文件中使用空格分割之后,单词出现的个数 input.txt java scala python hello world java pyfysf upuptop wintp ...
- [leetcode] 72. Edit Distance (hard)
原题 dp 利用二维数组dp[i][j]存储状态: 从字符串A的0~i位子字符串 到 字符串B的0~j位子字符串,最少需要几步.(每一次删增改都算1步) 所以可得边界状态dp[i][0]=i,dp[0 ...
- SQLyog 破解版
百度云:链接:http://pan.baidu.com/s/1eSMEzIE 密码:ubi2
- 盘一盘 synchronized (一)—— 从打印Java对象头说起
Java对象头的组成 Java对象的对象头由 mark word 和 klass pointer 两部分组成, mark word存储了同步状态.标识.hashcode.GC状态等等. klass ...
- liunx软件安装与网络管理和远程访问
三.软件安装1.RPM包管理器进行软件安装 语法:rpm[选项][软件包] -i 安装一个以前从来未安装过的软件包 -v 将安装过程打印在屏幕上 -h 以字符“#”显示安装进度条 rpm -q[软件包 ...
- PHP中的$_GET变量
定义 在 PHP 中,预定义的 $_GET 变量用于收集来自 method="get" 的表单中的值. $_GET 变量 预定义的 $_GET 变量用于收集来自 method=&q ...
- layer设置maxWidth及maxHeight解决方案
layer介绍 layer是一款近年来备受青睐的web弹层组件,她具备全方位的解决方案,致力于服务各水平段的开发人员,您的页面会轻松地拥有丰富友好的操作体验.下载及使用访问官方网站. area属性 l ...
- linux初学者-虚拟机联网篇
linux初学者-虚拟机联网篇 在虚拟机的使用过程中,本机可以连接WIFI直接上网,但是有时候需要用到虚拟机的联网,那么在本机联网的情况下,虚拟机怎么联网呢?接下来将介绍如何在本机已经连接到WIFI的 ...
- .gitignore文件我自己常用的配置
我项目中一般不需要上传到git服务器上的有 .idea ------.idea目录 .mvn ------.mvn目录 .iml mvnw mvnw.cmd logs/ --- 我生成的日志文件目录 ...