LMAX 架構
LMAX 是新的零售金融交易平台。因此,它必須以低延遲處理許多交易。系統建構在 JVM 平台上,並以商業邏輯處理器為中心,該處理器可以在單一執行緒上每秒處理 600 萬筆訂單。商業邏輯處理器使用事件來源完全在記憶體中執行。商業邏輯處理器周圍環繞著中斷器 - 一個並行元件,實作一個無需鎖定的佇列網路。在設計過程中,團隊得出結論,使用佇列的高效能並行模型的最新方向與現代 CPU 設計根本不一致。
2011 年 7 月 12 日
在過去幾年中,我們不斷聽到「免費午餐已經結束」[1] - 我們不能期待個別 CPU 速度的提升。因此,要撰寫快速程式碼,我們需要明確使用多個處理器和並行軟體。這不是好消息 - 撰寫並行程式碼非常困難。鎖定和旗標難以推理和測試 - 這表示我們花更多時間擔心滿足電腦需求,而不是解決網域問題。各種並行模型,例如 Actor 和軟體交易記憶體,旨在讓這項工作更容易 - 但仍然會帶來會造成錯誤和複雜性的負擔。
因此,去年三月,我對 LMAX 在倫敦 QCon 的演講感到著迷。LMAX 是個新的零售金融交易平台。它的商業創新在於它是一個零售平台,讓任何人皆可交易各種金融衍生性商品[2]。像這樣的交易平台需要極低的延遲,因為市場變動快速,交易必須快速處理。零售平台增加了複雜性,因為它必須為許多人執行此操作。因此,結果是更多使用者、大量交易,而所有交易都需要快速處理。[3]
考量到轉向多核心思考,這種嚴苛效能自然會建議使用明確的並行程式設計模型,而這確實是他們的起點。但在 QCon 引起人們注意的是,這並非他們的最終落腳處。事實上,他們最後在單一執行緒上執行平台的所有商業邏輯:來自所有客戶、所有市場的所有交易。執行緒將使用商品硬體每秒處理 600 萬筆訂單。[4]
低延遲處理大量交易,而且沒有並行程式碼的複雜性,我怎麼能抗拒深入探討呢?幸運的是,LMAX 與其他金融公司的另一個不同點是,他們很樂意討論他們的技術決策。因此,現在 LMAX 已投入生產一段時間,是時候探討其引人入勝的設計了。
整體結構

