disruptor笔记之七:等待策略
欢迎访问我的GitHub
https://github.com/zq2599/blog_demos
内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;
《disruptor笔记》系列链接
本篇概览
本文是《disruptor笔记》的第七篇,咱们一起阅读源码,学习一个重要的知识点:等待策略,由于Disruptor的源码短小精干、简单易懂,因此本篇是个轻松愉快的源码学习之旅;
提前小结
如果您时间不充裕,可以通过以下提前小结的内容,对等待策略有个大体的认识:
- BlockingWaitStrategy:用了ReentrantLock的等待&&唤醒机制实现等待逻辑,是默认策略,比较节省CPU
- BusySpinWaitStrategy:持续自旋,JDK9之下慎用(最好别用)
- DummyWaitStrategy:返回的Sequence值为0,正常环境是用不上的
- LiteBlockingWaitStrategy:基于BlockingWaitStrategy,在没有锁竞争的时候会省去唤醒操作,但是作者说测试不充分,不建议使用
- TimeoutBlockingWaitStrategy:带超时的等待,超时后会执行业务指定的处理逻辑
- LiteTimeoutBlockingWaitStrategy:基于TimeoutBlockingWaitStrategy,在没有锁竞争的时候会省去唤醒操作
- SleepingWaitStrategy:三段式,第一阶段自旋,第二阶段执行Thread.yield交出CPU,第三阶段睡眠执行时间,反复的的睡眠
- YieldingWaitStrategy:二段式,第一阶段自旋,第二阶段执行Thread.yield交出CPU
- PhasedBackoffWaitStrategy:四段式,第一阶段自旋指定次数,第二阶段自旋指定时间,第三阶段执行Thread.yield交出CPU,第四阶段调用成员变量的waitFor方法,这个成员变量可以被设置为BlockingWaitStrategy、LiteBlockingWaitStrategy、SleepingWaitStrategy这三个中的一个
关于等待策略
- 回顾一下前面的文章中实例化Disruptor的代码:
disruptor = new Disruptor<>(new OrderEventFactory(),
BUFFER_SIZE,
new CustomizableThreadFactory("event-handler-"));
- 展开上述构造方法,会见到创建RingBuffer的代码,默认使用了BlockingWaitStrategy作为等待策略:
public static <E> RingBuffer<E> createMultiProducer(EventFactory<E> factory, int bufferSize)
{
return createMultiProducer(factory, bufferSize, new BlockingWaitStrategy());
}
- 继续展开上面的createMultiProducer方法,可见每个Sequencer(注意不是Sequence)都有自己的watStrategy成员变量:
- 这个waitStrategy的最终用途是创建SequenceBarrier的时候,传给SequenceBarrier做成员变量:
- 在看看SequenceBarrier是如何使用waitStrategy的,一共两处用到,第一处如下图红框,原来是waitFor方法内部会用到,这个waitFor咱们前面已经了解过,对消费者来说,等待环形队列的指定位置有可用数据时,就是调用SequenceBarrier的waitFor完成的:
- SequenceBarrier第二处用到waitStrategy是唤醒的时候:
@Override
public void alert()
{
alerted = true;
waitStrategy.signalAllWhenBlocking();
}
- 现在咱们知道了WaitStrategy的使用场景,接下来看看这个接口有哪些具体实现吧,这样咱们在编程中就知道如何选择才最适合自己
BlockingWaitStrategy
- 作为默认的等待策略,BlockingWaitStrategy还有个特点就是代码量小(不到百行),很容易理解,其实就是用ReentrantLock+Condition来实现等待和唤醒操作的,如下图红框:
- 如果您更倾向于节省CPU资源,对高吞吐量和低延时的要求相对低一些,那么BlockingWaitStrategy就适合您了;
BusySpinWaitStrategy(慎用)
- 前面的BlockingWaitStrategy有个特点,就是一旦环形队列指定位置来了数据,由于线程是等待状态(底层调用了native的UNSAFE.park方法),因此还要唤醒后才能执行业务逻辑,在一些场景中希望数据一到就尽快消费,此时BusySpinWaitStrategy就很合适了,代码太简单,全部贴出:
public final class BusySpinWaitStrategy implements WaitStrategy
{
@Override
public long waitFor(
final long sequence, Sequence cursor, final Sequence dependentSequence, final SequenceBarrier barrier)
throws AlertException, InterruptedException
{
long availableSequence;
while ((availableSequence = dependentSequence.get()) < sequence)
{
barrier.checkAlert();
ThreadHints.onSpinWait();
}
return availableSequence;
}
@Override
public void signalAllWhenBlocking()
{
}
}
- 上述代码显示,整个while循环的关键就是ThreadHints.onSpinWait做了什么,源码如下,这里要格外注意,如果ON_SPIN_WAIT_METHOD_HANDLE为空,意味着外面的while循环是个非常消耗CPU的自旋:
public static void onSpinWait()
{
if (null != ON_SPIN_WAIT_METHOD_HANDLE)
{
try
{
ON_SPIN_WAIT_METHOD_HANDLE.invokeExact();
}
catch (final Throwable ignore)
{
}
}
}
- ON_SPIN_WAIT_METHOD_HANDLE为空是很可怕的事情,咱们来看看它是何方神圣?代码还是在ThreadHints.java中,如下所示,真相一目了然,它就是Thread类的onSpinWait方法,如果Thread类没有onSpinWait方法,那么使用BusySpinWaitStrategy作为等待策略就有很高的代价了,环形队列里没有数据时消费线程会执行自旋,很耗费CPU:
static
{
final MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle methodHandle = null;
try
{
methodHandle = lookup.findStatic(Thread.class, "onSpinWait", methodType(void.class));
}
catch (final Exception ignore)
{
}
ON_SPIN_WAIT_METHOD_HANDLE = methodHandle;
}
好吧,还剩两个问题:Thread类有没有onSpinWait方法还不能确定吗?这个onSpinWait方法是何方神圣?
去看JDK官方文档,如下图,原来这方法是从JDK9才有的,所以对于JDK8使用者来说来说,选用BusySpinWaitStrategy就意味着要面对没做啥事儿的while循环了:
- 第二个问题,onSpinWait方法干了些啥?前面的官方文档,以欣宸的英语水平显然是无法理解的,去看stackoverflow吧,如下图,简单的说,就是告诉CPU当前线程处于循环查询的状态,CPU得知后就会调度更多CPU资源给其他线程:
至此真像大白:环形队列的条件就绪后,BusySpinWaitStrategy策略是通过whlie死循环来做到快速响应的,如果JDK是9或者更高版本,这个死循环带来的CPU损耗由Thread.onSpinWait帮助缓解,如果JDK版本低于9,这里就是个简单的while死循环,至于这种死循环有多消耗CPU,您可以写段简单代码感受一下...
难怪Disruptor源码中会提醒最好是将使用此实例的线程绑定到指定CPU核:
DummyWaitStrategy
固定返回0,个人觉得这个策略在正常开发中用不上,因为环形队列可用位置始终是0的话,不论是生产还是消费都难以实现:
LiteBlockingWaitStrategy
- 看名字,LiteBlockingWaitStrategy是BlockingWaitStrategy策略的轻量级实现,在锁没有竞争的时候(例如独立消费的场景),会省略掉唤醒操作,不过如下图红框所示,作者说他没有充分验证过正确性,因此建议只用于体验,太好了,这个策略我不学了!!!
TimeoutBlockingWaitStrategy
- 顾名思义,TimeoutBlockingWaitStrategy表示只等待某段时长,超过了就算超时,其代码和BlockingWaitStrategy类似,只是等待的时候有个时长限制,如下图,一目了然:
- 其实我对抛出异常后的处理很感兴趣,去看看吧,外面是熟悉的BatchEventProcessor类,熟悉的processEvents方法,如下图,每次超时异常都交给notifyTimeout处理,而外部的主流程不受影响,依旧不断的从环形队列中等待和获取数据:
- 进入notifyTImeout方法,可见实际上是交给成员变量timeoutHandler去处理的,而且处理过程中发生的任何异常都会被捕获,不会抛出去影响外部调用:
- 再来看看成员变量是哪来的,如下图,真相大白,咱们开发的EventHandler实现类,如果也实现了Timeouthandler,就被当做成员变量timeoutHandler了:
- 至此TimeoutBlockingWaitStrategy也搞清楚了:用于有时间限制的场景,每次等待超时后都会调用业务定制的超时处理逻辑,这个逻辑写到EventHandler实现类中,这个实现类要实现Timeouthandler接口
LiteTimeoutBlockingWaitStrategy
- LiteTimeoutBlockingWaitStrategy与TimeoutBlockingWaitStrategy的关系,就像BlockingWaitStrategy与LiteBlockingWaitStrategy的关系:作为TimeoutBlockingWaitStrategy的变体,有TimeoutBlockingWaitStrategy的超时处理特性,而且没有锁竞争的时候,省略掉唤醒操作;
- 作者说LiteBlockingWaitStrategy可用于体验,但正确性并未经过充分验证,但是在LiteTimeoutBlockingWaitStrategy的注释中没有看到这种说法,看样子这是个靠谱的等待策略,可以用,用在有超时处理的需求,而且没有锁竞争的场景(例如独立消费)
SleepingWaitStrategy
- 和前面几个不同的是,SleepingWaitStrategy没有用到锁,这意味这无需调用signalAllWhenBlocking方法做唤醒处理,相当于省去了生产线程的通知操作,官方源码注释有这么句话引起了我的兴趣,如下图红框,大意是该策略在性能和CPU资源消耗之间取得了平衡,接下来去看看关键代码,来了解这个特性:
- 如下图,等到可用数据的过程是个死循环:
- 接下来是关键代码了,如下图,可见整个等待过程分为三段:计数器高于100时就只有一个减一的操作(最快响应),计数器在100到0之间时每次都交出CPU执行时间(最省资源),其他时候就睡眠固定时间:
YieldingWaitStrategy
- 看过SleepingWaitStrategy之后,再看YieldingWaitStrategy就很容易理解了,和SleepingWaitStrategy相比,YieldingWaitStrategy先做指定次数的自旋,然后不断的交出CPU时间:
- 由于在不断的执行Thread.yield()方法,因此该策略虽然很消耗CPU,不过一旦其他线程有CPU需求,很容易从这个线程得到;
PhasedBackoffWaitStrategy
- 最后是PhasedBackoffWaitStrategy,该策略的特点是将整个等待过程分成下图的四段,四个方块代表一个时间线上的四个阶段:
- 这里说明一下上图的四个阶段:
- 首先是自旋指定的次数,默认10000次;
- 自旋过后,开始带计时的自旋,执行的时长是spinTimeoutNanos的值;
- 执行时长达到spinTimeoutNanos的值后,开始执行Thread.yield()交出CPU资源,这个逻辑的执行时长是yieldTimeoutNanos-spinTimeoutNanos;
- 执行时长达到yieldTimeoutNanos-spinTimeoutNanos的值后,开始调用fallbackStrategy.waitFor,这个调用没有时间或者次数限制;
- 现在问题来了fallbackStrategy是何方神圣?PhasedBackoffWaitStrategy类准备了三个静态方法,咱们可以按需选用,让fallbackStrategy是BlockingWaitStrategy、LiteBlockingWaitStrategy、SleepingWaitStrategy这三个中的一个:
public static PhasedBackoffWaitStrategy withLock(
long spinTimeout,
long yieldTimeout,
TimeUnit units)
{
return new PhasedBackoffWaitStrategy(
spinTimeout, yieldTimeout,
units, new BlockingWaitStrategy());
}
public static PhasedBackoffWaitStrategy withLiteLock(
long spinTimeout,
long yieldTimeout,
TimeUnit units)
{
return new PhasedBackoffWaitStrategy(
spinTimeout, yieldTimeout,
units, new LiteBlockingWaitStrategy());
}
public static PhasedBackoffWaitStrategy withSleep(
long spinTimeout,
long yieldTimeout,
TimeUnit units)
{
return new PhasedBackoffWaitStrategy(
spinTimeout, yieldTimeout,
units, new SleepingWaitStrategy(0));
}
- 至此,Disruptor的九种等待策略就全部分析完毕了,除了选用等待策略的时候更加得心应手,还有个收获就是积攒了阅读优秀源码的经验,在读源码的路上更加有信心了;
你不孤单,欣宸原创一路相伴
欢迎关注公众号:程序员欣宸
微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界...
https://github.com/zq2599/blog_demos
disruptor笔记之七:等待策略的更多相关文章
- 近期业务大量突增微服务性能优化总结-3.针对 x86 云环境改进异步日志等待策略
最近,业务增长的很迅猛,对于我们后台这块也是一个不小的挑战,这次遇到的核心业务接口的性能瓶颈,并不是单独的一个问题导致的,而是几个问题揉在一起:我们解决一个之后,发上线,之后发现还有另一个的性能瓶颈问 ...
- disruptor笔记之二:Disruptor类分析
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- disruptor笔记之三:环形队列的基础操作(不用Disruptor类)
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- disruptor笔记之四:事件消费知识点小结
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- disruptor笔记之五:事件消费实战
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- disruptor笔记之六:常见场景
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- disruptor笔记之八:知识点补充(终篇)
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- disruptor笔记之一:快速入门
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- 【Visual C++】游戏编程学习笔记之七:键盘输入消息
本系列文章由@二货梦想家张程 所写,转载请注明出处. 作者:ZeeCoder 微博链接:http://weibo.com/zc463717263 我的邮箱:michealfloyd@126.c ...
随机推荐
- sql 中的with 语句使用
一直以来都很少使用sql中的with语句,但是看到了一篇文章中关于with的使用,它的确蛮好用,希望以后记得使用这个语句.一.with 的用法With alias_name as (select1)[ ...
- EL表达式学习(一)
一.初次接触EL表达式: <%@ page language="java" import="java.util.*" pageEncoding=" ...
- JDBC基础篇(MYSQL)——使用statement执行DML语句(insert/update/delete)
注意:其中的JdbcUtil是我自定义的连接工具类:代码例子链接: package day02_statement; import java.sql.Connection; import java.s ...
- Qt 6.0精简WebEngine SerialPort Multimedia等成为半残GUI框架一览
由于 Qt 集成了大量成熟模块,使之成为 C++ 领域中最好用的开源技术跨平台 GUI 开发框架.基于 Qt 能开发 Windows MacOS 传统桌面或无 GUI 应用程序.Unix/Linux ...
- [第五篇]——Docker 镜像加速之Spring Cloud直播商城 b2b2c电子商务技术总结
Docker 镜像加速 国内从 DockerHub 拉取镜像有时会遇到困难,此时可以配置镜像加速器.Docker 官方和国内很多云服务商都提供了国内加速器服务,例如: 科大镜像: 网易: 阿里云: 你 ...
- 用Java写了一个程序,将一个Mysql库中的表,迁移到另外一个server上的Mysql库中
用Navicat做数据迁移,因为数据量比较大,迁移过过程中一个是进展不直观,另外就是cpu占用率高的时候,屏幕跟死机了一样点不动按钮,不好中断. 想了想,干脆自己写一个. 在网上找了一个sqllite ...
- 解决国内npm安装太慢的方法,又不能FQ情况下,使用淘宝镜像教程
安装npm及cnpm(Windows) [工具官网] 因为国内上网下载组件太慢,淘宝给我们提供了镜像源,,但是我不是建意FQ上网.条件有限的可以使用下面的方法安装CNPM,原文转自网络,正好自己需要也 ...
- 【转】Linux 查看端口占用情况
Linux 查看端口占用情况可以使用 lsof 和 netstat 命令. lsof lsof(list open files)是一个列出当前系统打开文件的工具. lsof 查看端口占用语法格式: l ...
- scrum项目冲刺_day11 第一阶段总结
"智能垃圾分类APP"第一阶段总结 总任务: 一.appUI页面(已完成) 二.首页功能: 1.图像识别功能(已完成) 2.语音识别功能(已完成) 3.垃圾搜索功能(基本完成) 4 ...
- PHP匿名类的用法
在PHP7之后,PHP中加入了匿名类的特性.匿名类和匿名方法让PHP成为了更现代化的语言,也让我们的代码开发工作越来越方便.我们先来看看匿名类的简单使用. // 直接定义 $objA = new cl ...