distuptor概述中文文档

原文地址:https://github.com/LMAX-Exchange/disruptor/wiki/Getting-Started

基本事件产生与消费

为了开始使用Disruptor,我们将考虑一个非常简单的自定义例子,一个将单个Long值从生产者传递给消费者的例子,消费者只需打印出值。首先我们要定义携带数据的事件。

public class LongEvent
{
    private long value;

    public void set(long value)
    {
        this.value = value;
    }
}

为了让Disruptor为我们预先分配这些事件,我们需要一个事件工厂来执行构建

import com.lmax.disruptor.EventFactory;

public class LongEventFactory implements EventFactory<LongEvent>
{
    public LongEvent newInstance()
    {
        return new LongEvent();
    }
}

一旦我们定义了事件,我们就需要创建一个消费者来处理这些事件。在我们的例子中,我们要做的只是将值打印出控制台。

import com.lmax.disruptor.EventHandler;

public class LongEventHandler implements EventHandler<LongEvent>
{
    public void onEvent(LongEvent event, long sequence, boolean endOfBatch)
    {
        System.out.println("Event: " + event);
    }
}

我们需要这些事件的来源,在例子中,我假设数据来自某种I/O设备,例如网络或ByteBuffer形式的文件。

使用转换器发布事件

在Disruptor的3.0版本中,添加了一个更丰富的Lambda样式的API,来帮助开发人员将这种复杂性封装在环形缓冲区ring buffer中,因此在3.0之后,发布消息的首选方法是通过API的事件发布器/事件转换器部分。例如。

import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.EventTranslatorOneArg;

public class LongEventProducerWithTranslator
{
    private final RingBuffer<LongEvent> ringBuffer;
    
    public LongEventProducerWithTranslator(RingBuffer<LongEvent> ringBuffer)
    {
        this.ringBuffer = ringBuffer;
    }
    
    private static final EventTranslatorOneArg<LongEvent, ByteBuffer> TRANSLATOR =
        new EventTranslatorOneArg<LongEvent, ByteBuffer>()
        {
            public void translateTo(LongEvent event, long sequence, ByteBuffer bb)
            {
                event.set(bb.getLong(0));
            }
        };

    public void onData(ByteBuffer bb)
    {
        ringBuffer.publishEvent(TRANSLATOR, bb);
    }
}

这种方法的另一个优点是,转换器Translators可以被拉到一个单独的类中,并且很容易独立地进行单元测试。Disruptor提供了许多不同的接口(EventTranslator、eventtranslatoronerg、EventTranslatorTwoArg等),可以实现这些接口来提供转换器。允许将转换器表示为静态类或lambda表达式形式,原因是这两种形式可以作为参数通过对环形缓冲区的调用传递到转换器。

使用旧版本API发布事件

我们可以使用一种更“原始”的方法。

import com.lmax.disruptor.RingBuffer;

public class LongEventProducer
{
    private final RingBuffer<LongEvent> ringBuffer;

    public LongEventProducer(RingBuffer<LongEvent> ringBuffer)
    {
        this.ringBuffer = ringBuffer;
    }

    public void onData(ByteBuffer bb)
    {
        long sequence = ringBuffer.next();  // Grab the next sequence
        try
        {
            LongEvent event = ringBuffer.get(sequence); // Get the entry in the Disruptor
                                                        // for the sequence
            event.set(bb.getLong(0));  // Fill with data
        }
        finally
        {
            ringBuffer.publish(sequence);
        }
    }
}

显而易见的是,事件发布比使用简单队列更加复杂。这是由于对事件预分配的期望。它需要(在最低级别)一个两阶段的消息发布方法,即声明环缓冲区中的插槽,然后发布可用的数据。还需要将发布包装在try/finally块中。如果我们在环缓冲区中声明一个槽(调用RingBuffer.next())然后我们必须发布此序列。否则会导致Disruptor的状态破坏。具体来说,在多生产商的情况下,这将导致消费者停滞,如果不重新启动就无法恢复。因此,建议使用EventTranslator API。

最后一步是把所有部分组装起来。手动连接所有组件是可能的,但是可能有点复杂,因此提供了DSL来简化构造。一些更复杂的选项无法通过DSL获得,但是它适合大多数情况。

