简单用法

下面以一个简单的例子来看看Disruptor的用法:生产者发送一个long型的消息,消费者接收消息并打印出来。

首先,我们定义一个Event:

public class LongEvent
{
private long value; public void set(long value)
{
this.value = value;
}
}

为了使Disruptor对这些Event提前分配,我们需要创建一个EventFactory:

import com.lmax.disruptor.EventFactory;

public class LongEventFactory implements EventFactory<LongEvent>
{
public LongEvent newInstance()
{
return new LongEvent();
}
}

事件已经定义好了,我们需要创建一个消费者来处理这些消息。我们需要消费者在终端打印接收到的消息的值:

import com.lmax.disruptor.EventHandler;

public class LongEventHandler implements EventHandler<LongEvent>
{
public void onEvent(LongEvent event, long sequence, boolean endOfBatch)
{
System.out.println("Event: " + event);
}
}

我们需要创建一个事件源,我们假设数据来是来自一些I/O设备,比如网络或文件。

import com.lmax.disruptor.RingBuffer;

public class LongEventProducer
{
private final RingBuffer<LongEvent> ringBuffer; public LongEventProducer(RingBuffer<LongEvent> ringBuffer)
{
this.ringBuffer = ringBuffer;
} public void onData(ByteBuffer bb)
{
long sequence = ringBuffer.next(); // Grab the next sequence
try
{
LongEvent event = ringBuffer.get(sequence); // Get the entry in the Disruptor
// for the sequence
event.set(bb.getLong(0)); // Fill with data
}
finally
{
ringBuffer.publish(sequence);
}
}
}

显而易见的是,事件发布比使用简单队列更为复杂,这是事件预分配的缘故,如果2个生产者发布消息,即在RingBuffer中声明插槽发布可用数据,而且需要在try/finally块中发布。如果我们在RingBuffer中申请了一个插槽(RingBuffer.next()),那么我们必须发布这个Sequence,如果没有发布或者发布失败,那么Disruptor的将会failed,具体点来讲,在多生产者的情况下,这将导致消费者失速,而且除了重启没有其他办法可以解决了。

使用version3.0的Translator

Disruptor的version3.0给开发者提供了Lambda表达式风格的API,将RingBuffer的复杂性封装起来。所以,对于3.0以后的首选方法是通过API中发布事件的Translator部分来发布事件,例如:

import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.EventTranslatorOneArg; public class LongEventProducerWithTranslator
{
private final RingBuffer<LongEvent> ringBuffer; public LongEventProducerWithTranslator(RingBuffer<LongEvent> ringBuffer)
{
this.ringBuffer = ringBuffer;
} private static final EventTranslatorOneArg<LongEvent, ByteBuffer> TRANSLATOR =
new EventTranslatorOneArg<LongEvent, ByteBuffer>()
{
public void translateTo(LongEvent event, long sequence, ByteBuffer bb)
{
event.set(bb.getLong(0));
}
}; public void onData(ByteBuffer bb)
{
ringBuffer.publishEvent(TRANSLATOR, bb);
}
}

这种方法的另一个优点是可以将Translator代码拖到单独的类中,并方便对其进行单元测试。Disruptor提供了很多不同的接口(EventTranslator, EventTranslatorOneArg, EventTranslatorTwoArg等等)可以提供Translator。原因是可以表示为静态类或者非捕获的lambda作为参数通过Translator传递给RingBuffer。

最后一步是将所有的东西串联起来,可以手动的连接所有的组件,但是可能会有点复杂,因此可以通过DSL来简化构建,一些复杂的选项不是通过DSL提供的,但是可以适用于大多数情况。

