Disruptor筆記(五)-FAQ

1.  MultiThreadedClaimStrategy.java中pendingPublication的用處:

參考:http://blogs.lmax.com/

How does this apply to our concurrentsequencing problem? We could allow threads to continue to make progress whilewaiting for other threads to catch by maintaining a list of sequences that arepending publication. If a thread tries to publish a sequence that is greaterthan 1 higher than current cursor (i.e. it would need to wait for anotherthread to publish its sequence) it could place that sequence into the pendinglist and return. The thread that is currently running behind would publish itsown sequence, then check the pending list and publish those sequences beforeexiting.

 

2.  Sequencer.java中gatingSequences的作用?

主要是防止發佈申請序列越界。

/**
     * Set the sequences that will gatepublishers to prevent the buffer wrapping.
     *
     * This method must be called prior toclaiming sequences otherwise
     * a NullPointerException will be thrown.
     *
     * @param sequences to be to be gatedon.
     */
    public void setGatingSequences(final Sequence...sequences)
    {
        this.gatingSequences = sequences;
    }


 

例如:MultiThreadedClaimStrategy.java中

private void waitForFreeSlotAt(final long sequence, final Sequence[] dependentSequences, final MutableLongminGatingSequence)
    {
        final long wrapPoint = sequence - bufferSize;//可以預先放滿整個BufferSize
        if (wrapPoint > minGatingSequence.get())//說明minGatingSequence位置的event還沒有被消費掉。它的值爲minSequence
        {
            long minSequence;
            while (wrapPoint > (minSequence = getMinimumSequence(dependentSequences)))//說明eventProcessor的處理速度跟不上,minSequence爲 eventProcessor.sequence中最小的
            {
                LockSupport.parkNanos(1L);
            }
 
            minGatingSequence.set(minSequence);//重置minGatingSequence
        }
}


 

3.  BatchEventProcessor如何更新sequence來滿足eventPublisher繼續發佈event?

/**
     * It is ok to have another thread rerunthis method after a halt().
     */
    @Override
    public void run()
    {
        if (!running.compareAndSet(false, true))//置運行標誌爲true
        {
            throw new IllegalStateException("Thread is already running");
        }
        sequenceBarrier.clearAlert();//重置alert標誌
 
        notifyStart();//調用所有eventHandler的onStart()
 
        T event = null;
        long nextSequence = sequence.get() + 1L;//需要等待nextSequence對應的消息到達
        while (true)
        {
            try
            {
                final long availableSequence = sequenceBarrier.waitFor(nextSequence);//參考下面BlockingWaitStrategy的代碼說明,調用BlockingWaitStrategy.java中的waitFor
                while (nextSequence <= availableSequence)
                {
                    event = ringBuffer.get(nextSequence);
                    eventHandler.onEvent(event, nextSequence,nextSequence == availableSequence);
                    nextSequence++;
                }
 
                sequence.set(nextSequence - 1L);//設置gate sequence,說明已經消費到sequence的位置(該位置已經消費event)
            }
            catch (final AlertException ex)
            {
               if (!running.get())
               {
                   break;
               }
            }
            catch (final Throwable ex)
            {
                exceptionHandler.handleEventException(ex,nextSequence, event);
                sequence.set(nextSequence);//如果有exceptionHandler,則繼續,sequence遞增。
                nextSequence++;
            }
        }
 
        notifyShutdown();//調用所有的eventHandler的onShutdown()
 
        running.set(false);//置運行標誌爲false
    }


 

 

BlockingWaitStrategy.java中的

@Override
    public long waitFor(final long sequence, final Sequence cursor, final Sequence[] dependents, final SequenceBarrierbarrier)//cursor 爲ringbuffer的cursor sequence,表示當前發佈到的序列位置
        throws AlertException,InterruptedException
    {
        long availableSequence;
        if((availableSequence = cursor.get()) < sequence)//如何申請消費序列大於當前發佈到的序列,則等待新的消息發佈。
        {
            lock.lock();
            try
            {
                ++numWaiters;
                while ((availableSequence = cursor.get()) <sequence)
                {
                    barrier.checkAlert();
                    processorNotifyCondition.await();
                }
            }
            finally
            {
                --numWaiters;
                lock.unlock();
            }
        }
 
        if (0 != dependents.length)
        {
            while ((availableSequence = getMinimumSequence(dependents))< sequence)//如果依賴的序列還沒有消費到sequence位置,則等待。此場景爲多個串行的sequenceBarrier。
            {
                barrier.checkAlert();
            }
        }
 
        return availableSequence;
}


 

4.  RingBuffer(Sequencer)如果發佈的event超過BufferSize會出現什麼情況?

在調用next時,如果超過buffersize,

/**
     * Claim the next event in sequence forpublishing.
     *
     * @return the claimed sequence value
     */
    public long next()
    {
        if (null == gatingSequences)
        {
            throw new NullPointerException("gatingSequences must be set before claiming sequences");
        }
 
        return claimStrategy.incrementAndGet(gatingSequences);
}


 

調用MultiThreadedClaimStrategy.java的incrementAndGet

@Override

  

