新聞中心
這是 Disruptor 全方位解析(end-to-end view)中缺少的一章。當心,本文非常長。但是為了讓你能聯(lián)系上下文閱讀,我還是決定把它們寫進一篇博客里。

創(chuàng)新互聯(lián)是一家集網(wǎng)站建設,淮安企業(yè)網(wǎng)站建設,淮安品牌網(wǎng)站建設,網(wǎng)站定制,淮安網(wǎng)站建設報價,網(wǎng)絡營銷,網(wǎng)絡優(yōu)化,淮安網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強企業(yè)競爭力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學習、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網(wǎng)站。
本文的 重點 是:不要讓 Ring 重疊;如何通知消費者;生產(chǎn)者一端的批處理;以及多個生產(chǎn)者如何協(xié)同工作。
ProducerBarriers
Disruptor 代碼? 給 消費者 提供了一些接口和輔助類,但是沒有給寫入 Ring Buffer 的 生產(chǎn)者 提供接口。這是因為除了你需要知道生產(chǎn)者之外,沒有別人需要訪問它。盡管如此,Ring Buffer 還是與消費端一樣提供了一個 ProducerBarrier 對象,讓生產(chǎn)者通過它來寫入 Ring Buffer。
寫入 Ring Buffer 的過程涉及到兩階段提交 (two-phase commit)。首先,你的生產(chǎn)者需要申請 buffer 里的下一個節(jié)點。然后,當生產(chǎn)者向節(jié)點寫完數(shù)據(jù),它將會調用 ProducerBarrier 的 commit 方法。
那么讓我們首先來看看第一步。 “給我 Ring Buffer 里的下一個節(jié)點”,這句話聽起來很簡單。的確,從生產(chǎn)者角度來看它很簡單:簡單地調用 ProducerBarrier 的 nextEntry() 方法,這樣會返回給你一個 Entry 對象,這個對象就是 Ring Buffer 的下一個節(jié)點。
ProducerBarrier 如何防止 Ring Buffer 重疊
在后臺,由 ProducerBarrier 負責所有的交互細節(jié)來從 Ring Buffer 中找到下一個節(jié)點,然后才允許生產(chǎn)者向它寫入數(shù)據(jù)。
(我不確定 閃閃發(fā)亮的新手寫板? 能否有助于提高我畫圖片的清晰度,但是它用起來很有意思)。
在這幅圖中,我們假設只有一個生產(chǎn)者寫入 Ring Buffer。過一會兒我們再處理多個生產(chǎn)者的復雜問題。
ConsumerTrackingProducerBarrier 對象擁有所有正在訪問 Ring Buffer 的 消費者 列 表。這看起來有點兒奇怪-我從沒有期望 ProducerBarrier 了解任何有關消費端那邊的事情。但是等等,這是有原因的。因為我們不想與隊列“混為一談”(隊列需要追蹤隊列的頭和尾,它們有時候會指向相同的位 置),Disruptor 由消費者負責通知它們處理到了哪個序列號,而不是 Ring Buffer。所以,如果我們想確定我們沒有讓 Ring Buffer 重疊,需要檢查所有的消費者們都讀到了哪里。
在上圖中,有一個 消費者 順利的讀到了最大序號 12(用紅色/粉色高亮)。第二個消費者 有點兒落后——可能它在做 I/O 操作之類的——它停在序號 3。因此消費者 2 在趕上消費者 1 之前要跑完整個 Ring Buffer 一圈的距離。
現(xiàn)在生產(chǎn)者想要寫入 Ring Buffer 中序號 3 占據(jù)的節(jié)點,因為它是 Ring Buffer 當前游標的下一個節(jié)點。但是 ProducerBarrier 明白現(xiàn)在不能寫入,因為有一個消費者正在占用它。所以,ProducerBarrier 停下來自旋 (spins),等待,直到那個消費者離開。
申請下一個節(jié)點
現(xiàn)在可以想像消費者 2 已經(jīng)處理完了一批節(jié)點,并且向前移動了它的序號。可能它挪到了序號 9(因為消費端的批處理方式,現(xiàn)實中我會預計它到達 12,但那樣的話這個例子就不夠有趣了)。
上圖顯示了當消費者 2 挪動到序號 9 時發(fā)生的情況。在這張圖中我已經(jīng)忽略了ConsumerBarrier,因為它沒有參與這個場景。
ProducerBarier 會看到下一個節(jié)點——序號 3 那個已經(jīng)可以用了。它會搶占這個節(jié)點上的 Entry(我還沒有特別介紹 Entry 對象,基本上它是一個放寫入到某個序號的 Ring Buffer 數(shù)據(jù)的桶),把下一個序號(13)更新成 Entry 的序號,然后把 Entry 返回給生產(chǎn)者。生產(chǎn)者可以接著往 Entry 里寫入數(shù)據(jù)。
提交新的數(shù)據(jù)
兩階段提交的第二步是——對,提交。
綠色表示最近寫入的 Entry,序號是 13 ——厄,抱歉,我也是紅綠色盲。但是其他顏色甚至更糟糕。
當生產(chǎn)者結束向 Entry 寫入數(shù)據(jù)后,它會要求 ProducerBarrier 提交。
ProducerBarrier 先等待 Ring Buffer 的游標追上當前的位置(對于單生產(chǎn)者這毫無意義-比如,我們已經(jīng)知道游標到了 12 ,而且沒有其他人正在寫入 Ring Buffer)。然后 ProducerBarrier 更新 Ring Buffer 的游標到剛才寫入的 Entry 序號-在我們這兒是 13。接下來,ProducerBarrier 會讓消費者知道 buffer 中有新東西了。它戳一下 ConsumerBarrier 上的 WaitStrategy 對象說-“喂,醒醒!有事情發(fā)生了!”(注意-不同的 WaitStrategy 實現(xiàn)以不同的方式來實現(xiàn)提醒,取決于它是否采用阻塞模式。)
現(xiàn)在消費者 1 可以讀 Entry 13 的數(shù)據(jù),消費者 2 可以讀 Entry 13 以及前面的所有數(shù)據(jù),然后它們都過得很 happy。
ProducerBarrier 上的批處理
有趣的是 Disruptor 可以同時在生產(chǎn)者和 消費者? 兩端實現(xiàn)批處理。還記得伴隨著程序運行,消費者 2 最后達到了序號 9 嗎?ProducerBarrier 可以在這里做一件很狡猾的事-它知道 Ring Buffer 的大小,也知道最慢的消費者位置。因此它能夠發(fā)現(xiàn)當前有哪些節(jié)點是可用的。
如果 ProducerBarrier 知道 Ring Buffer 的游標指向 12,而最慢的消費者在 9 的位置,它就可以讓生產(chǎn)者寫入節(jié)點 3,4,5,6,7 和 8,中間不需要再次檢查消費者的位置。
多個生產(chǎn)者的場景
到這里你也許會以為我講完了,但其實還有一些細節(jié)。
在上面的圖中我稍微撒了個謊。我暗示了 ProducerBarrier 拿到的序號直接來自 Ring Buffer 的游標。然而,如果你看過代碼的話,你會發(fā)現(xiàn)它是通過 ClaimStrategy 獲取的。我省略這個對象是為了簡化示意圖,在單個生產(chǎn)者的情況下它不是很重要。
在多個生產(chǎn)者的場景下,你還需要其他東西來追蹤序號。這個序號是指當前可寫入的序號。注意這和“向 Ring Buffer 的游標加 1”不一樣-如果你有一個以上的生產(chǎn)者同時在向 Ring Buffer 寫入,就有可能出現(xiàn)某些 Entry 正在被生產(chǎn)者寫入但還沒有提交的情況。
讓我們復習一下如何申請寫入節(jié)點。每個生產(chǎn)者都向 ClaimStrategy 申請下一個可用的節(jié)點。生產(chǎn)者 1 拿到序號 13,這和上面單個生產(chǎn)者的情況一樣。生產(chǎn)者 2 拿到序號 14,盡管 Ring Buffer的當前游標僅僅指向 12。這是因為 ClaimSequence 不但負責分發(fā)序號,而且負責跟蹤哪些序號已經(jīng)被分配。
現(xiàn)在每個生產(chǎn)者都擁有自己的寫入節(jié)點和一個嶄新的序號。
我把生產(chǎn)者 1 和它的寫入節(jié)點涂上綠色,把生產(chǎn)者 2 和它的寫入節(jié)點涂上可疑的粉色-看起來像紫色。
現(xiàn)在假設生產(chǎn)者 1 還生活在童話里,因為某些原因沒有來得及提交數(shù)據(jù)。生產(chǎn)者 2 已經(jīng)準備好提交了,并且向 ProducerBarrier 發(fā)出了請求。
就像我們先前在 commit 示意圖中看到的一樣,ProducerBarrier 只有在 Ring Buffer 游標到達準備提交的節(jié)點的前一個節(jié)點時它才會提交。在當前情況下,游標必須先到達序號 13 我們才能提交節(jié)點 14 的數(shù)據(jù)。但是我們不能這樣做,因為生產(chǎn)者 1 正盯著一些閃閃發(fā)光的東西,還沒來得及提交。因此 ClaimStrategy 就停在那兒自旋 (spins), 直到 Ring Buffer 游標到達它應該在的位置。
現(xiàn)在生產(chǎn)者 1 從迷糊中清醒過來并且申請?zhí)峤还?jié)點 13 的數(shù)據(jù)(生產(chǎn)者 1 發(fā)出的綠色箭頭代表這個請求)。ProducerBarrier 讓 ClaimStrategy 先等待 Ring Buffer 的游標到達序號 12,當然現(xiàn)在已經(jīng)到了。因此 Ring Buffer 移動游標到 13,讓 ProducerBarrier 戳一下 WaitStrategy 告訴所有人都知道 Ring Buffer 有更新了?,F(xiàn)在 ProducerBarrier 可以完成生產(chǎn)者 2 的請求,讓 Ring Buffer 移動游標到 14,并且通知所有人都知道。
你會看到,盡管生產(chǎn)者在不同的時間完成數(shù)據(jù)寫入,但是 Ring Buffer 的內容順序總是會遵循 nextEntry() 的初始調用順序。也就是說,如果一個生產(chǎn)者在寫入 Ring Buffer 的時候暫停了,只有當它解除暫停后,其他等待中的提交才會立即執(zhí)行。
呼——。我終于設法講完了這一切的內容并且一次也沒有提到內存屏障(Memory Barrier)。
更新:最近的 RingBuffer? 版本去掉了 Producer Barrier。如果在你看的代碼里找不到 ProducerBarrier,那就假設當我講“Producer Barrier”時,我的意思是“Ring Buffer”。
更新2:注意 Disruptor 2.0 版使用了與本文不一樣的命名。如果你對類名感到困惑,請閱讀我寫的Disruptor 2.0更新摘要?。
網(wǎng)頁題目:如何使用 Disruptor(三)寫入 Ringbuffer
文章鏈接:http://m.fisionsoft.com.cn/article/cdoppco.html


咨詢
建站咨詢
