这篇博客将主要通过几个示例,简单讲述 Disruptor 的使用方法;

一、disruptor 简介

Disruptor 是英国外汇交易公司 LMAX 开发的一个无锁高性能的线程间消息传递的框架。目前包括 Apache Storm、Camel、Log4j2 等知名项目都是用了 Disruptor;

因为 Disruptor 中的一个很重要的结构 RingBuffer 和 JDK 中的 ArrayBlockingQueue 很相似,其内部都是一个环形数组,所以经常将他们放在一起比较,以下是官网公布测试结果

从图中可以明显看到他们之间性能的巨大差异;

此外在使用 Disruptor 的项目中也能看到其性能的差异,例如 Log4j

其中 Loggers all async 采用的是 Disruptor,Async Appender 采用的是 ArrayBlockingQueue, Sync 是同步模式;从图中可以看到,线程越多竞争越激烈的时候 Disruptor 的性能优势越明显,其原因很很容易想到,因为 ArrayBlockingQueue 的进出由同一把锁控制,所以竞争对其性能有巨大的影响;

此外我的笔记本配置为 “i7-8550U 8G”,使用的版本为:

<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>

二、ArrayBlockingQueue 性能对比

以下通过一个单线程的 demo,演示Disruptor 的基本用法,并个 ArrayBlockingQueue 做简单对比;

