Disruptor

LMAX Disruptor交易系统吞吐量快的关键原因。为什么我们要将其开源?我们意识到对高性能编程领域的一些传统观点有点不对劲。
尽管ringbuffer是整个模式(Disruptor)的核心,但是Disruptor对ringbuffer的访问控制策略才是真正的关键点所在。
1.首先介绍ringbuffer
1.1ringbuffer是一个环,换上的每个元素都有一个序号,序号随着元素的写入而递增(一直增加,绕过环以后继续增加),底层使用数组存储。它与常见队列的区别是:它不删除元素而是覆盖元素;他只有头指针没有尾指针。
1.1它比链表快,容易预测的访问模式(数组元素在内存连续存储);它缓存命中率高,因为根据CPU的缓存加载原则,会将元素附近64字节的内容一次性加载到缓存,这样相邻元素被加载到缓存后,下次访问则可以直接在缓存中取得。
1.2预先为数组分配内存,使得数组对象一直存在,避免了垃圾回收,节省了时间。不需要像链表那样,增删节点时都有内存操作,产生垃圾回收现象。
2.ConsummerBarrier与消费者
2.1 ComsummerBarrier由RingBuffer创建,代表消费者与RingBuffer进行交互。消费者调用Barrier.waitFor,传递它需要的下一个序号,比如9。消费者相当于告诉Barrier:你打到的数字比我这个要大的时候请告诉我。
final long availableSeq = consumerBarrier.waitFor(nextSequence);
当数据写入后,消费者会收到来自Barrier的通知。实际消费者是通过waitStrategy来获得通知的。通知9,10,11,12都已写入。现在序号12到了,消费者通过Barrier获得批量数据。
当多个消费者同时消费时,不需要锁机制。
3.ProducerBarrier与生产者
ProducerBarrier作为Producer与RingBuffer交流的媒介。同时,为了减轻Ringbuffer的复杂度,ProducerBarrier负责记录消费者分别消费到的序列号,所以ProducerBarrier也直接与Consumer通信。
3.1ProducerBarrier作为既知道Consumer消费到的序列号,又知道生产者想写入的序列号。它在整个Disruptor的访问控制上起到了十分关键的作用。由于每个消费者消费速率不同,有的快有的慢。会出现生产者要写入的节点位置,刚好还在被消费者消费,此时则需要等待。当消费者批量消费完以后,会通知ProducerBarrier,然后它则取到之前等待的节点Entry,然后交给Producer进行写入。Producer写入后要求ProducerBarrier进行提交。ProducerBarrier更新Ringbuffer上的游标到刚才更新的节点的序号。最后Producer通知Consumer的waitStrategy。
但是,要补充一下,其实整个对提交的管理是ProducerBarrier委托ClaimStrategy来做的。
ProducerBarrier通过ClaimStrategy来获取RingBuffer的游标。而且它还负责给Producer分发序号,以及追踪哪些序号被使用。在commit的时候,其实ClaimStrategy要等待游标到达待提交节点的上一个节点,才能进行提交。比如:游标在12,Prodcuer在14上先提取请求,此时并不能commit,需要等到13提交请求,然后游标更新到13,然后14才能commit。你会看到,尽管生产者在不同的时间完成数据写入,但是 Ring Buffer 的内容顺序总是会遵循 nextEntry() 的初始调用顺序。也就是说,如果一个生产者在写入 Ring Buffer 的时候暂停了,只有当它解除暂停后,其他等待中的提交才会立即执行
3.2ProducerBarrier上的批处理
因为它知道当前RingBuffer游标的位置,也知道各个消费者的消费到的位置,所以就知道哪些节点是可以写入的。