Disruptor簡單使用

  Disruptor從功能上來說,可以實現隊列的功能,也可以把它當成單機版的JMS來看待。從性能上來說,它比ArrayBlockingQueue有更好的性能表現,對於生產者消費者模型的業務,Disruptor是一個更好的選擇可以很好的實現業務的分離。

簡單入門

  • 定義消息類,這裏的消息在Disruptor裏稱爲Event,也就是我們系統裏生產消費的業務對象,示例代碼如下:
package com.example.disruptor;

/**
 * 產品
 */
public class Product {

    private int id;

    private String name;

    private double weight;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }
}

  • 定義生產者,也就是事件的來源。
package com.example.disruptor.singleton;

import com.example.disruptor.Product;
import com.lmax.disruptor.RingBuffer;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public class Producer implements Runnable {

    public static final int NUMBER = 10000000;
    private final CountDownLatch latch;
    private AtomicInteger idCount = new AtomicInteger(0);
    private RingBuffer<Product> ringBuffer;

    public Producer(RingBuffer<Product> ringBuffer, CountDownLatch latch) {
        this.ringBuffer = ringBuffer;
        this.latch = latch;
    }


    private void createData() {
        //1.可以把ringBuffer看做一個事件隊列,那麼next就是得到下面一個事件槽
        long sequence = ringBuffer.next();
        try {
            //2.用上面的索引取出一個空的事件用於填充(獲取該序號對應的事件對象)
            Product product = ringBuffer.get(sequence);
            //3.獲取要通過事件傳遞的業務數據
            product.setId(idCount.incrementAndGet());
        } finally {
            //4.發佈事件
            //注意,最後的 ringBuffer.publish 方法必須包含在 finally 中以確保必須得到調用;
            // 如果某個請求的 sequence 未被提交,將會堵塞後續的發佈操作或者其它的 producer。
            ringBuffer.publish(sequence);
        }
    }

    public void run() {
        for (int i = 0; i < NUMBER; i++) {
            createData();
        }
        //通過latch告訴主線程,完成了產品的生產
        latch.countDown();
    }
}

生產者在生成消息的過程中需要得到Disruptor裏的ringBuffer,將生產的消息加入到ringBuffer裏。Disruptor 的事件發佈過程是一個兩階段提交的過程:
  第一步:先從 RingBuffer 獲取下一個可以寫入的事件的序號;
  第二步:獲取對應的事件對象,將數據寫入事件對象;
  第三部:將事件提交到 RingBuffer;
事件只有在提交之後纔會通知消息消費者進行處理;

  • 定義消息的消費者,在Disruptor裏是EventHandler類型的實例。
package com.example.disruptor.singleton;

import com.example.disruptor.Product;
import com.lmax.disruptor.EventHandler;

import java.util.concurrent.CountDownLatch;

public class Consumer implements EventHandler<Product> {

   private int count = 0;

   private CountDownLatch latch;
    public Consumer(CountDownLatch latch) {
        this.latch = latch;
    }

    public void onEvent(Product event, long sequence, boolean endOfBatch) throws Exception {
       ;count++;
       //通過latch告訴主線程,完成了產品的消費
       if(count == Producer.NUMBER){
           latch.countDown();
       }
    }

    public int getCount() {
        return count;
    }
}

  • 通過Disruptor類,將生產者與消費者進行整合。具體的代碼如下:
package com.example.disruptor.singleton;

import com.example.disruptor.Product;
import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.YieldingWaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {

    //定義ringBuffer的大小
    private static final int RING_BUFFER_SIZE = 1024 * 8;

    public static void main(String[] args) {
        //構造消費者一個線程池, 實際項目中最好不要用Executors來構建
        ExecutorService consumerExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
        //構造生產者線程池
        ExecutorService produceerExecutor = Executors.newFixedThreadPool(1);
        //創建disruptor
        Disruptor<Product> disruptor =
                new Disruptor<Product>(new EventFactory<Product>() {
                    public Product newInstance() {
                        return new Product();
                    }
                }, RING_BUFFER_SIZE, consumerExecutor, ProducerType.SINGLE, new YieldingWaitStrategy());

        CountDownLatch latch = new CountDownLatch(2);
        // 連接消費事件方法
        Consumer consumer = new Consumer(latch);
        disruptor.handleEventsWith(consumer);
        // 啓動
        disruptor.start();
        //生產者開始生產數據
        Producer producer = new Producer(disruptor.getRingBuffer(), latch);
        produceerExecutor.submit(producer);

        try {
            latch.await();
        } catch (InterruptedException e) {
        }

        System.out.println(consumer.getCount());
        //關閉打開的資源
        disruptor.shutdown();
        consumerExecutor.shutdown();
        produceerExecutor.shutdown();
    }
}

