Disruptor學習筆記


前言

以前一直聽說有Disruptor這個東西,都說性能很強大,所以這幾天自己也看了一下。
下面是自己的學習筆記,另外推薦幾篇自己看到寫的比較好的博客:
Disruptor——一種可替代有界隊列完成並發線程間數據交換的高性能解決方案
Disruptor3.0的實現細節

DIsruptor的底層性能如此牛掰

  1. 數據結構層面:使用環形結構、數組、內存預加載
  2. 單線程寫方式、內存屏障
  3. 消除偽共享(填充緩存行)
  4. 序號柵欄(SequenceBarrier)配合使用來消除鎖和CAS

高性能之道-數據結構-內存加載機制

  1. RingBuffer使用數組Object[] entries作為存儲元素,如下圖所示

高性能之道-內核-使用單線程寫

  1. Disruptor的RingBuffer,之所以可以做到完全無鎖,也是因為 ”單線程寫“, 這是所有”前提的前提“。離開了這個前提條件,沒有任何技術可以做到完全無鎖
  2. Redis、Netty等等高性能技術框架的設計都是這個核心思想

高性能之道-系統內存優化-內存屏障

  1. 要正確的實現無鎖,還需要另一個關鍵技術:內存屏障。
  2. 對應到Java語言,就是valotile變量與happens before語義。
  3. 內存屏障-Linux的smp_wmb()/smp_rmb()

高性能之道-系統緩存優化-消除偽共享

  1. 緩存系統中是以緩存行(cache line)為單位存儲的
  2. 緩存行是2的整數冪個連續字節,一般為32-256個字節
  3. 最常見的緩存行大小是64個字節
  4. 當多線程修改互相獨立的變量時,如果這些變量共享同一個緩存行
  5. 就會無意中影響彼此的性能,這就是偽共享 -- 對應源碼中的:Sequence:

Disruptor核心-Sequence

  1. Sequence可以看成是一個AtomicLong,用於標識進度
  2. 還有另外一個目的就是防止不同Sequence之間CPU緩存偽共享(False Sharing)的問題-- 對應源碼中的:Sequence:

高性能之道-算法優化-序號柵欄機制

  1. 我們在生產者進行投遞Event的時候,總會使用:long sequence = ringBuffer.next();
  2. Disruptor 3.0中,序號柵欄SequenceBarrier和序號Sequence搭配使用,協同和管理消費者與生產者的工作節奏,避免了鎖和CAS的使用
  3. 在Disruptor3.0中,各個消費者和生產者持有自己的序號,這些序號的變化必須滿足如下基本條件:-- 參見源碼:SingleProducerSequencer
    a. 消費者的序號數值必須小於生產者序號數值;b. 消費者序號數值必須小於其前置(依賴關系)消費者的序號數值; c. 生產者序號數值不能大於消費者最小的序號數值以避免生產者速度過快,將還未來得及消費的消息覆蓋

WatiStrategy等待策略

  1. Disruptor之所以可以說是高性能,其實也有一部分原因取決於它的等待策略的實現:WaitStrategy接口:
    -- 查看源碼BlockingWaitStrategy
    -- 查看源碼YieldingWaitStrategy

Disruptor核心-EventProcessor

  1. EventProcessor:主要時間循環,處理Disruptor中的Event,擁有消費者的Sequence
  2. 它有一個實現類是BatchEventProcessor,包含了event loop有效的實現,並且將回調到一個EventHandler接口的思想對象 -- 參見BatchEventProcessor

源碼解讀

Disruptor:Disruptor的入口,主要封裝了環形隊列RingBuffer、消費者集合ConsumerRepository的引用;主要提供了獲取環形隊列、添加消費者、生產者向RingBuffer中添加事件(可以理解為生產者生產數據)的操作;
RingBuffer:Disruptor中隊列具體的實現,底層封裝了Object[]數組;在初始化時,會使用Event事件對數組進行填充,填充的大小就是bufferSize設置的值;此外,該對象內部還維護了Sequencer(序列生產器)具體的實現;
Sequencer:序列生產器,分別有MultiProducerSequencer(多生產者序列生產器) 和 SingleProducerSequencer(單生產者序列生產器)兩個實現類。上面的例子中,使用的是SingleProducerSequencer;在Sequencer中,維護了消費者的Sequence(序列對象)和生產者自己的Sequence(序列對象);以及維護了生產者與消費者序列沖突時候的等待策略WaitStrategy;
Sequence:序列對象,內部維護了一個long型的value,這個序列指向了RingBuffer中Object[]數組具體的角標。生產者和消費者各自維護自己的Sequence;但都是指向RingBuffer的Object[]數組;
Wait Strategy:等待策略。當沒有可消費的事件時,消費者根據特定的策略進行等待;當沒有可生產的地方時,生產者根據特定的策略進行等待;
Event:事件對象,就是我們Ringbuffer中存在的數據,在Disruptor中用Event來定義數據,並不存在Event類,它只是一個定義;
EventProcessor:事件處理器,單獨在一個線程內執行,判斷消費者的序列和生產者序列關系,決定是否調用我們自定義的事件處理器,也就是是否可以進行消費;
EventHandler:事件處理器,由用戶自定義實現,也就是最終的事件消費者,需要實現EventHandler接口;

RingBuffer:


Sequence:

這個里面緩存行的填充很經典,設計成前7后7 Long類型來填充,保證消除偽共享。
使用空間換時間,避免偽共享。Java8中使用@sun.misc.Contended 來消除偽共享,在運行時需要設置JVM啟動參數:-XX:-RestrictContended

這里前7后7加上本身的Value值,總共是有15個Long元素,無論如何拆分,Value和預填充的Long型數據一定會處於單獨的一個緩存行。

SingleProducerSequencer

這里就是用簡單的if else判斷,就避免了加鎖,CAS的消耗,這里是使用序號柵欄,通過巧妙的算法+自旋操作來實現等待的操作。
解析如下圖:

其中可以自己寫代碼去debug,創建ringBuffer長度為4,消費者阻塞在第0個元素的消費中。然后生產者再生產第5個元素的時候就會進行自旋等待。

BlockingWaitStrategy

BatchEventProcessor

waitFor 可以參考上面的BlockingWaitStrategy 的waitFor() 方法




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM