LMAX Disruptor 简介:

Disruptor 是一个高性能的线程间消息传递库,源码地址为:https://github.com/LMAX-Exchange/disruptor

Disruptor 简介:

理解“Disruptor 者”是什么最好的方法,就是把它与人们理解得很好的、功能非常相似的东西进行比较。对于破坏者,这将是Java的BlockingQueue。与队列类似,破坏者的目的是在同一进程中的线程之间移动数据(例如消息或事件)。然而,破坏者提供了一些关键特性,将其与队列区分开来。它们是:

     向有依赖关系图得消费者多播事件。

     为事件预先分配内存。

     可以选择无锁。

Disruptor 核心概念:

在我们理解Disruptor 如何工作之前,有必要定义一些概念,这些概念将在整个文档和代码中使用。对于那些有DDD倾向的人来说,可以把这看作是破坏者领域中无处不在的语言:

Ring Buffer

   环形缓冲区通常被认为是Disruptor 的主要方面,但是从3.0开始,环形缓冲区只负责存储和更新流经Disruptor 的数据(事件)。对于一些高级的用例,可以完全由用户替换。

Sequence

Disruptor 使用序列作为一种方法来识别特定组件的位置。每个消费者(事件处理器)都像Disruptor 本身一样维护一个序列。大多数并发代码依赖于这些序列值的移动,因此序列支持AtomicLong的许多当前特性。实际上,这两个值之间唯一真正的区别是序列包含额外的功能,以防止序列和其他值之间的错误共享。

Sequencer:

Sequencer 是Disruptor的真正核心。该接口的两种实现(单生产者、多生产者)实现了所有用于在生产者和消费者之间快速、正确传递数据的并发算法。

Sequence Barrier:

The Sequence Barrier由Sequencer生成,包含对来自Sequencer和任何依赖消费者的序列的主已发布序列的引用。它包含逻辑,以确定是否有任何事件可供使用者处理。

Wait Strategy

决定了消费者将如何等待事件被生产者放置到Disruptor 中。有关可选无锁的详细信息,请参阅本节。

Event

从生产者传递给消费者的数据单位。事件没有特定的代码表示,因为它完全由用户定义

EventProcessor

用于处理Disruptor 事件的主事件循环,并拥有使用者Sequence 的所有权。有一个名为BatchEventProcessor的表示,它包含事件循环的有效实现,并将调用EventHandler接口的一个已提供的实现。

EventHandler:

由用户实现并代表破坏者的消费者的接口

Producer

这是调用Disruptor 来排队事件的用户代码。这个概念在代码中也没有表示。

为了将这些元素放到上下文中,下面是LMAX如何在其高性能核心服务(例如exchange)中使用破坏者的例子。

 

Disruptor

可以看到有3个事件处理程序(JournalConsumer、ReplicationConsumer和ApplicationConsumer)在监听Disruptor,每个Handlers程序都将接收干扰程序中可用的所有消息(顺序相同)。这允许这些使用者的工作可以并行进行。

Multicast Events 多播事件:

这是queue和Disruptor 之间最大的行为差异。当多个消费者监听同一个Disruptor 时,所有事件都发布给所有消费者,而queue中只有一个事件将发送给一个消费者。Disruptor 的行为旨在用于需要对相同数据进行独立的多个并行操作的情况。来自LMAX的规范示例中,我们有三个操作:日志记录(将输入数据写入持久日志文件)、复制(将输入数据发送到另一台机器以确保有数据的远程副本)和业务逻辑(真正的处理工作)。执行程序风格的事件处理也可以使用WorkerPool,通过同时处理不同的事件来发现伸缩性。请注意,它被固定在现有的Disruptor 类之上,并且没有得到相同的一流支持,因此它可能不是实现特定目标的最有效方法.

Consumer Dependency Graph 消费者依赖图

为了支持实际应用中的并行处理行为,有必要支持消费者之间的协调。回顾上面描述的示例,在日志记录和复制使用者完成任务之前,有必要防止业务逻辑使用者取得进展。我们将这种概念称为门控(gating),或者更准确地说,这种行为的超集特性称为门控(gating)。门控发生在两个地方。首先,我们需要确保生产者不超过消费者。这是通过调用RingBuffer.addGatingConsumers()将相关的消费者添加到Disruptor 来处理的。其次,前面提到的情况是通过构造一个SequenceBarrier来实现的,其中包含必须首先完成处理的组件的序列。

参考图。有3个消费者在侦听来自环缓冲区的事件。本例中有一个依赖关系图。应用程序使用者依赖于日志使用者和复制使用者。这意味着JournalConsumer和ReplicationConsumer可以自由地并行运行。从ApplicationConsumer的SequenceBarrier到JournalConsumer和ReplicationConsumer的序列之间的连接可以看到依赖关系。同样值得注意的是, Sequencer 与下游消费者之间的关系。它的作用之一是确保发布不包装环缓冲区。要做到这一点,下游用户中没有一个序列的大小小于环缓冲区的大小。然而,使用依赖关系图可以进行有趣的优化。因为ApplicationConsumers序列被保证小于或等于JournalConsumer和ReplicationConsumer(这是依赖关系确保的),所以排序器只需要查看ApplicationConsumer的序列。在更一般的意义上, Sequencer 只需要知道作为依赖关系树中的叶子节点的消费者的序列。

Event Preallocation 事件预先机制

Disruptor 的目标之一是在低延迟环境中启用使用。在低延迟系统中,有必要减少或删除内存分配。在基于java的系统中,这样做的目的是减少由于垃圾收集而导致的停机数量(在低延迟的C/ c++系统中,由于内存分配器上的争用,繁重的内存分配也是有问题的)。

为了支持这一点,用户可以预先分配Disruptor 内部事件所需的存储。在施工期间,EventFactory由用户提供,并将为破坏者的环缓冲区中的每个条目调用。当向Disruptor 发布新数据时,API将允许用户获得已构造的对象,以便他们可以调用该存储对象上的方法或更新字段。Disruptor 提供了保证,只要这些操作被正确地实现,它们将是并发安全的。

Optionally Lock-free 可选择无锁

低延迟需求推动的另一个关键实现细节是广泛使用无锁算法来实现Disruptor 。所有内存可见性和正确性保证都是使用内存屏障和/或比较和交换操作实现的。只有一种用例需要实际的锁,并且在BlockingWaitStrategy中。这样做的唯一目的是使用一个条件,以便在等待新事件到达时可以将正在使用的线程停靠。许多低延迟系统将使用繁忙等待来避免使用条件可能引起的抖动,但是在许多系统中,繁忙等待操作可能导致性能显著下降,特别是在CPU资源受到严重限制的情况下。例如,虚拟环境中的web服务器。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值