在構造Disruptor對象,有幾個核心的概念:
1:事件工廠(Event Factory)定義瞭如何實例化事件(Event),Disruptor 通過 EventFactory 在 RingBuffer 中預創建 Event 的實例。
2:ringBuffer這個數組的大小,一般根據業務指定成2的指數倍。
3:消費者線程池,事件的處理是在構造的線程池裏來進行處理的。
4:指定等待策略,Disruptor 定義了 com.lmax.disruptor.WaitStrategy 接口用於抽象 Consumer 如何等待Event事件。Disruptor 提供了多個 WaitStrategy 的實現,每種策略都具有不同性能和優缺點,根據實際運行環境的 CPU 的硬件特點選擇恰當的策略,並配合特定的 JVM 的配置參數,能夠實現不同的性能提升。
  BlockingWaitStrategy 是最低效的策略,但其對CPU的消耗最小並且在各種不同部署環境中能提供更加一致的性能表現;
  SleepingWaitStrategy 的性能表現跟 BlockingWaitStrategy 差不多,對 CPU 的消耗也類似,但其對生產者線程的影響最小,適合用於異步日誌類似的場景;
  YieldingWaitStrategy 的性能是最好的,適合用於低延遲的系統。在要求極高性能且事件處理線數小於 CPU 邏輯核心數的場景中,推薦使用此策略;例如,CPU開啓超線程的特性。

多消費者模型

  在生產者消費者模型中,爲了防止生產者生產的數據覆蓋掉還未消費的數據,Disruptor中每個消費者都各自有個Sequence,而消費者的Sequence狀態需要通過SequenceBarrier同步到ringBuffer中。生產者產生數據的Sequence是通過ringBuffer進行生成的。下面是具體的代碼:

  • 定義生產者
package com.example.disruptor.mult;

import com.example.disruptor.Product;
import com.lmax.disruptor.RingBuffer;

import java.util.concurrent.atomic.AtomicInteger;

public class Producer implements Runnable {

    public static final int NUMBER = 10000000;
    public static AtomicInteger idCount = new AtomicInteger(0);
    private RingBuffer<Product> ringBuffer;

    public Producer(RingBuffer<Product> ringBuffer) {
        this.ringBuffer = ringBuffer;
    }


    private void createData() {
        //1.可以把ringBuffer看做一個事件隊列,那麼next就是得到下面一個事件槽
        long sequence = ringBuffer.next();
        try {
            //2.用上面的索引取出一個空的事件用於填充(獲取該序號對應的事件對象)
            Product product = ringBuffer.get(sequence);
            //3.獲取要通過事件傳遞的業務數據
            product.setId(idCount.incrementAndGet());
        } finally {
            //4.發佈事件
            //注意,最後的 ringBuffer.publish 方法必須包含在 finally 中以確保必須得到調用;
            // 如果某個請求的 sequence 未被提交,將會堵塞後續的發佈操作或者其它的 producer。
            ringBuffer.publish(sequence);
        }
    }

    public void run() {
        for (int i = 0; i < NUMBER; i++) {
            createData();
        }
    }
}

  • 定義消費者
package com.example.disruptor.mult;

import com.example.disruptor.Product;
import com.lmax.disruptor.WorkHandler;

/**
 * 多消費者需要繼承自WorkHandler
 */
public class Consumer implements WorkHandler<Product> {


    private int count = 0;

    public Consumer() {
    }


    public void onEvent(Product event) throws Exception {
        count++;
    }

    public int getCount() {
        return count;
    }

}

  • 啓動類:
package com.example.disruptor.mult;

