这篇博客将主要通过几个示例,简单讲述 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. 如何完全备份android在系统system分区和data分

    安德鲁斯系统备份是非常的情况下,可以使用.下面的这个python脚本.它可以用来备份整个data分:所有data分区的文件和文件夹打包data.zip.并产生recovery专用edify脚本upda ...

  2. AngularJS radio绑定与取值

    <div id="commentModal" class="modal fade" role="dialog" ng-app=&quo ...

  3. linuxC动态内存泄漏追踪方法

    C里面没有垃圾回收机制,有时候你申请了动态内存却忘记释放,这就尴尬了(你的程序扮演了强盗角色,有借有还才是好孩子).当你想找出内存泄露的地方时,有的投入海量的代码中,头痛不已.还好GNU C库提供了些 ...

  4. delphi中WebBrowser的parent改变时变成空白问题的解决(覆盖CreateWnd和DestroyWnd)

    这段时间在做一个delphi界面打开网页的功能,且此网页所在窗口可完整显示,可缩小到另一个窗口的panel上显示 可是在改变网页所在窗口时,WebBrowser控件变成了空白 上网google了半天, ...

  5. php延时执行

    sleep(秒) usleep(毫秒) 让它睡上一会.

  6. TidHttpServer 使用示例

    unit Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ...

  7. iOS9 Spotlight使用

    1.Spotloight是什么? Spotlight在iOS9上做了一些新的改进, 也就是开放了一些新的API, 通过Core Spotlight Framework你可以在你的app中集成Spotl ...

  8. G1 安装 Linux Debian system

    开发Android第五步,G1 安装 Linux Debian system 在 G1 上安装 Linux Debian system (Debian ARMEL) 要具备以下条件: (a) 最好是 ...

  9. AFN小结(简单的封装)

    AFN小结 1,AFN概念.原理 2,AFN的封装使用 3,AFN与其它框架对比 ————————————————————————————————— 1 , AFN的概念原理: AFN的基础是NSUR ...

  10. SignalR的简单使用(二)

    原文:SignalR的简单使用(二) 之前提到SignalR代理在网页,通过生成的Js来完成相关的功能.但我不禁想一个问题, 难到SignalR的服务端就能寄存在web端吗,通过访问网页能方式才能启动 ...