圖片來源: 

Discord

知名免費語音通訊軟體Discord將讀取狀態相關的程式碼,從Go轉移到了Rust。Discord軟體開發工程師Jesse Howarth在自家部落格寫道,受惠於Rust處理記憶體的機制,把讀取狀態服務實作從Go切換到Rust,極大程度地提升服務的效能。

以Rust改寫的是Discord讀取狀態服務,該服務追蹤用戶已經訂閱的頻道和訊息,當用戶連接到Discord,每次發送訊息和閱讀訊息時,都會存取讀取狀態服務,也就是說,讀取狀態是不停地被使用的程式區塊,其效能表現會直接影響整個Discord應用程式的使用體驗。

Jesse Howarth表示,為了要確保Discord用起來總是非常順暢快速,便要確保讀取狀態效能良好,使用Go實作的讀取狀態服務,在大多數的時候執行速度很快,但是每隔幾分鐘,程式就會出現大量的延遲高峰,經過Discord工程團隊調查,發現這些延遲是由Go的核心功能,包括記憶體模型和垃圾回收器所造成。

紫色線為Go,藍色線為Rust

用來儲存讀取狀態資訊的資料結構通稱為讀取狀態,整個Discord擁有數十億個讀取狀態,每個用戶每個頻道都有一個讀取狀態,每個讀取狀態具有多個自動更新計數器,用來記錄像是頻道中的提及@mentions數量等資訊。

而為了加速計數器的更新,每個讀取狀態伺服器都有讀取狀態LRU(Least Recently Used)快取來紀錄這些資訊,每個快取需要服務數百萬名使用者,而每秒會有數百萬個快取更新。為了維持持久性,Discord以Cassandra資料庫來建立快取,當快取金鑰驅逐(Eviction)時,讀取狀態便會被提交到資料庫中,且每當讀取狀態被更新時,也會被排程30秒後提交到資料庫,平均每秒會有數萬次寫入。而Discord對Go服務採樣,發現每兩分鐘就會有回應時間延遲與CPU使用率高峰出現。

之所以每兩分鐘就會有高峰出現,Jesse Howarth解釋,在Go中,當快取金鑰驅逐時,記憶體不會馬上釋放,而會等待垃圾回收器查詢,沒有受到參照的記憶體才會被釋放,也就是說,記憶體不會在閒置時立即釋放,而需要停留一段時間,當垃圾回收器確定該記憶體真正閒置之後,才會將他釋放。

由於Discord其他以Rust撰寫的部分發展越來越好,且處理讀取狀態的程式容量小且自包含,很適合移植到Rust上,因此Discord工程團隊便決定要用Rust來建構讀取狀態服務。Rust執行速度很快且記憶體效率很好,不需要Runtime或是垃圾收集器來回收記憶體,可以用來開發講究效能的服務。

Jesse Howarth提到,因為Rust沒有垃圾回收器,而是使用特殊的記憶體管理方法,該方法結合記憶體所有權的概念,Rust會追蹤讀取和寫入記憶體的程式,不再用到的記憶體會立即釋放,且因為Rust會在編譯時強制執行記憶體規則,因此在運作時,幾乎不會出現記憶體錯誤。Rust版本的讀取狀態服務,當用戶的讀取狀態從LRU快取中驅逐時,Rust便會立即釋放該記憶體,不需要等待垃圾回收器釋放。

Discord現在於許多部分都使用Rust開發,包括遊戲SDK、影片捕捉和編碼,以及部分後端服務等,現在當工程團隊在開始新專案時,也會優先考慮Rust。


Advertisement

更多 iThome相關內容