使用Chronicle Queue建立低延遲的TB級別的佇列 - DZone
本文介紹如何使用開源 Chronicle Queue建立巨大的持久佇列,同時保持可預測和一致的低延遲。
在本文中,目標是維護來自市場資料饋送的物件佇列(例如,在交易所交易的證券的最新價格)。也可以選擇其他業務領域,例如來自物聯網裝置的感官輸入或讀取汽車行業的碰撞記錄資訊。原理是一樣的。
首先,定義一個持有市場資料的類:
public class MarketData extends SelfDescribingMarshallable { int securityId; long time; float last; float high; float low; // Getters and setters not shown for brevity } |
注意:在實際場景中,使用float和double儲存貨幣值時必須非常小心,否則可能會導致舍入問題 [Bloch18, Item 60]。但是,在這篇介紹性文章中,我想保持簡單。
還有一個小的實用函式MarketDataUtil::create將在呼叫時建立並返回一個新的隨機MarketData物件:
static MarketData create() { MarketData marketData = new MarketData(); int id = ThreadLocalRandom.current().nextInt(1000); marketData.setSecurityId(id); float nextFloat = ThreadLocalRandom.current().nextFloat(); float last = 20 + 100 * nextFloat; marketData.setLast(last); marketData.setHigh(last * 1.1f); marketData.setLow(last * 0.9f); marketData.setTime(System.currentTimeMillis()); return marketData; } |
現在,目標是建立一個持久、併發、低延遲、可從多個程式訪問並且可以容納數十億個物件的佇列。
天真的方法
有了這些類,就可以探索使用ConcurrentLinkedQueue的簡單方法:
public static void main(String[] args) { final Queue<MarketData> queue = new ConcurrentLinkedQueue<>(); for (long i = 0; i < 1e9; i++) { queue.add(MarketDataUtil.create()); } } |
導致失敗的有幾個原因:
- ConcurrentLinkedQueue將為新增到佇列中的每個元素建立一個包裝節點。這將有效地使建立的物件數量增加一倍。
- 物件放置在 Java 堆上,導致堆記憶體壓力和垃圾收集問題。在我的機器上,這導致我的整個 JVM 變得無響應,唯一的辦法是使用“kill -9”強行殺死它。
- 無法從其他程式(即其他 JVM)讀取佇列。
- 一旦 JVM 終止,佇列的內容就會丟失。因此,佇列不是持久的。
檢視其他各種標準 Java 類,可以得出結論,不支援大型持久佇列。
Chronicle Queue
Chronicle Queue 是一個開源庫,旨在滿足上述要求。這是設定和使用它的一種方法:
public static void main(String[] args) { final MarketData marketData = new MarketData(); final ChronicleQueue q = ChronicleQueue .single("market-data"); final ExcerptAppender appender = q.acquireAppender(); for (long i = 0; i < 1e9; i++) { try (final DocumentContext document = appender.acquireWritingDocument(false)) { document .wire() .bytes() .writeObject(MarketData.class, MarketDataUtil.recycle(marketData)); } } } |
使用配備 2.3 GHz 8 核英特爾酷睿 i9 的 MacBook Pro 2019 時,僅使用一個執行緒即可插入每秒超過 3,000,000 條訊息。佇列透過給定目錄“market-data”中的記憶體對映檔案持久化。人們會期望MarketData物件至少佔用 4 (int securityId) + 8 (long time) + 4*3 (float last, high 和 low) = 24 位元組。
在上面的示例中,附加了 10 億個物件,導致對映檔案佔用 30,148,657,152 個位元組,這意味著每條訊息大約 30 個位元組。在我看來,這確實非常有效。
從編年史佇列中讀取很簡單。繼續上面的示例,下面顯示瞭如何從佇列中讀取前兩個MarketData物件:
public static void main(String[] args) { final ChronicleQueue q = ChronicleQueue .single("market-data"); final ExcerptTailer tailer = q.createTailer(); for (long i = 0; i < 2; i++) { try (final DocumentContext document = tailer.readingDocument()) { MarketData marketData = document .wire() .bytes() .readObject(MarketData.class); System.out.println(marketData); } } } |
還有許多其他功能超出了本文的範圍。例如,可以將佇列檔案設定為以特定間隔(例如每天、每小時或每分鐘)滾動,從而有效地建立資訊分解,以便隨著時間的推移清理舊資料。還有一些規定能夠隔離 CPU 並將 Java 執行緒鎖定到這些隔離的 CPU,從而顯著減少應用程式抖動。
相關文章
- Laravel 延遲佇列Laravel佇列
- redis 延遲佇列Redis佇列
- 實現簡單延遲佇列和分散式延遲佇列佇列分散式
- 延遲阻塞佇列 DelayQueue佇列
- hyperf redis延遲佇列Redis佇列
- 使用RabbitMq原生實現延遲佇列MQ佇列
- Golang 實現 RabbitMQ 的延遲佇列GolangMQ佇列
- RabbitMQ實戰《延遲佇列》MQ佇列
- RabbitMQ實現延遲佇列MQ佇列
- RabbitMQ 實現延遲佇列MQ佇列
- [Redis]延遲訊息佇列Redis佇列
- 基於Dynomite的分散式延遲佇列MIT分散式佇列
- Timestone:Netflix 的高吞吐量、低延遲優先佇列系統佇列
- php+redis實現延遲佇列PHPRedis佇列
- 如何用RabbitMQ實現延遲佇列MQ佇列
- Spring Boot(十四)RabbitMQ延遲佇列Spring BootMQ佇列
- RabbitMQ、RocketMQ、Kafka延遲佇列實現MQKafka佇列
- RabbitMQ延時佇列的使用MQ佇列
- JDK QUEUE佇列JDK佇列
- Team Queue(佇列)佇列
- RabbitMQ使用 prefetch_count優化佇列的消費,使用死信佇列和延遲佇列實現訊息的定時重試,golang版本MQ優化佇列Golang
- Delayer 基於 Redis 的延遲訊息佇列中介軟體Redis佇列
- 你知道Redis可以實現延遲佇列嗎?Redis佇列
- 高可用延遲佇列設計與實現佇列
- 靈感來襲,基於Redis的分散式延遲佇列(續)Redis分散式佇列
- 一張圖帶你理解和實現RabbitMQ的延遲佇列功能MQ佇列
- Netflix使用ZGC實現低延遲GC
- Laravel 在事件監聽中實現佇列的方法以及指定加入的佇列名稱和佇列延遲時間Laravel事件佇列
- python佇列QueuePython佇列
- C# 佇列(Queue)C#佇列
- Delayed Message 外掛實現 RabbitMQ 延遲佇列MQ佇列
- 【RabbitMQ】一文帶你搞定RabbitMQ延遲佇列MQ佇列
- RabbitMQ 學習筆記 -- 12 死信佇列 DLX + TTL 方式實現延遲佇列MQ筆記佇列
- 百行程式碼實現基於Redis的可靠延遲佇列行程Redis佇列
- Team Queue (佇列的一種應用)佇列
- Dyno-queues 分散式延遲佇列 之 輔助功能分散式佇列
- 基於訊息佇列(RabbitMQ)實現延遲任務佇列MQ
- 佇列(Queue)-c實現佇列