import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.util.DaemonThreadFactory;
import java.nio.ByteBuffer;

public class LongEventMain
{
    public static void main(String[] args) throws Exception
    {
        // The factory for the event
        LongEventFactory factory = new LongEventFactory();

        // Specify the size of the ring buffer, must be power of 2.
        int bufferSize = 1024;

        // Construct the Disruptor
        Disruptor<LongEvent> disruptor = new Disruptor<>(factory, bufferSize, DaemonThreadFactory.INSTANCE);

        // Connect the handler
        disruptor.handleEventsWith(new LongEventHandler());

        // Start the Disruptor, starts all threads running
        disruptor.start();

        // Get the ring buffer from the Disruptor to be used for publishing.
        RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();
          LongEventProducer producer = new LongEventProducer(ringBuffer);

        ByteBuffer bb = ByteBuffer.allocate(8);
        for (long l = 0; true; l++)
        {
            bb.putLong(0, l);
            producer.onData(bb);
            Thread.sleep(1000);
        }
	}
}

使用Java8

Disruptor的API的设计影响之一是Java 8将依赖函数接口的概念来作为Java Lambdas的类型声明。Disruptor API中的大多数接口定义都符合函数式接口的要求,因此可以使用Lambda来代替自定义类,这样可以减少所需的代码空间。

import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.util.DaemonThreadFactory;
import java.nio.ByteBuffer;

public class LongEventMain
{
    public static void main(String[] args) throws Exception
    {
        // Specify the size of the ring buffer, must be power of 2.
        int bufferSize = 1024;

        // Construct the Disruptor
        Disruptor<LongEvent> disruptor = new Disruptor<>(LongEvent::new, bufferSize, DaemonThreadFactory.INSTANCE);

        // Connect the handler
        disruptor.handleEventsWith((event, sequence, endOfBatch) -> System.out.println("Event: " + event));

        // Start the Disruptor, starts all threads running
        disruptor.start();

        // Get the ring buffer from the Disruptor to be used for publishing.
        RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();
      	    ByteBuffer bb = ByteBuffer.allocate(8);
        for (long l = 0; true; l++)
        {
            bb.putLong(0, l);
            ringBuffer.publishEvent((event, sequence, buffer) -> 				         event.set(buffer.getLong(0)), bb);
            Thread.sleep(1000);
        }
    }
  }

请注意,许多类(例如handler, translator)不再是必需的。还要注意publishEvent()使用的lambda如何只引用传入的参数。如果我们把代码写成:

ByteBuffer bb = ByteBuffer.allocate(8);
for (long l = 0; true; l++)
{
    bb.putLong(0, l);
    ringBuffer.publishEvent((event, sequence) -> event.set(bb.getLong(0)));
    Thread.sleep(1000);
}

这将创建一个捕获lambda,这意味着在将lambda传递给publishEvent()调用时,它需要实例化一个对象来保存ByteBuffer bb变量。这将创建额外的(不必要的)垃圾,因此,如果需要低GC压力,则应首选将参数传递给lambda的调用。

假设可以使用方法引用而不是匿名lamdba,则可以用这种方式重写示例。

import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.util.DaemonThreadFactory;
import java.nio.ByteBuffer;

public class LongEventMain
{
    public static void handleEvent(LongEvent event, long sequence, boolean endOfBatch)
    {
        System.out.println(event);
    }

    public static void translate(LongEvent event, long sequence, ByteBuffer buffer)
    {
        event.set(buffer.getLong(0));
    }

    public static void main(String[] args) throws Exception
    {
        // Specify the size of the ring buffer, must be power of 2.
        int bufferSize = 1024;
        // Construct the Disruptor
        Disruptor<LongEvent> disruptor = new Disruptor<>(LongEvent::new, bufferSize, DaemonThreadFactory.INSTANCE);

        // Connect the handler
        disruptor.handleEventsWith(LongEventMain::handleEvent);

        // Start the Disruptor, starts all threads running
        disruptor.start();

        // Get the ring buffer from the Disruptor to be used for publishing.
        RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();

        ByteBuffer bb = ByteBuffer.allocate(8);
        for (long l = 0; true; l++)
        {
            bb.putLong(0, l);
            ringBuffer.publishEvent(LongEventMain::translate, bb);
            Thread.sleep(1000);
        }
		}
}

