disruptor 介紹

文章原版:https://www.yuque.com/simonalong/jishu/qhdcb2

一、背景


1.來源

Disruptor是英國外匯交易公司LMAX開發(fā)的一個高性能隊列,研發(fā)的初衷是解決內(nèi)部的內(nèi)存隊列的延遲問題,而不是分布式隊列?;贒isruptor開發(fā)的系統(tǒng)單線程能支撐每秒600萬訂單,2010年在QCon演講后,獲得了業(yè)界關(guān)注。

2.應(yīng)用背景和介紹

據(jù)目前資料顯示:應(yīng)用Disruptor的知名項目有如下的一些:Storm, Camel, Log4j2,還有目前的美團點評技術(shù)團隊也有很多不少的應(yīng)用,或者說有一些借鑒了它的設(shè)計機制。
Disruptor是一個高性能的線程間異步通信的框架,即在同一個JVM進程中的多線程間消息傳遞。

二、傳統(tǒng)隊列問題


首先這里說的隊列也僅限于Java內(nèi)部的消息隊列

隊列 有界性 結(jié)構(gòu) 隊列類型
ArrayBlockingQueue 有界 加鎖 數(shù)組 阻塞
LinkedBlockingQueue 可選 加鎖 鏈表 阻塞
ConcurrentLinkedQueue 無界 無鎖 鏈表 非阻塞
LinkedTransferQueue 無界 無鎖 鏈表 阻塞
PriorityBlockingQueue 無界 加鎖 阻塞
DelayQueue 無界 加鎖 阻塞

隊列的底層數(shù)據(jù)結(jié)構(gòu)一般分成三種:數(shù)組、鏈表和堆。其中,堆這里是為了實現(xiàn)帶有優(yōu)先級特性的隊列,暫且不考慮。在穩(wěn)定性和性能要求特別高的系統(tǒng)中,為了防止生產(chǎn)者速度過快,導(dǎo)致內(nèi)存溢出,只能選擇有界隊列;同時,為了減少Java的垃圾回收對系統(tǒng)性能的影響,會盡量選擇array/heap格式的數(shù)據(jù)結(jié)構(gòu)。這樣篩選下來,符合條件的隊列就只有ArrayBlockingQueue。但是ArrayBlockingQueue是通過加鎖的方式保證線程安全,而且ArrayBlockingQueue還存在偽共享問題,這兩個問題嚴重影響了性能。
其中對于影響性能的兩種方式:加鎖和偽共享我們這里首先介紹下。加鎖方式和不加鎖的CAS方式這里不再進行介紹,我們這里首先對其中的偽共享問題講解下。

1.偽共享概念

  • 共享

計算機早就支持多核,軟件也越來越多的支持多核運行,其實也可以叫做多處理運行。一個處理器對應(yīng)一個物理插槽。其中一個插槽對應(yīng)一個L3 Cache,一個槽包含多個cpu。一個cpu包含寄存器、L1 Cache、L2 Cache,如下圖所示:


1.png

其中越靠近cpu則,速度越快,容量則越小。其中L1和L2是只能給一個cpu進行共享,但是L3是可以給同一個槽內(nèi)的cpu共享,而主內(nèi)存,是可以給所有的cpu共享,這就是內(nèi)存的共享。
其中cpu執(zhí)行運算的流程是這樣:首先回去L1里面查找對應(yīng)數(shù)據(jù),如果沒有則去L2、L3,如果都沒有,則就會去主內(nèi)存中去拿,走的路越長,則耗費時間越久,性能就會越低。
需要注意的是,當(dāng)線程之間進行共享數(shù)據(jù)的,需要將數(shù)據(jù)寫回到主內(nèi)存中,而另一個線程通過訪問主內(nèi)存獲得新的數(shù)據(jù)。
有人就會問了,多個線程之間不是會有一些非主內(nèi)存的緩存進行共享么,那么另外一個線程會不會直接訪問到修改之前的內(nèi)存呢。答案是會的,但是有一點,就是這種數(shù)據(jù)我們可以通過設(shè)置緩存失效測試來進行保證緩存的最新,這個方式其實在cpu這里進行設(shè)置的,叫內(nèi)存屏障(其實就是在cpu這里設(shè)置一條指令,這個指令就是禁止cpu重排序,這個屏障之前的不能出現(xiàn)在屏障之后,屏障之后的處理不能出現(xiàn)屏障之前,也就是屏障之后獲取到的數(shù)據(jù)是最新的),對應(yīng)到應(yīng)用層面就是一個關(guān)鍵字volatile,下面會有一些進行介紹。

  • 緩存行

