Java多線程(12) - Disruptor多線程併發框架入門詳解

爲什麼用Disruptor?

       傳統阻塞的隊列使用鎖保證線程安全,而鎖通過操作系統內核上下文切換實現,會暫停線程去等待鎖,直到鎖釋放。

執行這樣的上下文切換,會丟失之前保存的數據和指令。由於消費者和生產者之間的速度差異,隊列總是接近滿或者空的狀態,這種狀態會導致高水平的寫入爭用。

       畢竟鎖的技術會導致性能變差,而且還有可能會造成死鎖。

 

什麼是Disruptor?

       Disruptor的流行起源於Martin Fowloer在自己的網站上寫了一篇LMAX架構的文章,文章中介紹了LMAX是一種新型零售金融交易平臺,能夠以很低的延遲產生大量的交易,這個系統是建立在JVM平臺之上的,其核心是一個業務邏輯處理器,一個線程裏每秒處理六百萬訂單。業務邏輯處理器完全是允許在內存中,使用事件源驅動方式,而這個處理器的核心就是Disruptor。

       Disruptor是一個多線程併發框架,能夠在無鎖的情況下實現網絡的Queue併發操作,是一個高性能的異步處理框架,也可以認爲是一個觀察者或事件監聽模式的實現。

       Disruptor根本不用鎖,取而代之的是,在需要確保操作是線程安全的(特別是,在多生產者場景下,更新下一個可用的序列號)地方,使用CAS(Compare And Swap/Set)操作。這是一個CPU級別的指令,工作方式有點像樂觀鎖,CPU去更新一個值,但如果想改的值不再是原來的值,操作就失敗,因爲很明顯,有其他操作先改變了這個值。

       CAS操作比鎖消耗的資源少很多,主要不涉及操作系統,直接在CPU上操作。但並非是沒有才加的,只是比使用鎖耗時少,比不需要考慮競爭的單線程耗時多。

       在Disruptor中,多線程那個使用了AtomicLong(Java提供的CAS操作),而單線程使用long,沒有鎖也沒有CAS,意味着單線程版本非常快,不會產生序號上的衝突。在整個框架中,只有一個地方出現多線程競爭修改同一個變量值,如果只有一個生產者,那麼系統中國的每一個序號由一個線程寫入,這意味着沒有競爭,也不需要鎖、甚至不需要CAS。如果存在多個生產者,唯一會被多線程寫入的序號就是ClaimStrate對象裏的那個。

 

核心組件

       RingBuffer被看做Disruptor最主要的組件,在3.X後,RingBuffer僅僅負責存儲和更新在Disruptor中流通的數據,對一些特殊使用場景能夠被其他數據結構完全替代。

       SequenceDisruptor使用Sequence來表示一個特殊組織間處理的序號,每個消費者(EventProcessor)都維持着一個Sequence。大部分的併發代碼都依賴這些Sequence值運算,因此Sequence支持多種當前爲AtomicLong類的特性。

       Sequencer這是Disruptor真正的核心,實現了這個接口的兩種生產者(單生產者和多生產者)均實現了所有的併發算法,爲了在生產者和消費者之間進行準確的數據傳遞。

       SequenceBarrirer由Sequence生產,包含已經發布的Sequence的引用,這些Sequence源於Sequencer和一些獨立的消費者的Sequence,包含了決定是否有供消費者來消費的Event的邏輯。

       WaitStrategy決定一個消費者將如何等待生產者將Event置入Disruptor中。

     Event從生產者到消費者的過程中所處理的數據單元。Disruptor中沒有代表標識Event,完全由用戶定義的。

     EventProcessor主要事件循環,處理Disruptor中的Event,並且擁有消費者的Sequence。有一個實現類就是BatchEventProcessor,包含了EventLoop有效的實現,並且將回調到一個EventHandler接口的實現對象。

       EventHandler用戶實現,代表了Disruptor中的一個消費者的接口。

       Producer用戶實現,調用RingBuffer來插入Event(事件),在Disruptor中沒有相應的實現代碼。

       WorkProcessor確保每個Sequence只被一個Processor消費,在同一個WorkPool中的處理多個WorkProcessor不會消費同樣的Sequence。

       WorkerPool一個WorkProcessor池,其中的WorkerProcess將消費Sequence,所以任務可以在實現WorkHandler接口的Worker之間移交。

       LifecycleAware但BatchEventProcessor啓動和停止時,與實現這個接口用於接收通知。

import java.nio.ByteBuffer;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

 

import com.lmax.disruptor.*;

import com.lmax.disruptor.dsl.*;

 

public class D01HelloWorld {

    public static void main(String[] args) {

         //線程池

         ExecutorService pool = Executors.newCachedThreadPool();

         //工廠,生成數據

         EventFactory<Data> factory = new DataFactory();

         //buffSize,也就是RingBuffer2N次方是最好的

         int ringBufferSize = 1024 * 1024;

        

         /*

          * BlockingWaitStrategy:最低效的策略,但對CPU的消耗最小,並且在各種不通過部署環境中能提供更加一致的性能。

          * SleepingWaitStreategy:性能表現跟BlockingWaitStrategy差不多,對CPU的消耗也類似,但其對生產者線程影響最小,適合異步日誌類似的場景。

          * YieldingWaitStrategy:性能最好,適合用於低延遲的系統,在要求極高性能而且事件處理線程數小於CPU邏輯核心數的場景中,例如CPU開啓超特性。

          */

        

         //創建Disruptor

         Disruptor<Data> disruptor = new Disruptor<>(factory, ringBufferSize, pool,

                                                         ProducerType.SINGLE,new YieldingWaitStrategy());

        

         //處理事件的消費者

         disruptor.handleEventsWith(new DataConsumer());

        

         //啓動

         disruptor.start();

        

         //發佈事件

         RingBuffer<Data> ringBuffer = disruptor.getRingBuffer();

        

         //生產者

         DataProducer dataProducer = new DataProducer(ringBuffer);

//       DataProducer2 dataProducer = new DataProducer2(ringBuffer);

        

         //預先分配空間

         ByteBuffer byteBuffer = ByteBuffer.allocate(10);

         for (byte i = 0; i < 100; i++) {

             byteBuffer.put(0,i);

             //生產者發佈數據

             dataProducer.publish(byteBuffer);

         }

        

         disruptor.shutdown(); //所有事件處理完畢才關閉

         pool.shutdown();

        

         //1、啓動項目

         //2、註釋DataProducer dataProducer,打開新的DataProducer2 dataProducer,查看簡化的發佈

    }

}

/**

 * 傳輸的數據

 */

class Data {

    private String value;

 

    public String getValue() {

         return value;

    }

 

    public void setValue(String value) {

         this.value = value;

    }

}

/**

 * 生產者

 */

class DataProducer {

    //環(可以當成隊列),存儲數據的地方

    private RingBuffer<Data> ringBuffer;

 

    public DataProducer(RingBuffer<Data> ringBuffer) {

         super();

         this.ringBuffer = ringBuffer;

    }

   

    /**

     * 調用一次就發佈一次數據,會用事件的形式傳遞給消費者。

     * 用一個簡單隊列來發布事件(數據)的時候會涉及很多細節,這是因爲事件(數據)對象需要預先創建好。

     * 發佈事件(數據)至少要兩個操作:

     * 1、獲取下一個事件(數據)槽併發布事件。

     * 如果使用RingBuffer.next()獲取事件(數據)槽,那麼一定要發佈對應的事件。

     * 如果不能發佈事件(數據),那麼就會引起Disruptor狀態的混亂。

     * 尤其在多個事件生產者的情況下會閥值事件消費者失速,從而不得不重啓應用才能恢復。

     */

