當前位置: 妍妍網 > 碼農

Garnet: 力壓Redis的C#高效能分布式儲存資料庫

2024-03-19碼農

今天看到微軟研究院開源了一個新的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將繼續在這一領域展現出更多的可能性,並推動相關技術的發展和創新。

    .NET效能最佳化交流群

    相信大家在開發中經常會遇到一些效能問題,苦於沒有有效的工具去發現效能瓶頸,或者是發現瓶頸以後不知道該如何最佳化。之前一直有讀者朋友詢問有沒有技術交流群,但是由於各種原因一直都沒建立,現在很高興的在這裏宣布,我建立了一個專門交流.NET效能最佳化經驗的群組,主題包括但不限於:

  • 如何找到.NET效能瓶頸,如使用APM、dotnet tools等工具

  • .NET框架底層原理的實作,如垃圾回收器、JIT等等

  • 如何編寫高效能的.NET程式碼,哪些地方存在效能陷阱

  • 希望能有更多誌同道合朋友加入,分享一些工作中遇到的.NET效能問題和寶貴的效能分析最佳化經驗。 目前一群已滿,現在開放二群。

    如果提示已經達到200人,可以加我微信,我拉你進群: ls1075

    另外也建立了 QQ群 ,群號: 687779078,歡迎大家加入。