原文地址:http://mechanitis.blogspot.com/2011/06/dissecting-disruptor-how-do-i-read-from.html 作者是 Trisha Gee, LMAX 公司的一位女工程師。
這是理解 LMAX 開發的 Disruptor 模式 系列博客的下一篇。
從 上一篇博客 我們都明白了什么是 Ring Buffer 以及 它有多棒。遺憾的是,我還沒有提到當你實際使用 Disruptor 時,怎樣讀寫數據。
ConsumerBarrier 與消費者
這里我要稍微反過來介紹,因為總的來說這一段比較容易理解。假設一些魔法已經把數據填入 Ring Buffer 了,怎樣從 Ring Buffer 讀出這些數據?
(唔,我開始后悔使用 Paint/Gimp 了。盡管這是個購買繪圖板的好借口,如果我繼續寫下去的話... UML 界的權威們大概也在詛咒我的名字了。)
消費者(Consumer)是一個想從 Ring Buffer 里拿出數據的線程,它可以訪問 ConsumerBarrier 對象——這個對象由 RingBuffer 創建並且代表消費者與它互動。就像 Ring Buffer 顯然需要序號才能找到下一個可用節點一樣,消費者一樣需要知道序號——每個消費者都需要找到下一個它要訪問的序號。在上面的例子中,消費者處理完了 Ring Buffer 里序號 8 之前的所有數據,那么它期待訪問的下一個序號是 9。
消費者可以調用 ConsumerBarrier 對象的 waitFor() 方法,傳遞它需要的下一個序號:
- final long availableSeq = consumerBarrier.waitFor(nextSequence);
final long availableSeq = consumerBarrier.waitFor(nextSequence);
ConsumerBarrier 返回 RingBuffer 的最大可訪問序號——在上面的例子中是 12。ConsumerBarrier 持有一個 WaitStrategy 值來決定它如何等待這個序號,我現在暫時不會描述它的細節,代碼里已經概括了每一種 WaitStrategy 的優點和缺點 。
接下來怎么做?
接下來,消費者會一直逛來逛去,等待更多數據被寫入 Ring Buffer。並且,寫入數據后消費者會收到通知——節點 9,10,11 和 12 已寫入。現在序號 12 到了,消費者可以指示 ConsumerBarrier 去拿這些序號里的數據了。
拿到了數據后,消費者會更新自己的游標 (cursor)。
你應該已經感覺得到,這樣做是怎樣有助於抹平延遲曲線尖峰了——代替逐個逐個節點的詢問“我能拿下一個數據嗎?現在怎么樣了?現在呢?”,消費者 Consumer 只需要簡單的說“當你拿到的數字比這個要大的時候請告訴我”,函數返回值會告訴它有多少個新的數據節點可以讀取。因為這些新的節點的確已經寫入(Ring Buffer 本身的序號已經更新),而且消費者對這些節點的唯一操作是讀而不是寫,因此訪問不用加鎖。這樣簡直太好了,不僅代碼可以更加安全和簡單,而且不用加鎖的速度超快。
另一個額外的好處是——你可以用多個消費者 Consumer 讀同一個 RingBuffer, 不需要加鎖,也不需要用另外的隊列來協調不同的線程。這樣你可以在 Disruptor 的協調下實現真正的並發數據處理。
BatchConsumer 是一個消費端的例子代碼。如果你實現了 BatchHandler, 你可以用 BatchConsumer 來完成上面我提到的復雜工作。它很容易實現需要成批處理節點(例如上文 9-12 的節點)的功能而不用單獨讀取每一個節點。
更新:注意 Disruptor 2.0 版使用了與本文不一樣的命名。如果你對類名感到困惑,請閱讀我的 變更總結。