    public void publish(ByteBuffer byteBuffer) {

         //可以把RingBuffer看成一個隊列。

         long nextIndex = ringBuffer.next();

        

         try {

             //根據索引獲取都數據

             Data data = ringBuffer.get(nextIndex);

             data.setValue("生產者生產的數據:" + byteBuffer.get(0));

         } finally {

             //發佈事件(數據)給消費者去消費

             //最好是包含在finally中,確保必須得到調用,如果某個請求的nextIndex未被提交,將會堵塞,後續的發佈操作或者其他的producer

             ringBuffer.publish(nextIndex);

         }

    }

}

/**

 * 消費者:也就是事件處理器

 */

class DataConsumer implements EventHandler<Data> {

    @Override

    public void onEvent(Data data, long l, boolean arg2) throws Exception {

         System.out.println("消費到:" + data.getValue() + " - " + l);

    }

}

/**

 * Disruptor創建事件,同時還聲明瞭一個Factory來實例化事件(數據)對象填充到RingBuffer

 */

class DataFactory implements EventFactory<Data> {

    @Override

    public Data newInstance() {

         return new Data();

    }

}

/**

 * 簡化發佈:3.x中提供Lambda表達式API,可以把一些複雜的操作放在RingBuffer

 * 所以3.x以後的版本最好用Event Publisher或者Event Translator來發布

 */

class DataProducer2 {

    private static final EventTranslatorOneArg<Data, ByteBuffer> TRANSLATOR =

                          new EventTranslatorOneArg<Data, ByteBuffer>() {

 

         @Override

         public void translateTo(Data data, long index, ByteBuffer byteBuffer) {

             data.setValue(byteBuffer.get(0) + "");

         }

    };

   

    private RingBuffer<Data> ringBuffer;

 

    public DataProducer2(RingBuffer<Data> ringBuffer) {

         super();

         this.ringBuffer = ringBuffer;

    }

   

    /**

     * 發佈數據

     */

    public void publish(ByteBuffer byteBuffer) {

         ringBuffer.publishEvent(TRANSLATOR,byteBuffer);

    }

}

 

RingBuffer

       RingBuffer就是一個環,每個槽都有一個序號,序號指向數組中下一個可用的元素,隨着不斷的填充這個Buffer,這個序號會一直增長,直到繞過這個環。

       要找到數組中的當前序號指向的元素,可通過取模操作,所以這個槽的個數是2的N次方更有利於基於二進制的計算。

       環是沒有尾指針的,維護了一個指向下一個可用位置的序號,最初的原因就是想要提供可靠的消息傳遞。

       環和隊列的區別:不刪除環中的數據,也就是說數據一直存在環中,知道新的數據覆蓋,這也是不需要尾指針的原因。因爲是數組,所以比鏈表快,而且有一個容易預測的訪問模式,這對CPU緩存友好,在硬件級別上,數組中的元素是會被預加載的,因此在RingBuffer中,CPU不需要時不時去主內存在加載數組中的下一個元素。環也可以爲數組預先分配內存,使得數組對象一直存在,這意味着不需要大量的數據用於垃圾回收,不像鏈表那樣,需要尾每一個添加到其上面的對象創造節點對象對應的。當刪除節點時,需要執行相應的內存清理操作。

       爲什麼這麼快:一是Disruptor通過將基本對象填充榮譽基本類型變量來充滿整個緩存行,就是一個緩存行8個變量,預設7個變量,然後保存一個唯一變量,這樣就不會出現相同的變量。二是無鎖隊列的實現,對於傳統併發隊列,至少要維護兩個指針,頭指針和尾指針。在併發訪問修改時,頭指針和尾指針的維護不可避免的應用了鎖,Disruptor由於是環狀隊列,對於Produce而言只有頭指針而且鎖是樂觀鎖,在標準的Disruptor應用中,只有一個生產者,避免了頭指針鎖的爭用,這也是爲什麼理解Disruptor爲無鎖隊列。

/**

 * 直接使用RingBuffer發佈,簡化操作

 */

public class D02RingBuffer {

   

