简单用法

下面以一个简单的例子来看看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. 使用Express开发个人网站(一)

    Express,基于Node.js平台,快速.开放.极简的 web 开发框架. Node的出现,让js有了运行在服务器端的可能,基于此的Express,可以快速,简单的搭建起一个服务器与个人网站. 安 ...

  2. 【算法系列学习】codeforces D. Mike and distribution 二维贪心

    http://codeforces.com/contest/798/problem/D http://blog.csdn.net/yasola/article/details/70477816 对于二 ...

  3. logback配置文件详解

    一:根节点<configuration>包含的属性: scan: 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true. scanPeriod: 设置监测配置文 ...

  4. Oracle GoldenGate中HANDLECOLLISIONS参数使用详解

    Oracle GoldenGate中HANDLECOLLISIONS参数使用详解   HANDLECOLLISIONS 是一个 replicat 进程参数,主要在 initial load 中使用.在 ...

  5. 使用JavaEE的ServerAuthModule模块和web.xml进行相应配置,实现对用户的权限控制

    ServerAuthModule这里不细说,可以自行百度. 重点在注释: <!-- 声明用于安全约束的角色 --> <security-role> <role-name& ...

  6. 1013 Realtime Status

    Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission( ...

  7. 1.javascript节点的操作 创建、添加、移除、移动、复制、插入(修改)

    (1)创建新节点 createDocumentFragment() //创建一个DOM片段 createElement() //创建一个具体的元素 createTextNode() //创建一个文本节 ...

  8. springboot(四):thymeleaf使用详解

    在上篇文章springboot(二):web综合开发中简单介绍了一下thymeleaf,这篇文章将更加全面详细的介绍thymeleaf的使用.thymeleaf 是新一代的模板引擎,在spring4. ...

  9. 如何在.Net中使用MongoDB

    最近在研究mongodb,针对.net 中使用mongodb的文章要么是早期的驱动版本,要么资料很少,所以写个随笔记录一下 本文主要记录 1.什么是MongoDB 2.MongoDB windows ...

  10. shell群发邮件脚本

    linux版本:CentOS  6.7        //可以使用lsb_release -a查看 一.修改/etc/mail.rc set from=123456@qq.com //你自己的真实邮箱 ...