基本调整选项

使用上述方法将在最广泛的部署方案集中发挥功能。但是,如果您能够对Disruptor将运行的硬件和软件环境做出某些假设,那么您可以利用许多优化选项来提高性能。有两个主要的调整选项,单生产者与多生产者和可选的等待策略。

单/多生产者

在并发系统中,提高性能的最好方法之一是坚持单生产者原则,这适用于Disruptor。如果您处于只有一个线程向Disruptor生成事件的情况下,那么您可以利用这一点来获得额外的性能。

public class LongEventMain
{
    public static void main(String[] args) throws Exception
    {
        //.....
        // Construct the Disruptor with a SingleProducerSequencer
        Disruptor<LongEvent> disruptor = new Disruptor(
            factory, bufferSize, DaemonThreadFactory.INSTANCE, ProducerType.SINGLE, new BlockingWaitStrategy());
        //.....
    }
}

可选的等待策略

  • BlockingWaitStrategy

    Disruptor使用的默认等待策略是BlockingWaitStrategy。在内部,BlockingWaitStrategy使用一个典型的锁和条件变量来处理线程唤醒。BlockingWaitStrategy是可用等待策略中最慢的,但是在CPU使用方面是最保守的,并且将在最广泛的部署选项中提供最一致的行为。但是,对已部署系统的进一步了解同样可以提高性能。

  • SleepingWaitStrategy

    像BlockingWaitStrategy一样,SleepingWaitStrategy试图通过使用一个简单的busy wait循环来稳健的使用CPU,但是在循环的中间调用一次LockSupport.parkNanos(1)。在典型的Linux系统上,这将使线程暂停大约60微秒。但是,这样做的好处是,生产者线程不需要采取任何其他操作,而是增加适当的计数器,并且不需要发送条件变量的信号。但是,在生产者线程和消费者线程之间移动事件的平均延迟将更高。它在不需要低延迟的情况下工作得最好,但需要对生成线程的影响较小。一个常见的用例是用于异步日志记录。

  • YieldingWaitStrategy

    YieldingWaitStrategy是可以在低延迟系统中使用的两种等待策略之一,在低延迟系统中可以选择以提高延迟为目标来消耗CPU周期。YieldingWaitStrategy将一直自选等待序列增加到适当的值。在自选循环内部Thread.yield()将被调用以允许其他排队线程运行。当需要非常高的性能且事件处理程序线程数小于逻辑核心总数时,建议使用此等待策略,例如启用了超线程。

  • BusySpinWaitStrateg

    BusySpinWaitStrategy是性能最高的等待策略,但对部署环境的限制最高。只有当事件处理程序线程数小于环境机器的物理核心数时,才应使用此等待策略。例如,应禁用超线程。

从环形缓冲区清除对象

当通过Disruptor传递数据时,对象可能比预期的寿命更长。为了避免这种情况发生,可能有必要在处理完事件后清除它。如果只有一个事件处理程序,那么清除同一处理程序中的值就足够了。如果您有一个事件处理程序链,那么您可能需要在链的末尾放置一个特定的处理程序来处理清除对象。

class ObjectEvent<T>
{
    T val;

    void clear()
    {
        val = null;
    }
}

public class ClearingEventHandler<T> implements EventHandler<ObjectEvent<T>>
{
    public void onEvent(ObjectEvent<T> event, long sequence, boolean endOfBatch)
    {
        // Failing to call clear here will result in the 
        // object associated with the event to live until
        // it is overwritten once the ring buffer has wrapped
        // around to the beginning.
        event.clear(); 
    }
}
public static void main(String[] args)
{
    Disruptor<ObjectEvent<String>> disruptor = new Disruptor<>(
        () -> ObjectEvent<String>(), bufferSize, DaemonThreadFactory.INSTANCE);

    disruptor
        .handleEventsWith(new ProcessingEventHandler())
        .then(new ClearingObjectHandler());
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章