Garnet: 力壓Redis的C#高性能分佈式存儲數據庫

今天看到微軟研究院開源了一個新的C#項目,叫Garnet,它實現了Redis協議,可以直接將Redis替換爲Garnet,客戶端不需要任何修改。根據其官網的信息,簡單的介紹一下它。

開源倉庫地址:https://github.com/microsoft/garnet
文檔地址:https://microsoft.github.io/garnet/

Garnet是微軟研究院基於C# .NET8.0開發的一種新型遠程緩存存儲系統,它設計目的是實現極速、可擴展和低延遲。Garnet能夠在單節點內進行線程擴展,並支持分片集羣執行,具備複製、檢查點、故障轉移和事務處理功能。它可以在主內存以及分層存儲(如SSD和Azure存儲)上運行。Garnet提供豐富的API接口和強大的可擴展性模型。

Garnet使用Redis的RESP協議作爲其主要通信協議,因此可以使用大多數編程語言中現成的Redis客戶端,例如C#中的StackExchange.Redis。與其他開源緩存存儲相比,Garnet在性能、延遲、可擴展性和持久性方面都有顯著提升。

需要注意的是,Garnet是微軟研究院的一個研究項目,應當作爲研究項目來對待。儘管如此,我們是一羣對此充滿熱情的研究人員和開發人員,目前正全職工作於此,以使其儘可能穩定和高效。我們的目標是圍繞Garnet建立一個活躍的社區。實際上,Garnet的質量已經足夠高,以至於微軟的幾個一線團隊和平臺團隊已經在內部部署了Garnet多個月。

Garnet提供以下主要優勢:

  • 與可比的開源緩存存儲相比,在小批量和多客戶端會話中,服務器吞吐量(每秒操作數)提高了數個數量級。
  • 在標準雲端(Azure)機器上,啓用加速TCP的情況下,單個操作的極低延遲(在99.9百分位數時常常少於300微秒),適用於Windows和Linux。
  • 隨着客戶端數量的增加,無論是否客戶端批處理,都能實現更好的可伸縮性。
  • 使用單個共享內存服務器實例,可以利用服務器機器的所有CPU/內存資源(無需節點內集羣)。
  • 支持超大內存數據集,可以溢出到本地和雲存儲設備。
  • 具備數據庫功能,如快速檢查點和恢復,以及發佈/訂閱。
  • 支持多節點分片哈希分區(Redis "集羣"模式)、狀態遷移和複製。
  • 經過全面測試,擁有包括Garnet及其存儲層Tsavorite在內的數千個單元測試。
  • 一個易於進化和擴展的C#代碼庫。

性能

上面的簡介顯示出Garnet有非常多的優點,不過我最關心的是它的性能到底怎麼樣,看了基準測試的相關結果,總體還是讓我非常喫驚,一起來看看性能到底怎麼吧。

具體性能測試詳情可以查看鏈接: https://microsoft.github.io/garnet/docs/benchmarking/results-resp-bench

性能測試環境

我們配置了兩臺運行Linux(Ubuntu 20.04)的Azure Standard F72s v2虛擬機(每臺提供72個虛擬CPU和144 GiB內存),並啓用了加速TCP功能。這種SKU的好處在於我們可以確保不會與其他虛擬機共置,這將優化性能。其中一臺機器運行不同的緩存服務器,另一臺專門用於發出工作負載。我們使用名爲Resp.benchmark的基準測試工具來生成所有結果。我們將Garnet與撰寫本文時最新的開源版本Redis(v7.2)、KeyDB(v6.3.4)和Dragonfly(v6.2.11)進行了比較。在這些實驗中,我們使用均勻隨機分佈的鍵(Garnet的共享內存設計在傾斜工作負載下的好處更大)。所有數據在這些實驗中都適合內存。基準系統根據可用信息進行了儘可能多的調整和優化。下面,我們總結了我們實驗中使用的每個系統的啓動配置。

基本命令性能

我們通過改變負載大小、批量大小和客戶端線程數,對基本的GET/SET操作的吞吐量和延遲進行了測量。在吞吐量實驗中,我們在運行實際工作負載之前,先向Garnet預加載一個小型數據庫(1024個鍵)和一個大型數據庫(256M個鍵)。相比之下,我們的延遲實驗是在一個空數據庫上進行的,並且是對一個小鍵空間(1024個鍵)的GET/SET命令的組合工作負載進行的。

吞吐量 GET

