技术收藏-开发篇程序员

Disruptor简介

2018-04-01  本文已影响0人  沈渊

前言

学习Disruptor前,首先要有一个清晰的认识:Disruptor类似于BlockingQueue,但会有诸多不同,最明显的不同是BlockingQueue一个节点只能被一个消费者消费,而Disruptor一个节点可被多个消费者消费。本文主要翻译字Github上Disruptor简介,英文好的同学可以直接查看 原文。本文主要帮助英文不太好的同学学习,尽自己所能、翻译的最贴近原文,易于理解。

1、简介

理解Disruptor是什么的最好方法是将其与理解度很好并且目的相当相似的东西进行比较。 在Disruptor的情况下,这将是Java的BlockingQueue。 与队列类似,Disruptor的目的是在同一进程内的线程之间移动数据(例如消息或事件)。 但是,Disruptor提供的一些关键特性可以将其与队列区分开来。他们是:

2、核心概念

在我们理解Disruptor如何工作之前,有必要定义一些在整个文档和代码中使用的术语。 对于那些有DDD倾向的人来说,可以将其视为Disruptor域的无处不在的语言。

为了将这些元素置于上下文中,下面是LMAX如何在其高性能核心服务中使用Disruptor的例子:


图1. 有多个消费者的Disruptor

多播事件

这是队列和Disruptor之间最大的行为差异。当多个用户在同一个Disruptor上侦听时,所有事件都会发布给所有使用者;对应的,队列是一个事件只发送给某一个使用者。 Disruptor的行为旨在用于需要对相同数据进行独立的多个并行操作的情况。 LMAX的典型例子是我们有三个操作,journalling(将输入数据写入持久日志文件),复制(将输入数据发送到另一台机器以确保存在数据的远程副本)以及业务逻辑(真正的处理工作)。 Executor风格的事件处理,通过使用WorkerPool,可以通过并行处理不同事件来查找规模。请注意,它是在现有的Disruptor类的顶部,而不是用相同的第一类支持处理,因此它可能不是实现该特定目标的最有效方式。
从图1中可以看到有3个事件处理器在侦听Disruptor中RingBuffer (JournalConsumer,ReplicationConsumer和ApplicationConsumer),这些事件处理器中的每一个都会收到Disruptor中所有可用的消息(以相同的顺序)。 这允许每个消费者并行操作。

消费者依赖图

为了支持并行处理行为的实际应用,有必要支持消费者之间的协调。 回顾上述示例,必须防止业务逻辑消费者在日程安排和复制消费者完成其任务之前取得进展。 我们称这种概念为门控(Gating),或者更准确地说,这种行为的超级集合的功能称为门控。 门控发生在两个地方:

图1中有3个消费者正在从环缓冲器中侦听事件。在这个例子中有一个依赖关系图。 ApplicationConsumer依赖于JournalConsumer和ReplicationConsumer。这意味着JournalConsumer和ReplicationConsumer可以相互并行自由运行。依赖关系可以通过从ApplicationConsumer的SequenceBarrier到JournalConsumer和ReplicationConsumer的序列的连接来看到。值得注意的是Sequencer与下游消费者之间的关系。其中一个角色是确保发布不包装环缓冲区。要做到这一点,下游用户可能没有一个序列低于环缓冲区的序列减去环缓冲区的大小。然而,使用依赖关系图可以进行有趣的优化。由于ApplicationConsumers Sequence保证小于或等于JournalConsumer和ReplicationConsumer(这是依赖关系确保的内容),因此Sequencer只需查看ApplicationConsumer的Sequence。在更一般的意义上,Sequencer只需要知道依赖关系树中叶节点的消费者的序列。

事件预分配

Disruptor的目标之一是在低延迟环境下使用。 在低延迟系统中,有必要减少或删除内存分配。 在基于Java的系统中,目的是为了减少由于垃圾收集造成停顿的次数(在低延迟的C / C ++系统中,由于存储器分配器上存在争用而造成的大量内存分配也存在问题)。
为了支持这一点,用户能够预先分配Disruptor事件所需的存储空间。 在构建过程中,EventFactory由用户提供,并将针对Disruptor的 Ring Buffer 中的每个条目进行调用。 将新数据发布到Disruptor时,API将允许用户获取构造的对象,以便它们可以调用该方法或更新该存储对象上的字段。 Disruptor提供保证,只要正确实施这些操作将是并发安全的。

可选择无锁

另一个由低延迟需求推动的关键实现细节是在Disruptor中广泛使用的无锁算法。 所有的内存可见性和正确性都是由于使用内存屏障 和/或 比较和交换操作的实现来保证的。
只有BlockingWaitStrategy中一个用例需要实际的锁: 这仅仅是为了使用条件的目的而完成的,以便在等待新事件到达时消耗线程可以停放。 许多低延迟系统将使用繁忙等待来避免使用条件可能导致的抖动,但系统繁忙等待操作的数量可能导致性能显着下降,特别是在CPU资源严重受限的情况下。 例如。 虚拟化环境中的Web服务器。

Disruptor 工作流程

知乎上有一篇 文章,详细介绍了Disruptor原理及工作流程,包括:

强烈推荐没有相关基础的同学前往学习。

上一篇下一篇

猜你喜欢

热点阅读