1. Disruptor概述
1.1 背景
Disruptor是英国外汇交易公司LMAX开发的一个高性能内存队列,研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级),基于Disruptor开发的系统单线程能支撑每秒600万订单,2010年在QCon演讲后,获得了业界关注,2011年,企业应用软件专家Martin Fowler专门撰写长文介绍。同年它还获得了Oracle官方的Duke大奖。
目前,包括Apache Storm、Camel、Log4j 2在内的很多知名项目都应用了Disruptor以获取高性能。
需要特别指出的是,这里所说的队列是系统内部的内存队列,而不是Kafka这样的分布式队列。
有界无锁 高并发队列
1.2 什么是Disruptor
Disruptor是用于一个JVM中多个线程之间的消息队列,作用与ArrayBlockingQueue有相似之处,但是Disruptor从功能、性能都远好于ArrayBlockingQueue,当多个线程之间传递大量数据或对性能要求较高时,可以考虑使用Disruptor作为ArrayBlockingQueue的替代者。
官方也对Disruptor和ArrayBlockingQueue的性能在不同的应用场景下做了对比,目测性能有5~10倍左右的提升。
1.3 为什么使用Disruptor
传统阻塞的队列使用锁保证线程安全,而锁通过操作系统内核上下文切换实现,会暂停线程去等待锁,直到锁释放。
执行这样的上下文切换,会丢失之前保存的数据和指令。由于消费者和生产者之间的速度差异,队列总是接近满或者空的状态,这种状态会导致高水平的写入争用。
1.3.1 传统队列问题
首先这里说的队列也仅限于Java内部的消息队列
队列 | 有界性 | 锁 | 结构 | 队列类型 |
---|---|---|---|---|
ArrayBlockingQueue | 有界 | 加锁 | 数组 | 阻塞 |
LinkedBlockingQueue | 可选 | 加锁 | 链表 | 阻塞 |
ConcurrentLinkedQueue | 无界 | 无锁 | 链表 | 非阻塞 |
LinkedTransferQueue | 无界 | 无锁 | 链表 | 阻塞 |
PriorityBlockingQueue | 无界 | 加锁 | 堆 | 阻塞 |
DelayQueue | 无界 | 加锁 | 堆 | 阻塞 |
1.3.2 Disruptor应用场景
参考使用到disruptor的一些框架
1.3.2.1 log4j2
Log4j2异步日志使用到了disruptor, 日志一般是有缓冲区, 满了才写到文件, 增量追加文件结合NIO等应该也比较快, 所以无论是EventHandler还是WorkHandler处理应该延迟比较小的, 写的文件也不多, 所以场景是比较合适的。
1.3.2.2 Jstorm
在流处理中不同线程中数据交换,数据计算可能蛮多内存中计算, 流计算快进快出,disruptor应该不错的选择。
1.3.2.3 百度uid-generator
部分使用Ring buffer和去伪共享等思路缓存已生成的uid, 应该也部分参考了disruptor吧。
1.4 Disruptor 的核心概念
先从了解 Disruptor 的核心概念开始,来了解它是如何运作的。下面介绍的概念模型,既是领域对象,也是映射到代码实现上的核心对象。
1.4.1 Ring Buffer
Disruptor中的数据结构,用于存储生产者生产的数据
如其名,环形的缓冲区,曾经 RingBuffer 是 Disruptor 中的最主要的对象,但从3.0版本开始,其职责被简化为仅仅负责对通过 Disruptor 进行交换的数据(事件)进行存储和更新。在一些更高级的应用场景中,Ring Buffer 可以由用户的自定义实现来完全替代。
1.4.2 Sequence
序号,在Disruptor框架中,任何地方都有序号
生产者生产的数据放在RingBuffer中的哪个位置,消费者应该消费哪个位置的数据,RingBuffer中的某个位置的数据是什么,这些都是由这个序号来决定的。这个序号可以简单的理解为一个AtomicLong类型的变量。其使用了padding的方法去消除缓存的伪共享问题。
1.4.3 Sequencer
序号生成器,这个类主要是用来协调生产者的
在生产者生产数据的时候,Sequencer会产生一个可用的序号(Sequence),然后生产者就就知道数据放在环形队列的那个位置了。
Sequencer是Disruptor的真正核心,此接口有两个实现类 SingleProducerSequencer、MultiProducerSequencer ,它们定义在生产者和消费者之间快速、正确地传递数据的并发算法。
1.4.4 Sequence Barrier
序号屏障
我们都知道,消费者在消费数据的时候,需要知道消费哪个位置的数据。消费者总不能自己想取哪个数据消费,就取哪个数据消费吧。这个SequencerBarrier起到的就是这样一个“栅栏”般的阻隔作用