import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.RingBuffer;
import java.nio.ByteBuffer;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors; public class LongEventMain
{
public static void main(String[] args) throws Exception
{
// Executor that will be used to construct new threads for consumers
Executor executor = Executors.newCachedThreadPool(); // Specify the size of the ring buffer, must be power of 2.
int bufferSize = 1024; // Construct the Disruptor
Disruptor<LongEvent> disruptor = new Disruptor<>(LongEvent::new, bufferSize, executor); // Connect the handler
disruptor.handleEventsWith((event, sequence, endOfBatch) -> System.out.println("Event: " + event)); // Start the Disruptor, starts all threads running
disruptor.start(); // Get the ring buffer from the Disruptor to be used for publishing.
RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer(); ByteBuffer bb = ByteBuffer.allocate(8);
for (long l = 0; true; l++)
{
bb.putLong(0, l);
ringBuffer.publishEvent((event, sequence, buffer) -> event.set(buffer.getLong(0)), bb);
Thread.sleep(1000);
}
}
}

注意不再需要一些类(例如处理程序、翻译程序),还需要注意lambda用于publishEvent()指的是传入的参数,如果我们把代码写成:

ByteBuffer bb = ByteBuffer.allocate(8);
for (long l = 0; true; l++)
{
bb.putLong(0, l);
ringBuffer.publishEvent((event, sequence) -> event.set(bb.getLong(0)));
Thread.sleep(1000);
}

这将创建一个可捕获的lambda,这意味需要通过publishEvent()实例化一个对象以保存ByteBuffer bb,这将创建额外的垃圾,为了降低GC压力,则将调用传递给lambda的调用应该是首选。

方法的引用可以用lambda来代替,fashion的写法:

import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.RingBuffer;
import java.nio.ByteBuffer;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors; public class LongEventMain
{
public static void handleEvent(LongEvent event, long sequence, boolean endOfBatch)
{
System.out.println(event);
} public static void translate(LongEvent event, long sequence, ByteBuffer buffer)
{
event.set(buffer.getLong(0));
} public static void main(String[] args) throws Exception
{
// Executor that will be used to construct new threads for consumers
Executor executor = Executors.newCachedThreadPool(); // Specify the size of the ring buffer, must be power of 2.
int bufferSize = 1024; // Construct the Disruptor
Disruptor<LongEvent> disruptor = new Disruptor<>(LongEvent::new, bufferSize, executor); // Connect the handler
disruptor.handleEventsWith(LongEventMain::handleEvent); // Start the Disruptor, starts all threads running
disruptor.start(); // Get the ring buffer from the Disruptor to be used for publishing.
RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer(); ByteBuffer bb = ByteBuffer.allocate(8);
for (long l = 0; true; l++)
{
bb.putLong(0, l);
ringBuffer.publishEvent(LongEventMain::translate, bb);
Thread.sleep(1000);
}
}
}

基本变种

使用上述方法在最常用的场景中已经够用了,但是如果你希望追求极致,还能够针对需要运行的硬件和软件,利用一些优化选项来提高性能。调优主要有两种方法:单或多生产者和可选等待策略。

单或多生产者

一个在concurrent系统提高性能的最好方式是单个Write的原则,这同样也适用于Disruptor,如果你在这种只有一个单线程的生产者发送Event的的Disruptor中,那么你能利用这个来获得额外的性能。

public class LongEventMain
{
public static void main(String[] args) throws Exception
{
//.....
// Construct the Disruptor with a SingleProducerSequencer
Disruptor<LongEvent> disruptor = new Disruptor(
factory, bufferSize, ProducerType.SINGLE, new BlockingWaitStrategy(), executor);
//.....
}
}

能有多少性能优势可以通过 OneToOne performance test测试,Tests运行在i7 Sandy Bridge MacBook Air。

多生产者:

Run 0, Disruptor=26,553,372 ops/sec
Run 1, Disruptor=28,727,377 ops/sec
Run 2, Disruptor=29,806,259 ops/sec
Run 3, Disruptor=29,717,682 ops/sec
Run 4, Disruptor=28,818,443 ops/sec
Run 5, Disruptor=29,103,608 ops/sec
Run 6, Disruptor=29,239,766 ops/sec

单生产者:

Run 0, Disruptor=89,365,504 ops/sec
Run 1, Disruptor=77,579,519 ops/sec
Run 2, Disruptor=78,678,206 ops/sec
Run 3, Disruptor=80,840,743 ops/sec
Run 4, Disruptor=81,037,277 ops/sec
Run 5, Disruptor=81,168,831 ops/sec
Run 6, Disruptor=81,699,346 ops/sec

选择等待策略

默认的Disruptor使用的等待策略是BlockingWaitStrategy(阻塞等待策略),阻塞等待策略内部使用的是典型的锁和Condition条件变量来处理线程唤醒,这是最慢的等待策略了,但是在CPU使用率上最保守而且能给予肯定的一致性行为。

休眠等待策略(SleepingWaitStrategy)

和BlockingWaitStrategy一样,为了保证CPU的使用率,不是通过一个简单的忙等待循环,而是使用一个叫LockSupport.parknanos(1)在循环中,在典型的Linux系统中将暂停60µs,这样显然是有优势的,生产者线程不需要增加计数器,也不需要信号条件。但是在生产者和消费者之间移动事件的平均延迟事件会更高。休眠等待策略在不需要低延迟的情况下效果最好,但是对生成线程的影响是很小的,一个常见的用例是异步日志记录。

退出等待策略(YieldingWaitStrategy)

退出等待策略是两个等待策略中可以被用到低延迟的策略,消耗CPU来提高实时性。YieldingWaitStrategy会忙等待Sequence增加为适当的值。在循环体中Thread.yield()将会允许其他线程运行,当需要非常高的性能和事件处理线程的的数量小于逻辑核心的总数时,这是推荐的等待策略,启用了超线程。

自旋等待策略(BusySpinWaitStrategy)

自旋等待策略是常见的等待策略,但是对部署环境也有很高的要求。自旋等待策略应该只能被用在处理线程数小于实际核数的时候。另外,超线程应该被关闭。

从RingBuffer清除对象

当通过Disruptor传递数据的时候,对象的存活寿命可能比预期的要长,为了避免这种情况发生,可能需要在处理完事件以后清除它。如果只有一个单事件处理程序,那么在一个处理程序中清除data就够了。如果有一个事件处理链,那么需要在链结束的地方利用特殊的处理程序来清除对象。

class ObjectEvent<T>
{
T val; void clear()
{
val = null;
}
} public class ClearingEventHandler<T> implements EventHandler<ObjectEvent<T>>
{
public void onEvent(ObjectEvent<T> event, long sequence, boolean endOfBatch)
{
// Failing to call clear here will result in the
// object associated with the event to live until
// it is overwritten once the ring buffer has wrapped
// around to the beginning.
event.clear();
}
} public static void main(String[] args)
{
Disruptor<ObjectEvent<String>> disruptor = new Disruptor<>(
() -> ObjectEvent<String>(), bufferSize, executor); disruptor
.handleEventsWith(new ProcessingEventHandler())
.then(new ClearingObjectHandler());
}

参考资料:

Getting Started

