Disruptor是什么
Disruptor是一個(gè)由英國外匯交易公司LMAX開源的Java高性能隊(duì)列,它能夠以很低的延遲產(chǎn)生大量交易,能夠在一個(gè)線程里每秒處理 6 百萬訂單,可以認(rèn)為它是線程間通信高效低延時(shí)的內(nèi)存消息組件(簡單的看其實(shí)就是一個(gè)有界隊(duì)列,類似于ArrayBlockingQueue,我們知道 BlockingQueue 是一個(gè) FIFO 隊(duì)列,生產(chǎn)者Producer往隊(duì)列里發(fā)布publish一項(xiàng)事件時(shí),消費(fèi)者Consumer能獲得通知;如果沒有事件時(shí),消費(fèi)者被堵塞,直到生產(chǎn)者發(fā)布了新的事件)。
傳統(tǒng)隊(duì)列
java提供的隊(duì)列
我們先看看java常用的隊(duì)列:

常用的主要分兩大類:基于數(shù)組線程安全的隊(duì)列,比較典型的是ArrayBlockingQueue,它主要通過加鎖的方式來保證線程安全;基于鏈表的線程安全隊(duì)列分成LinkedBlockingQueue和ConcurrentLinkedQueue兩大類,前者也通過鎖的方式來實(shí)現(xiàn)線程安全,而后者以及上面表格中的LinkedTransferQueue都是通過原子變量compare and swap這種不加鎖的方式來實(shí)現(xiàn)的。
通過不加鎖的方式實(shí)現(xiàn)的隊(duì)列都是無界的(無法保證隊(duì)列的長度在確定的范圍內(nèi));而加鎖的方式,可以實(shí)現(xiàn)有界隊(duì)列。在穩(wěn)定性要求特別高的系統(tǒng)中,為了防止生產(chǎn)者速度過快,導(dǎo)致內(nèi)存溢出,只能選擇有界隊(duì)列;同時(shí),為了減少Java的垃圾回收對系統(tǒng)性能的影響,會(huì)盡量選擇array/heap格式的數(shù)據(jù)結(jié)構(gòu)(地址是連續(xù)的)。平時(shí)業(yè)務(wù)中用得最多的是ArrayBlockingQueue;
加鎖
上面說了需要加鎖來保證線程并發(fā)安全,加鎖通常會(huì)影響性能。線程會(huì)因?yàn)楦偁幉坏芥i而被掛起,等鎖被釋放的時(shí)候,線程又會(huì)被恢復(fù),這個(gè)過程中存在著很大的開銷,并且通常會(huì)有較長時(shí)間的中斷,因?yàn)楫?dāng)一個(gè)線程正在等待鎖時(shí),它不能做任何其他事情。如果一個(gè)線程在持有鎖的情況下發(fā)生了缺頁錯(cuò)誤、調(diào)度延遲或者其它類似情況,那么所有需要這個(gè)鎖的線程都無法執(zhí)行下去。另外還可能發(fā)生死鎖的情況。
官方給出了一個(gè)實(shí)驗(yàn):
- 調(diào)用了一個(gè)函數(shù),該函數(shù)會(huì)對一個(gè)64位的計(jì)數(shù)器循環(huán)自增5億次
- 機(jī)器環(huán)境:2.4G 6核
- 運(yùn)算: 64位的計(jì)數(shù)器累加5億次

性能對比
官方對比
下面官方給出了和ArrayBlockingQueue的比較結(jié)果
https://github.com/LMAX-Exchange/disruptor/wiki/Performance-Results
Log4j 2應(yīng)用場景
Log4j 2相對于Log4j 1最大的優(yōu)勢在于多線程并發(fā)場景下性能更優(yōu),loggers all async采用的是Disruptor,而Async Appender采用的是ArrayBlockingQueue隊(duì)列。

Disruptor為什么快
緩存行和偽共享
我們知道為了提高訪問速度,CPU和主內(nèi)存之間是有好幾層緩存,越靠近CPU的緩存越快也越小