import com.example.disruptor.Product;
import com.lmax.disruptor.*;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {

    //定義ringBuffer的大小
    private static final int RING_BUFFER_SIZE = 1024 * 8;

    public static void main(String[] args) {
        //線程數
        int processor = Runtime.getRuntime().availableProcessors() * 2;
        //構造消費者一個線程池, 實際項目中最好不要用Executors來構建
        ExecutorService consumerExecutor = Executors.newFixedThreadPool(processor);
        //構造生產者線程池
        ExecutorService produceerExecutor = Executors.newFixedThreadPool(processor);
        //定義一個ringBuffer,也就是相當於一個隊列
        RingBuffer ringBuffer = RingBuffer.create(ProducerType.MULTI, new EventFactory<Product>() {
            public Product newInstance() {
                return new Product();
            }
        }, RING_BUFFER_SIZE, new YieldingWaitStrategy());
        //定義一個消費者池,
        Consumer[] consumers = new Consumer[processor];
        for (int i = 0; i < processor; i++) {
            consumers[i] = new Consumer();
        }
        WorkerPool workerPool = new WorkerPool<Product>(ringBuffer,
                ringBuffer.newBarrier(), new IgnoreExceptionHandler(), consumers);
        //每個消費者,也就是 workProcessor都有一個sequence,表示上一個消費的位置,這個在初始化時都是-1
        Sequence[] sequences = workerPool.getWorkerSequences();
        //將其保存在ringBuffer中的 sequencer 中,在爲生產申請slot時要用到,也就是在爲生產者申請slot時不能大於此數組中的最小值,否則產生覆蓋
        ringBuffer.addGatingSequences(sequences);
        //用executor 來啓動 workProcessor 線程
        workerPool.start(consumerExecutor);

        //生產者開始生產數據
        for (int i = 0; i < processor; i++) {
            Producer producer = new Producer(ringBuffer);
            produceerExecutor.submit(producer);
        }

        while (true) {
            int count = 0;
            for (Consumer consumer : consumers) {
                count += consumer.getCount();
            }
            System.out.println("生產了多少數據" + Producer.idCount.get());
            System.out.println("消費了多少數據" + count);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

ringBuffer需要將WorkPool裏所有消費者的Sequence加到ringBuffer中,以防止出現數據覆蓋的問題。

將disruptor當成JMS,處理消息流

可以將disruptor當成單機版的JMS,用來處理數據流,disruptor提供了消費者處理消息的先後順序,能很好的實現根據指定規則來實現消息的處理。比如可以將消息形成如下圖的數據流:
  • 定義上面四個handler的處理邏輯, 我這裏只貼出一個類的實現
package com.example.disruptor.complex;


import com.example.disruptor.Product;
import com.lmax.disruptor.EventHandler;

public class StartHandler implements EventHandler<Product> {

    public void onEvent(Product product, long l, boolean b) throws Exception {
        System.out.println("start set name");
        product.setName("start");
    }

}

  • 定義生產都,用於生產消息
package com.example.disruptor.complex;

import com.example.disruptor.Product;
import com.lmax.disruptor.RingBuffer;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public class Producer implements Runnable {

    public static final int NUMBER = 2;
    private AtomicInteger idCount = new AtomicInteger(0);
    private RingBuffer<Product> ringBuffer;

    public Producer(RingBuffer<Product> ringBuffer) {
        this.ringBuffer = ringBuffer;
    }


    private void createData() {
        //1.可以把ringBuffer看做一個事件隊列,那麼next就是得到下面一個事件槽
        long sequence = ringBuffer.next();
        try {
            //2.用上面的索引取出一個空的事件用於填充(獲取該序號對應的事件對象)
            Product product = ringBuffer.get(sequence);
            //3.獲取要通過事件傳遞的業務數據
            product.setId(idCount.incrementAndGet());
        } finally {
            //4.發佈事件
            //注意,最後的 ringBuffer.publish 方法必須包含在 finally 中以確保必須得到調用;
            // 如果某個請求的 sequence 未被提交,將會堵塞後續的發佈操作或者其它的 producer。
            ringBuffer.publish(sequence);
        }
    }

    public void run() {
        for (int i = 0; i < NUMBER; i++) {
            createData();
        }
    }
}

  • 將disruptor與上面四個handler進行關聯
package com.example.disruptor.complex;

import com.example.disruptor.Product;
import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {

    //定義ringBuffer的大小
    private static final int RING_BUFFER_SIZE = 1024 * 8;

    public static void main(String[] args) {
        //構造消費者一個線程池, 實際項目中最好不要用Executors來構建
        ExecutorService consumerExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
        //構造生產者線程池
        ExecutorService produceerExecutor = Executors.newFixedThreadPool(1);
        //創建disruptor
        Disruptor<Product> disruptor =
                new Disruptor<Product>(new EventFactory<Product>() {
                    public Product newInstance() {
                        return new Product();
                    }
                }, RING_BUFFER_SIZE, consumerExecutor, ProducerType.SINGLE, new BlockingWaitStrategy());

        //定義處理消息的handler
        StartHandler start = new StartHandler();
        LeftHandler left = new LeftHandler();
        RightHandler right = new RightHandler();
        EndHandler end = new EndHandler();
        //定義處理消息的順序
        disruptor.handleEventsWith(start).then(left, right).then(end);

        // 啓動
        disruptor.start();
//        //生產者開始生產數據
        Producer producer = new Producer(disruptor.getRingBuffer());
        produceerExecutor.submit(producer);


        //關閉打開的資源
/*        disruptor.shutdown();
        consumerExecutor.shutdown();
        produceerExecutor.shutdown();*/
    }
}

可以看到,disruptor通過提供了then方法來實現消息的先後順序語義。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章