原文地址:http://ifeve.com/dissecting-disruptor-wiring-up/

作者:Trisha   译者:廖涵  校对:方腾飞

现在我已经讲了 RingBuffer​ 本身,如何从它 读取​ 以及如何向它 写入​。从逻辑上来说,下一件要做的事情就是把所有的东西拼装到在一起。

我前面提到过多生产者的情况——他们通过 ProducerBarrier 保证写入操作顺序与可控。我也提到过简单场景下的多消费者数据访问。更多的消费者的场景会变得更加复杂,我们​ 实现了一些聪明的机制允许多个消费者在访问 Ring Buffer 的时候互相等待(依赖)。像很多应用里,有一连串的工作需要在实际执行业务逻辑之前完成 (happen before) —— 例如,在做任何操作之前,我们都必须先保证消息写入磁盘。

Disruptor 论文​ 和性能测试里包含了你可能想到的一些基本结构。我准备讲一下其中最有趣的那个,这多半是因为我需要练习如何使用画图板。

菱形结构

DiamondPath1P3CPerfTest​ 展示了一个并不罕见的结构——独立的一个生产者和三个消费者。最棘手的一点是:第三个消费者必须等待前两个消费者处理完成后,才能开始工作。

消费者 C3 也许是你的业务逻辑。消费者 C1 可能在备份接收到的数据,而消费者 C2 可能在准备数据或者别的东西。

用队列实现菱形结构

在一个 SEDA-风格的架构​ 中,每个处理阶段都会用队列分开:


(为什么单词 Queue 里必须有这么多 “e” 呢?这是我在画这些图时遇到的最麻烦的词)。

你也许从这里看到了问题的端倪:一条消息从 P1 传输到 C3 要完整的穿过四个队列,每个队列在消息进入队列和取出队列时都会产生消耗成本。

用 Disruptor 实现菱形结构

在 Disruptor​ 的世界里,一切都由一个单独的 Ring Buffer 管理:

这张图看起来更复杂。不过所有的参与者都只依赖 Ring Buffer 作为一个单独的联结点,而且所有的交互都是基于 Barrier 对象与检查依赖的目标序号来实现的。

生产者这边比较简单,它是我在 上文​ 中描述过的单生产者模型。有趣的是,生产者并不需要关心所有的消费者。它只关心消费者 C3,如果消费者 C3 处理完了 Ring Buffer 的某一个节点,那么另外两个消费者肯定也处理完了。因此,只要 C3 的位置向前移动,Ring Buffer 的后续节点就会空闲出来。

管理消费者的依赖关系需要两个 ConsumerBarrier 对象。第一个仅仅与 Ring Buffer 交互,C1 和 C2 消费者向它申请下一个可访问节点。第二个 ConsumerBarrier 只知道消费者 C1 和 C2,它返回两个消费者访问过的消息序号中较小的那个。

Disruptor 怎样实现消费者等待(依赖)

Hmmm。我想需要一个例子。

我们从这个故事发生到一半的时候来看:生产者 P1 已经在 Ring Buffer 里写到序号 22 了,消费者 C1 已经访问和处理完了序号 21 之前的所有数据。消费者 C2 处理到了序号 18。消费者 C3,就是依赖其他消费者的那个,才处理到序号 15。

生产者 P1 不能继续向 RingBuffer 写入数据了,因为序号 15 占据了我们想要写入序号 23 的数据节点 (Slot)。


(抱歉,我真的试过用其他颜色来代替红色和绿色,但是别的都更容易混淆。)

第一个 ConsumerBarrier(CB1)告诉 C1 和 C2 消费者可以去访问序号 22 前面的所有数据,这是 Ring Buffer 中的最大序号。第二个 ConsumerBarrier (CB2) 不但会检查 RingBuffer 的序号,也会检查另外两个消费者的序号并且返回它们之间的最小值。因此,三号消费者被告知可以访问 Ring Buffer 里序号 18 前面的数据。

注意这些消费者还是直接从 Ring Buffer 拿数据节点——并不是由 C1 和 C2 消费者把数据节点从 Ring Buffer 里取出再传递给 C3 消费者的。作为替代的是,由第二个 ConsumerBarrier 告诉 C3 消费者,在 RingBuffer 里的哪些节点可以安全的处理。

这产生了一个问题——如果任何数据都来自于 Ring Buffer,那么 C3 消费者如何读到前面两个消费者处理完成的数据呢?如果 C3 消费者关心的只是先前的消费者是否已经完成它们的工作(例如,把数据复制到别的地方),那么这一切都没有问题—— C3 消费者知道工作已完成就放心了。但是,如果 C3 消费者需要访问先前的消费者的处理结果,它又从哪里去获取呢?

更新数据节点

秘密在于把处理结果写入 Ring Buffer 数据节点 (Entry) 本身。这样,当 C3 消费者从 Ring Buffer 取出节点时,它已经填充好了 C3 消费者工作需要的所有信息。这里 真正 重要的地方是节点 (Entry) 对象的每一个字段应该只允许一个消费者写入。这可以避免产生并发写入冲突 (write-contention) 减慢了整个处理过程。

你可以在 DiamondPath1P3CPerfTest​ 里看到这个例子—— FizzBuzzEntry​ 有两个字段:fizz 和 buzz。如果消费者是 Fizz Consumer, 它只写入字段 fizz。如果是 Buzz Consumer, 它只写入字段 buzz。第三个消费者 FizzBuzz,它只去读这两个字段但是不会做写入,因为读没问题,不会引起争用。

一些实际的 Java 代码

这一切看起来都要比队列实现更复杂。是的,它涉及到更多的内部协调。但是这些细节对于消费者和生产者是隐藏的,它们只和 Barrier 对象交互。诀窍在消费者结构里。上文例子中提到的菱形结构可以用下面的方法创建:

01 ConsumerBarrier consumerBarrier1 =
02     ringBuffer.createConsumerBarrier();
03 BatchConsumer consumer1 =
04     new BatchConsumer(consumerBarrier1, handler1);
05 BatchConsumer consumer2 =
06     new BatchConsumer(consumerBarrier1, handler2);
07 ConsumerBarrier consumerBarrier2 =
08     ringBuffer.createConsumerBarrier(consumer1, consumer2);
09 BatchConsumer consumer3 =
10     new BatchConsumer(consumerBarrier2, handler3);
11 ProducerBarrier producerBarrier =
12     ringBuffer.createProducerBarrier(consumer3);

总结

现在你知道了——如何关联 Disruptor 与相互依赖(等待)的多个消费者。关键点是:

  • 使用多个 ConsumerBarrier 来管理消费者之间的依赖(等待)关系。
  • 使用 ProducerBarrier 监视结构图中最后一个消费者。
  • 只允许一个消费者更新数据节点 (Entry) 的每一个独立字段。

更新:Adrian 写了一个非常好的 DSL 工具让拼接 Disruptor 更加简单了。

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

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 解析Disruptor的依赖关系

解析Disruptor的依赖关系相关推荐

  1. 【nestjs】Nest can‘t resolve dependencies of xxx无法解析xxx的依赖关系

    问题原因 因为A类需要B类,B类需要A类.在Nest.js中,模块之间和提供者之间可能会出现循环依赖.但是需要你尽量避免循环依赖. 所以正常的引入模块是不行的了. 解决流程 需要使用forwardRe ...

  2. Prism 文档 第三章 管理组件之间的依赖关系

                                                                          第3章:管理组件之间的依赖关系 基于Prism库的复合应用程 ...

  3. 4、IOC 之Bean的依赖关系

    4.IOC 之Bean的依赖关系 4.1.依赖注入(DI) 依赖关系注入 (DI) 是一个过程,其中对象仅通过构造函数参数.工厂方法的参数或在对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义 ...

  4. octave源代码安装之——依赖关系解析(致数学爱好者)

    octave源代码安装之--依赖关系解析(致数学爱好者) 我的系统是gentoo 3. 0 .6, gnome-3 ,gcc-4.5.3, 安装好系统,和一些必要的autotool, 就可以下载oct ...

  5. linux 依赖关系解析失败,关于linux依赖关系出错的解决

    我在装caffe时遇到的错误如下: apt-get: 代码: sudo apt-get install libgl1-mesa-dev 正在读取软件包列表... 完成 正在分析软件包的依赖关系树 正在 ...

  6. ORDNet:为场景分割捕获全范围依赖关系

    点击上方"深度学习爱好者",选择加"星标"或"置顶" 重磅干货,第一时间送达小白导读论文是学术研究的精华和未来发展的明灯.小白决心每天为大家 ...

  7. 查看动态链接库依赖关系的命令

    inux下查看动态链接库依赖关系的命令  x86: ldd    *.so arm: arm-linux-readelf    -d    *.so 实际例子: 以项目中用到的库librtsp.so分 ...

  8. 数据库中的实体、元组、字段、属性、码、分量、依赖关系、完全部份传递依赖、范式等你了解吗?【笔记自用】

    我们读不同的描写数据库的文章,会看到不同的概念名称,从某种意义上来讲,是公说公有理,婆说婆有理的问题,只是个人理解不同而称呼有异,这也给一些人,尤其是初学者带来一定的困扰,鉴于此,特整理<数据库 ...

  9. 使用jMeter构造逻辑上有依赖关系的一系列并发请求

    相信前端开发工程师对CSRF(Cross-site request forgery)跨站请求伪造这个概念都非常熟悉,有的时候也简写成XSRF,是一种对网站的恶意利用. 尽管听起来像跨站脚本(XSS), ...

  10. 巧用 Lazy 解决.NET Core中的循环依赖关系

    原文作者: Thomas Levesque 原文链接:https://thomaslevesque.com/2020/03/18/lazily-resolving-services-to-fix-ci ...

最新文章

  1. 洛谷 P2163 [SHOI2007]Tree 园丁的烦恼
  2. 行为模式之Command模式
  3. 关于 TCP 并发连接的几个思考题与试验
  4. Java 8系列之Stream中万能的reduce
  5. uva 10047 the monocyle (四维bfs)
  6. Android下常见的内存泄露 经典
  7. 数据库和ORMS:使用SQLAlchemy与数据库通信
  8. java算法判断链表有没有闭环_前端算法系列之二:数据结构链表、双向链表、闭环链表、有序链表...
  9. mysql安装、配置、连接
  10. LeetCode刷题(27)
  11. Python基础-“百钱百鸡”入门逻辑题(刚开始的建议藏起来)
  12. html能在hade中注释吗,A第1章 HTML超文本标记语言(1-20)OK.doc
  13. java se mac 10.9_jdk9mac下载|jdk8(Java SE Development Kit)8 mac2017 最新版_ - 极光下载站
  14. FTA故障树分析法-DFMEA的另外一张脸
  15. 统信UOS个人版安装与激活教程2020.5.17
  16. 关心国事-21世纪经济报道:周鸿祎人治雅虎中国
  17. 百度站点Logo权限获取与使用说明
  18. Java读取OPC DA报错:org.jinterop.dcom.common.JIException: Access is denied. [0x80070005]
  19. pip 使用豆瓣镜像
  20. 云计算实战应用案例精讲-【深度学习】多模态融合(论文篇四)

热门文章

  1. C#:在u3d中操作sqlite的数据库
  2. 那些年 我们都在...
  3. 3.struts2中Action的三种写法
  4. @ResponseBody与@RestController的作用与区别
  5. JS中类方法、对象方法、原型方法
  6. Android中的Handler的机制与用法详解
  7. android selector的item属性
  8. [RMQ] [线段树] POJ 3368 Frequent Values
  9. sharepoint学习资料-个人博客
  10. 架构设计师(Architect)的专业与角色