高性能队列Disruptor系列2--浅析Disruptor

 

目录

1. Disruptor简单介绍
2. 为什么Disruptor如此之快
3. Disruptor结构分析

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的用法:生产者发送一个long型的消息,消费者接收消息并打印出来。 首先,我们定义一个Event: 为了使Disruptor对这些Event提前分配,我们需要创建一个EventFactory: 事件已经定义好了,我们需要创建一个消费者来处理这些消阅读全文
posted @ 2017-06-20 11:08 aheizi 阅读(198) | 评论 (1) 编辑
 
摘要: 1. Disruptor简单介绍 Disruptor是一个由LMAX开源的Java并发框架。LMAX是一种新型零售金融交易平台,这个系统是建立在 JVM 平台上,核心是一个业务逻辑处理器,它能够在一个线程里每秒处理 6 百万订单。业务逻辑处理器完全是运行在内存中(in memory),使用事件源驱动阅读全文
posted @ 2017-06-19 10:32 aheizi 阅读(199) | 评论 (0) 编辑
 
摘要: 在前一篇文章 "Java中的阻塞队列(BlockingQueue)" 中介绍了Java中的阻塞队列。从性能上我们能得出一个结论:数组优于链表,CAS优于锁。那么有没有一种队列,通过数组的方式实现,而且采用无锁的结构?嗯,那就是Disruptor,而且比想象中更为强大。 1. 无处不在的锁 Java中阅读全文
posted @ 2017-06-09 10:28 aheizi 阅读(323) | 评论 (0) 编辑
分类: 并发编程

Disruptor的更多相关文章

  1. 架构师养成记--15.Disruptor并发框架

    一.概述 disruptor对于处理并发任务很擅长,曾有人测过,一个线程里1s内可以处理六百万个订单,性能相当感人. 这个框架的结构大概是:数据生产端 --> 缓存 --> 消费端 缓存中 ...

  2. 并发框架Disruptor浅析

    1.引言 Disruptor是一个开源的Java框架,它被设计用于在生产者—消费者(producer-consumer problem,简称PCP)问题上获得尽量高的吞吐量(TPS)和尽量低的延迟.D ...

  3. LMAX Disruptor—多生产者多消费者中,消息复制分发的高性能实现

    解决的问题 当我们有多个消息的生产者线程,一个消费者线程时,他们之间如何进行高并发.线程安全的协调? 很简单,用一个队列. 当我们有多个消息的生产者线程,多个消费者线程,并且每一条消息需要被所有的消费 ...

  4. Disruptor 极速体验

    已经不记得最早接触到 Disruptor 是什么时候了,只记得发现它的时候它是以具有闪电般的速度被介绍的.于是在脑子里, Disruptor 和"闪电"一词关联了起来,然而却一直没 ...

  5. disruptor - Concurrent Programming Framework 并发编程框架

    disruptor发布了Java的2.0版本(.Net版本见这里),disruptor是一个高性能的异步处理框架,或者可以认为是最快的消息框架(轻量的JMS),也可以认为是一个观察者模式实现,或者事件 ...

  6. 剖析Disruptor:为什么会这么快?(二)神奇的缓存行填充

    原文链接:http://mechanitis.blogspot.com/2011/07/dissecting-disruptor-why-its-so-fast_22.html 需FQ 计算机入门   ...

  7. The LMAX disruptor Architecture--转载

    原文地址: LMAX is a new retail financial trading platform. As a result it has to process many trades wit ...

  8. Disruptor 源码阅读笔记--转

    原文地址:http://coderbee.net/index.php/open-source/20130812/400 一.Disruptor 是什么? Disruptor 是一个高性能异步处理框架, ...

  9. 构建高性能服务(三)Java高性能缓冲设计 vs Disruptor vs LinkedBlockingQueue--转载

    原文地址:http://maoyidao.iteye.com/blog/1663193 一个仅仅部署在4台服务器上的服务,每秒向Database写入数据超过100万行数据,每分钟产生超过1G的数据.而 ...

  10. LMAX Disruptor – High Performance, Low Latency and Simple Too 转载

    原文地址:http://www.symphonious.net/2011/07/11/lmax-disruptor-high-performance-low-latency-and-simple-to ...

随机推荐

  1. 解决Eclipse中Android SDK Manager图标不见了的问题

    在Eclipse中安装完ADT后,发现 Android SDK Manager图标不见了,Android Virtual Device Manager图标也不见了. 解决这个问题,可用如下方法: 打开 ...

  2. Hibernate 一对一映射(惟一外键)

  3. Linux中常用头文件的作用--转

    http://blog.sina.com.cn/s/blog_5c93b2ab0100q62k.html 1. Linux中一些头文件的作用: <assert.h>:ANSI C.提供断言 ...

  4. c++字符前面的L和_T

    字符串前面加L表示该字符串是Unicode字符串._T是一个宏,如果项目使用了Unicode字符集(定义了UNICODE宏),则自动在字符串前面加上L,否则字符串不变.因此,Visual C++里边定 ...

  5. 连锁咖啡厅B/S架构点餐系统开发

    需求号:5255128 http://task.zhubajie.com/5255128/ 具体要求: 网站用途:连锁咖啡厅B/S架构点餐系统开发开发周期:1.5个月功能要求: 支持触摸屏点菜.无线P ...

  6. CSS基础知识---浮动,定位和盒模型

    转载请注明出处! 需要掌握的三个最重要的CSS概念是浮动,定位和盒模型. 盒模型概述: 页面上的每个元素都被看做一个矩形框(元素框or盒模型),这个框由元素内容,内边距,边框和外边距组成. 内边距出现 ...

  7. css常用左右布局方案整理

     实际项目开发过程中我们经常会遇到页面div左右布局的需求:左侧 div 固定宽度,右侧 div 自适应宽度,填充满剩余页面,下面整理几种常用的方案  1 左侧 div 设置 float 属性为 le ...

  8. 06.密封类 sealed

    sealed   修饰 密封类是不能被继承的. 密封类是可以去继承别的类. namespace _07.密封类 { class Program { static void Main(string[] ...

  9. Error:All flavors must now belong to a named flavor dimension.

    环境 android studio 3.0 错误 Error:All flavors must now belong to a named flavor dimension. 解决 在build.gr ...

  10. java 断点续传(springMvc),可支持html5 vedio在线播放 posted @ 2017年3月11日 16:15:44

    Controller @RequestMapping(value = "/getVedio") public void getVedio(HttpServletRequest re ...