鍍金池/ 教程/ Java/ 通過 Axon 和 Disruptor處理 1M tps
Ringbuffer的特別之處
寫入 Ringbuffer
鎖的缺點
神奇的緩存行填充
如何從Ringbuffer讀取
Disruptor Wizard已死,Disruptor Wizard永存!
線程間共享數(shù)據(jù)無需競爭
LMAX Disruptor——一個高性能、低延遲且簡單的框架
通過 Axon 和 Disruptor處理 1M tps
揭秘內存屏障
Disruptor (無鎖并發(fā)框架)-發(fā)布
LMAX架構
偽共享(False Sharing)
解析 Disruptor 的依賴關系
Disruptor 2.0 更新摘要

通過 Axon 和 Disruptor處理 1M tps

原文地址:http://blog.trifork.nl/2011/07/20/processing-1m-tps-with-axon-framework-and-the-disruptor/

作者: Allard Buijze 譯者:程曉明

LMAX,一家在英國的金融公司,最近開源了其(新型零售金融交易平臺的)核心組件之一:Disruptor。這個組件通過刪除必須的鎖來降低執(zhí)行開銷,且任然保證正確的處理訂單。如果你問我,我會說這是一個優(yōu)美精巧的工程。我嘗試把 Disruptor 應用到 Axon 控制總線中,就是想看看它到底有多大的潛力。結果相當驚人。

The Disruptor

Disruptor 是一個并發(fā)編程框架,它允許開發(fā)者使用多線程技術去創(chuàng)建基于任務的工作流。Disruptor 能用來并行創(chuàng)建任務,同時保證多個處理過程的有序性。它刪除了保證處理有序性所需要的隊列。它擁有幾個技術上的特征,可以明確把它和其他并發(fā)編程框架區(qū)分開來,這些并發(fā)技術從 java 5 開始為程序員可用。主要的一個是沒有使用鎖機制。

考慮處理完成一個大任務,需要從 A 到 D 的 4 個步驟(4 個子任務)。如果任務 B 和 C 都要依賴于任務 A 的執(zhí)行結果,但 B 和 C 不依賴任何其他任務,它們可能被并行化執(zhí)行。在這個例子中,任務 D 依賴任務 B 和 C 的執(zhí)行結果。

http://wiki.jikexueyuan.com/project/disruptor-getting-started/images/14-1.png" alt="" />

如果在 Java5 中要創(chuàng)建一個這樣的流式處理機制,需要使用諸如隊列這樣的技術。這會使任務的處理變的復雜;更重要的是,處理過程會比較慢。因為在向隊列 put 或 poll 元素時,線程需要獲取鎖。

Disruptor 使用其他途徑來執(zhí)行這些任務序列。它的主要組件是 RingBuffer。RingBuffer 實現(xiàn)了一個環(huán)形緩沖器,這個 RingBuffer 能控制任務的執(zhí)行。不同的任務讀/寫包含在緩沖器中的元素;一個任務的讀和寫,獨立于其他任務的讀和寫。這通常是令人滿意的。有時候你需要去確認,在任務 B 和 C 完成它們的工作之前,任務 D 有沒有開始執(zhí)行。

環(huán)形緩沖器上面的不同“消費者”,時刻觀察它們依賴的消費者的執(zhí)行進展。這個功能是通過跟蹤其他消費者已經處理完成的任務的序列號來實現(xiàn)的。這允許 Disruptor 最小化“內部消費者(inter-consumer)”的通信總量。舉一個例子,假設消費者 D 剛剛處理完了標號為8的任務,它將需要知道是否允許處理 9。它將詢問消費者 B 和 C:“你們在哪里”?B 和 C 回答:“23”和“12”。在這種情況下,消費者 D 能明白它可以安全的處理 9,10 和 11。在這個期間,消費者 D 不需要詢問 B 和 C 的進展情況。消費者 D 處理完 11 之后,它將需要再次詢問消費者 B 和 C。

減少“內部線程(inter-thread)”的通信總量,可以讓 CPU 優(yōu)化對緩存的使用。LMAX 開發(fā)團隊稱這個機制為感應(Sympathy)。為了取得最好的結果,代碼已經根據(jù) CPU 的工作機制做了優(yōu)化。

