单机最快的队列Disruptor解析和使用
前言
介绍高性能队列Disruptor原理以及使用例子。
Disruptor是什么?
Disruptor是外汇和加密货币交易所运营商 LMAX group 建立高性能的金融交易所的结果。用于解决生产者、消费者及其数据存储的设计问题的高性能队列实现。可以对标JDK中的ArrayBlockingQueue。是目前单机且基于内存存储的最高性能的队列实现。见 与ArrayBlockingQueue性能对比。
Disruptor高性能秘诀
使用CAS代替锁
锁非常昂贵,因为它们在竞争时需要仲裁。这种仲裁是通过到操作系统内核的上下文切换来实现的,该内核将挂起等待锁的线程,直到它被释放。系统提供的原子操作CAS(Compare And Swap/Set)是很好的锁替代方案,Disruptor中同步就是使用的这种。
比如多生产者模式中com.lmax.disruptor.MultiProducerSequencer就是用了Java里sun.misc.Unsafe类基于CAS实现的API。

等待策略com.lmax.disruptor.BlockingWaitStrategy使用了基于CAS实现的ReentrantLock。

独占缓存行
为了提高效率CPU硬件不会以字节或字为单位移动内存,而是以缓存行,通常大小为 32-256 字节的缓存行,最常见的缓存行是 64 字节。这意味着,如果两个变量在同一个缓存行中,并且由不同的线程写入,那么它们会出现与单个变量相同的写入争用问题。为了获得高性能,如果要最小化争用,那么确保独立但同时写入的变量不共享相同的缓存行是很重要的。
比如com.lmax.disruptor.RingBuffer中属性前后都用未赋值的long来独占。com.lmax.disruptor.SingleProducerSequencerPad也有相同处理方式。


环形队列
- 使用有界队列,减少线程争用
队列相比链表在访问速度上占据优势,而有界队列相比可动态扩容的无界队列则避免扩容产生的同步问题效率更高。Disruptor和JDK中的ArrayBlockingQueue一样使用有界队列。队列长度要设为2的n次幂,有利于二进制计算。
- 使用环形数组,避免生产和消费速度差异导致队列头和尾争用
Disruptor在逻辑上将数组的的头尾看成是相连的,即一个环形数组(RingBuffer)。
- Sequence
生产和消费都需要维护自增序列值(Sequence),从0开始。
生产方只维护一个代表生产的最后一个元素的序号。代表生产的最后一个元素的序号。每次向Disruptor发布一个元素都调用Sequenced.next()来获取下个位置的写入权。
在单生产者模式(SINGLE)由于不存在并发写入,则不需要解决同步问题。在多生产者模式(MULTI)就需要借助JDK中基于CAS(Compare And Swap/Set)实现的API来保证线程安全。
多个消费者各自维护自己的消费序列值(Sequence)保存数组中。
而环形通过与运算(sequence & indexMask)实现的,indexMask就是环形队列的长度-1。以环形队列长度8为例,第9个元素Sequence为8,8 & 7 = 0,刚好又回到了数组第1个位置。
见com.lmax.disruptor.RingBuffer.elementAt(long sequence)

预分配内存
环形队列存放的是Event对象,而且是在Disruptor创建的时候调用EventFactory创建并一次将队列填满。Event保存生产者生产的数据,消费也是通过Event获取,后续生产则只需要替换掉Event中的属性值。这种方式避免了重复创建对象,降低JVM的GC产频率。
见com.lmax.disruptor.RingBuffer.fill(EventFactory eventFactory)

消费者8种等待策略
当消费速度大于生产速度情况下,消费者执行的等待策略。
| 策略类名 | 描述 |
|---|---|
| BlockingWaitStrategy(常用) | 使用ReentrantLock,失败则进入等待队列等待唤醒重试。当吞吐量和低延迟不如CPU资源重要时使用。 |
| YieldingWaitStrategy(常用) | 尝试100次,全失败后调用Thread.yield()让出CPU。该策略将使用100%的CPU,如果其他线程请求CPU资源,这种策略更容易让出CPU资源。 |
| SleepingWaitStrategy(常用) | 尝试200次 。前100次直接重试,后100次每次失败后调用Thread.yield()让出CPU,全失败线程睡眠(默认100纳秒 )。 |
| BusySpinWaitStrategy | 线程一直自旋等待,比较耗CPU。最好是将线程绑定到特定的CPU核心上使用。 |
| LiteBlockingWaitStrategy | 与BlockingWaitStrategy类似,区别在增加了原子变量signalNeeded,如果两个线程同时分别访问waitFor()和signalAllWhenBlocking(),可以减少ReentrantLock加锁次数。 |
| LiteTimeoutBlockingWaitStrategy | 与LiteBlockingWaitStrategy类似,区别在于设置了阻塞时间,超过时间后抛异常。 |
| TimeoutBlockingWaitStrategy | 与BlockingWaitStrategy类似,区别在于设置了阻塞时间,超过时间后抛异常。 |
| PhasedBackoffWaitStrategy | 根据时间参数和传入的等待策略来决定使用哪种等待策略。当吞吐量和低延迟不如CPU资源重要时,可以使用此策略。 |
消费者序列
所有消费者的消费序列(Sequence)都放在一个数组中,见com.lmax.disruptor.AbstractSequencer,通过SEQUENCE_UPDATER来更新对应的序列值。