@Override
    public long incrementAndGet(final Sequence[] dependentSequences)
    {
        final MutableLong minGatingSequence = minGatingSequenceThreadLocal.get();
        waitForCapacity(dependentSequences, minGatingSequence);

        final long nextSequence = claimSequence.incrementAndGet();
waitForFreeSlotAt(nextSequence, dependentSequences, minGatingSequence);//這裏同2 Sequencer.java中gatingSequences的作用?
的描述了,等待消費序列增加可以繼續發佈。

        return nextSequence;
    }





 

       

 

5.  Sequence的無限增長超過Long.MAX_VALUE=(2<<63)-1怎麼辦?

從代碼看,爲了簡化seqence的比較(頭尾的判斷邏輯),一直遞增,總會超過Long.MAX_VALUE,後續會出現負數,進而出現死循環代碼。可以靠定期重啓系統來解決。

Martin Flower的解釋:This does mean that if they process a billion transactionsper second the counter will wrap in 292 years, causing some hell to breakloose. They have decided that fixing this is not a high priority.

 

我的思路:

類似halt的實現方式(),增加booleanisReset  和checkReset()

 

由RingBuffer判斷需要重置時來發出指令isReset = true,加一個CountDownLatch(n) n位1(RingBuffercursor)+其他eventProcessor,用來保證所有的sequence都重置, RingBuffer在next()控制,如果isReset爲true,cursor&=indexMask(bufferSize-1),eventProcessor根據自身的waitStradegy來暫停,sequence&= indexMask(bufferSize-1),這裏主要是和ringbuffer中的entry保持一致 。RingBuffer  latch.wait(),等待所有sequence和自身的cursor更新完成。

 

 

6.  暫停處理後是否會拋棄未處理的消息?

 

/**
     * Calls {@link com.lmax.disruptor.EventProcessor#halt()} on all of the event processors created via this disruptor.
     */
    public void halt()
    {
        for (EventProcessorInfo<?>eventprocessorInfo : eventProcessorRepository)
        {
           eventprocessorInfo.getEventProcessor().halt();//所有event processor halt(),並且激活alert標誌,讓sequenceBarrier.waitFor時拋出異常,跳出循環,結束線程處理邏輯。
        }
}


這種方式比較粗暴,對於交易型應用沒有問題,因爲調用端會收到SocketException.

 

個人覺得這個是可以優化的shutdown策略,如果要讓消息繼續處理完再停止,可以在停止publish event,做

claimSequence = ringBuffer.next();
oldEvent = ringBuffer.get(claimSequence);
oldEvent.copy(expectedEvent);
ringBuffer.publish(claimSequence);
int checkSequence  = claimSequence;//暫停發佈event後
 
while(true) {
       long sequence = sequenceBarrier.waitFor(claimSequence, 30, TimeUnit.SECONDS);
if (sequence> checkSequence  ) {
    checkSequence  = sequence;
} else {
    //說明已經沒有消費了。
   halt();
}
}


 

7.  多線程併發訪問RingBuffer來獲取可以用的Sequence發佈event,如果避免衝突?

MultiThreadedClaimStrategy.java中

a.有針對每個線程的守護序列

private finalThreadLocal<MutableLong> minGatingSequenceThreadLocal = newThreadLocal<MutableLong>()
    {
        @Override
        protected MutableLong initialValue()
        {
            return new MutableLong(Sequencer.INITIAL_CURSOR_VALUE);
        }
};


 

private final PaddedAtomicLongclaimSequence = new PaddedAtomicLong(Sequencer.INITIAL_CURSOR_VALUE);//爲atomic的,它保證原子性,因爲是遞增的,所以不會出現ABA現象。

 

 

8.  那個雙生產者的情況下,當1號生產者提交失敗時會怎麼樣?2號生產者會被一直阻止完成提交嗎?那樣的話ring buffer會出現死鎖嗎?

(disruptor開發者)生產者應該小心應付這種情況,因爲它們確實需要注意它們阻塞了其他生產者。
如果其中一個生產者因爲壞掉了而提交失敗,那你的整個系統會有比死鎖更大的問題。但是,如果它是因爲事務失敗而提交失敗的話,有兩點需要留意:1)重試直到提交成功(其它生產者會被阻塞直到成功)或者2)向ring buffer提交一個“死消息”以使被阻塞的生產者可以繼續,並且消費者會忽略這個死消息,仍然讓序列號像預期的那樣遞增。

 

9.你們對於耗時的消費者或生產者有應對策略嗎(多生產者的情況)?

Martin Fowler的文章給出了關於LMAX架構的更多內容,但是我們目前還沒有打算公開全部祕密;)

http://martinfowler.com/articles/lmax.html

(disruptor開發者)先不管你的架構怎麼樣,如果你的消費者一直都比生產者慢,那你的系統就應該加固。解決這問題的一個方法是你可以用兩個消費者來做同樣的事,其中一個用偶數序列,另一個用奇數的。這樣你就可以有潛力並行地處理更多東西(當然你可以擴展2個以上)。
如果你的生產者慢,這可能是你的設計中把很多事情放在一個地方做。我們的生產者通常做簡單的事情-它們從一個地方拿數據然後插入到ring buffer。如果你的生產者在做大量的工作,你可以考慮一下把那個邏輯移到一個較早的消費者中。你的生產者可以向ring buffer寫入原始數據,新的消費者可以從ring buffer讀取原始數據,處理它,把處理過的數據寫到Entry,擁有所有依賴它的下線消費者。這也許可以給你些並行處理這個事的建議。

 


發佈了73 篇原創文章 · 獲贊 60 · 訪問量 32萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章