數(shù)據(jù)在緩存中不是以獨(dú)立的項(xiàng)來存儲(chǔ)的,緩存是由緩存行組成的,通常是64字節(jié),并且它有效地引用主內(nèi)存中的一塊地址。一個(gè)Java的long類型是8字節(jié),因此在一個(gè)緩存行中可以存8個(gè)long類型的變量。如果你訪問一個(gè)long數(shù)組,當(dāng)數(shù)組中的一個(gè)值被加載到緩存中,它會(huì)額外加載另外7個(gè)。因此你能非??斓乇闅v這個(gè)數(shù)組。事實(shí)上,你可以非??焖俚谋闅v在連續(xù)的內(nèi)存塊中分配的任意數(shù)據(jù)結(jié)構(gòu)。這就會(huì)存在一個(gè)問題,當(dāng)你去除一個(gè)緩存行的時(shí)候(cpu規(guī)定必須以整個(gè)緩存行作為單位來處理),如果你拿到了其他線程的不相關(guān)數(shù)據(jù),并且更新了你自己的數(shù)據(jù),緩存中的值和內(nèi)存中的值都被更新了,而其他所有存儲(chǔ)當(dāng)前緩存行都會(huì)都會(huì)失效,那么一個(gè)和你的消費(fèi)者無關(guān)的線程讀一個(gè)和它無關(guān)的值,它被緩存未命中給拖慢了。這就是所謂的偽共享。

Disruptor如何設(shè)計(jì)
緩存行填充
因此沒有偽共享,就沒有和其它任何變量的意外沖突,沒有不必要的緩存未命中。-
環(huán)形數(shù)組結(jié)構(gòu)RingBuffer
RingBuffer是一個(gè)內(nèi)存環(huán),每一次讀寫操作都循環(huán)利用這個(gè)內(nèi)存環(huán),從而避免頻繁分配和回收內(nèi)存,減輕GC壓力,同時(shí)由于RingBuffer可以實(shí)現(xiàn)為無鎖的隊(duì)列,從而整體上大幅提高系統(tǒng)性能。RingBuffer是由一個(gè)大數(shù)組組成的。RingBuffer沒有尾指針,維護(hù)了一個(gè)指向下一個(gè)可用位置的序號(hào)。RingBuffer和常用的隊(duì)列之間的區(qū)別是,不刪除buffer中的數(shù)據(jù),也就是說這些數(shù)據(jù)一直存放在buffer中,直到新的數(shù)據(jù)覆蓋他們。(比鏈表快,對CPU緩存友好,在硬件級(jí)別,數(shù)組中的元素是會(huì)被預(yù)加載的,因此在ringbuffer當(dāng)中,cpu無需時(shí)不時(shí)去主存加載數(shù)組中的下一個(gè)元素)
image 元素位置定位
數(shù)組長度2^n,通過位運(yùn)算,加快定位的速度。下標(biāo)采取遞增的形式。不用擔(dān)心index溢出的問題。index是long類型,即使100萬QPS的處理速度,也需要30萬年才能用完。無鎖設(shè)計(jì)
每個(gè)生產(chǎn)者或者消費(fèi)者線程,會(huì)先申請可以操作的元素在數(shù)組中的位置,申請到之后,直接在該位置寫入或者讀取數(shù)據(jù)。
消費(fèi)數(shù)據(jù)

消費(fèi)者(Consumer)是一個(gè)想從Ring Buffer里讀取數(shù)據(jù)的線程,它可以訪問ConsumerBarrier對象——這個(gè)對象由RingBuffer創(chuàng)建并且代表消費(fèi)者與RingBuffer進(jìn)行交互,當(dāng)消費(fèi)者處理完了Ring Buffer里序號(hào)8之前(包括8)的所有數(shù)據(jù),那么它期待訪問的下一個(gè)序號(hào)是9。接下來,消費(fèi)者會(huì)一直原地停留,等待更多數(shù)據(jù)被寫入Ring Buffer。并且,一旦數(shù)據(jù)寫入后消費(fèi)者會(huì)收到通知——節(jié)點(diǎn)9,10,11和12 已寫入。現(xiàn)在序號(hào)12到了,消費(fèi)者可以讓ConsumerBarrier去拿這些序號(hào)節(jié)點(diǎn)里的數(shù)據(jù)了。
生產(chǎn)數(shù)據(jù)

生產(chǎn)者這邊有個(gè)consumerTrackingProducerBarrier 對象擁有所有正在訪問 Ring Buffer 的消費(fèi)者列表,維護(hù)了當(dāng)前所有訪問的消費(fèi)者所處的位置。現(xiàn)在生產(chǎn)者想要寫入 Ring Buffer 中序號(hào) 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 停下來自旋 等待,直到那個(gè)消費(fèi)者離開。

提交新的數(shù)據(jù),當(dāng)生產(chǎn)者結(jié)束向 Entry 寫入數(shù)據(jù)后,它會(huì)要求 ProducerBarrier 提交。如果沒有沖突提交成功后,則會(huì)通知ConsumerBarrier 上的 WaitStrategy,有新的數(shù)據(jù)產(chǎn)生,這時(shí)候消費(fèi)者就可以消費(fèi)的新的數(shù)據(jù)了。