在圖1所示的實驗中,我們使用了大批量的GET操作(每批4096個請求)和小負載(8字節的鍵和值)來最小化網絡開銷。隨着客戶端會話數的增加,我們觀察到Garnet的可擴展性比Redis或KeyDB更好。Dragonfly展示了類似的擴展性,儘管只能達到16個線程。還要注意,DragonFly是一個純內存系統。總的來說,即使數據庫大小(即預加載的不同鍵的數量)更大(達到2.56億個鍵)超過了處理器緩存的大小,Garnet的吞吐量相對於其他系統始終更高。

圖1:在數據庫大小爲(a) 1024個鍵,和(b) 2.56億個鍵的情況下,隨着客戶端會話數的變化,吞吐量(對數尺度)。

即使對於小批量大小,Garnet也通過獲得一致更高的吞吐量,超過了競爭系統,如圖2所示。這一結果不受實際數據庫大小的影響。


圖2:在數據庫大小爲(a) 1024個鍵,和(b) 2.56億個鍵的情況下,隨着批量大小的變化,吞吐量(對數尺度)。

延遲 GET/SET

接下來,我們通過發出80%的GET和20%的SET請求的混合體,來測量各種系統的客戶端延遲,並將其與Garnet進行比較。因爲我們關心的是延遲,所以我們保持數據庫大小較小,同時變化工作負載的其他參數,如客戶端線程數、批量大小和負載大小。

圖3展示了隨着客戶端會話數的增加,Garnet的延遲(以微秒計)在各個百分位數上都一直較低且更穩定,與其他系統相比。請注意,這個實驗不使用批處理。


圖3:在不同的客戶端會話數下,延遲變化,(a) 中位數,(b) 第99百分位數,和(c) 第99.9百分位數

Garnet的延遲經過了精細調整,以適應客戶端的批處理和高效處理查詢系統的多個會話。在我們的下一組實驗中,我們將批量大小從1增加到64,並在下面的圖中以128個活躍客戶端連接繪製不同百分位數的延遲。如圖4所示,當批量大小增加時,Garnet保持穩定性並實現了比其他系統更低的整體延遲。


圖4:在不同的批量大小下,延遲變化,(a) 中位數,(b) 第99百分位數,和(c) 第99.9百分位數

複雜數據結構性能

Garnet 支持大量不同的複雜數據結構,如Hyperloglog、位圖、有序集合、列表等。下面,我們將爲其中幾個精選的數據結構展示性能指標。

Hyperloglog

Garnet支持其內置的Hyperloglog(HLL)數據結構。該結構使用C#實現,支持更新(PFADD)、計算估算值(PFCOUNT)以及合併(PFMERGE)兩個或更多不同的HLL結構的操作。HLL數據結構通常在內存佔用方面進行優化。我們的實現也不例外,當非零計數數量較低時採用稀疏表示法,超過給定的固定閾值後採用密集表示法,此時內存節省和解壓縮所需額外工作之間的權衡已不再吸引人。爲併發系統(如Garnet)有效更新HyperLogLog(HLL)結構至關重要。因此,我們的實驗特別關注PFADD的性能,並且有意設計了以下情景來壓力測試我們的系統:

  • 大量高爭用更新(例如,批量大小爲4096,數據庫鍵爲1024)隨着線程數量的增加或有效載荷大小的增加。幾次插入後,構建的HyperLogLog(HLL)結構將轉爲使用密集表示法。
  • 大量低爭用更新(例如,批量大小爲4096,數據庫鍵爲256M)隨着線程數量的增加或有效載荷大小的增加。這種調整將增加構建的HyperLogLog(HLL)結構使用稀疏表示法的可能性。因此,我們的測量將考慮處理壓縮數據或爲非零值遞增分配更多空間的額外開銷。

在圖5中,我們展示了第一個實驗場景的結果。Garnet在高爭用情況下擴展性非常好,並且在增加線程數量方面的原始吞吐量一致超過其他所有系統。同樣,隨着有效載荷大小的增加,Garnet 展示了比其他系統更高的總吞吐量。在所有測試的系統中,我們注意到隨着有效載荷大小的增加,吞吐量明顯下降。由於固有的TCP網絡瓶頸,這種行爲是預期之中的。


圖 5:數據庫大小爲1024個鍵時,(a)客戶端會話數量增加,(b)有效載荷大小增加的吞吐量(對數刻度)。

圖 6顯示了上述第二個實驗場景的結果。即使在操作HLL稀疏表示時,Garnet的性能也比任何其他系統都要好,並且在增加客戶端會話數量時能夠實現一致性更高的吞吐量。同樣地,對於增加的有效載荷大小,Garnet通過實現整體更高的吞吐量而勝過競爭對手。請注意,在這兩種情況下,由於操作壓縮數據的開銷,吞吐量與之前的實驗相比都有所降低。


圖 6:數據庫大小爲1M個鍵時,(a)客戶端會話數量增加,(b)有效載荷大小增加的吞吐量(對數刻度)。

在圖 7中,我們進行了與前面所述相同類型的實驗,將客戶端會話數量固定爲64,有效載荷固定爲128字節,同時增加批量大小。請注意,即使對於批量大小爲4,Garnet的吞吐量增益也明顯高於我們測試的任何其他系統。這表明即使對於小批量大小,我們仍然能夠勝過競爭對手的系統。


圖 7:通過64個客戶端會話增加批量大小的吞吐量(對數刻度),對於數據庫有(a)1024個鍵,(b)1M個鍵。

Bitmap

Garnet支持對字符串數據類型的一系列位操作符。這些操作符可以在常數時間內(例如GETBIT、SETBIT)或線性時間內(例如BITCOUNT、BITPOS、BITOP)進行處理。爲了加快處理速度,對於線性時間操作符,我們使用了硬件和SIMD指令。下面我們將呈現這些操作符子集的基準測試結果,包括兩種複雜度類別。與之前類似,我們使用小型數據庫大小(1024個鍵)來評估每個系統在高競爭下的性能,同時通過增加有效載荷大小(1MB)避免所有數據常駐CPU緩存。

在圖8中,我們展示了GETBIT和SETBIT命令的性能指標。在這兩種情況下,隨着客戶端會話數量的增加,Garnet始終保持較高的吞吐量和更好的可擴展性。

圖8:吞吐量(對數刻度),變化的客戶端會話數量,對於數據庫大小爲1024個鍵和1MB有效載荷。

在圖9中,我們評估了BITOP NOT和BITOP AND(有兩個源鍵)對於增加線程數量和1MB有效載荷大小的性能。Garnet在客戶端會話數量增加時保持整體更高的吞吐量,與我們測試的每一個其他系統相比。鑑於我們的數據庫大小相對較小(即只有1024個鍵),在高競爭下它也表現得非常好。


圖9:吞吐量(對數刻度),變化的客戶端會話數量,對於數據庫大小爲1024個鍵和1MB有效載荷。

如圖10和圖11所示,即使對於小批量大小,Garnet也獲得了比我們測試的任何其他系統更高的吞吐量。實際上,即使在批量大小爲4的情況下,Garnet也顯著更快,觀察到吞吐量的明顯差異並不需要太多。


圖10:吞吐量(對數刻度),對於64個客戶端會話的不斷增加批量大小的數據庫,數據庫大小爲1024個鍵和1MB有效載荷。


圖11:吞吐量(對數刻度),對於64個客戶端會話的不斷增加批量大小的數據庫,數據庫大小爲1024個鍵和1MB有效載荷。

總結

上述對Garnet數據庫系統的性能測試,包括基本命令的吞吐量和延遲,以及複雜數據結構的性能。在吞吐量測試中,Garnet在預加載不同大小的數據庫後,表現出比Redis或KeyDB更好的可擴展性和更高的吞吐量,無論是在小數據庫(1024個鍵)還是大數據庫(256M個鍵)上。延遲測試顯示,Garnet在不同客戶端會話數下都保持了較低且穩定的延遲。對於複雜數據結構,如Hyperloglog和Bitmaps,Garnet在處理高爭用更新和位操作時,也展現了優越的性能和可擴展性。在所有測試中,Garnet的性能通常優於其他系統,即使在數據庫大小、客戶端會話數量和負載大小等參數變化時也是如此。

C# .NET以其卓越的性能在技術界備受推崇,這一點從TechEmpower的排名以及衆多性能測試中都得到了充分的體現。它擁有一系列高效的編程特性,包括結構體、內存操作、unsafe代碼塊、Span<T>以及async/await等,這些特性極大地提高了代碼的執行效率和開發的靈活性。在過去,使用C# .NET技術的構建的中間件產品並不常見,但.NET的這些先進特性已經證明了其在高性能中間件領域的巨大潛力。隨着技術的不斷進步,我們有理由相信,C# .NET將繼續在這一領域展現出更多的可能性,並推動相關技術的發展和創新。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章