剛剛說的緩存失效其實指的是Cache line的失效,也就是緩存行,Cache是由很多個Cache line 組成的,每個緩存行大小是32~128字節(jié)(通常是64字節(jié))。我們這里假設(shè)緩存行是64字節(jié),而java的一個Long類型是8字節(jié),這樣的話一個緩存行就可以存8個Long類型的變量,如下圖所示:

2.png
 一個緩存對應(yīng)的緩存行的結(jié)構(gòu)圖

cpu 每次從主內(nèi)存中獲取數(shù)據(jù)的時候都會將相鄰的數(shù)據(jù)存入到同一個緩存行中。假設(shè)我們訪問一個Long內(nèi)存對應(yīng)的數(shù)組的時候,如果其中一個被加載到內(nèi)存中,那么對應(yīng)的后面的7個數(shù)據(jù)也會被加載到對應(yīng)的緩存行中,這樣就會非??斓脑L問數(shù)據(jù)。

  • 偽共享

剛我們說了緩存的失效其實就是緩存行的失效,緩存行失效的原理是什么,這里又涉及到一個MESI協(xié)議(緩存一致性協(xié)議),我們這里不介紹這個,感興趣的可以看下附錄部分,首先我們用Disruptor中很經(jīng)典的講解偽共享的圖來講解下:

3.png

上圖中顯示的是一個槽的情況,里面是多個cpu, 如果cpu1上面的線程更新了變量X,根據(jù)MESI協(xié)議,那么變量X對應(yīng)的所有緩存行都會失效,這個時候如果cpu2中的線程進行讀取變量Y,發(fā)現(xiàn)緩存行失效,就會按照緩存查找策略,往上查找,如果cpu1對應(yīng)的線程更新變量X后又訪問了變量X,那么左側(cè)的L1、L2和槽內(nèi)的L3 緩存行都會得到生效。這個時候cpu2線程可以在L3 Cache 中得到生效的數(shù)據(jù),否則的話(即cpu1對應(yīng)的線程更新X后沒有訪問X)cpu2的線程就只能從主內(nèi)存中獲取數(shù)據(jù),對性能就會造成很大的影響,這就是偽共享。
表面上 X 和 Y 都是被獨立線程操作的,而且兩操作之間也沒有任何關(guān)系。只不過它們共享了一個緩存行,但所有競爭沖突都是來源于共享。

2.ArrayBlockingQueue 的偽共享問題

剛我們已經(jīng)講了偽共享的問題,那么ArrayBlockingQueue的這個偽共享問題存在于哪里呢,分析下核心的部分源碼

    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        //獲取當(dāng)前對象鎖
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                //阻塞并釋放鎖,等待notFull.signal()通知
                notFull.await();
            //將數(shù)據(jù)放入數(shù)組
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }
    private void enqueue(E x) {
        final Object[] items = this.items;
        //putIndex 就是入隊的下標
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();
    }
