Clever Castle
1348 words
7 minutes
disruptor简介

简介#

Disruptor使用环形数组来作为数据容器。利用SequenceBarrier来作为消费者的读屏障,利用消费的sequence作为生产者的写屏障。

大量的文章都是直接开始分析代码,这样的方式有一个问题:由于生产者和消费者以及数据容器之间有一定交互,单独从一部分分析代码都会使初学者有所困扰。所以本文先介绍Disruptor的几个概念以及Disruptor的总体设计。

我使用kotlin实现了一个简单的Disruptor,代码 。这个代码是我在学习Disruptor的时候写的,所以可能有一些不完善的地方。但是它可以很好的帮助你理解Disruptor的原理。

几个概念#

由于Disruptor为了性能对数组及其它一些类都有填充,这些填充只是基于性能考虑,对代码逻辑没有什么影响。下面所说的数组长度等都不包括填充部分。

RingBuffer#

RingBuffer是Disruptor的核心,是其存储数据的地方,本质是一个环形数组,其数组长度是bufferSize 。环形数组只是一种形象的说明,它仍然是一个普通的Java数组。之所以称其为环形数组是因为Disruptor利用下面介绍的Sequence 来作为下标,Sequence的值是不断地增加的,只要与数组的size取模即可计算出真实的数组下标(Disruptor并不会取模),看起来就像是一个下标可以无限增长的定长数组。

上面说到Disruptor并不会取模,这是因为Disruptor对数组的长度有一个规定,必须是2的幂,这样可以用index&(bufferSize-1) 来进行下标计算而不是用取模来计算下边。这是因为位操作的速度要远快于取模速度。

RingBuffer在初始化的时候会将数组内的每个位置用开发人员自定义的EventFactory 将其填满。同时会生成另外一个与存储数据大小相同的标志位数组。用来标记该位是否可以消费,但是并不是我们常见的boolean 型标记位。原因很简单,因为如果使用boolean来标记该位是否可以消费,当生产者生产了个数为bufferSize 的数据之后,标志位就全都是true,之后就起不到标志位的作用。Disruptor是将当前的sequence的值写入其中,之后通过巧妙的计算来得出该位是否可以写入。

Sequence#

Sequencejava.util.concurrent中的AtomicLong功能是类似的。只不过做了一些填充。左右各填充8个Long(仅为性能考量,与代码逻辑无关)。

Sequencer#

由于Disruptor使用Sequence来作为数组的下标的来源,所以对数组的操作(例如将数据插入数组,覆盖数据)实质上都是对Sequence 的操作。

Disruptor对于Sequence的使用也提供了一个专门的接口即SequencerSequencer又继承了两个接口:SequencedCursoredSequenced接口主要是提供给生产者使用的。Sequencer 接口本身填充了一些方法主要是为了生成一个SequenceBarrier. 。它有两个实现:SingleProducerSequencerMultiProducerSequencer

生产者生产实质上是调用其中的两个方法 1. next(int n) 2. publish(long index)

第1步是申请当前位置的之后的n个位置,成功的条件是不能覆盖最慢的消费者,即currentSequence+n所在的位置必须已经被最慢的消费者消费才可以成功。否则就使用LockSupport.parkNanos(1); 等待再重试。

第二步是在将利用EventTranslator将生产的数据填充到数组之后,将标志位数组对应的位置setAvailable

SequenceBarrier#

SequencerBarrier可以视作消费者的读屏障,它确保了消费者不会消费还未生产的数据,即消费者的下标不会越过生产者的下标。

图片解释#

直接用文字解释起来相对比较抽象,我画了一张图来解释。

相关的文章#

Disruptor是要追求比较高的性能的,所以它在很多细节处都有不一样的设计。下面是几篇关于Disruptor的文章。

伪共享#

很多时候在代码层面并不存在共享的问题,但是因为硬件系统的一些设计会出现一些意想不到的共享问题,即伪共享。伪共享对性能也有一定的影响。

可以阅读下面的文章了解

伪共享(false sharing),并发编程无声的性能杀手

锁的缺点#

Disruptor是一个无锁队列。不使用锁当然是因为使用锁有一定的缺点。

可以阅读下面的文章了解

Dissecting the Disruptor: Why it’s so fast (part one) - Locks Are Bad

对应的中文译文 剖析Disruptor:为什么会这么快?(一)锁的缺点

内存屏障#

Java中的volatile 关键词提供了关于内存屏障的支持。它会在写操作后插入一个写屏障指令,在读操作前插入一个读屏障指令。使用volatile 的消耗是小于锁的,但是仍然有一定的性能损耗。

可以阅读下面的文章了解

Dissecting the Disruptor: Demystifying Memory Barriers 对应的中文译文 剖析Disruptor:为什么会这么快?(三)揭秘内存屏障

disruptor简介
https://blog.ivyxjc.com/posts/disruptor-intro-1/
Author
ivyxjc
Published at
2019-06-30