圖 1:LMAX 的三區塊架構
在頂層,架構有三個部分
- 商業邏輯處理器[5]
- 輸入中斷器
- 輸出中斷器
顧名思義,商業邏輯處理器處理應用程式中的所有商業邏輯。如上所述,它以單執行緒 Java 程式執行此操作,該程式會對方法呼叫做出反應並產生輸出事件。因此,它是一個簡單的 Java 程式,除了 JVM 本身之外,不需要任何平台架構來執行其他程式,這讓它可以在測試環境中輕鬆執行。
儘管 Business Logic Processor 可以執行在一個簡單的環境中進行測試,但要讓它在生產環境中執行,則需要更複雜的編排。輸入訊息需要從網路閘道器中取出並取消封送、複製和記錄。輸出訊息需要封送以利網路傳輸。這些任務由輸入和輸出中斷器處理。與 Business Logic Processor 不同的是,這些是並行元件,因為它們涉及到同時緩慢且獨立的 IO 作業。它們是專門為 LMAX 設計和建置的,但它們(就像整體架構一樣)適用於其他地方。
商業邏輯處理器
將所有內容保存在記憶體中
Business Logic Processor 以順序的方式接收輸入訊息(以方法呼叫的形式),對其執行商業邏輯,並發出輸出事件。它完全在記憶體中執行,沒有資料庫或其他永久儲存。將所有資料保留在記憶體中有兩個重要的優點。首先,它很快 - 沒有資料庫提供緩慢的 IO 存取,也沒有任何交易行為需要執行,因為所有處理都是順序完成的。第二個優點是它簡化了程式設計 - 不需要進行物件/關聯性對應。所有程式碼都可以使用 Java 的物件模型撰寫,而無需對資料庫對應進行任何妥協。
使用記憶體中結構有一個重要的後果 - 如果所有東西都崩潰了會發生什麼事?即使是最有韌性的系統也容易受到有人切斷電源的影響。處理此問題的核心是事件來源 - 這意味著 Business Logic Processor 的目前狀態完全可以透過處理輸入事件來推導出來。只要輸入事件串流保留在耐用的儲存體中(這是輸入中斷器的工作之一),您就可以透過重新播放事件來隨時重新建立商業邏輯引擎的目前狀態。
了解這一點的一個好方法是想到版本控制系統。版本控制系統是一連串的提交,您可以在任何時候透過套用這些提交來建置一個工作副本。VCS 比 Business Logic Processor 還要複雜,因為它們必須支援分支,而 Business Logic Processor 則是一個簡單的順序。
因此,理論上,您可以透過重新處理所有事件來隨時重建 Business Logic Processor 的狀態。然而,在實務上,如果您需要啟動一個,這將會花費太長的時間。因此,就像版本控制系統一樣,LMAX 可以建立 Business Logic Processor 狀態的快照,並從快照中還原。它們會在低活動期間每晚建立一個快照。重新啟動 Business Logic Processor 很快速,完整的重新啟動 - 包括重新啟動 JVM、載入最近的快照,以及重新播放一天的記錄 - 不到一分鐘。
快照可以讓啟動新的 Business Logic Processor 變得更快,但如果 Business Logic Processor 在下午 2 點崩潰,則還不夠快。因此,LMAX 讓多個 Business Logic Processor 一直執行[6]。每個輸入事件都由多個處理器處理,但除了其中一個處理器之外,其他處理器的輸出都被忽略。如果即時處理器發生故障,系統就會切換到另一個處理器。這種處理故障轉移的能力是使用事件來源的另一個好處。
透過事件來源到複製品中,他們可以在微秒內切換處理器。除了每晚拍攝快照外,他們每晚也會重新啟動商業邏輯處理器。複製允許他們在沒有停機時間的情況下執行此操作,因此他們可以持續 24/7 處理交易。
事件來源之所以有價值,是因為它允許處理器完全在記憶體中執行,但它對診斷來說還有另一個相當大的優點。如果發生一些意外行為,團隊會將事件順序複製到他們的開發環境中並在那裡重播它們。這使他們能夠比在大多數環境中更容易地檢查發生了什麼事。
這種診斷能力延伸到商業診斷。有些商業任務,例如風險管理,需要大量的運算,而處理訂單並不需要這些運算。一個範例是根據客戶目前的交易部位取得風險概況前 20 名客戶的清單。團隊透過啟動一個複製的網域模型並在那裡執行運算來處理這個問題,在那裡它不會干擾核心訂單處理。這些分析網域模型可以有不同的資料模型,在記憶體中保留不同的資料集,並在不同的機器上執行。
調整效能
到目前為止,我已經解釋了商業邏輯處理器速度的關鍵在於循序漸進地在記憶體中執行所有事情。僅執行此操作(並且沒有任何愚蠢的事情)就能讓開發人員撰寫可以處理 10K TPS[7]的程式碼。然後他們發現專注於良好程式碼的簡單元素可以將此提升到 100K TPS 範圍。這只需要良好的程式碼和小型方法,基本上這讓 Hotspot 能夠更好地進行最佳化,讓 CPU 在執行時更有效率地快取程式碼。
要再提升一個數量級需要更聰明一點。LMAX 團隊發現有幾件事有助於達到這個目標。其中一件事是撰寫 java 集合的客製化實作,這些實作被設計成快取友善且小心處理垃圾[8]。一個範例是使用原始 java long 作為 hashmap 鍵,並使用特別撰寫的陣列為後盾的 Map 實作 (LongToObjectHashMap
)。一般來說,他們發現資料結構的選擇通常會產生很大的不同,大多數程式設計師只是抓取他們上次使用的清單,而不是思考哪個實作最適合這個脈絡。[9]
達到此頂尖效能的另一項技術是將注意力放在效能測試上。我早已注意到,人們常談論改善效能的技術,但真正產生差異的,是測試它。即使是優秀的程式設計師,也很擅長建構效能論證,但最後卻是錯的,因此,最好的程式設計師偏好使用剖析器和測試案例,而非推測。[10] LMAX 團隊也發現,先撰寫測試是效能測試非常有效的守則。
程式設計模型
這種處理方式會在撰寫和組織商業邏輯的方式中引入一些限制。第一個限制是,您必須找出與外部服務的任何互動。外部服務呼叫會很慢,而且單一執行緒會暫停整個訂單處理機器。因此,您無法在商業邏輯中呼叫外部服務。相反地,您需要使用輸出事件完成該互動,並等待另一個輸入事件再次將其取回。
我將使用一個簡單的非 LMAX 範例來說明。想像您正在使用信用卡訂購果凍豆。一個簡單的零售系統會取得您的訂單資訊,使用信用卡驗證服務檢查您的信用卡號碼,然後確認您的訂單,所有動作都在單一作業中完成。處理您訂單的執行緒會在等待信用卡檢查時封鎖,但對使用者來說,封鎖時間不會很長,而且伺服器可以在等待時,在處理器上執行另一個執行緒。
在 LMAX 架構中,您會將此作業分成兩個。第一個作業會擷取訂單資訊,並透過輸出一個事件(信用卡驗證要求)給信用卡公司來完成。然後,商業邏輯處理器會繼續處理其他客戶的事件,直到在輸入事件串流中收到信用卡驗證事件。在處理該事件時,它會執行該訂單的確認任務。
以這種事件驅動、非同步的方式工作,有點不尋常,不過使用非同步來改善應用程式的回應性,是一項熟悉的技術。它也有助於讓商業流程更具復原力,因為您必須更明確地思考遠端應用程式可能發生的不同情況。
程式設計模型的第二個特點在於錯誤處理。傳統的階段和資料庫交易模式提供有用的錯誤處理功能。如果發生任何問題,很容易捨棄互動中到目前為止發生的一切。階段資料是暫時的,而且可以捨棄,如果使用者在進行複雜的事情時,代價是會造成一些困擾。如果資料庫端發生錯誤,您可以回滾交易。
LMAX 的記憶體中結構在輸入事件中是持續存在的,因此如果出現錯誤,不讓記憶體處於不一致的狀態非常重要。但沒有自動回滾機制。因此,LMAX 團隊非常重視在對記憶體中持續狀態進行任何變動之前,確保輸入事件完全有效。他們發現,在投入生產之前,測試是找出這類問題的關鍵工具。
輸入和輸出中斷器
儘管商業邏輯發生在單一執行緒中,但在呼叫商業物件方法之前,仍有許多任務需要完成。處理的原始輸入以訊息形式從網路上傳送過來,此訊息需要轉換成商業邏輯處理器易於使用的形式。事件溯源依賴於保留所有輸入事件的持久性日誌,因此每個輸入訊息都需要記錄到持久性儲存體中。最後,架構依賴於商業邏輯處理器叢集,因此我們必須在這個叢集中複製輸入訊息。同樣地,在輸出端,輸出事件需要轉換成可透過網路傳輸的格式。

