disruptor是LAMX用于交易场景的一个环形队列。按照disruptor的官方wiki中说的,学习disruptor的最好的方式就是与java中的BlockingQueue进行比较。在disruptor中同一个进程中的线程间数据的移动是依托于 messages或者events。和queue相同的一些关键特性中,disruptor提供了更好的实现,比如:
这是普通队列和disruptor之间行为上最大的一个不同点。当你有多个消费者监听同一个disruptor时,所有的events(数据)被发送到所有的消费者中,但是普通队列与此相反,一个event只会被发送 到一个消费者。disruptor的这个行为主要用于你需要对同一个数据并行地做不同的处理的场景。在LAMX中的应用实例是,当需要同时进行三个操作时,这三个操作分别是:
为了支持真实使用中的并行处理行为,所以需要支持多个消费者之间的协作。反过头来看一下上面描述的例子,我们需要让业务逻辑处理的消费者ApplicationConsumer等待journalConsumer和ReplicationConsumer 完成他们的任务之后再运行。我们把这部分称为闸,更准确的说法是这种行为的超级称为闸。闸主要出现在两个地方。首先我们需要确保生产者的生产能力不能超出消费者太多,这个通过调用RingBuffer.addGatingConsumers() 添加相关的消费者到Disruptor中来处理。还有一点就是,前面提到的场景是通过构造一个SequenceBarrier,这个SequenceBarrier中包含着很多个Sequence,这些Sequence必须先完成它们对应的操作。
上面提到的三个消费者同时监听Ring Buffer中的Events.这个例子是有一个依赖图的。ApplicationConsumer依赖于JournalConsumer和ReplicationConsumer。这意味着JournalConsumer和ReplicationConsumer 可以不受约束的相互之间并行运行。这种依赖关系可以通过ApplicationConsumer的SequenceBarrier与JournalConsumer和ReplicationConsumer的Sequences之间的联系来理解。Sequencer与它下游的消费者之间的关系 也是值得好好说一说的。它的角色之一是用来确保生产者发布的events不要包裹住整个RingBuffer环。为了达到这个,下游消费者的Sequence要小于RingBuffer的Sequence,而RingBuffer的Sequence大小要小于RingBuffer的 大小。使用依赖图是一件很有趣的优化操作。因为ApplicationConsumer的Sequence保证要小于或等于JournalConsumer和ReplicationConsumer的Sequence(这就是依赖关系所需要确保的),所以Sequencer仅仅只需要关注ApplicationConsumer的Sequence变化。在更常见的情况下,Sequencer只需要知道消费者们的所有Sequence都是整个依赖树的叶子节点。
disruptor的一个目标是提供一个低延迟的环境。在低延迟的系统中需要减少或消除内存的分配。在java系统中这么做的目的主要是为了减少gc。在低延迟的c/c++系统中,频繁的内存分配也会是一个问题,因为在内存分配器的竞争使用上会频繁。
为了支持低延迟,disruptor提供了内存预分配。在构造时由用户提供的EventFactory会被Disruptor的RingBuffer的每个entry中被调用。当发布新的数据到disruptor中去时,api会允许用户持有构造过的object以便于它们能调用保存的这些object中的方法或者 更新里面的字段。disruptor保证了这些操作的线程安全,只要它们被正确的实现了。
另外一个低延迟的关键点是disruptor中的无锁算法拓展实现。所有的内存可见性和正确性的保证都是通过使用内存屏障和CAS操作实现的。唯一的一个使用了真实的锁的地方就是BlockingWaitStrategy。这个单独这样做的目的是使用了一个Condition对象来让一个消费线程在等 待新的events(数据)时会被阻塞住。大多数的低延迟系统会使用忙时等待来避免使用Condition可能引起的抖动,但是很大一部分系统的忙时等待操作会导致系统性能的下降,尤其是cpu资源的严重占用。比如虚拟环境下的web服务器。
本节主要了解disruptor的一些核心组件和一些关键特性。对disruptor的一些基础名词进行了解,以便接下来源码的研究。
参考: