Aeron 是什麼
Aeron 是一款開源的高效能訊息傳遞框架,專為低延遲和高吞吐場景設計。它被廣泛應用於金融、遊戲、分散式系統等需要快速通訊的領域。Aeron 的核心優勢在於透過零複製技術和直接記憶體訪問,最大限度地降低訊息傳遞的延遲,同時利用高效的網路協議實現資料的可靠傳輸。
其架構包括媒體驅動器和客戶端 API,支援單播、廣播和程序間通訊。Aeron 還提供持久化模組(Aeron Archive),便於訊息流的儲存與回放,滿足日誌重放等需求。
Aeron 支援 Java、C++、C# 等多語言,適配多種場景,如分散式日誌複製、高頻交易等。開發者可靈活選擇嵌入式或獨立部署模式,以滿足不同應用需求。憑藉極低延遲、強穩定性和簡潔的程式設計模型,Aeron 成為高效能通訊領域的佼佼者,是構建實時系統的理想選擇。
Aeron 的優勢
選擇 Aeron 的理由在於其專注於極致效能和低延遲的最佳化。相比傳統訊息中介軟體如 Kafka 和 RabbitMQ,Aeron 的零複製技術和直接記憶體操作將延遲降至微秒級,滿足高頻交易、實時資料分發等對響應速度極為敏感的場景。此外,Aeron 透過靈活的單播、多播及程序間通訊模式,提供更簡單的部署方式和開發體驗,避免了傳統中介軟體依賴複雜分散式管理工具的麻煩。
與 Kafka 注重吞吐和持久化,RabbitMQ 強調功能豐富不同,Aeron 更傾向於實時性需求,專為高效能任務而生。它不僅能穩定處理高併發流量,還具備訊息回放功能,兼顧了效能與可靠性,成為構建實時應用的優選。
基礎概念
Aeron 架構組成
Media Driver:Media Driver 是 Aeron 的通訊核心,負責底層資料的傳輸處理,包括程序內(IPC)和網路間(UDP)的通訊操作。它利用直接記憶體和記憶體對映檔案,實現高效的訊息傳遞,同時支援單播和多播模式,滿足不同場景的需求。
Client API:Client API 是 Aeron 面向開發者的高階介面,簡化了訊息釋出和訂閱的操作流程。透過它,開發者可以輕鬆建立 Publication 和 Subscription,專注於業務邏輯,而無需處理複雜的底層實現。
Log Buffer:Log Buffer 是共享記憶體區域,用於儲存釋出的訊息資料,供訂閱者訪問。它基於環形緩衝區設計,確保高效的訊息寫入和讀取,同時支援流量控制和重傳功能。
核心術語
- Publication 和 Subscription:Publication 是訊息的釋出端,負責將資料寫入 Log Buffer;Subscription 是訊息的訂閱端,從 Log Buffer 讀取資料,形成 Aeron 的釋出 - 訂閱模型核心。
- Channel:Channel 是訊息通訊的介質,指定訊息的傳輸方式,例如程序內(IPC)或跨主機的網路通訊(UDP)。它是連線釋出者和訂閱者的橋樑。
- Stream ID:Stream ID 用於標識同一 Channel 內的不同邏輯訊息流,允許多個獨立的資料流共用一個通訊通道,提高資源利用率。
-
Session ID:Session ID 用於區分同一 Stream ID 內不同釋出者的會話。即使多個釋出者共享一個邏輯流,每個會話的資料都能獨立追蹤。
傳輸方式
IPC:IPC(程序間通訊)是 Aeron 的最快通訊模式,依託共享記憶體直接傳輸訊息,適合部署在同一主機內的高效能應用場景。
UDP:UDP 支援主機間的網路通訊,是 Aeron 的跨網路傳輸方式。它透過單播或多播傳送資料,保證了低延遲和高擴充套件性,適合分散式環境。
code 實踐
建立 MediaDriver
建立服務端比較簡單,如下:
// 建立並啟動不帶存檔功能的
MediaDriverMediaDriver mediaDriver = MediaDriver.launch();
System.out.println("Aeron Archive Server is running...");
在我查詢資料的過程中,有一些教程會在這行程式碼後面加上無限休眠來保障 MediaDriver
的執行。在我實際測試當中,並不會發生程式碼執行完就終止的情況,可能是早期版本的設計差異導致,各位在使用當中可以以實際測試結果為準。
建立 Publisher
相對來說複雜一些,但是照著官方的教程還是比較容易寫個 Demo 出來的。PS:官方教程會將各個步驟單獨拿出來演示,並說明作用。沒有耐心的可以直接原始碼倉庫找 Demo。
import io.aeron.Aeron;
import io.aeron.ConcurrentPublication;
import io.aeron.driver.MediaDriver;
import org.agrona.concurrent.IdleStrategy;
import org.agrona.concurrent.SleepingIdleStrategy;
import org.agrona.concurrent.UnsafeBuffer;
public class ArchivePublisher {
public static void main(String[] args) throws InterruptedException {
// 建立並啟動不帶存檔功能的 MediaDriver MediaDriver mediaDriver = MediaDriver.launch();
System.out.println("Aeron Archive Server is running...");
// 建立一個空閒策略,用於在沒有資料時進行空閒
IdleStrategy idleStrategy = new SleepingIdleStrategy();
// 建立Aeron上下文
Aeron.Context ctx = new Aeron.Context();
// 建立Aeron例項
Aeron aeron = Aeron.connect(ctx);
// 宣告 channel 和 streamId String channel = "aeron:udp?endpoint=localhost:40123";
int streamId = 10;
ConcurrentPublication publication = aeron.addPublication(channel, streamId);// 建立獨佔釋出者,作用是確保只有一個釋出者,避免多個釋出者同時釋出資料
// 等待發布者連線
while (!publication.isConnected()) {
idleStrategy.idle();
}
System.out.println("Publication connected");
for (int i = 0; i < 10000; i++) {
Thread.sleep(1000);
String s = "Hello World! From SDET!" + i;
byte[] bytes = s.getBytes();
UnsafeBuffer buffer = new UnsafeBuffer(bytes);
while (publication.offer(buffer) < 0) {
idleStrategy.idle();
}
System.out.println("Published message: " + s);
}
}
}
建立 Subscription
同上,建議大家去直接官方原始碼中獲取完成的 Demo。
import io.aeron.Aeron;
import io.aeron.Subscription;
import org.agrona.concurrent.BackoffIdleStrategy;
import org.agrona.concurrent.IdleStrategy;
public class AeronSubscription {
public static void main(String[] args) {
// 建立Aeron上下文
Aeron.Context aeronCtx = new Aeron.Context();
// 建立Aeron例項
Aeron aeron = Aeron.connect(aeronCtx);
// 建立訂閱,並指定channel和streamId
Subscription subscription = aeron.addSubscription("aeron:udp?endpoint=localhost:40123", 10);
// 建立空閒策略
IdleStrategy idleStrategy = new BackoffIdleStrategy();
while (true) {
// 從訂閱中獲取資料
int fragments = subscription.poll((buffer, offset, length, header) -> {
// 建立一個位元組陣列,用於存放資料
byte[] data = new byte[length];
buffer.getBytes(offset, data);// 將資料從buffer中讀取到data中
System.out.println(buffer);// 列印buffer
System.out.println(offset);// 列印offset
System.out.println(header.position());// 列印position
System.out.println(header.termId());// 列印termId
System.out.println(header.sessionId());// 列印sessionId
System.out.println("Received message: " + new String(data));
System.out.println("------------------------");
}, 10);
idleStrategy.idle(fragments);
}
}
}
Aeron 效能為何這麼高
Aeron 的高效能得益於多個設計上的最佳化,特別是在記憶體管理、網路傳輸和延遲控制等方面。以下是其主要的效能優勢:
零複製記憶體管理
Aeron 的一個關鍵特性是採用了零複製 (Zero-Copy) 技術,特別是在資料傳輸和儲存中。資料透過直接在記憶體中傳輸,而不是進行昂貴的複製操作,避免了 CPU 和記憶體之間不必要的資料移動。透過 Direct Memory,Aeron 可以直接從使用者空間寫入到核心緩衝區,減少了資料複製和上下文切換的開銷。
高效的共享記憶體模型
Aeron 使用了 共享記憶體 (Shared Memory) 模型來進行程序間通訊。這種設計使得多個程序能夠直接讀寫共享的記憶體區域,而不需要經過傳統的作業系統緩衝區或磁碟檔案。這種方式顯著降低了延遲,提高了資料傳輸速度。對於同一臺機器上的程序間通訊(IPC),Aeron 透過共享記憶體提供幾乎為零的延遲。
面向流的架構
Aeron 將通訊設計成流(Stream)模型,透過 Stream ID 來標識不同的資料流。每個流都擁有獨立的緩衝區和流控制機制,這種設計提高了訊息的傳輸效率,並降低了多個流之間的相互干擾。每個流的訊息都能獨立處理,最佳化了吞吐量和併發效能。
低延遲網路協議
Aeron 支援 UDP 協議,允許在不同機器間進行快速資料傳輸。相較於基於 TCP 的傳統訊息中介軟體,UDP 更輕量,減少了協議棧中的處理步驟,進一步降低了延遲。同時,Aeron 內部實現了自己的 flow control 和 error recovery 機制,確保即使在高負載的情況下也能保證訊息的有序和可靠傳輸。
高效的傳輸機制
Aeron 最大程度地減少了上下文切換、記憶體分配和同步鎖等開銷。它透過使用單執行緒模型來減少多執行緒上下文切換帶來的開銷,同時利用現代硬體的多核架構進行高效的資料處理。
在 Aeron 中,釋出者(Publication)和訂閱者(Subscription)的設計使得訊息可以以高效的方式傳遞。在釋出和訂閱的過程中,Aeron 透過直接記憶體對映和環形緩衝區實現了低延遲的資料傳遞,不會像傳統的訊息中介軟體那樣存在大量的佇列或緩衝區複製。
FunTester 原創精華
- 混沌工程、故障測試、Web 前端
- 服務端功能測試
- 效能測試專題
- Java、Groovy、Go
- 白盒、工具、爬蟲、UI 自動化
- 理論、感悟、影片