在高頻交易領域,自動化應用程序天天須要處理數億個市場交易信號,並在全球各交易所之間發送成千上萬的訂單。java
爲了保持競爭力,響應時間必須始終保持在微秒級,特別是在發生相似「黑天鵝」事件的異常高峯期。git
在一個典型的架構中,金融市場的交易信號被轉換成內部的市場數據格式(使用各類協議,如 TCP/IP、UDP 組播和多種格式,如二進制、SBE、JSON、FIX 等)。程序員
這些規範化的消息被髮送到算法服務器、統計引擎、用戶界面、日誌服務器和各類類型的數據庫(內存數據庫、物理數據庫、分佈式數據庫)。github
這條路徑上的任何一個延遲都有可能帶來嚴重後果(好比基於舊價格作出戰略決策或訂單到達交易市場的時間太遲),併爲此付出慘重代價。算法
爲了加快相當重要的幾微秒,大多數券商投入了昂貴的硬件:服務器組配備了超頻液冷的 CPU(在 2020 年,你能夠買到單臺配備了 56 核 5.6 GHz CPU 和 1 TB 內存的服務器)、靠近交易所的數據中心、納秒級高端網絡交換機、海底專線(Hibernian Express 是一家主要的供應商),甚至是微波網絡。數據庫
咱們常常看到高度定製的能夠繞過操做系統的 Linux 內核,數據能夠直接從網卡「跳轉」到應用程序、IPC(進程間通訊),甚至是 FPGA(可編程單用途芯片)。編程
在編程語言方面,C++彷佛是服務器端應用程序的自然競爭者:它速度快,與機器碼很是接近,並且一旦針對目標平臺進行編譯,就能夠提供恆定的處理時間。設計模式
可是,咱們作了一個不同的選擇。性能優化
在過去的14年裏,咱們一直在用Java開發外匯算法交易系統,並使用了很棒但價格實惠的硬件。
因爲團隊規模小,資源有限,技術能力強的開發人員難找,因此使用 Java 意味着咱們能夠快速地改進軟件功能,由於 Java 生態系統比 C 語言生態系統的發佈速度更快。上午討論功能改進,下午就能夠實現、測試併發布到生產環境。服務器
與那些須要幾周甚至幾個月才能發佈更新的大公司相比,這是一個關鍵的優點。在高頻交易領域,一個漏洞能夠在幾秒鐘內抹掉一全年的利潤,因此咱們不打算在質量上作任何妥協。咱們搭建了一個嚴格的敏捷開發環境,包括 Jenkins、Maven、單元測試、夜晚構建和 Jira,使用了不少開源庫和項目。
使用 Java,開發人員能夠專一於直觀的面向對象業務邏輯,而不是浪費時間去調試一些晦澀的內存核心轉儲或管理 C++指針。並且,因爲 Java 強大的內存管理能力,即便是初級程序員也能夠在第一天加入項目時爲系統帶來價值,並且風險很小。
有了良好的設計模式和乾淨的編碼習慣,Java 的速度可與 C++相媲美。
例如,Java 會優化和編譯在應用程序運行期間觀察到的最佳路徑,但 C++會預先編譯全部東西,所以即便未被使用的方法也會成爲可執行二進制文件的一部分。
可是,Java 有一個問題,它讓 Java 成爲一門強大且使人喜好的編程語言,但也成了 Java 的缺點之一(至少對於微秒級應用程序來講)——Java 虛擬機(JVM):
GC 是低延遲應用程序開發人員可能會放棄 Java 的主要緣由。
市場上有一些可用的 Java 虛擬機。
最多見的是 Oracle Hotspot JVM,它在 Java 社區中被普遍使用,主要是一些歷史緣由。
對於很是苛刻的應用程序,有一個很棒的替代方案,也就是 Azul Systems 的 Zing。
Zing 是標準 Oracle Hotspot JVM 的一個強大的替代品。Zing 解決了 GC 停頓和 JIT 編譯問題。
接下來,讓咱們來研究一下 Java 的一些固有問題和可能的解決方案。
像 C++這樣的編程語言被稱爲編譯型語言,由於發佈的代碼徹底是二進制的,能夠直接在 CPU 上執行。
PHP 或 Perl 被稱爲解釋型語言,由於解釋器(安裝在目標機器上)會在運行時編譯每一行代碼。
Java 介於二者之間,它將代碼編譯成 Java 字節碼,並在必要時再將其編譯成二進制的。
Java 不在啓動時編譯代碼的緣由與後續的性能優化有關。經過觀察應用程序運行並分析實時方法調用和類初始化狀況,Java 對常常被調用的代碼部分進行編譯。它甚至可能會根據經驗作出一些假設(某些代碼永遠不會被調用,或者某個對象始終是一個字符串)。
編譯過的代碼執行速度很是快,但有三個缺點:
Zing 經過讓它的 JVM「保存」已編譯的方法和類的狀態(也就是所謂的 profile)來解決這些問題。這個獨特的功能叫作 ReadyNow,也就是說 Java 應用程序能夠始終以最佳速度運行,即便是在重啓以後。
當你使用已有的 profile 從新啓動應用程序,Azul JVM 會當即收回之前的決策並直接編譯重要的方法,以解決 Java 的預熱問題。
此外,你也能夠在開發環境中構建一個 profile 來模擬生產行爲。優化後的 profile 能部署到生產環境中,並知道全部關鍵路徑都已通過編譯和優化。
下圖顯示了交易應用程序(在模擬環境中)的最大延遲。
Hotspot JVM 的延時峯值是顯而易見的,而 Zing 的延時保持得至關穩定。
百分比分佈代表,在 1%的時間內,Hotspot JVM 發生的延遲是 Zing JVM 的 16 倍。
第二個問題是在垃圾回收期間,整個應用程序可能會停頓幾毫秒到幾秒鐘(延遲會隨着代碼複雜性和堆大小的增長而增長),更糟糕的是,你沒法控制這種狀況什麼時候發生。
雖然對不少 Java 應用程序來講,暫停應用程序幾毫秒甚至幾秒是能夠接受的,但對於低延遲應用程序來講,這是一場災難,不管是在汽車、航空航天、醫療仍是金融領域。
GC 影響對於 Java 開發人員來講是一個很大話題,Full GC 一般也叫做「中止世界的停頓(stop-the-world)」,由於它會凍結整個應用程序。
多年來,有不少 GC 算法都試圖下降吞吐量(有多少 CPU 時間用於應用程序邏輯執行而不是垃圾回收)和 GC 停頓(我能夠暫停應用程序多長時間)。
從 Java 9 發佈以來,G1 一直是默認的垃圾回收器,其主要思想是根據用戶提供的時間目標對 GC 停頓進行劃分。它一般提供較短的停頓時間,但以下降吞吐量爲代價。此外,停頓時間隨着堆的大小而增長。
Java 提供了大量的設置參數,從堆大小到回收算法以及分配給 GC 的線程數。所以,Java 應用程序一般會配置大量的參數:
不少開發人員經過各類技術來避免 GC。最主要的是,若是咱們少建立一些對象,那麼後續要清除的對象就越少。
一種古老的(仍然在使用)技術是使用對象池。例如,數據庫鏈接池能夠保存 10 個已經打開的數據庫鏈接,以便在須要時使用。
多線程一般須要鎖,這會致使同步延遲和停頓(特別是當它們共享資源時)。一種流行的方式是使用環形緩衝隊列系統,多個線程能夠在一個無鎖的環境中(請參考disruptor)進行讀寫操做。
一些專家甚至處於無奈而選擇徹底覆蓋 Java 的內存管理機制,由本身來管理內存分配,這雖然解決了問題,但也帶來了更多的複雜性和風險。
所以,咱們須要考慮使用其餘 JVM,因而咱們決定嘗試 Azul Zing JVM。
很快,咱們就可以在幾乎無停頓的狀況下實現很高的吞吐量。
這是由於 Zing 使用了一個叫做 C4(Continuously Concurrent Compacting Collector,連續併發壓縮回收器)的垃圾回收器,它能夠進行無停頓的垃圾回收,而無論 Java 堆有多大(能夠達到 8TB)。
這是經過在應用程序運行時併發映射和壓縮內存來實現的。
此外,它不須要修改代碼,並且延遲和速度方面的改進都是開箱即用的,不須要進行繁雜的配置。
Java 程序員能夠享受到兩方面的好處:Java 的簡單性(不須要擔憂建立太多的新對象)和 Zing 的底層性能,容許系統中出現高度可預測的延遲。
GCeasy提供了通用 GC 日誌分析器,咱們能夠在真實的自動交易應用程序(在模擬環境中)中快速地對 JVM 進行比較。
在咱們的應用程序中,使用 Zing 的 GC 大約比使用標準 Oracle Hotspot JVM 的 GC 快 180 倍。
更使人印象深入的是,GC 停頓一般對應於實際的應用程序停頓時間,而 Zing 的 GC 一般是並行發生的,實際的停頓不多,甚至沒有停頓。
總之,在享受 Java 的簡單和特性的同時,仍然有可能實現高性能和低延遲。C++通常用於開發特定的底層組件,如驅動程序、數據庫、編譯器和操做系統,但大多數現實生活中的應用程序可使用 Java 開發,甚至是要求很高的應用程序。
這就是爲何 Java 是排名第一的編程語言(根據 Oracle 的說法),並擁有數百萬開發者,在全世界有超過 510 億個 Java 虛擬機。
爲何阿里巴巴的程序員成長速度這麼快,看完他們的內部資料我懂了
若是你以爲這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
點贊,轉發,有大家的 『點贊和評論』,纔是我創造的動力。
關注公衆號 『 Java鬥帝 』,不按期分享原創知識。
同時能夠期待後續文章ing🚀