圖 2:輸入中斷器執行的活動(使用 UML 活動圖符號)
複製器和日誌記錄器涉及 IO,因此相對較慢。畢竟,商業邏輯處理器的核心概念是避免執行任何 IO。此外,這三個任務相對獨立,所有任務都需要在商業邏輯處理器處理訊息之前完成,但可以按任何順序完成。因此,與商業邏輯處理器(其中每個交易都會改變後續交易的市場)不同,並行運算非常合適。
為了處理這種並行運算,LMAX 團隊開發了一個特殊的並行運算元件,他們稱之為中斷器[11]。
粗略地說,您可以將中斷器視為佇列的多播圖形,其中生產者在其中放置物件,並透過個別的下游佇列傳送給所有消費者以進行平行使用。當您查看內部時,您會看到這個佇列網路實際上是一個單一資料結構 - 環形緩衝區。每個生產者和消費者都有順序計數器,用於指出緩衝區中它目前正在處理的插槽。每個生產者/消費者會寫入自己的順序計數器,但可以讀取其他順序計數器。這樣,生產者可以讀取消費者的計數器,以確保它要寫入的插槽可用,而無需對計數器進行任何鎖定。同樣地,消費者可以透過觀察計數器,確保它只在其他消費者處理完訊息後才處理訊息。

