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());
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章