新聞中心
最近,我們開源了LMAX Disruptor, 它是我們的交易系統(tǒng)吞吐量快(LMAX是一個(gè)新型的交易平臺(tái),號(hào)稱能夠單線程每秒處理數(shù)百萬(wàn)的訂單)的關(guān)鍵原因。為什么我們要將其開源?我們意識(shí)到對(duì)高性 能編程領(lǐng)域的一些傳統(tǒng)觀點(diǎn),有點(diǎn)不對(duì)勁。我們找到了一種更好、更快地在線程間共享數(shù)據(jù)的方法,如果不公開于業(yè)界共享的話,那未免太自私了。同時(shí)開源也讓我 們覺(jué)得看起來(lái)更酷。

從這個(gè)站點(diǎn),你可以下載到一篇解釋什么是Disruptor及它為什么如此高性能的文檔。這篇文檔的編寫過(guò)程,我并沒(méi)有參與太多,只是簡(jiǎn)單地插入了一些標(biāo)點(diǎn)符號(hào)和重組了一些我不懂的句子,但是非常高興的是,我仍然從中提升了自己的寫作水平。
我發(fā)現(xiàn)要把所有的事情一下子全部解釋清楚還是有點(diǎn)困難的,所有我準(zhǔn)備一部分一部分地解釋它們,以適合我的NADD聽眾。
首先介紹ringbuffer。我對(duì)Disruptor的最初印象就是ringbuffer。但是后來(lái)我意識(shí)到盡管ringbuffer是整個(gè)模式(Disruptor)的核心,但是Disruptor對(duì)ringbuffer的訪問(wèn)控制策略才是真正的關(guān)鍵點(diǎn)所在。
ringbuffer到底是什么?
嗯,正如名字所說(shuō)的一樣,它是一個(gè)環(huán)(首尾相接的環(huán)),你可以把它用做在不同上下文(線程)間傳遞數(shù)據(jù)的buffer。
(好吧,這是我通過(guò)畫圖板手畫的,我試著畫草圖,希望我的強(qiáng)迫癥不會(huì)讓我畫完美的圓和直線)
基本來(lái)說(shuō),ringbuffer擁有一個(gè)序號(hào),這個(gè)序號(hào)指向數(shù)組中下一個(gè)可用的元素。(校對(duì)注:如下圖右邊的圖片表示序號(hào),這個(gè)序號(hào)指向數(shù)組的索引4的位置。)
隨著你不停地填充這個(gè)buffer(可能也會(huì)有相應(yīng)的讀?。@個(gè)序號(hào)會(huì)一直增長(zhǎng),直到繞過(guò)這個(gè)環(huán)。
要找到數(shù)組中當(dāng)前序號(hào)指向的元素,可以通過(guò)mod操作:
sequence mod array length = array index
以上面的ringbuffer為例(java的mod語(yǔ)法):12 % 10 = 2。很簡(jiǎn)單吧。
事實(shí)上,上圖中的ringbuffer只有10個(gè)槽完全是個(gè)意外。如果槽的個(gè)數(shù)是2的N次方更有利于基于二進(jìn)制的計(jì)算機(jī)進(jìn)行計(jì)算。
(校對(duì)注:2的N次方換成二進(jìn)制就是1000,100,10,1這樣的數(shù)字, sequence & (array length-1) = array index,比如一共有8槽,3&(8-1)=3,HashMap就是用這個(gè)方式來(lái)定位數(shù)組元素的,這種方式比取模的速度更快。)
那又怎么樣?
如果你看了維基百科里面的關(guān)于環(huán)形buffer的 詞條,你就會(huì)發(fā)現(xiàn),我們的實(shí)現(xiàn)方式,與其最大的區(qū)別在于:沒(méi)有尾指針。我們只維護(hù)了一個(gè)指向下一個(gè)可用位置的序號(hào)。這種實(shí)現(xiàn)是經(jīng)過(guò)深思熟慮的—我們選擇用 環(huán)形buffer的最初原因就是想要提供可靠的消息傳遞。我們需要將已經(jīng)被服務(wù)發(fā)送過(guò)的消息保存起來(lái),這樣當(dāng)另外一個(gè)服務(wù)通過(guò)nak (校對(duì)注:拒絕應(yīng)答信號(hào))告訴我們沒(méi)有成功收到消息時(shí),我們能夠重新發(fā)送給他們。
聽起來(lái),環(huán)形buffer非常適合這個(gè)場(chǎng)景。它維護(hù)了一個(gè)指向尾部的序號(hào),當(dāng)收到nak(校對(duì)注:拒絕應(yīng)答信號(hào))請(qǐng)求,可以重發(fā)從那一點(diǎn)到當(dāng)前序號(hào)之間的所有消息:
我們實(shí)現(xiàn)的ring buffer和大家常用的隊(duì)列之間的區(qū)別是,我們不刪除buffer中的數(shù)據(jù),也就是說(shuō)這些數(shù)據(jù)一直存放在buffer中,直到新的數(shù)據(jù)覆蓋他們。這就是 和維基百科版本相比,我們不需要尾指針的原因。ringbuffer本身并不控制是否需要重疊(決定是否重疊是生產(chǎn)者-消費(fèi)者行為模式的一部分–如果你等 不急我寫blog來(lái)說(shuō)明它們,那么可以自行檢出Disruptor項(xiàng)目)。
它為什么如此優(yōu)秀?
之所以ringbuffer采用這種數(shù)據(jù)結(jié)構(gòu),是因?yàn)樗诳煽肯鬟f方面有很好的性能。這就夠了,不過(guò)它還有一些其他的優(yōu)點(diǎn)。
首先,因?yàn)樗菙?shù)組,所以要比鏈表快,而且有一個(gè)容易預(yù)測(cè)的訪問(wèn)模式。(譯者注:數(shù)組內(nèi)元素的內(nèi)存地址的連續(xù)性存儲(chǔ)的)。這是對(duì)CPU緩存友好的—也就是說(shuō),在硬件級(jí)別,數(shù)組中的元素是會(huì)被預(yù)加載的,因此在ringbuffer當(dāng)中,cpu無(wú)需時(shí)不時(shí)去主存加載數(shù)組中的下一個(gè)元素。(校對(duì)注:因?yàn)橹灰粋€(gè)元素被加載到緩存行,其他相鄰的幾個(gè)元素也會(huì)被加載進(jìn)同一個(gè)緩存行)
其次,你可以為數(shù)組預(yù)先分配內(nèi)存,使得數(shù)組對(duì)象一直存在(除非程序終止)。這就意味著不需要花大量的時(shí)間用于垃圾回收。此外,不像鏈表那樣,需要為每一個(gè)添加到其上面的對(duì)象創(chuàng)造節(jié)點(diǎn)對(duì)象—對(duì)應(yīng)的,當(dāng)刪除節(jié)點(diǎn)時(shí),需要執(zhí)行相應(yīng)的內(nèi)存清理操作。
缺少的部分
我并沒(méi)有在本文中介紹如何避免ringbuffer產(chǎn)生重疊,以及如何對(duì)ringbuffer進(jìn)行讀寫操作。你可能注意到了我將ringbuffer和鏈表那樣的數(shù)據(jù)結(jié)構(gòu)進(jìn)行比較,因?yàn)槲也⒄J(rèn)為鏈表是實(shí)際問(wèn)題的標(biāo)準(zhǔn)答案。
當(dāng)你將Disruptor和基于 隊(duì)列之類的實(shí)現(xiàn)進(jìn)行比較時(shí),事情將變得很有趣。隊(duì)列通常注重維護(hù)隊(duì)列的頭尾元素,添加和刪除元素等。所有的這些我都沒(méi)有在ringbuffer里提到,這 是因?yàn)閞ingbuffer不負(fù)責(zé)這些事情,我們把這些操作都移到了數(shù)據(jù)結(jié)構(gòu)(ringbuffer)的外部
原文鏈接:http://ifeve.com/ringbuffer/
譯文鏈接:http://ifeve.com/dissecting-disruptor-whats-so-special/
名稱欄目:如何使用Disruptor(一)Ringbuffer的特別
標(biāo)題鏈接:http://www.dlmjj.cn/article/cccdgdc.html


咨詢
建站咨詢