圖 3:輸入中斷器協調一個生產者和四個消費者
輸出中斷器類似,但它們只有兩個用於轉換和輸出的順序消費者。[12]輸出事件組織成數個主題,以便可以將訊息傳送給對它們感興趣的接收者。每個主題都有自己的中斷器。
我所描述的中斷器以一個生產者和多個消費者的方式使用,但這並非中斷器設計的限制。中斷器也可以與多個生產者一起使用,在這種情況下,它仍然不需要鎖定。[13]
中斷器設計的優點之一是,如果消費者遇到問題並落後,它可以讓消費者更容易快速追趕上進度。如果非封送器在處理第 15 個插槽時遇到問題,並在接收器位於第 31 個插槽時返回,它可以從第 16-30 個插槽中一次批次讀取資料以追趕進度。從中斷器批次讀取資料可以讓落後的消費者更容易快速追趕上進度,從而降低整體延遲。
我已在此描述事項,其中包含一個記錄器、一個複製器和一個非封送器,這的確是 LMAX 所執行的動作。但此設計允許多個這些元件執行。如果您執行兩個記錄器,一個記錄器會採用偶數插槽,另一個記錄器會採用奇數插槽。如果需要,這允許這些 IO 作業進一步並行。
環狀緩衝區很大:輸入緩衝區有 2000 萬個插槽,每個輸出緩衝區有 400 萬個插槽。順序計數器是 64 位元長整數,即使環狀插槽換行也會單調遞增。[14]緩衝區設定為 2 的冪次,因此編譯器可以執行有效率的模數運算,將順序計數器編號對應到插槽編號。與系統的其他部分一樣,中斷器會在過夜時彈跳。此彈跳主要是為了清除記憶體,以便在交易期間減少昂貴的垃圾回收事件發生的機會。(我也認為定期重新啟動是個好習慣,這樣您就可以演練如何在緊急情況下執行此動作。)
記錄器的任務是將所有事件儲存在耐用的形式中,以便在發生任何問題時可以重新播放。LMAX 不使用資料庫來執行此動作,只使用檔案系統。它們將事件串流到磁碟上。以現代術語來說,機械磁碟的隨機存取速度非常慢,但串流速度非常快,因此標語為「磁碟是新的磁帶」。[15]
我之前提到 LMAX 在叢集中執行其系統的多個副本,以支援快速故障轉移。複製器會讓這些節點保持同步。LMAX 中的所有通訊都使用 IP 多播,因此客戶端不需要知道哪個 IP 位址是領導節點。只有領導節點會直接聆聽輸入事件並執行複製器。複製器會將輸入事件廣播到追隨節點。如果領導節點當機,它的心跳就會消失,另一個節點會成為領導節點,開始處理輸入事件,並啟動其複製器。每個節點都有自己的輸入中斷器,因此有自己的記錄檔,並執行自己的非封送動作。
即使使用 IP 多播,仍需要複製,因為 IP 訊息可能會在不同的節點中以不同的順序到達。領導節點會為其餘處理提供確定性的順序。
解碼器會將事件資料從線路上轉換成 Java 物件,可用於呼叫商業邏輯處理器上的行為。因此,與其他消費者不同,它需要修改環狀緩衝區中的資料,以便儲存此已解碼的物件。此處的規則是,消費者被允許寫入環狀緩衝區,但每個可寫入欄位只能有一個並行的消費者被允許寫入。這保留了僅有一個寫入器的原則。 [16]

圖 4:擴充中斷器的 LMAX 架構
中斷器是一個通用元件,可以在 LMAX 系統外部使用。通常,金融公司對其系統保密,即使對與其業務無關的項目也保持沉默。LMAX 不僅公開其整體架構,還開放中斷器程式碼的原始碼,這讓我非常高興。這不僅允許其他組織使用中斷器,還允許對其並行屬性進行更多測試。
佇列及其缺乏機械同情心
LMAX 架構之所以引起人們的注意,是因為它是一種與大多數人思考的高效能系統截然不同的方法。到目前為止,我已經討論了它的運作方式,但尚未深入探討為什麼要以這種方式開發它。這個故事本身就很有趣,因為這個架構並非憑空出現。在團隊決定採用這個架構之前,花費了很長一段時間嘗試更傳統的替代方案,並了解它們的缺陷。
現今大多數商業系統的核心架構依賴於透過交易資料庫協調的多個主動式工作階段。LMAX 團隊熟悉這種方法,並且確信它不適用於 LMAX。此評估是基於 Betfair(成立 LMAX 的母公司)的經驗。Betfair 是個博彩網站,允許人們投注體育賽事。它處理大量流量,且有許多競爭,體育博彩往往會在特定賽事周圍爆發。為了讓它發揮作用,他們擁有周圍最熱門的資料庫安裝,並且必須採取許多不自然的方式才能讓它發揮作用。根據此經驗,他們知道要維持 Betfair 的效能有多麼困難,並且確信這種架構不適用於交易網站所需的極低延遲。因此,他們必須找到不同的方法。
他們最初的做法是遵循現今許多人所說的話 - 要獲得高性能,您需要使用明確的並行性。對於這個場景,這表示允許多個執行緒平行處理訂單。然而,並行性通常會遇到的問題是,這些執行緒必須彼此通訊。處理訂單會改變市場狀況,而這些狀況需要傳達。
他們在早期探討的做法是 Actor 模型及其表親 SEDA。Actor 模型仰賴於獨立、主動的物件,這些物件擁有自己的執行緒,並透過佇列彼此通訊。許多人發現這種並行性模型比嘗試執行基於鎖定原語的事情容易處理得多。
該團隊使用 Actor 模型建立了一個原型交易所,並對其執行效能測試。他們發現處理器花費更多時間在管理佇列,而非執行應用程式的實際邏輯。佇列存取是一個瓶頸。
當效能被推升到這種程度時,考量現代硬體的建構方式便開始變得重要。Martin Thompson 喜歡使用的詞彙是「機械同理心」。這個詞彙源自於賽車駕駛,反映出駕駛對車輛具有天生的感覺,因此他們能夠感受到如何發揮車輛的最佳效能。許多程式設計師(我承認我屬於這個陣營)對於程式設計如何與硬體互動並沒有太多機械同理心。更糟的是,許多程式設計師認為自己擁有機械同理心,但這建立在硬體運作方式的觀念上,而這些觀念現在已經過時好幾年了。
影響延遲的現代 CPU 主要因素之一,是 CPU 如何與記憶體互動。現今進入主記憶體對於 CPU 來說是一個非常緩慢的操作。CPU 具有多個層級的快取,每個層級都顯著地更快。因此,要提升速度,您需要將程式碼和資料放入那些快取中。
在某個層面上,演員模型在此有所幫助。你可以將演員視為其自己的物件,用於群組程式碼和資料,這是一個用於快取的自然單元。但演員需要溝通,而他們透過佇列進行溝通,而 LMAX 團隊觀察到,干擾快取的是佇列。
說明如下:為了將一些資料放入佇列,你需要寫入該佇列。同樣地,為了從佇列中取出資料,你需要寫入佇列以執行移除。這是寫入競爭,多個客戶端可能需要寫入相同的資料結構。為了處理寫入競爭,佇列通常使用鎖定。但如果使用鎖定,可能會導致發生至核心程式的內容切換。當這發生時,所涉及的處理器可能會失去其快取中的資料。
他們得出的結論是,為了獲得最佳快取行為,你需要一個設計,其中只有一個核心寫入任何記憶體位置[17]。多個讀取器沒問題,處理器通常在其快取之間使用特殊的高速連結。但佇列不符合單一寫入原則。
此分析讓 LMAX 團隊得出幾個結論。首先,它導致了中斷器的設計,它堅定地遵循單一寫入限制。其次,它導致了探索單執行緒業務邏輯方法的想法,提問如果單一執行緒免於並行管理,它的速度可以有多快。
在單一執行緒上工作的精髓,是確保你有一個執行緒在一個核心上執行,快取會升溫,並且盡可能多的記憶體存取會進入快取,而不是主記憶體。這表示程式碼和資料的工作集需要盡可能持續地存取。此外,將小型物件與程式碼和資料放在一起,允許它們作為一個單元在快取之間交換,簡化快取管理並再次改善效能。
LMAX 架構路徑的必要部分是效能測試的使用。基於建立和效能測試原型,考量並放棄基於行為者的方法。同樣地,改善各種元件效能的許多步驟都是由效能測試啟用的。機械同理心非常有價值,它有助於形成關於你可以做出哪些改進的假設,並引導你向前邁進,而不是後退,但最終,測試會給你令人信服的證據。
然而,這種風格的效能測試並不是一個理解良好的主題。LMAX 團隊經常強調,提出有意義的效能測試通常比開發生產程式碼更困難。機械同理心對於開發正確的測試很重要。除非你考慮 CPU 的快取行為,否則測試低階並行元件是沒有意義的。
一個特別的教訓是撰寫針對空元件的測試的重要性,以確保效能測試足夠快,才能真正衡量實際元件的執行情況。撰寫快速的測試程式碼並不比撰寫快速的生產程式碼容易,而且很容易因為測試不如它試圖衡量的元件快而得到錯誤的結果。
您應該使用這個架構嗎?
乍看之下,這個架構似乎只適用於非常小的利基市場。畢竟,導致它的驅動力是要能夠以非常低的延遲執行大量複雜的交易,大多數應用程式不需要以 600 萬 TPS 執行。
但這個應用程式讓我著迷的是,他們最終得到了一個設計,消除了困擾許多軟體專案的大部分程式設計複雜性。圍繞交易資料庫的傳統並行工作階段模型並非沒有麻煩。與資料庫的關係通常需要付出不小的努力。物件/關係對應工具可以幫助處理資料庫的大部分痛苦,但它並不能處理所有問題。大多數企業應用程式的效能調整都涉及對 SQL 的調整。
現在,你可以讓你的伺服器擁有比我們這些老傢伙更多的主記憶體,而我們只能獲得磁碟空間。越來越多的應用程式完全有能力將它們所有的工作集放入主記憶體中,從而消除了複雜性和遲緩的來源。事件來源提供了解決記憶體中系統耐用性問題的方法,在單一執行緒中執行所有內容可以解決並行問題。LMAX 的經驗表明,只要你需要的 TPS 少於幾百萬,你就會有足夠的效能空間。
這裡與日益增長的 CQRS 興趣有相當大的重疊。事件來源的內存處理器是 CQRS 系統命令端的自然選擇。(儘管 LMAX 團隊目前不使用 CQRS。)
那麼,什麼跡象表明你不應該走這條路?對於像這樣的鮮為人知的技術來說,這始終是一個棘手的問題,因為這個行業需要更多時間來探索其界限。然而,一個起點是思考鼓勵這種架構的特徵。
一個特徵是,這是一個連接的域,其中處理一個交易總是有可能改變後續交易的處理方式。對於彼此更獨立的交易,協調的需求較少,因此使用並行運行的單獨處理器變得更具吸引力。
LMAX 專注於找出事件如何改變世界的後果。許多網站更注重於獲取現有的信息存儲,並向他們能找到的盡可能多的眼球呈現該信息的各種組合 - 例如,想想任何媒體網站。在這裡,架構挑戰通常集中在正確獲取緩存。
LMAX 的另一個特點是,這是一個後端系統,因此考慮它對在交互模式中運作的事物的適用性是合理的。越來越多的 Web 應用程序幫助我們習慣於對請求做出反應的伺服器系統,這是一個與此架構非常契合的方面。這種架構比大多數此類系統更進一步的地方是其對異步通信的絕對使用,從而導致了我之前概述的編程模型的變化。
這些變更對大多數團隊來說需要一些時間適應。大多數人傾向於以同步方式思考程式設計,而且不習慣處理非同步性。然而,非同步通訊早已是回應性的必要工具。隨著 AJAX 和 node.js 在 javascript 世界中更廣泛使用非同步通訊,觀察這是否會鼓勵更多人研究這種風格會很有趣。LMAX 團隊發現,雖然需要一些時間才能適應非同步風格,但很快就變得自然且通常更容易。特別是,在此方法下,錯誤處理變得容易得多。
LMAX 團隊確實認為協調交易資料庫的日子已屈指可數。因為使用這種架構可以更輕鬆地撰寫軟體,而且執行速度更快,消除了傳統中央資料庫的大部分合理性。
對我來說,我發現這是一個非常令人興奮的故事。我的許多目標是專注於模擬複雜領域的軟體。像這樣的架構提供了良好的關注點分離,讓人們可以專注於領域驅動設計,並將大部分平台複雜性分開。網域物件與資料庫之間的緊密結合一直令人惱火,像這樣的做法提出了解決之道。
腳註
1: 免費午餐已經結束
這是 Herb Sutter 的一篇著名文章的標題。他將「免費午餐」描述為處理器不斷增加的時脈速度,這讓我們每年都能獲得更多 CPU 效能。他的觀點是,此類時脈週期增加不再會發生,取而代之的是,效能提升將以多核心方式呈現。但要善用多核心,你需要能夠同時運作的軟體,因此,如果不改變程式設計風格,人們將不再免費獲得效能午餐。
2: 對於我認為這項創新的價值,我將保持沉默
3: 使用者基礎
所有交易系統都需要低延遲,因為一筆交易可能會影響後續交易,而且基於快速反應有許多競爭。大多數交易平台都是針對專業人士(銀行、經紀人等),通常有數百名使用者。零售系統有潛力擁有更多使用者,Betfair 有數百萬使用者,而 LMAX 則針對該規模設計。(LMAX 團隊不得透露其實際交易量。)
事實證明,儘管零售系統有很多使用者,但大部分活動都來自於造市商。在波動時期,一個工具每秒可以得到數百次更新,在一個微秒內會有數百筆交易的不尋常微爆發。
4: 硬體
600 萬 TPS 基準是在搭載 32GB RAM 的 3Ghz 雙插槽四核心 Nehalem 架構 Dell 伺服器上測量的。
5: 該團隊不使用業務邏輯處理器這個名稱,事實上他們沒有為該組件命名,僅將其稱為業務邏輯或核心服務。我給它取了一個名字,以便在本文中更輕鬆地討論它。
6: 目前,LMAX 在其主要資料中心執行兩個業務邏輯處理器,並在災難復原站點執行第三個。所有三個都處理輸入事件。
7: 交易中包含什麼
當人們談論交易時機時,問題之一就是交易中到底包含什麼。在某些情況下,它僅比在資料庫中插入新記錄多一點。LMAX 的交易相當複雜,比典型的零售銷售更複雜。
在交易所下單涉及
- 檢查目標市場是否開放接受訂單
- 檢查訂單是否對該市場有效
- 針對訂單類型選擇正確的匹配政策
- 對訂單進行排序,以便每個訂單都能以最佳價格匹配,並與正確的流動性匹配
- 建立並公佈因匹配而進行的交易
- 根據新交易更新價格
8: 在這種延遲規模下,您必須注意垃圾收集器。對於當今幾乎所有系統而言,現代 GC 壓縮對效能不會有任何明顯影響。但是,當您嘗試以最小的抖動處理每秒數百萬筆交易時,GC 暫停就會成為一個問題。要記住的是,短暫存在的物件是可以的,因為它們會被快速收集。永久的物件也是如此,因為它們將永遠存在。有問題的物件是那些將被提升到舊世代,但最終會消失的物件。由於這會使舊世代區域產生碎片,因此會觸發壓縮。
9: 我很少思考要使用哪種集合實作。當您不在效能關鍵的程式碼中時,這是完全合理的。不同的脈絡建議不同的行為。
10: 一個有趣的旁注。雖然 LMAX 團隊分享了許多目前對函數式程式設計的興趣,但他們相信物件導向方法為此類問題提供了更好的方法。他們注意到,當他們努力撰寫更快的程式碼時,他們會從函數式風格轉向物件導向風格。部分原因是函數式風格需要複製資料以維持不變性。但這也是因為物件提供了複雜領域的更好模型,並提供了更豐富的資料結構選擇。
11: 「中斷器」這個名稱是受到幾個來源的啟發。其一是 LMAX 團隊將此元件視為某種會中斷當前並行思考的事物。另一個是回應 Java 正在引入相位器的事實,因此自然也包含中斷器。
12: 也可以記錄輸出事件。這具有不需要重新計算的優點,如果需要為下游服務重新播放這些事件。然而,在實務上,這是沒有價值的。業務邏輯是確定且非常快速的,因此儲存結果沒有好處。
13: 儘管它確實需要在此情況下使用 CAS 指令。請參閱中斷器技術文件以取得更多資訊。
14: 這表示如果他們每秒處理十億筆交易,計數器將在 292 年後換行,導致一些地獄爆發。他們已決定修復此問題並非優先事項。
15: SSD 在隨機存取方面表現得更好,但類似磁碟的 IO 系統會讓它們變慢。
16: 撰寫欄位時的另一個複雜性是您必須確保寫入的任何欄位都分隔到不同的快取行中。
17: 確保單一寫入器到記憶體位置
遵循單一寫入器原則的一個複雜性是處理器不會一次擷取一個位置的記憶體。相反地,它們會將多個連續的位置(稱為快取行)一次掃入快取中。以快取行區塊存取記憶體顯然更有效率,但也表示您必須確保您沒有由不同核心寫入的快取行中的位置。因此,例如,中斷器的順序計數器會填補以確保它們出現在不同的快取行中。
致謝
金融機構通常對其技術工作保密,通常沒有什麼理由。這是一個問題,因為它阻礙了專業人員從經驗中學習的能力。因此,我特別感謝 LMAX 在討論他們的經驗時表現出的開放態度,無論是在這篇文章中還是他們其他材料中。
Disruptor 的主要創建者是 Martin Thompson、Mike Barker 和 Dave Farley
Martin Thompson 和 Dave Farley 詳細介紹了 LMAX 架構,作為本文的基礎。他們還迅速回答了電子郵件問題,以改進我的早期草稿。
並行程式設計是一個棘手的領域,需要大量關注才能勝任,而我並沒有付出這樣的努力。因此,我完全依賴他人來理解並行性,並感謝他們耐心的建議。
進一步閱讀
如果您希望 LMAX 團隊成員提供 LMAX 架構的影片說明,您的最佳選擇是 Martin Thompson 和 Michael Barker 在 2010 年舊金山舉行的 QCon 簡報。
Disruptor 的原始碼 可作為開放原始碼取得。還有一份不錯的 技術文件 (pdf),深入探討了更多內容,以及一系列 部落格和文章。
LMAX 團隊的各個成員都有自己的部落格:Martin Thompson、Michael Barker 和 Trisha Gee。
重大修訂
2011 年 7 月 12 日:首次出版
2011 年 6 月 22 日:開始起草