public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        //加鎖
        lock.lockInterruptibly();
        try {
            while (count == 0)
                //阻塞并釋放對象鎖,并等待notEmpty.signal()通知
                notEmpty.await();
            //在數(shù)據(jù)不為空的情況下
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
private E dequeue() {
    final Object[] items = this.items;
    //takeIndex 是出隊的下標
    E x = (E) items[takeIndex];
    items[takeIndex] = null;
    if (++takeIndex == items.length)
        takeIndex = 0;
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    notFull.signal();
    return x;
}

其中最核心的三個成員變量為
putIndex:入隊下標
takeIndex:出隊下標
count:隊列中元素的數(shù)量
而三個成員的位置如下:

8.png

這三個變量很容易放到同一個緩存行中,為此專門用一個偽共享檢測工具進行檢測,目前檢測偽共享的工具只有Intel的Intel Vtune 目前剛發(fā)現(xiàn)有mac os 版本,但是經(jīng)過測試發(fā)現(xiàn),該工具無法分析macOs 的處理器配置,用的時候發(fā)現(xiàn)如下錯誤“無法檢測到支持的處理器配置”,這個可以遺留給其他同學(xué),工具的安裝和使用方式,可以查看附錄中的另外的一個連接。

三、高性能原理


剛說了上面隊列的兩個性能問題:一個是加鎖,一個是偽共享,那么disruptor是怎么解決這兩個問題的,以及除了解決這兩個問題之外,還引入了其他什么先進的東西提升性能的。
這里簡單列舉下:

  • 引入環(huán)形的數(shù)組結(jié)構(gòu):數(shù)組元素不會被回收,避免頻繁的GC,
  • 無鎖的設(shè)計:采用CAS無鎖方式,保證線程的安全性
  • 屬性填充:通過添加額外的無用信息,避免偽共享問題
  • 元素位置的定位:采用跟一致性哈希一樣的方式,一個索引,進行自增

1.環(huán)形數(shù)組結(jié)構(gòu)

環(huán)形數(shù)組結(jié)構(gòu)是整個Disruptor的核心所在。
首先因為是數(shù)組,所以要比鏈表快,而且根據(jù)我們對上面緩存行的解釋知道,數(shù)組中的一個元素加載,相鄰的數(shù)組元素也是會被預(yù)加載的,因此在這樣的結(jié)構(gòu)中,cpu無需時不時去主存加載數(shù)組中的下一個元素。而且,你可以為數(shù)組預(yù)先分配內(nèi)存,使得數(shù)組對象一直存在(除非程序終止)。這就意味著不需要花大量的時間用于垃圾回收。此外,不像鏈表那樣,需要為每一個添加到其上面的對象創(chuàng)造節(jié)點對象—對應(yīng)的,當(dāng)刪除節(jié)點時,需要執(zhí)行相應(yīng)的內(nèi)存清理操作。環(huán)形數(shù)組中的元素采用覆蓋方式,避免了jvm的GC。
其次結(jié)構(gòu)作為環(huán)形,數(shù)組的大小為2的n次方,這樣元素定位可以通過位運算效率會更高,這個跟一致性哈希中的環(huán)形策略有點像。在disruptor中,這個牛逼的環(huán)形結(jié)構(gòu)就是RingBuffer,既然是數(shù)組,那么就有大小,而且,結(jié)構(gòu)如下:

4.png

其實質(zhì)只是一個普通的數(shù)組,只是當(dāng)放置數(shù)據(jù)填充滿隊列(即到達2^n-1位置)之后,再填充數(shù)據(jù),就會從0開始,覆蓋之前的數(shù)據(jù),于是就相當(dāng)于一個環(huán)。

2.生產(chǎn)和消費模式

根據(jù)上面的環(huán)形結(jié)構(gòu),我們來具體分析一下Disruptor的工作原理。
Disruptor 不像傳統(tǒng)的隊列,分為一個隊頭指針和一個隊尾指針,而是只有一個角標(上面的seq),那么這個是如何保證生產(chǎn)的消息不會覆蓋沒有消費掉的消息呢。
在Disruptor中生產(chǎn)者分為單生產(chǎn)者和多生產(chǎn)者,而消費者并沒有區(qū)分。單生產(chǎn)者情況下,就是普通的生產(chǎn)者向RingBuffer中放置數(shù)據(jù),消費者獲取最大可消費的位置,并進行消費。而多生產(chǎn)者時候,又多出了一個跟RingBuffer同樣大小的Buffer,稱為AvailableBuffer。在多生產(chǎn)者中,每個生產(chǎn)者首先通過CAS競爭獲取可以寫的空間,然后再進行慢慢往里放數(shù)據(jù),如果正好這個時候消費者要消費數(shù)據(jù),那么每個消費者都需要獲取最大可消費的下標,這個下標是在AvailableBuffer進行獲取得到的最長連續(xù)的序列下標。

  • 多生產(chǎn)者——生產(chǎn)

假設(shè)現(xiàn)在又兩個生產(chǎn)者,開始寫數(shù)據(jù),通過CAS競爭,w1得到的34的空間,w2得到了78的空間,其中6是代表已被寫入或者沒有被消費的數(shù)據(jù)。

5.png
  • 多生產(chǎn)者——消費

    綠色代表已經(jīng)寫OK的數(shù)據(jù)
    假設(shè)三個生產(chǎn)者在寫中,還沒有置位AvailableBuffer,那么消費者可獲取的消費下標只能獲取到6,然后等生產(chǎn)者都寫OK后,通知到消費者,消費者繼續(xù)重復(fù)上面的步驟。如下圖
6.png
  • 消費者常見的等待

    BusySpinWaitStrategy : 自旋等待,類似Linux Kernel使用的自旋鎖。低延遲但同時對CPU資源的占用也多。
    __BlockingWaitStrategy __: 使用鎖和條件變量。CPU資源的占用少,延遲大,默認等待策略。
    __SleepingWaitStrategy __: 在多次循環(huán)嘗試不成功后,選擇讓出CPU,等待下次調(diào)度,多次調(diào)度后仍不成功,嘗試前睡眠一個納秒級別的時間再嘗試。這種策略平衡了延遲和CPU資源占用,但延遲不均勻。
    __YieldingWaitStrategy __: 在多次循環(huán)嘗試不成功后,選擇讓出CPU,等待下次調(diào)。平衡了延遲和CPU資源占用,但延遲也比較均勻。
    PhasedBackoffWaitStrategy : 上面多種策略的綜合,CPU資源的占用少,延遲大

3.牛逼的下標指針

RingBuffer的指針(Sequence)屬于一個volatile變量,同時也是我們能夠不用鎖操作就能實現(xiàn)Disruptor的原因之一,而且通過緩存行補充,避免偽共享問題。 該所謂指針是通過一直自增的方式來獲取下一個可寫或者可讀數(shù)據(jù),該數(shù)據(jù)是Long類型,不用擔(dān)心會爆掉。有人計算過:long的范圍最大可以達到9223372036854775807,一年365 * 24 * 60 * 60 = 31536000秒,每秒產(chǎn)生1W條數(shù)據(jù),也可以使用292年。

class LhsPadding{
    //緩存行補齊, 提升cache緩存命中率
    protected long p1, p2, p3, p4, p5, p6, p7;
}

class Value extends LhsPadding{
    protected volatile long value;
}

class RhsPadding extends Value{
    //緩存行補齊, 提升cache緩存命中率
    protected long p9, p10, p11, p12, p13, p14, p15;
}

public class Sequence extends RhsPadding{
    ...
}

四、用法


用法很簡單,一共三個角色:生產(chǎn)者,消費者,disruptor對象

1.簡單用法

  • disruptor對象

disruptor 就兩個構(gòu)造方法

public Disruptor(
        final EventFactory<T> eventFactory,    // 數(shù)據(jù)實體構(gòu)造工廠
        final int ringBufferSize,              // 隊列大小,必須是2的次方
        final ThreadFactory threadFactory,     // 線程工廠
        final ProducerType producerType,       // 生產(chǎn)者類型,單個生產(chǎn)者還是多個
        final WaitStrategy waitStrategy){      // 消費者等待策略
    ...
}

public Disruptor(
    final EventFactory<T> eventFactory,     
    final int ringBufferSize, 
    final ThreadFactory threadFactory){
    ...
}
  • 生產(chǎn)處理

生產(chǎn)者這里沒有固定的對象,只是需要獲取放置數(shù)據(jù)的位置,然后進行publish

public void send(String data){
    RingBuffer<MsgEvent> ringBuffer = this.disruptor.getRingBuffer();
    //獲取下一個放置數(shù)據(jù)的位置
    long next = ringBuffer.next();
    try{
        MsgEvent event = ringBuffer.get(next);
        event.setValue(data);
    }finally {
        //發(fā)布事件
        ringBuffer.publish(next);
    }
}
  • 消費處理

    消費處理可以有如下幾種
public EventHandlerGroup<T> handleEventsWith(final EventHandler<? super T>... handlers){
    ...
}
public EventHandlerGroup<T> handleEventsWith(final EventProcessor... processors){
    ...
}
public EventHandlerGroup<T> handleEventsWith(final EventProcessorFactory<T>... eventProcessorFactories){
    ...
}
public EventHandlerGroup<T> handleEventsWithWorkerPool(final WorkHandler<T>... workHandlers){
    ...
}

簡單用例

//消費者
public class MsgConsumer implements EventHandler<MsgEvent>{
    private String name;
    public MsgConsumer(String name){
        this.name = name;
    }
    @Override
    public void onEvent(MsgEvent msgEvent, long l, boolean b) throws Exception {
        System.out.println(this.name+" -> 接收到信息: "+msgEvent.getValue());
    }
}

//生產(chǎn)者處理
public class MsgProducer {
    private Disruptor disruptor;
    public MsgProducer(Disruptor disruptor){
        this.disruptor = disruptor;
    }

    public void send(String data){
        RingBuffer<MsgEvent> ringBuffer = this.disruptor.getRingBuffer();
        long next = ringBuffer.next();
        try{
            MsgEvent event = ringBuffer.get(next);
            event.setValue(data);
        }finally {
            ringBuffer.publish(next);
        }
    }

    public void send(List<String> dataList){
        dataList.stream().forEach(data -> this.send(data));
    }
}


//觸發(fā)測試
public class DisruptorDemo {
    @Test
    public void test(){
        Disruptor<MsgEvent> disruptor = new Disruptor<>(MsgEvent::new, 1024, Executors.defaultThreadFactory());

        //定義消費者
        MsgConsumer msg1 = new MsgConsumer("1");
        MsgConsumer msg2 = new MsgConsumer("2");
        MsgConsumer msg3 = new MsgConsumer("3");

        //綁定配置關(guān)系
        disruptor.handleEventsWith(msg1, msg2, msg3);
        disruptor.start();

        // 定義要發(fā)送的數(shù)據(jù)
        MsgProducer msgProducer = new MsgProducer(disruptor);
        msgProducer.send(Arrays.asList("nihao","hah"));
    }
}

輸出(消費沒有固定順序):

1 -> 接收到信息: nihao
3 -> 接收到信息: nihao
3 -> 接收到信息: hah
2 -> 接收到信息: nihao
2 -> 接收到信息: hah
1 -> 接收到信息: hah

2.其他用法

上面主要介紹了多消費統(tǒng)一消費,但是在生產(chǎn)者模型中是有很多種,如下,一對一,一對多,多對多,多對一

7.png

1.單生產(chǎn)者生產(chǎn)數(shù)據(jù),單消費者消費數(shù)據(jù),一般用在后臺處理的業(yè)務(wù)邏輯中。
2.單生產(chǎn)者生產(chǎn)數(shù)據(jù),多個消費者消費數(shù)據(jù)(這里面有兩種情況:同一個消息,可以被多個消費者分別消費?;蛘叨鄠€消費者組成一個組,一個消費者消費一個數(shù)據(jù))。
3.多個生產(chǎn)者生產(chǎn)數(shù)據(jù),單個消費者消費數(shù)據(jù),可以用在限流或者排隊等候單一資源處理的場景中。
4.多個生產(chǎn)者分別生產(chǎn)數(shù)據(jù),多個消費者消費數(shù)據(jù)(這里面有兩種情況:同一個消息,可以被多個消費者分別消費?;蛘叨鄠€消費者組成一個組,一個消費者消費一個數(shù)據(jù))。

  • 生產(chǎn)者配置

其中生產(chǎn)模式中的單生產(chǎn)者模式和多生產(chǎn)模式,這里主要是通過一個枚舉:ProduceType來區(qū)分,建議,多個生產(chǎn)者用多生產(chǎn)者模式,性能會好點。

  • 消費者配置

消費者模式這里分為兩種:

統(tǒng)一消費:每個消費者都消費一份生產(chǎn)者生產(chǎn)的數(shù)據(jù)
分組消費:每個生產(chǎn)這生產(chǎn)的數(shù)據(jù)只被消費一次

統(tǒng)一消費像上面簡單用法中運用即可,對于分組消費,用函數(shù) handleEventsWithWorkerPool 即可

/**
 * 分組處理 handleEventWithWorkerPool
 */
@Test
public void test1(){
    Disruptor<MsgEvent> disruptor = new Disruptor(MsgEvent::new, 1024, Executors.defaultThreadFactory());
    disruptor.handleEventsWithWorkerPool(new MyWorkHandler("work1"), new MyWorkHandler("work2"));

    disruptor.start();

    MsgProducer msgProducer = new MsgProducer(disruptor);
    msgProducer.send(Arrays.asList("aaa","bbb"));
}

輸出:

work1 : MsgEvent(value=bbb)
work2 : MsgEvent(value=aaa)
work1 : MsgEvent(value=cc)
work2 : MsgEvent(value=dd)
  • 消費順序配置

在消費配置中,這里可以有很多種消費方式,比如:

1.消費者的順序消費

/**
 * 測試順序消費
 * 每一條消息的消費者1和3消費完畢后,消費者2再進行消費
 */
@Test
public void test2(){
    MsgConsumer msg1 = new MsgConsumer("1");
    MsgConsumer msg2 = new MsgConsumer("2");
    MsgConsumer msg3 = new MsgConsumer("3");

    Disruptor<MsgEvent> disruptor = new Disruptor(MsgEvent::new, 1024, Executors.defaultThreadFactory());
    disruptor.handleEventsWith(msg1, msg3).then(msg2);
    disruptor.start();

    MsgProducer msgProducer = new MsgProducer(disruptor);
    msgProducer.send(Arrays.asList("aaa", "bbb", "ccc", "ddd"));
}

輸出(里面的是根據(jù)每一條消息的消費者順序):

1 -> 接收到信息: aaa
3 -> 接收到信息: aaa
1 -> 接收到信息: bbb
1 -> 接收到信息: ccc
2 -> 接收到信息: aaa
3 -> 接收到信息: bbb
3 -> 接收到信息: ccc
3 -> 接收到信息: ddd
1 -> 接收到信息: ddd
2 -> 接收到信息: bbb
2 -> 接收到信息: ccc
2 -> 接收到信息: ddd

2.消費分為多個支線,而且也有消費順序問題

/**
 * 測試多支線消費
 * 消費者1和消費者3一個支線,消費者2和消費者4一個支線,消費者3和消費者4消費完畢后,消費者5再進行消費
 */
@Test
public void test3(){
    MsgConsumer msg1 = new MsgConsumer("1");
    MsgConsumer msg2 = new MsgConsumer("2");
    MsgConsumer msg3 = new MsgConsumer("3");
    MsgConsumer msg4 = new MsgConsumer("4");
    MsgConsumer msg5 = new MsgConsumer("5");

    //支線:消費者1和消費者3
    disruptor.handleEventsWith(msg1, msg3);
    //支線:消費者2和消費者4
    disruptor.handleEventsWith(msg2, msg4);
    //消費者3和消費者4執(zhí)行完之后,指向消費者5
    disruptor.after(msg3, msg4).handleEventsWith(msg5);
    disruptor.start();

    MsgProducer msgProducer = new MsgProducer(disruptor);
    msgProducer.send(Arrays.asList("aaa", "bbb", "ccc", "ddd"));
}
1 -> 接收到信息: aaa
2 -> 接收到信息: aaa
2 -> 接收到信息: bbb
3 -> 接收到信息: aaa
3 -> 接收到信息: bbb
4 -> 接收到信息: aaa
4 -> 接收到信息: bbb
5 -> 接收到信息: aaa
1 -> 接收到信息: bbb
5 -> 接收到信息: bbb

五、常見問題


下面介紹下一些常見問題。

1.disruptor應(yīng)該如何用才能發(fā)揮最大功效?

disruptor原本就是事件驅(qū)動的設(shè)計,其整個架構(gòu)跟普通的多線程很不一樣。比如一種用法,將disruptor作為業(yè)務(wù)處理,中間帶I/O處理,這種玩法比多線程還慢;相反,如果將disruptor做業(yè)務(wù)處理,需要I/O時采用nio異步調(diào)用,不阻塞disruptor消費者線程,等到I/O異步調(diào)用回來后在回調(diào)方法中將后續(xù)處理重新塞到disruptor隊列中,可以看出來,這是典型的事件處理架構(gòu),確實能在時間上占據(jù)優(yōu)勢,加上ringBuffer固有的幾項性能優(yōu)化,能讓disruptor發(fā)揮最大功效。

2.如果buffer常常是滿的怎么辦?

一種是把buffer變大,另一種是從源頭解決producer和consumer速度差異太大問題,比如試著把producer分流,或者用多個disruptor,使每個disruptor的load變小。

3. 什么時候使用disruptor?

如果對延遲的需求很高,可以考慮使用。

六、參考:


官方git
https://github.com/LMAX-Exchange/disruptor
https://lmax-exchange.github.io/disruptor/
偽共享:
https://mechanical-sympathy.blogspot.com/2011/07/false-sharing.html
內(nèi)存屏障:
http://in355hz.iteye.com/blog/1797829
MESI(緩存一致性協(xié)議)
https://www.cnblogs.com/cyfonly/p/5800758.html
ArrayBlockingQueue 偽共享問題
http://www.itdecent.cn/p/71c9bc3bfe1a
jdk 本身針對偽共享做的處理
https://www.cnblogs.com/Binhua-Liu/p/5620339.html
intel vtune 分析偽共享案例
https://software.intel.com/zh-cn/vtune-amplifier-cookbook-false-sharing
RingBuffer工作原理
http://www.itdecent.cn/p/71c9bc3bfe1a
http://wiki.jikexueyuan.com/project/disruptor-getting-started/the-framework.html
http://www.itdecent.cn/p/d6375295fad4
http://colobu.com/2014/12/22/why-is-disruptor-faster-than-ArrayBlockingQueue/
https://blog.csdn.net/kobejayandy/article/details/18329583
https://blog.csdn.net/u014313492/article/details/42556341
Disruptor 是如何工作的
http://in355hz.iteye.com/blog/1797829
隊列的各種場景
http://www.uml.org.cn/zjjs/2016060310.asp
消費順序
http://357029540.iteye.com/blog/2395677

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載自https://tech.meituan.com/disruptor.html Disruptor是英國外匯...
    檀文淵閱讀 2,701評論 0 25
  • 最近事情比較多, 所有擱了些日子才寫, 此篇主要講技術(shù), 不喜繞開。 先聲明此篇文章和具體的語言無關(guān),語言之間的比...
    企開老K閱讀 4,712評論 0 214
  • LMAX是一種新型零售金融交易平臺,它能夠以很低的延遲(latency)產(chǎn)生大量交易(吞吐量). 這個系統(tǒng)是建立在...
    舉頭明鑒閱讀 6,629評論 2 10
  • 本文是筆者在研究Disruptor過程中翻譯的Disruptor1.0論文精選,中間穿插了一些感想和說明,均以“譯...
    coder_jerry閱讀 5,385評論 3 53
  • 磨礪牽年輪 能有幾度春 冰霜且挺熬 何懼風(fēng)雨聲 不濕一身衣 咋換里外新 任憑風(fēng)雨驟 我自勝閑庭 落根于紅塵 注定染塵灰
    蜀山袖手人閱讀 357評論 0 7

友情鏈接更多精彩內(nèi)容