    public static void main(String[] args) throws InterruptedException, ExecutionException {

   

         D02RingBuffer d02RingBuffer = new D02RingBuffer();

         d02RingBuffer.main1();

//       d02RingBuffer.main2();

        

    }

   

    /*

     * 創建單個生產者的RingBuffer

     * 參數1:產生數據,填充到RingBuffer

     * 參數2:必須是2的指數倍,目的是爲了將取模運算轉爲&運算提高效率

     * 參數3:等待策略

     */

    RingBuffer<RData> ringBuffer = RingBuffer.createSingleProducer(new EventFactory<RData>() {

         @Override

         public RData newInstance() {

             return new RData();

         }

   

    }, 1024);

   

    //線程池

    ExecutorService pool = Executors.newFixedThreadPool(5);

   

    //創建SequenceBarrier,決定是否有供消費者來消費的Event的邏輯

    SequenceBarrier sequenceBarrier = ringBuffer.newBarrier();

   

    /**

     * 寫法1

     */

    public void main1() throws InterruptedException, ExecutionException {

        

         //創建消息處理器,指定消費者,單個消費者

         BatchEventProcessor<RData> batchEventProcessor = new BatchEventProcessor<RData>(ringBuffer, sequenceBarrier, new RConsumer());

        

         //把消費者的位置信息引入注入到生產者,讓生產者可以根據消費者的情況決定生產的速度,避免一個快、一個慢,如果只有一個消費者的情況可以省略

         ringBuffer.addGatingSequences(batchEventProcessor.getSequence());

        

         //把消息處理器給線程池

         pool.submit(batchEventProcessor);

        

         //如果有多個消費者則重複執行BatchEventProcessorringBuffer.addGatingSequencespool.submit這三行代碼

        

         Future<Void> future = pool.submit(new Callable<Void>() {

             @Override

             public Void call() throws Exception {

                  //發佈數據給消費者消費

                  publish();

                  return null;

             }

         });

        

         future.get();//等待生產者結束

         Thread.sleep(1000); //等待處理結束

         batchEventProcessor.halt();//通知事件(或者說消息)處理器,可用結束了

         pool.shutdown();//關閉線程池

    }

   

    /**

     * 寫法2

     * @throws InterruptedException

     */

    public void main2() throws InterruptedException {

         WorkHandler<RData> workHandler = new RConsumer();

        

         //配置都是類似的

         WorkerPool<RData> workerPool = new WorkerPool<RData>(ringBuffer, sequenceBarrier, new IgnoreExceptionHandler(),workHandler);

         workerPool.start(pool);

        

         //發佈數據給消費者消費

         publish();

        

         Thread.sleep(1000); //等待處理結束

         workerPool.halt();//通知事件(或者說消息)處理器,可用結束了

         pool.shutdown();//關閉線程池

    }

   

    /**

     * 發佈

     */

    public void publish() {

         for (int i = 0; i < 10; i++) {

             long nextSeq = ringBuffer.next(); //佔坑,ringBuffer一個可用區塊

             RData rData = ringBuffer.get(nextSeq);//給這個位置放入數據

             rData.setAge(i);

             rData.setName("Test " + i);

             ringBuffer.publish(nextSeq);

         }

    }

}

class RData {

    private String name;

   

    private int age;

 

    public String getName() {

         return name;

    }

 

    public void setName(String name) {

         this.name = name;

    }

 

    public int getAge() {

         return age;

    }

 

    public void setAge(int age) {

         this.age = age;

    }

}

/**

 * 消費者,實現那個都是可以的

 * @author suzhiwei

 */

class RConsumer implements EventHandler<RData>,WorkHandler<RData> {

 

    @Override

    public void onEvent(RData data, long arg1, boolean arg2) throws Exception {

         onEvent(data);

    }

   

    @Override

    public void onEvent(RData data) throws Exception {

//       data.setAge(new Random().nextInt(),);

         System.out.println(data.getName() + " - " + data.getAge());

    }

}

 

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