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

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


咨詢
建站咨詢
