RingBuffer顾名思义,就是一个内存环,每一次读写操做都循环利用这个内存环,从而避免频繁分配和回收内存,减轻GC压力,同时因为Ring Buffer能够实现为无锁的队列,从而总体上大幅提升系统性能。java
1.Ring Buffer是由一个大数组组成的。数组
2..Ring Buffer的“指针”(也称为序列或游标)是java long类型的(64位有符号数),指针采用往上计数自增的方式。缓存
3..Ring Buffer中的指针进行按ring buffer的size取模找出数组的下标来定位入口。为了提升性能,咱们一般将ring buffer的size大小设置成实际使用的2倍。安全
RingBuffer没有尾指针,维护了一个指向下一个可用位置的序号。RingBuffer和经常使用的队列之间的区别是,不删除buffer中的数据,也就是说这些数据一直存放在buffer中,直到新的数据覆盖他们。数据结构
之因此ringbuffer采用这种数据结构,是由于它在可靠消息传递方面有很好的性能。并发
首先,由于它是数组,因此要比链表快,并且有一个容易预测的访问模式。(数组内元素的内存地址是连续性存储的)。这是对CPU缓存友好的—也就是说,在硬件级别,数组中的元素是会被预加载的,所以在ringbuffer当中,cpu无需时不时去主存加载数组中的下一个元素。(注:由于只要一个元素被加载到缓存行,其余相邻的几个元素也会被加载进同一个缓存行)app
其次,你能够为数组预先分配内存,使得数组对象一直存在(除非程序终止)。这就意味着不须要花大量的时间用于垃圾回收。此外,不像链表那样,须要为每个添加到其上面的对象创造节点对象—对应的,当删除节点时,须要执行相应的内存清理操做。函数
消费者(Consumer)是一个想从Ring Buffer里读取数据的线程,它能够访问ConsumerBarrier对象——这个对象由RingBuffer建立而且表明消费者与RingBuffer进行交互。就像Ring Buffer显然须要一个序号才能找到下一个可用节点同样,消费者也须要知道它将要处理的序号——每一个消费者都须要找到下一个它要访问的序号。在上面的例子中,消费者处理完了Ring Buffer里序号8以前(包括8)的全部数据,那么它期待访问的下一个序号是9。性能
消费者能够调用ConsumerBarrier对象的waitFor()方法,传递它所须要的下一个序号.spa
final long availableSeq = consumerBarrier.waitFor(nextSequence);
ConsumerBarrier返回RingBuffer的最大可访问序号——在上面的例子中是12。
接下来,消费者会一直原地停留,等待更多数据被写入Ring Buffer。而且,一旦数据写入后消费者会收到通知——节点9,10,11和12 已写入。如今序号12到了,消费者可让ConsumerBarrier去拿这些序号节点里的数据了。
拿到了数据后,消费者(Consumer)会更新本身的标识(cursor)。
这样作有助于平缓延迟的峰值?
之前须要逐个节点地询问“我能够拿下一个数据吗?如今能够了么?如今呢?”,消费者(Consumer)如今只须要简单的说“当你拿到的数字比我这个要大的时候请告诉我”,函数返回值会告诉它有多少个新的节点能够读取数据了。由于这些新的节点的确已经写入了数据(Ring Buffer自己的序号已经更新),并且消费者对这些节点的惟一操做是读而不是写,所以访问不用加锁。这太好了,不只代码实现起来能够更加安全和简单,并且不用加锁使得速度更快。另外一个好处是你能够用多个消费者(Consumer)去读同一个RingBuffer ,不须要加锁,也不须要用另外的队列来协调不一样的线程(消费者)。这样你能够在Disruptor的协调下实现真正的并发数据处理。
写入 Ring Buffer 的过程涉及到两阶段提交 (two-phase commit)。首先,你的生产者须要申请 buffer 里的下一个节点。而后,当生产者向节点写完数据,它将会调用 ProducerBarrier 的 commit 方法。
Ring Buffer 仍是与消费端同样提供了一个 ProducerBarrier 对象,让生产者经过它来写入 Ring Buffer。
在这幅图中,咱们假设只有一个生产者写入 Ring Buffer。
ConsumerTrackingProducerBarrier 对象拥有全部正在访问 Ring Buffer 的 消费者 列表。这看起来有点儿奇怪-我从没有指望 ProducerBarrier 了解任何有关消费端那边的事情。可是等等,这是有缘由的。由于咱们不想与队列“混为一谈”(队列须要追踪队列的头和尾,它们有时候会指向相同的位置),Disruptor 由消费者负责通知它们处理到了哪一个序列号,而不是 Ring Buffer。因此,若是咱们想肯定咱们没有让 Ring Buffer 重叠,须要检查全部的消费者们都读到了哪里。
在上图中,有一个 消费者 顺利的读到了最大序号 12(用红色/粉色高亮)。第二个消费者 有点儿落后——可能它在作 I/O 操做之类的——它停在序号 3。所以消费者 2 在遇上消费者 1 以前要跑完整个 Ring Buffer 一圈的距离。
如今生产者想要写入 Ring Buffer 中序号 3 占据的节点,由于它是 Ring Buffer 当前游标的下一个节点。可是 ProducerBarrier 明白如今不能写入,由于有一个消费者正在占用它。因此,ProducerBarrier 停下来自旋 (spins),等待,直到那个消费者离开。
ProducerBarier 会看到下一个节点——序号 3 那个已经能够用了。它会抢占这个节点上的 Entry(我尚未特别介绍 Entry 对象,基本上它是一个放写入到某个序号的 Ring Buffer 数据的桶),把下一个序号(13)更新成 Entry 的序号,而后把 Entry 返回给生产者。生产者能够接着往 Entry 里写入数据。
当生产者结束向 Entry 写入数据后,它会要求 ProducerBarrier 提交。
ProducerBarrier先等待Ring Buffer的游标追上当前的位置(对于单生产者这毫无心义-好比,咱们已经知道游标到了 12 ,并且没有其余人正在写入 Ring Buffer)。而后 ProducerBarrier 更新 Ring Buffer 的游标到刚才写入的 Entry 序号-在咱们这儿是 13。接下来,ProducerBarrier 会让消费者知道 buffer 中有新东西了。它戳一下 ConsumerBarrier 上的 WaitStrategy 对象说-“喂,醒醒!有事情发生了!”(注意-不一样的 WaitStrategy 实现以不一样的方式来实现提醒,取决于它是否采用阻塞模式)。如今消费者 1 能够读 Entry 13 的数据,消费者 2 能够读 Entry 13 以及前面的全部数据,而后它们都过得很 happy。
Disruptor 能够同时在生产者和消费者两端实现批处理。
ProducerBarrier 知道 Ring Buffer 的游标指向 12,而最慢的消费者在 9 的位置,它就可让生产者写入节点 3,4,5,6,7 和 8,中间不须要再次检查消费者的位置。
如今生产者 1 申请提交节点 13 的数据(生产者 1 发出的绿色箭头表明这个请求)。ProducerBarrier 让 ClaimStrategy 先等待 Ring Buffer 的游标到达序号 12,固然如今已经到了。所以 Ring Buffer 移动游标到 13,让 ProducerBarrier 戳一下 WaitStrategy 告诉全部人都知道 Ring Buffer 有更新了。如今 ProducerBarrier 能够完成生产者 2 的请求,让 Ring Buffer 移动游标到 14,而且通知全部人都知道。
Ring Buffer的内容顺序老是会遵循nextEntry()的初始调用顺序。也就是说,若是一个生产者在写入 Ring Buffer 的时候暂停了,只有当它解除暂停后,其余等待中的提交才会当即执行。