首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 开源软件 >

Disruptor 全解析(四):拼接依赖

2012-06-26 
Disruptor 全解析(4):拼接依赖原文地址:http://mechanitis.blogspot.com/2011/07/dissecting-disruptor-wi

Disruptor 全解析(4):拼接依赖

原文地址:http://mechanitis.blogspot.com/2011/07/dissecting-disruptor-wiring-up.html??作者是 Trisha Gee, LMAX 公司的一位美女工程师


现在我已经讲过了?RingBuffer??本身,从它?读取? 以及向它?写入?。
从逻辑上来说,下一件要做的事情就是把所有的东西拼接在一起。
我提到了多生产者的情况——他们通过 ProducerBarrier 保持操作顺序与可控。我也提过多个消费者在一个简单场景的情况。多个消费者的情况会变得有点儿更复杂,我们? 实现了一些聪明的机制允许多个消费者与 RingBuffer?之间互相依赖。像在很多应用,有一连串的事情需要在实际执行业务逻辑之前发生 (happen before)——例如,做任何操作之前,我们必须先确保消息已经被写到磁盘。
Disruptor 论文? 和 性能测试 里包含了你可能想要的一些基本结构。我准备讲一下最有趣的那个,这多半是因为我需要练习如何使用绘图板。
钻石结构DiamondPath1P3CPerfTest? 展示了一个并不非常罕见的结构——单独的一个生产者和三个消费者。最棘手的一点是:第三个消费者依赖前两个消费者处理结束后,才能开始工作。Disruptor 全解析(四):拼接依赖

三号消费者可能是你的业务逻辑。一号消费者可能正在备份接收到的数据,而二号消费者可能正在准备数据或者其他的。
用队列实现的钻石结构在一个?SEDA-风格的架构? 中,每个阶段都会用队列隔开:

Disruptor 全解析(四):拼接依赖
(为什么 Queue 必须有这么多 "e" 呢?这是我画这些图中遇到的最麻烦的词)。
你也许从这看到了问题的端倪:一条消息从 P1 到 C3 要穿过四个完整的队列,每个队列都在消息进入队列和取出队列上消耗成本。
用 Disruptor 实现的钻石结构在?Disruptor? 的世界里,一切都由单个 RingBuffer 管理:
Disruptor 全解析(四):拼接依赖
它看起来更复杂。但 RingBuffer 保持作为一个单点联系所有的参与者,并且所有交互都基于屏障 (Barriers) 检查它所依赖的对象序号来实现。
生产者这边比较简单,它是我在?上文? 中描述过的单生产者模型。有趣的是,生产者并不需要关心所有的消费者。它只关心三号消费者,因为如果三号消费者处理完 RingBuffer 中的一个节点,另外两个消费者肯定也处理完了。因此如果 C3 向前走,RingBuffer 的节点就空余出来了。
管理这些消费者的依赖关系需要两个 ConsumerBarrier 对象。第一个仅仅与 ?RingBuffer 交流,一号和二号消费者向它请求下一个可用节点。第二个 ConsumerBarrier 只知道一号和二号消费者,并且它返回两个消费者处理的消息序号中较小的那个。
消费者依赖怎样在 Disruptor 下工作嗯。我想我需要一个例子。Disruptor 全解析(四):拼接依赖
我们从这个故事发生到一半的时候来看:生产者 (P1) 已经写入 RingBuffer 到序号 22 了,一号消费者 (C1) 已经读和处理完了序号 21 之前的所有数据。二号消费者 (C2) 处理到了 18。三号消费者 (C3),就是依赖其他消费者的那个,才处理到 15。
生产者不能再向 RingBuffer 写数据了,因为序号 15 占据了我们想要写入序号 23 的节点 (Slot)。Disruptor 全解析(四):拼接依赖
(抱歉,我真的试过用其他颜色来代替红色和绿色,但是其它的都容易混淆。)
第一个 ConsumerBarrier?(CB1) 告诉一号和二号消费者可以去拿 22 号前面的所有东西,这是 RingBuffer 当前的最高序号。第二个 ConsumerBarrier (CB2) 不但检查 RingBuffer 的序号,也会检查另外两个消费者的序号并返回最小值。因此,三号消费者被告知可以获取 RingBuffer 序号 18 之前的所有东西。
注意这些消费者仍然直接从 RingBuffer 获取节点——并不是由一号和二号消费者把节点从 RingBuffer 取出然后传递给三号消费者的。作为代替,第二个 ConsumerBarrier 告诉三号消费者,RingBuffer 中哪些节点可以安全的处理。
这产生了一个问题——如果任何东西都来自 RingBuffer,那么三号消费者怎样拿到前两个消费者所完成的东西?如果三号消费者关心的只是早先的消费者是否已经完成它们的工作(例如,把数据复制到其他地方),这一切都很好——当三号消费者收到工作已完成,它就开心了。但是,如果三号消费者需要早先消费者的处理结果,它从哪里获取呢?
修改节点秘密在于把处理结果写入 RingBuffer 节点 (Entry) 自身。这样,当三号消费者从 RingBuffer 取出节点时,它已经填好了三号消费者工作需要的所有信息。这里?真正?重要的部分是节点 (Entry) 的每个字段只允许一个消费者写入。这避免了造成写争用 (write-contention) 减缓整个处理过程。
Disruptor 全解析(四):拼接依赖

你可以在?DiamondPath1P3CPerfTest? 看到这个例子——?FizzBuzzEntry? 有两个字段和值:fizz 和 buzz。如果消费者是 Fizz Consumer, 它只写 fizz。如果是 Buzz Consumer, 它只写 buzz。第三个消费者 FizzBuzz,会读这两个字段但是不向它们写入,因为读没问题,不会引起冲突。
一些实际的 Java 代码这一切看起来比队列实现更复杂。是的,它涉及更多的协调。但是这些对于消费者和生产者是隐藏的,它们只与 Barrier 交互。诀窍在结构里。上文例子里的钻石结构可以用类似下面的方法来创建:
ConsumerBarrier consumerBarrier1 =     ringBuffer.createConsumerBarrier();BatchConsumer consumer1 =     new BatchConsumer(consumerBarrier1, handler1);BatchConsumer consumer2 =     new BatchConsumer(consumerBarrier1, handler2);ConsumerBarrier consumerBarrier2 =     ringBuffer.createConsumerBarrier(consumer1, consumer2); BatchConsumer consumer3 =     new BatchConsumer(consumerBarrier2, handler3);ProducerBarrier producerBarrier =     ringBuffer.createProducerBarrier(consumer3); 

总结现在你知道了——如何拼接 Disruptor 与相互依赖的多个消费者。关键点是:?※ 使用多个 ConsumerBarrier 来管理消费者之间的依赖关系。?※?使用 ProducerBarrier 监视结构图中的最后一个消费者。?※ 只允许有一个消费者更新节点 (Entry) 中的一个单独字段。
修改:Adrian 写了一个很好的?DSL?工具让拼接 Disruptor 更加简单了。

?修改 2:注意 Disruptor 2.0 使用了与这篇文章不一样的命名。如果你对类名感到困惑,请阅读我的?变更总结??。另外,Adrian 的 DSL 工具现在是 Disruptor 主干代码的一部分了。

?

本文翻译自原作者博客, 译者是博客园?﹎〾敏ō?(http://www.cnblogs.com/adaikiss/??????), 我只是转载并修改了若干字句, 特别感谢。 ?

热点排行