1. Disruptor简单介绍

Disruptor是一个由LMAX开源的Java并发框架。LMAX是一种新型零售金融交易平台,这个系统是建立在 JVM 平台上,核心是一个业务逻辑处理器,它能够在一个线程里每秒处理 6 百万订单。业务逻辑处理器完全是运行在内存中(in-memory),使用事件源驱动方式(event sourcing),具有低延迟,高吞吐的特性。

disruptor有多快?官方给出了和ArrayBlockingQueue的比较图表:

Disruptor可以用来解决并发编程中的一个普遍的问题: 消息队列的处理(producer和consumer)。

2. 为什么Disruptor如此之快

Disruptor 相对于传统方式的优点:

  • 无锁,没有竞争
  • 所有访问者都记录自己的序号的实现方式,允许多个生产者与多个消费者共享相同的数据结构
  • 缓存行填充,解决伪共享,提高cache命中率
  • 环形数组RingBuffer,避免GC开销

3. Disruptor结构分析

在了解disruptor如何工作之前,我们先看一下disruptor一些重要组件的介绍(翻译自官方文档,略有修改):

  • Ring Buffer:Ring Buffer通常被认为是Disruptor的主要方面,但是从3.0开始Ring Buffer只负责数据(Events)的存储和更新。对于一些高级用例,完全可以由用户自己替换。
  • Sequence:Disruptor利用Sequences来标志一个特定的组件,每一个消费者(EventProcessor)都维护一个Sequence。Disruptor中大多数的并发代码都是依赖于这些Sequence的移动,生产者对RingBuffer的互斥访问,生产者与消费者之间的协调以及消费者之间的协调,都是通过Sequence实现。几乎每一个重要的组件都包含Sequence。由于需要在线程间共享,所以Sequence是引用传递,并且是线程安全的;再次,Sequence支持CAS操作;最后,为了提高效率,Sequence通过padding来避免伪共享。
  • Sequencer:Sequencer是Disruptor的真正的核心,此接口有两个实现类 SingleProducerSequencer、MultiProducerSequencer ,它们定义在生产者和消费者之间快速、正确地传递数据的并发算法。
  • Sequence Barriers:Sequence Barriers是由Sequencer创建的,包含Sequencer主发布的Sequence的引用和任何一个依赖消费者的Sequences。它包含了判断是否有任何事件可供消费者处理的逻辑。
  • Wait Strategy:等待策略决定了消费者会等待event被生产者放入Disruptor。Disruptor提供了多个等待策略的实现。1. BusySpinWaitStrategy:自旋等待,类似Linux Kernel使用的自旋锁。低延迟但同时对CPU资源的占用也多。2. BlockingWaitStrategy :使用锁和条件变量。CPU资源的占用少,延迟大。3. SleepingWaitStrategy :在多次循环尝试不成功后,选择让出CPU,等待下次调度,多次调度后仍不成功,尝试前睡眠一个纳秒级别的时间再尝试。这种策略平衡了延迟和CPU资源占用,但延迟不均匀。5. YieldingWaitStrategy :在多次循环尝试不成功后,选择让出CPU,等待下次调度。平衡了延迟和CPU资源占用,但延迟比较均匀。6. PhasedBackoffWaitStrategy :上面多种策略的综合,CPU资源的占用少,延迟大。
  • Event:数据从生产者传递给消费者的数据单元。
  • EventProcessor:处理Disruptor中的events的主事件循环,拥有消费者Sequence的所有权。其中BatchEventProcessor即实现了有效率的event loop,而且可以回调给实现了EventHandler接口的类。
  • EventHandler:Disruptor 定义的事件处理接口,由用户实现,用于处理事件,是Consumer的真正实现。
  • Producer:即生产者,只是泛指调用 Disruptor 发布事件的用户代码,Disruptor 没有定义特定接口或类型。

将这些元素放入Disruptor的context中,Disruptor的整体结构图如下:

多播事件

Queue和Disruptor之间最大的差异。当有多个消费者监听在同一Disruptor的所有事件,一个单一的事件只会被发送到一个单一的消费者。Disruptor一个使用的case是当你需要对同样的数据进行不一样的操作的时候。LMAX典型的例子是,我们有三个操作,日志(输入数据写入持久性日志文件),复制(将输入数据发送到另一台机器以确保有数据的远程复制),和业务逻辑(实际处理工作)。普通的Executor-style处理,可能是利用WorkPool并行的来处理这些不同的事件。这样却不是实现这个目标最有效的途径。

如上图所示,我们有三个EventHandler(JournalConsumer, ReplicationConsumer and ApplicationConsumer)监听着Disruptor,每一个Handler都会顺序的收到Disruptor里所有可用的消息,这样就使得这些消费者可以并行的处理这些消息了。

为了支持现实中并行处理的应用,必须支持消费者之间的协调。回到上面的例子,防止业务逻辑的消费还在继续,日志和复制的消费者已经完成了他们的任务是必须的。我们把这个概念称为门,或者更准确地说,这个行为的超级集合的特征叫做门。门发生在两个地方。首先,我们需要确保生产者不超过消费者。这是通过添加有关消费者到Disruptor时通过调用RingBuffer.addgatingconsumers() 实现的。其次,通过实现一个SequenceBarrier(内存屏障)的结构可以实现必须先完成某些操作的需求。

参考图1,有三个消费者监听唤醒队列中的事件,在图中有一个依赖图,ApplicationConsumer依赖于 JournalConsumer 和 ReplicationConsumer,这就说明 JournalConsumer 和 ReplicationConsumer可以互相自由的并发,这层依赖关系可以从 ApplicationConsumer的 SequenceBarrier连接到 JournalConsumer和 ReplicationConsumer的 Sequences看出来。值得注意的是 Sequencer和下游消费者之间的关系。作用之一就是确保发布不会覆盖Ring Buffer。为了做到这一点,下游消费者没有一个序列比RingBuffer的Sequence还要小,比RingBuffer的size还要小,然而,利用这个依赖图可以做一些有意思的操作,因为ApplicationConsumers Sequence是小于JournalConsumer 和 ReplicationConsumer(这就是依赖图所保证的),Sequencer只用关注ApplicationConsumer的Sequence即可,其实一般意义上,Sequencer只用知道消费者的Sequences依赖树中的叶子节点即可。

事件预分配

Disruptor的设计的一个目标就是能被用在一个低延迟的环境中。在低延迟系统中,必须减少或移除内存分配操作,基于Java开发的目的就是减少垃圾回收。(在低延迟的C/C++系统中,大内存分配也存在问题,因为内存分配器也会存在竞争)

为了实现低延迟,Disruptor允许用户对事件的内存进行预分配,在构造过程和用户提供的EventFactory中都会在Disruptor 的 RingBuffer中为每个实体分配。当发布新数据到Disruptor中,API就会允许用户获取构造方法的对象,以至于可以调用方法或者更新字段。Disruptor对这些操作提供并发安全性的保障。

可选的无锁操作

另一个关键的实现低延迟的细节就是在Disruptor中利用无锁的算法,所有内存的可见性和正确性都是利用内存屏障或者CAS操作。使用CAS来保证多线程安全,与大部分并发队列使用的锁相比,CAS显然要快很多。CAS是CPU级别的指令,更加轻量,不必像锁一样需要操作系统提供支持,所以每次调用不需要在用户态与内核态之间切换,也不需要上下文切换。

只有一个用例中锁是必须的,那就是BlockingWaitStrategy(阻塞等待策略),唯一的实现方法就是使用Condition实现消费者在新事件到来前等待。许多低延迟系统使用忙等待去避免Condition的抖动,然而在系统忙等待的操作中,性能可能会显著降低,尤其是在CPU资源严重受限的情况下,例如虚拟环境下的WEB服务器。

参考资料:
LMAX Disruptor
Spark性能优化指南——基础篇- - 美团点评技术团队
Disruptor入门

高性能队列Disruptor系列2--浅析Disruptor的更多相关文章

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

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

  2. 高性能队列Disruptor系列3--Disruptor的简单使用(译)

    简单用法 下面以一个简单的例子来看看Disruptor的用法:生产者发送一个long型的消息,消费者接收消息并打印出来. 首先,我们定义一个Event: public class LongEvent ...

  3. 高性能队列——Disruptor

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

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

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

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

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

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

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

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

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

  8. disruptor笔记之三:环形队列的基础操作(不用Disruptor类)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  9. Disruptor 系列(二)使用场景

    Disruptor 系列(二)使用场景 今天用一个订单问题来加深对 Disruptor 的理解.当系统中有订单产生时,系统首先会记录订单信息.同时也会发送消息到其他系统处理相关业务,最后才是订单的处理 ...

随机推荐

  1. 脚本语言:Xmas(三)

    自从将Xmas的GC换成现在的非迁移式的全局收集器后,最近几个月一直耗在Xmas上面:最明显的改变就是:更彻底地支持了面向对象.更强大的编译器. 所以,本文就来说说,真正的Xmas. 一.目标 一门语 ...

  2. IO和socket编程

    五一假期结束了,突然想到3周前去上班的路上看到槐花开的正好.放假也没能采些做槐花糕,到下周肯定就老了.一年就开一次的东西,比如牡丹,花期也就一周.而花开之时,玫瑰和月季无法与之相比.明日黄花蝶也愁.想 ...

  3. 1014 Uniform Generator

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

  4. Ninja 之路:试炼!求生演习——异步 I/O、http

    鸣人火影之路的第一步,就是跟着卡卡西学习基本的忍术,让自己先在忍者的世界里生存下来,so,想要在 node 的世界里游刃有余,必须要掌握异步 I/O.http等核心技能. ok,第一步先学会读懂需求 ...

  5. 【初识Python】

    一.Python的简介 1.什么是python? Python(发音:[ 'paiθ(ə)n; (US) 'paiθɔn ]),是一种面向对象的解释性的计算机程序设计语言,也是一种功能强大而完善的通用 ...

  6. Welcome to my Blog

    Only English? Oh my god!!! why open live write can't write by Chinese?

  7. 学习SpringMVC中优秀的代码编写风格

    在org.springframework.web.servlet.FrameworkServlet 中有下面这段代码 private class ContextRefreshListener impl ...

  8. 面试(2)-java-se-HashSet和TreeSet

    Set是java中一个不包含重复元素的collection.更正式地说,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素.正如其名称所暗示的, ...

  9. (中级篇 NettyNIO编解码开发)第六章-编解码技术

    基于Java提供的对象输入/输出流ObjectlnputStream和ObjectOutputStream,可以直接把Java对象作为可存储的字节数组写入文件,也可以传输到网络上.对程序员来说,基于J ...

  10. oracle配置odbc数据源

    今天配置oracle数据源心得: 1.需安装oracle客户端,若校验报错,将杀毒软件全部退出之后再重新安装: 2.安装完成后,运行odbcad32(64位),在odbc界面可找到相应驱动: 3.客户 ...