调用更新的地方在com.lmax.disruptor.RingBuffer.addGatingSequences(Sequence... gatingSequences)。
消费太慢队列满了怎么办?
生产者线程被阻塞。生产者调用Sequenced.next()争夺写入权的时候需要判断最小的消费序列值进行比较。如果写入的位置还未消费则会进入循环不断获取最小消费序列值进行比较。
见包com.lmax.disruptor下SingleProducerSequencer或MultiProducerSequencer中next(int n)方法。

Disruptor开发步骤
- 创建Event、EventFactory、EventHandler和ExceptionHandler类
Event是环形队列(RingBuffer)中的元素,是生产者数据的载体;EventFactory是定义Event创建方式的工厂类;EventHandler则是Event的处理器,定义如何消费Event中的数据。
另外有必要定义一个消费异常处理器ExceptionHandler,它是和EventHandler绑定的。当EventHandler.onEvent()执行抛出异常时会执行对应的异常回调方法。
- 实例化Disruptor
创建Disruptor需要指定5个参数eventFactory、ringBufferSize、threadFactory、producerType、waitStrategy。
EventFactory是上面定义的Event工厂类;
ringBufferSize是环形队列的长度,这个值要是2的N次方;
threadFactory是定义消费者线程创建方式的工厂类;
producerType是指明生产者是一个(SINGLE)还是多个(MULTI)。默认是MULTI,会使用CAS(Compare And Swap/Set)保证线程安全。如果指定为SINGLE,则不使用没必要的CAS,使单线程处理更高效。
waitStrategy指明消费者等待生产时的策略。
- 设置消费者
指明EventHandler并绑定ExceptionHandler。指定多个EventHandler时,会为每个EventHandler分配一个线程,一个Event会被多个并行EventHandler处理。
也可以指明多个WorkHandler,每个WorkHandler分配一个线程并行消费队列中的Event,一个Event只会被一个WorkHandler处理。
- 创建/实例化EventTranslator
EventTranslator定义生产者数据转换为Event的方式,不同数量参数有不同的接口用来实现。
- 最后用Disruptor.publishEvent() 来发布元素指明EventTranslator和参数
例子程序
- 先引入Maven依赖
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.4</version>
</dependency>
- Event
/**
* 事件
*
* @param <T>发布的数据类型
*/
public class MyEvent<T> {
private T data;
public T getData() {
return data;
}
public MyEvent<T> setData(T data) {
this.data = data;
return this;
}
}
- EventFactory
import com.lmax.disruptor.EventFactory;
/**
* 创建事件的工厂
*
* @param <T>发布的数据类型
*/
public class MyEventFactory<T> implements EventFactory<MyEvent<T>> {
@Override
public MyEvent<T> newInstance() {
return new MyEvent<>();
}
}
- EventHandler
import com.lmax.disruptor.EventHandler;
/**
* 事件消费方法
*
* @param <T>发布的数据类型
*/
public class MyEventHandler<T> implements EventHandler<MyEvent<T>> {
@Override
public void onEvent(MyEvent<T> tMyEvent, long l, boolean b) throws Exception {
System.out.println(Thread.currentThread().getName() + "MyEventHandler消费:" + tMyEvent.getData());
}
}
- ExceptionHandler
import com.lmax.disruptor.ExceptionHandler;
/**
* 消费者异常处理器
*
* @param <T>发布的数据类型
*/
public class MyExceptionHandler<T> implements ExceptionHandler<MyEvent<T>> {
@Override
public void handleEventException(Throwable ex, long sequence, MyEvent<T> event) {
System.out.println("handleEventException");
}
@Override
public void handleOnStartException(Throwable ex) {
System.out.println("handleOnStartException");
}
@Override
public void handleOnShutdownException(Throwable ex) {
System.out.println("handleOnShutdownException");
}
}
单消费者
import com.lmax.disruptor.EventTranslatorOneArg;
import com.lmax.disruptor.ExceptionHandler;
import com.lmax.disruptor.SleepingWaitStrategy;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import static com.lmax.disruptor.dsl.ProducerType.SINGLE;
/**
* 单消费者
*/
public class SingleConsumerSample {
public static void main(String[] args) {
// 环形数组长度,必须是2的n次幂
int ringBufferSize = 1024;
// 创建事件(Event)对象的工厂
MyEventFactory<String> eventFactory = new MyEventFactory<>();
// 创建消费者线程工厂
ThreadFactory threadFactory = Executors.defaultThreadFactory();
// 等待策略
WaitStrategy waitStrategy = new SleepingWaitStrategy();
Disruptor<MyEvent<String>> disruptor =
new Disruptor<>(eventFactory, ringBufferSize, threadFactory, SINGLE, waitStrategy);
// 指定一个处理器
MyEventHandler<String> eventHandler = new MyEventHandler<>();
disruptor.handleEventsWith(eventHandler);
// 处理器异常处理器
ExceptionHandler<MyEvent<String>> exceptionHandler = new MyExceptionHandler<>();
disruptor.setDefaultExceptionHandler(exceptionHandler);
disruptor.start();
// 通过事件转换器(EventTranslator)来指明如何将发布的数据转换到事件对象(Event)中
// 这里是一个参数的转换器,另外还有两个(EventTranslatorTwoArg)、三个(EventTranslatorThreeArg)
// 和多个(EventTranslatorVararg)参数的转换器可以使用,参数类型可以不一样
EventTranslatorOneArg<MyEvent<String>, String> eventTranslatorOneArg =
new EventTranslatorOneArg<MyEvent<String>, String>() {
@Override
public void translateTo(MyEvent<String> event, long sequence, String arg0) {
event.setData(arg0);
}
};
// 发布
for (int i = 0; i < 10; i++) {
disruptor.publishEvent(eventTranslatorOneArg, "One arg " + i);
}
disruptor.shutdown();
}
}
单消费者Lambda写法
这种只是迎合Java8 Lambda语法特性,代码更简洁。
import com.lmax.disruptor.*;
import com.lmax.disruptor.dsl.Disruptor;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.stream.Collectors;
import static com.lmax.disruptor.dsl.ProducerType.SINGLE;
public class LambdaSample {
public static void main(String[] args) {
// 环形数组长度,必须是2的n次幂
int ringBufferSize = 1024;
// 创建消费者线程工厂
ThreadFactory threadFactory = Executors.defaultThreadFactory();
// 等待策略
WaitStrategy waitStrategy = new SleepingWaitStrategy();
Disruptor<MyEvent<String>> disruptor =
new Disruptor<>(MyEvent::new, ringBufferSize, threadFactory, SINGLE, waitStrategy);
// 指定一个处理器
EventHandler<MyEvent<String>> eventHandler = (event, sequence, endOfBatch) ->
System.out.println(Thread.currentThread().getName() + "MyEventHandler消费:" + event.getData());
disruptor.handleEventsWith(eventHandler);
// 处理器异常处理器
ExceptionHandler<MyEvent<String>> exceptionHandler = new MyExceptionHandler<>();
disruptor.setDefaultExceptionHandler(exceptionHandler);
disruptor.start();
// 通过事件转换器(EventTranslator)来指明如何将发布的数据转换到事件对象(Event)中
// 一个参数的转换器
disruptor.publishEvent((event, sequence, param) -> event.setData(param), "One arg ");
// 两个参数的转换器
disruptor.publishEvent((event, sequence, pA, pB) -> event.setData(pA + pB), "Two arg ", 1);
// 三个参数的转换器
disruptor.publishEvent((event, sequence, pA, pB, pC) -> event.setData(pA + pB + pC)
, "Three arg ", 1, false);
// 多个参数的转换器
disruptor.getRingBuffer().publishEvent((event, sequence, params) -> {
List<String> paramList = Arrays.stream(params).map(Object::toString).collect(Collectors.toList());
event.setData("Var arg " + String.join(",", paramList));
}, "param1", "param2", "param3");
disruptor.shutdown();
}
}
多消费者重复消费元素
关键只在于指定多个EventHandler,并且EventHandler还可以分别绑定不同的ExceptionHandler。
每个EventHandler分配一个线程,一个Event会被每个EventHandler处理,适合两个不同的业务都需要处理同一个元素的情况,类似广播模式。
import com.lmax.disruptor.*;
import com.lmax.disruptor.dsl.Disruptor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import static com.lmax.disruptor.dsl.ProducerType.SINGLE;
/**
* 一个元素多个消费者重复消费
*/
public class RepetitionConsumerSample {
public static void main(String[] args) {
// 环形数组长度,必须是2的n次幂
int ringBufferSize = 1024;
// 创建事件(Event)对象的工厂
MyEventFactory<String> eventFactory = new MyEventFactory<>();
// 创建消费者线程工厂
ThreadFactory threadFactory = Executors.defaultThreadFactory();
// 等待策略
WaitStrategy waitStrategy = new SleepingWaitStrategy();
Disruptor<MyEvent<String>> disruptor =
new Disruptor<>(eventFactory, ringBufferSize, threadFactory, SINGLE, waitStrategy);
// 这里指定了2个消费者,那就会产生2个消费线程,一个事件会被消费2次
EventHandler<MyEvent<String>> eventHandler = (event, sequence, endOfBatch) ->
System.out.println(Thread.currentThread().getName() + "MyEventHandler消费:" + event.getData());
EventHandler<MyEvent<String>> eventHandler2 = (event, sequence, endOfBatch) ->
System.out.println(Thread.currentThread().getName() + "MyEventHandler——2消费:" + event.getData());
disruptor.handleEventsWith(eventHandler, eventHandler2);
// 分别指定异常处理器
ExceptionHandler<MyEvent<String>> exceptionHandler = new MyExceptionHandler<>();
disruptor.handleExceptionsFor(eventHandler).with(exceptionHandler);
disruptor.handleExceptionsFor(eventHandler2).with(exceptionHandler);
disruptor.start();
for (int i = 0; i < 10; i++) {
disruptor.publishEvent((event, sequence, param) -> event.setData(param), "One arg " + i);
}
disruptor.shutdown();
}
}
多消费者
关键只在于定义WorkHandler,然后实例化多个来消费。
每个WorkHandler分配一个线程,一个元素只会被一个WorkHandler处理。
import com.lmax.disruptor.ExceptionHandler;
import com.lmax.disruptor.SleepingWaitStrategy;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.WorkHandler;
import com.lmax.disruptor.dsl.Disruptor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import static com.lmax.disruptor.dsl.ProducerType.SINGLE;
public class MultiConsumerSample {
public static void main(String[] args) {
// 环形数组长度,必须是2的n次幂
int ringBufferSize = 1024;
// 创建事件(Event)对象的工厂
MyEventFactory<String> eventFactory = new MyEventFactory<>();
// 创建消费者线程工厂
ThreadFactory threadFactory = Executors.defaultThreadFactory();
// 等待策略
WaitStrategy waitStrategy = new SleepingWaitStrategy();
Disruptor<MyEvent<String>> disruptor =
new Disruptor<>(eventFactory, ringBufferSize, threadFactory, SINGLE, waitStrategy);
// 处理器异常处理器
ExceptionHandler<MyEvent<String>> exceptionHandler = new MyExceptionHandler<>();
disruptor.setDefaultExceptionHandler(exceptionHandler);
// 设置2个消费者,2个线程,一个Event只被一个消费者消费
WorkHandler<MyEvent<String>> workHandler = tMyEvent ->
System.out.println(Thread.currentThread().getName() + "WorkHandler消费:" + tMyEvent.getData());
disruptor.handleEventsWithWorkerPool(workHandler, workHandler2);
disruptor.start();
for (int i = 0; i < 10; i++) {
disruptor.publishEvent((event, sequence, param) -> event.setData(param), "One arg " + i);
}
disruptor.shutdown();
}
}
参考链接
GitHub Disruptor Getting Started
Maven Repository Disruptor Framework
单机最快的队列Disruptor解析和使用的更多相关文章
- 高性能队列Disruptor系列1--传统队列的不足
在前一篇文章Java中的阻塞队列(BlockingQueue)中介绍了Java中的阻塞队列.从性能上我们能得出一个结论:数组优于链表,CAS优于锁.那么有没有一种队列,通过数组的方式实现,而且采用无锁 ...
- 高性能无锁队列 Disruptor 初体验
原文地址: haifeiWu和他朋友们的博客 博客地址:www.hchstudio.cn 欢迎转载,转载请注明作者及出处,谢谢! 最近一直在研究队列的一些问题,今天楼主要分享一个高性能的队列 Disr ...
- Dnsmasq安装与配置-搭建本地DNS服务器 更干净更快无广告DNS解析
默认的情况下,我们平时上网用的本地DNS服务器都是使用电信或者联通的,但是这样也导致了不少的问题,首当其冲的就是上网时经常莫名地弹出广告,或者莫名的流量被消耗掉导致网速变慢.其次是部分网站域名不能正常 ...
- 高性能队列disruptor为什么这么快?
背景 Disruptor是LMAX开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级).基于Disruptor开发的系统单线程能支撑每秒600万 ...
- 高性能队列——Disruptor
背景 Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级).基于Disruptor开发的系统单线程能 ...
- 高性能队列Disruptor系列2--浅析Disruptor
1. Disruptor简单介绍 Disruptor是一个由LMAX开源的Java并发框架.LMAX是一种新型零售金融交易平台,这个系统是建立在 JVM 平台上,核心是一个业务逻辑处理器,它能够在一个 ...
- JUC并发编程与高性能内存队列disruptor实战-下
并发理论 JMM 概述 Java Memory Model缩写为JMM,直译为Java内存模型,定义了一套在多线程读写共享数据时(成员变量.数组)时,对数据的可见性.有序性和原子性的规则和保障:JMM ...
- 高性能队列Disruptor系列3--Disruptor的简单使用(译)
简单用法 下面以一个简单的例子来看看Disruptor的用法:生产者发送一个long型的消息,消费者接收消息并打印出来. 首先,我们定义一个Event: public class LongEvent ...
- Java队列——Disruptor 的使用
.什么是 Disruptor 从功能上来看,Disruptor 是实现了“队列”的功能,而且是一个有界队列.那么它的应用场景自然就是“生产者-消费者”模型的应用场合了. 可以拿 JDK 的 Bloc ...
- 高性能队列Disruptor的使用
一.什么是 Disruptor 从功能上来看,Disruptor 是实现了"队列"的功能,而且是一个有界队列.那么它的应用场景自然就是"生产者-消费者"模型的应 ...
随机推荐
- 【pytest】pytest.mark.dependency用例依赖标签,并解决依赖失效的问题
pytest第三方插件,用来解决用例之间的依赖关系.如果依赖的用例执行失败后 后续的用例会被跳过执行,相当于智能执行了pytest.mark.skip, 首先要安装插件:pip install pyt ...
- pads:数据格式不正确,网络必须包含一个以上管脚
1,如果已经有pcb封装,则在pads logic软件里面-元件编辑器-重新做封装,在--编辑电参数--里面匹配对应pcb封装, 2,点击-工具--,--从库中更新--,更新一下,之后导入pcb la ...
- 【NumPy】Python将数组中低于一定百分比的值替换
情景举例 现有一个一维数组(或二维进行遍历)存放着很多,找到低于中位数20%的值并将小于该值的数全部替换为该值. 涉及方法 np.median(data, axis=0)用于计算数组中元素的中位数(中 ...
- QTreewidget勾选功能
//connect(ui->treeWidget,&QTreeWidget::itemClicked,this,&PushSelectUser::treeItemChanged) ...
- 中国台湾BSMI认证变动
China Taiwan 中国台湾 2022 年 11 月 1 日,BSMI和经济部发布了针对 18 种音像产品的修订法定检验要求. 自发布之日起,CNS 15598-1:2020 Audio/vid ...
- Python 错误:ModuleNotFoundError: No module named 'conf'
问题描述: 编译器无法使用cmd命令来执行py文件,结果可能报ModuleNotFoundError的错误. 比如在cmd任务栏执行:python E:\myProgram\Python\spider ...
- 服务器安装node
卸载步骤[未安装请忽略] 1.卸载npm sudo npm uninstall npm -g 2.卸载node yum remove nodejs npm -y 安装步骤 1.下载 wget http ...
- java注解和反射(Annotation and Reflect)
摘要: 注解和反射是相互联系的知识,所以应该放到一起来说. 注解:JDK5之后才有的技术,为了增加对元数据的支持,可以将注解理解为代码中的特殊标记,一种修饰.而这些标记是可以在代码编译,类的加载,和运 ...
- 初学TCP的一些感想
因为工作原因,想学习一下编程,最近在学习Qt相关的知识,学到了TCP/IP这里,跟着视频做了一个简单的C/S的小例程.例程中没有关于连接状态的判断.经过修改,可是没能达到我要的效果,在百度上进行搜索, ...
- Leecode 3.无重复字符的最大字串长度(Java 暴力拆解)
想法: 1.暴力解法,遇到重复字符就重新开辟空间,最后比较字串长度. 2.指针,但思路不太清晰 ----查看答案和思路,重新整理 滑动窗口: 1.设left,right用于下标值,length ...