高性能队列Disruptor系列3--Disruptor的简单使用(译)的更多相关文章

  1. 高性能队列Disruptor系列1--传统队列的不足

    在前一篇文章Java中的阻塞队列(BlockingQueue)中介绍了Java中的阻塞队列.从性能上我们能得出一个结论:数组优于链表,CAS优于锁.那么有没有一种队列,通过数组的方式实现,而且采用无锁 ...

  2. 从构建分布式秒杀系统聊聊Disruptor高性能队列

    前言 秒杀架构持续优化中,基于自身认知不足之处在所难免,也请大家指正,共同进步.文章标题来自码友 简介 LMAX Disruptor是一个高性能的线程间消息库.它源于LMAX对并发性,性能和非阻塞算法 ...

  3. 高性能队列——Disruptor

    背景 Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级).基于Disruptor开发的系统单线程能 ...

  4. 高性能队列Disruptor的使用

    一.什么是 Disruptor 从功能上来看,Disruptor 是实现了"队列"的功能,而且是一个有界队列.那么它的应用场景自然就是"生产者-消费者"模型的应 ...

  5. 高性能队列disruptor为什么这么快?

    背景 Disruptor是LMAX开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级).基于Disruptor开发的系统单线程能支撑每秒600万 ...

  6. Disruptor 系列(一)快速入门

    Disruptor 系列(一)快速入门 Disruptor:是一个开源的并发框架,能够在 无锁 的情况下实现网络的 Queue 并发操作,所以处理数据的能力比 Java 本身提供的并发类容器要大的多, ...

  7. Disruptor系列(三)— 组件原理

    前言 前两篇文章都是从github wiki中翻译而来,旨在能够快速了解和上手使用Disruptor.但是为了能够掌握该技术的核心思想,停留在使用层面还远远不够,需要了解其设计思想,实现原理,故这篇从 ...

  8. Disruptor系列(一)— disruptor介绍

    本文翻译自Disruptor在github上的wiki文章Introduction,原文可以看这里. 一.前言 作为程序猿大多数都有对技术的执着,想在这个方面有所提升.对于优秀的事物保持积极学习的心态 ...

  9. 从零开始实现lmax-Disruptor队列(五)Disruptor DSL风格API原理解析

    MyDisruptor V5版本介绍 在v4版本的MyDisruptor实现多线程生产者后.按照计划,v5版本的MyDisruptor需要支持更便于用户使用的DSL风格的API. 由于该文属于系列博客 ...

随机推荐

  1. Andriod相机开发关于startPreview Failed的错误的特别记录(重要)

    年前,自己做的Android相机嵌入到一款火爆的游戏中去了,经过几次的修改升级,相机运行上报的错误也越来越少, 但是,前几天刚上线的最新相机,却老是在Android 2.3机器上出现startPrev ...

  2. PowerDesigner 12.5破解方法

    PowerDesigner 12.5破解方法 创建于 2017-05-07 22:18:04   一.下载   1 . PowerDesigner 12.5 官方下载地址  http://downlo ...

  3. ueditor 文件上传的分析和总结

    正式开始之前,先写两个常用又容易被我忘掉的文件和流相互转化的方法. 1,文件转流 FileStream fs = new FileStream(filename,FileMode.Open,FileA ...

  4. Java 7之基础 - 强引用、弱引用、软引用、虚引用

    1.强引用(StrongReference) 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器绝不会回收它.如下: Object o=new Object();   //  强引用 当内 ...

  5. 第二章 基本图像处理(Image Processing)

    主要内容: 图像的表示----介绍图像是如何表示的,以及所有基本操作的作用对象 高斯滤波-----滤波操作的原理与应用 图像金字塔-----高斯和拉普拉斯 边缘检测-----Sobel算子和Lapla ...

  6. Spring+SpringMVC+MyBatis深入学习及搭建(一)——MyBatis的基础知识

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/6812311.html 1.对原生态jdbc程序中问题总结 1.1 jdbc程序 需求:使用jdbc查询mys ...

  7. 005---query接口初步

    Query session.createQuery(String hql)方法; * hibernate的session.createQuery()方法是使用HQL(hibernate的查询语句)语句 ...

  8. mac系统下给文件夹加密方法

    电脑里我们往往会有许多隐私的文件,不希望被别人看到,在过去的Windows电脑里,我们习惯性的会在文件夹中将该文件隐藏,但是这个隐藏是不安全的,遇到稍微会点电脑技术的人就可以给你解开,安全性不高,ma ...

  9. C#访问ORALCE数据库

    随着时间的推移知识也在更新,原来可用的技术也会被淘汰或更新. framework4.0开始不再支持System.Data.OracleClient了,但是令人欣慰的是ORACLE公司自己出了一个Ora ...

  10. 使用Charles Proxy提升iOS开发效率

    以前做前端开发的时候,使用最多的工具就是 Fiddler ,用来定位问题.模拟特定场景非常方便,极大提升了开发效率.而转做 iOS 开发以后,一大头疼的问题是 Fiddler 没有 Mac 版,幸亏找 ...