public class Contrast {
public static final int count = 50000000;
public static final int size = 1024;
private static CountDownLatch latch = new CountDownLatch(1); public void testDisruptor() throws InterruptedException {
long start = System.currentTimeMillis();
final Disruptor<Event> disruptor = new Disruptor<>(
() -> new Event(), // 绑定事件工厂,主要用于初始化 RingBuffer
size, // RingBuffer 大小
DaemonThreadFactory.INSTANCE, // 指定生产者线程工厂,也可以直接传入线程池
ProducerType.SINGLE, // 指定生产者为单线程,也支持多线程模式
new YieldingWaitStrategy() // 等待策略
// new BlockingWaitStrategy()
); Handler handler = new Handler();
disruptor.handleEventsWith(handler); // 绑定事件处理程序
disruptor.start(); RingBuffer<Event> ringBuffer = disruptor.getRingBuffer(); // 开始之后 RingBuffer 的所有位置就已经初始化完成
for (int i = 0; i < count; i++) {
long seq = ringBuffer.next(); // 获取下一个放置位置
Event event = ringBuffer.get(seq); // 等到指定位置的槽
event.seId(i); // 更新事件,注意这里是更新,不是放入新的,所以不会有 GC 产生
ringBuffer.publish(seq); // 发布事件
} latch.await();
System.out.println("time: " + (System.currentTimeMillis() - start));
} private void testQueue() throws InterruptedException {
long start = System.currentTimeMillis();
final BlockingQueue<Event> queue = new ArrayBlockingQueue<>(size);
new Thread(() -> {
for (int i = 0; i < count; i++) {
try {
queue.put(new Event(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start(); new Thread(() -> {
for (int i = 0; i < count; i++) {
try {
Event event = queue.take();
if (i == count - 1) {
System.out.println("last: " + event.getLogId());
latch.countDown();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start(); latch.await();
System.out.println("time: " + (System.currentTimeMillis() - start));
} class Event {
private long id;
Event() {}
Event(long id) { this.id = id; }
public long getLogId() { return id; }
public void seId(int id) { this.id = id; }
} class Handler implements EventHandler<Event> {
private int i = 0; @Override
public void onEvent(Event event, long seq, boolean bool) {
if (++i == count) {
System.out.println("last: " + event.getLogId());
latch.countDown();
}
}
} public static void main(String[] args) throws InterruptedException {
Contrast contrast = new Contrast();
contrast.testDisruptor();
// contrast.testQueue();
}
}

Disruptor-YieldingWaitStrategy: 919

Disruptor-BlockingWaitStrategy: 3142

ArrayBlockingQueue : 4307

其中 BlockingWaitStrategy 等待策略和 ArrayBlockingQueue 大致相识

三、多消费者

上面的例子在使用多个消费这时,会出现重复消费的情况,如果想要一条消息只消费一次,可以参照下面的代码:

public class MoreConsumer {
public static final int count = 5000;
public static final int size = 16; public void testDisruptor() {
long start = System.currentTimeMillis();
final Disruptor<Event> disruptor = new Disruptor<>(
() -> new Event(),
size, DaemonThreadFactory.INSTANCE,
ProducerType.SINGLE,
new BlockingWaitStrategy()
); disruptor.handleEventsWithWorkerPool(new Handler("h1"), new Handler("h2"), new Handler("h3"));
disruptor.start(); RingBuffer<Event> ringBuffer = disruptor.getRingBuffer();
for (int i = 0; i < count; i++) {
long seq = ringBuffer.next();
Event event = ringBuffer.get(seq);
event.id = i;
ringBuffer.publish(seq);
} System.out.println("time: " + (System.currentTimeMillis() - start));
} class Event { public long id; } class Handler implements WorkHandler<Event> {
private String name; Handler(String name) { this.name = name; } @Override
public void onEvent(Event event) { System.out.println(name + ": " + event.id); }
} public static void main(String[] args) {
MoreConsumer moreConsumer = new MoreConsumer();
moreConsumer.testDisruptor();
}
}

如上面的代码所示使用 WorkHandler 即可,同时还需要注意选择等待策略,策略不同也可能导致重复消费的问题,同时官网也只出需要在代码里面保证重复消费问题;

四、复杂业务逻辑

很多也业务逻辑会出现以下的类似情况,第三个消费者,需要等待前面的任务完成后才能继续执行的情况;通常我们会使用锁、同步工具以及一些其他的方式,但都显得比较麻烦,而且效率比较低,这里如果我们使用 Disruptor 就能很方便的解决;

disruptor.handleEventsWith(c1Handler, c2Handler);
disruptor.after(c1Handler, c2Handler).handleEventsWith(c3Handler);

如此仅需两行代码,就能将上面的关系表述清楚,对于更复杂的情况同样;

对于更多的使用技巧就需要你根据实际情况分析了,下一篇博客将主要分析 Disruptor 为什么会那么快;

Disruptor 详解 一的更多相关文章

  1. Disruptor 详解 二

    Disruptor 的大名从很久以前就听说了,但是一直没有时间:看完以后才发现其内部的思想异常清晰,很容易就能前移到其他的项目,所以仔细了解一下还是很有必要的这.篇博客将主要从源码角度分析,Disru ...

  2. Disruptor 详解

    想了解一个项目,最好的办法就是,把它的源码搞到本地自己捣鼓. 在网上看了 N 多人对 Disruptor 速度的吹捧,M 多人对它的机制分析,就连 Disruptor 官方文档中,也 NB 哄哄自诩: ...

  3. Java中日志组件详解

    avalon-logkit Java中日志组件详解 lanhy 发布于 2020-9-1 11:35 224浏览 0收藏 作为开发人员,我相信您对日志记录工具并不陌生. Java还具有功能强大且功能强 ...

  4. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

  5. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

  6. EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

    前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...

  7. Java 字符串格式化详解

    Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...

  8. Android Notification 详解(一)——基本操作

    Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...

  9. Android Notification 详解——基本操作

    Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...

随机推荐

  1. CefSharp部分效果实现

    CefSharp For WPF隐藏滚动条 CefSharp For WPF自定义右键菜单栏 CefSharp For WPF响应页面点击事件

  2. 用蓝牙连接debian和诺基亚手机

    本方法已经用debian 4.0.诺基亚9300和一个hl-united牌子的USB蓝牙适配器测试过了,效果很好.             1.安装必要的软件包:   #apt-get install ...

  3. 理解 node.js 的事件循环

    node.js 的第一个基本观点是,I/O 操作是昂贵的: 目前的编程技术最大的浪费来自等待 I/O 操作的完成.有几种方法可以解决这些对性能的影响(来自Sam Rushing): 同步:依次处理单个 ...

  4. Win8 Metro(C#)数字图像处理--2.66FloodFill算法

    原文:Win8 Metro(C#)数字图像处理--2.66FloodFill算法  [函数名称]   洪水填充算法函数 WriteableBitmap FloodfillProcess(Write ...

  5. Win8 Metro(C#)数字图像处理--2.42图像光照效果算法

    原文:Win8 Metro(C#)数字图像处理--2.42图像光照效果算法  [函数名称] 图像光照效果  SunlightProcess(WriteableBitmap src,int X,in ...

  6. VS2013环境里安装QT插件-“X86”与目标计算机类型“x64”冲突

    在VS2013环境里搭载QT老是出现模块计算机类型“X86”与目标计算机类型“x64”冲突 2.解决方案2.1 项目右键,属性>配置管理选择>x64,没有的话新建:2.2  项目右键,属性 ...

  7. 苹果ios用js的Date()出现NaN问题解决办法

    原文:苹果ios用js的Date()出现NaN问题解决办法 ios使用如下方法获得NaN,安卓手机则是正常计算,解决方法是换个这个时间的格式 new Date("2017-04-28 23: ...

  8. SQLServer 事务复制中使用脚本添加某个对象的发布

    原文:SQLServer 事务复制中使用脚本添加某个对象的发布 -- use [发布库] --添加表:创建项目并将其添加到发布中 exec sp_addarticle @publication = N ...

  9. 【转】编程之道 之 Rob Pike

    1.你无法断定程序会在什么地方耗费运行时间.瓶颈经常出现在想不到的地方,所以别急于胡乱找个地方改代码,除非你已经证实那儿就是瓶颈所在. 2.估量.在你没对代码进行估量,特别是没找到最耗时的那部分之前, ...

  10. storm(二)消息的可靠处理

    storm 通过 trident保证了对消息提供不同的级别.beast effort,at least once, exactly once. 一个tuple 从spout流出,可能会导致大量的tup ...