LMAX的Disruptor如何工作?(stackoverflow的回答)

原文地址http://stackoverflow.com/questions/6559308/how-does-lmaxs-disruptor-pattern-work

第一个回答(answered Jul 3 '11 at 8:03 Michael Barker):

Disruptor最简单的描述就是:它是线程间通信最高效的方式。它可以用来替代队列,同时有很多SEDA和Actors模式的特性。
和队列比较:
Disruptor可以向其他线程发送消息,并在需要的时候唤醒其他线程(和BlockingQueue相似)。不过,他们之间有三个主要的区别。
1. 使用者通过继承Entry类并提供一个做预分配之用的工厂来定义消息如何存储。这样可以重复利用(复制)内存,或者所实现的Entry可以持有其他对象的引用。
2. 把消息放入Disruptor需要2个步骤,首先在ring buffer中声名一个接口,这个接口提供使用者一个可以存放正确数据的Entry。然后必须提交这个entry,要想像上文提到的那样灵活使用内存,这两个步骤是必需的。提交操作使得消费者线程可以读取消息。
3. ring buffer中被消费的消息应该由消费者来追踪。不让ring buffer来追踪消息可以减少写冲突的出现,因为每个线程都自己维护计数器。
与Actors比较
Actor模型是最接近Disruptor的程序模型,尤其是使用BatchConsumer/BatchHandler类。这些类隐藏了所有保持已消费的序列号的复杂实现的并且在重要事情发生的时候会提供一些简单的回调方法。不过有两个小小的区别。
1. Disruptor使用一个线程对应一个消费者的模型,而Actors使用多对多的模型,比如你可以拥有尽可能多的actor,它们会分布在一定数量的线程上(一般一个核心一个actor)。
2. BatchHandler接口提供了一个额外的(而且是很重要的)回调方法onEndOfBatch().它允许耗时操作,比如将I/O操作放到批处理中一起执行以提高吞吐量。使用其他Actor框架也可以作批处理,但是它们几乎都没有提供批处理执行结束的回调方法,你只能使用超时来判断批处理是否结束,从而导致差的延时。
和SEDA比较
LMAX设计Disruptor模式是为了替代SEDA。
1. 和SEDA相比,disruptor主要的改进就是可以并行工作。Disruptor支持组播消息来实现这个功能,相同的消息(以一致的顺序)被发送给多个消费者。这样可以避免在管道中交叉层。
2. 消费者可以依赖其他消费者的处理结果,通过把一个队列化的层放在它们之间。一个消费者可以简单地看到自己所依赖的消费者的序列号。这样可以避免在管道中合并层。
和内存障比较
可以把dispruptor理解为一个结构化的,有序的内存障。其中生产者障碍相当于写障碍,消费者障相当于读障碍。

 

第二个回答(answered Jul 16 '11 at 5:48 irreputable):

首先我们来理解一下它提供的编程模型。
它有一个或多个作者和读者。有一排从旧到新的条目(从左到右)。作者可以在右侧新增条目。每个读者按从左到右的顺序读取条目。读者显然不能跳过作者先读取。
条目不能被删除。我用“读者”代替“消费者”来避免让人以为条目会被消费掉。不过我们知道最后一个读者左边的条目是没有用处的。
通常读者可以并发地独立地读取条目。但是我们可以声明读者之间的依赖关系。读者间可以有任意非环形依赖。如果读者B依赖于读者A,读者B不能跳过读者A先读取。
读者A可以注解一个条目,而读者B依赖于这个注解,所以就有了读者间的依赖。例如,A在一个条目上做一些计算,然后将结果保存到条目中的a字段。然后A前进,之后B可以读取这个条目和条目中的a。如果读者C没有依赖于A,那么C就不应该读取a。
这的确是一个有趣的编程模型。不管性能如何,单单这个模型就对应用很有好处。
当然了,LMAX的主要目标就是性能。Disruptor使用一个预分配的条目环。这个环足够大,但是有上限,从而不会超出容量。如果环满了,作者会一直等待,直到最慢的作者前进而腾出空间。
条目对象是预分配的并且会一直存在,以减少GC的消耗。我们不会增加新的条目对象或是删除旧的,相反的,作者会请求一个已存在的条目,填充它的字段,然后通知读者。这两个步骤真的是很简单的原子操作。

setNewEntry(EntryPopulator);
interface EntryPopulator{ void populate(Entry existingEntry); }

预分配的条目相当于adjacent memory cells中的adjacent entries分配(很像),并且因为读者是顺序读取条目,所以利用CPU缓存很重要。
还有做了很多努力来避免锁,CAS,甚至是内存障(比如如果只有一个作者的话就使用不变的序列变量)。
写给开发者:不同注解的读者应该写到条目中不同的字段,从而避免写冲突。(事实上他们应该写在不同的缓存行中。)一个特定注解的读者不应该去碰无依赖的读者可能读到的任何东西。

你可能感兴趣的:(disruptor)