我沒有試圖去解釋 disruptor 在技術上的所有來龍去脈,Trisha 已經完成了一個系列的文章,這里還有一篇技術論文。

Axon 控制總線的基準測試

Disruptor 模式適合在基于 CQRS(命令查詢責任分類,Command Query Responsibility Segregation)架構的基礎上來處理命令。當某個進程“預加載(pre-load)”一個聚合對象群(aggregate)時,其他進程將執(zhí)行命令處理器(command handler),之后其它的存儲事件(store events)進入事件存儲(event store)中,且在事件總線(event bus)上發(fā)布這個存儲事件。我想嘗試一下,看能得到什么樣的結果。

使用 disruptor 來實現(xiàn)一個概念驗證風格(proof-of-concept style)的命令總線,是非常容易的事情。disruptor 的 jar 文件中有一個 helper 類,它可以幫助你優(yōu)化處理速度(有些顯而易見的事情,比如通過緩存行的填充來避免假共享)。

我的基準應用包含一個簡單的配置:一個命令處理器加載一個聚合對象群(在基準應用中只使用了 1 個聚合對象)。并在聚合對象上執(zhí)行“doSomething”的方法。這個方法生成一個單一事件,這個事件需要存儲到在內存中(in-memory)的事件存儲中,而且這個事件被發(fā)布到一個事件總線中(沒有監(jiān)聽器監(jiān)聽到這個事件)。這個基準應用的目標是關注“單純的命令處理”的速度。

我在筆記本電腦上運行了這個基準應用,這個筆記本的配置是:英特爾酷睿 i7640M 處理器(2.8 GHz,雙核,4線程)。

當使用 SimpleCommandBus 和 CachingGenericEventSourcingRepository 時,在我的電腦上得到了大約每秒處理 150 000 個命令的成績。這個成績很可能遠遠超過了大多數(shù)應用程序可以取得的成績(吹牛吹出來的成績不算:))。

然后,我使用 disruptor 創(chuàng)建了一個基于命令總線的簡單應用。這個應用在一秒中執(zhí)行了大約 250 000 個命令。這個應用差不多比上面的基準應用快了一倍。但結果還是讓我失望。這里必然有某種方法來提高命令的執(zhí)行速度。

于是,我開始稍作調整。Axon 使用 java.util.UUID 來獲得唯一事件標識符,我想徹底刪除它(別擔心,這只是為了測試)。你猜怎么了,我取得了大約每秒處理 700 000 個命令的成績?,F(xiàn)在取得了一些進展,但沒有標識符的應用是一個不真實的應用(應用使用的是隨機 UUID)。

接下來,我改變 UUID 生成機制為一個基于時間的版本,得到的成績倒退到 50K,但現(xiàn)在至少我有了我自己的標識符。

這時候,我發(fā)現(xiàn)我是在一個 32 位的 JVM 上運行應用。當我把同樣的基準程序在 64 位的 JVM 上運行時,執(zhí)行結果幾乎翻了一番。這聽起來合乎邏輯,但 Oracle 說遷移到 64 位 JVM 將降低性能。通過基于時間的 UUID 和其它的小優(yōu)化,我取得了每秒處理 1.3M (1 300 000)個命令的成績。這個成績比同樣的情況下使用鎖的機制取得的成績,高 50 多倍。

譯注– CQRS 架構圖如下,供讀者參考: http://wiki.jikexueyuan.com/project/disruptor-getting-started/images/14-2.png" alt="" />

總結

看起來,在同樣的硬件上處理同樣的邏輯,disruptor 比基于鎖的實現(xiàn)機制更快。使用 disruptor 來編寫好的應用,需要一些編程的慣用法則。但一旦生產者,消費者和它們之間的依賴關系被確定,開始運行將非常簡單。

原創(chuàng)文章,轉載請注明: 轉載自并發(fā)編程網(wǎng) – ifeve.com

本文鏈接地址: 通過 Axon 和 Disruptor 處理 1M tps