淺談Disruptor
Disruptor是一個低延遲(low-latency),高吞吐量(high-throughput)的事件釋出訂閱框架。通過Disruptor,可以在一個JVM中釋出事件,和訂閱事件。相對於Java中的阻塞佇列(ArrayBlockingQueue,LinkedBlockingQueue),Disruptor的優點是效能更高。它採用了一種無鎖的資料結構設計,利用環形陣列(RingBuffer)來存放事件,通過物件複用減少垃圾回收進一步提高效能。
從"慢日誌"說起
線上有一個介面最近頻繁報警(tp99變高),通過監控報警系統定位到問題主要出現在日誌列印環節。介面方法入參和出參都會列印"info"日誌,我們採用的日誌是logback。它預設的是同步列印日誌,在日誌報文過大時,磁碟IO耗時會變得更加明顯。某個慢請求90%的處理時間都消耗在日誌列印中。於是我們決定採用非同步的方式列印日誌。sl4j2日誌框架支援非同步的日誌列印,改成非同步日誌列印之後介面效能報警消失。而sl4j2高效能的祕密就在於Disruptor。
Disruptor解決的問題
設想一下,在一個JVM中當我們有多個訊息的生產者執行緒,一個消費者執行緒時,他們之間如何進行高併發、執行緒安全的協調?很簡單,用一個阻塞佇列。 當我們有多個訊息的生產者執行緒,多個消費者執行緒,並且每一條訊息需要被所有的消費者都消費一次(這就不是一般佇列,只消費一次的語義了),該怎麼做? 這時仍然需要一個佇列。但是:
- 每個消費者需要自己維護一個指標,知道自己消費了佇列中多少資料。這樣同一條訊息,可以被多個人獨立消費。
- 佇列需要一個全域性指標,指向最後一條被所有生產者加入的訊息。消費者在消費資料時,不能消費到這個全域性指標之後的位置——因為這個全域性指標,已經是代表隊列中最後一條可以被消費的訊息了。
- 需要協調所有消費者,在消費完所有佇列中的訊息後,阻塞等待。
- 如果消費者之間有依賴關係,即對同一條訊息的消費順序,在業務上有固定的要求,那麼還需要處理誰先消費,誰後消費同一條訊息的問題。
總而言之,如果有多個生產者,多個消費者,並且同一條訊息要給到所有的消費者都去處理一下,需要做到以上4點。這是不容易的。 LMAX Disruptor,正是這種場景下,滿足以上4點要求的單機跨執行緒訊息傳遞、分發的開源、高效能實現。