我們爲什麼選 Rust 重寫核心服務?

兩年多來,Kraken 的 Core Backend 團隊一直在用 Rust 來對原本使用 PHP 編寫的服務進行現代化改造,同時還在用 Rust 開發新產品、擴展功能集合並支持不斷增長的加密貨幣交易活動。

1 重寫核心服務

2011 年 Kraken 成立時,PHP 提供了一個兼顧執行安全性、速度和生產力的選項。彼時我們用 PHP 構建了那麼多功能,實在令人印象深刻。但多年以來,Kraken 取得了長足發展,而 PHP 代碼庫開始變得難以擴展,很難共享知識並安全地做出較大的更改。這些核心服務處理的是分佈式數據存儲、加密和信息安全方面的事宜,這類技能組合在 PHP 開發人員中並不常見,他們通常更專注於在現有的 Web 和電商框架上構建內容。

總體而言,Kraken 已進入了爆發式增長階段,代碼庫和工具都需要跟上腳步。考慮到這一點,動態類型的編程語言非常適合早期的構建階段,但隨着代碼庫的擴張和工程師人數的增加,代碼維護起來愈加困難。強類型提供了保證(和格式化的文檔),從而加快了開發速度,讓單個代碼庫可以支持更多開發人員。

我們重寫核心服務的主要目標是:

  1. 儘可能保持系統安全性

  2. 即使系統變得越來越大,也讓系統更易維護、更加健壯

  3. 獲得更好的性能

2 爲什麼選擇 Rust?

2018 年初,Kraken 已經有了用 Go 和 C++ 編寫的生產服務。儘管 Rust 提供了出色的性能、安全性和現代語言結構,但將其作爲重寫核心服務的語言選項還是一種賭注。

Kraken 非常注重安全性。因此,我們不想讓 C++ 代碼參與用戶輸入。即使是世界上最好的 C++ 團隊(如構建 Windows™或 Chrome™的團隊),做出來的代碼中也有約 70%的 CVE 來自於內存安全性問題——諸如釋放後使用、緩衝區溢出、兩次釋放等,這可能會導致內存訪問控制和特權升級攻擊。可是在 Java、Go 或 Rust 等語言中,這些漏洞是被徹底堵死的。

儘管 Go 可以抵禦這類漏洞,但它不提供諸如泛型或求和類型之類的現代編程特性,結果會導致數據建模或重複問題。Kotlin 提供了一個更復雜的類型系統,並且像 Go 一樣,它簡化了異步編程,但是帶有一個承載諸多遺產的 Java 生態系統。

再來看 Rust。它的可靠性和性能讓它在加密貨幣和區塊鏈項目中取得了成功。一些 Kraken 工程師開始拿它做實驗,並視其爲構建可以長期滿足 Kraken 後端需求的系統的一種選項:性能匹敵 C++、現代語言構造有助於準確地建模業務邏輯和錯誤用例、對異步編程有着一流支持、編譯時線程安全,還有充滿活力的生態系統。Rust 的價值主張和社區取得的成功促使 Kraken 在 2018 年中開始用 Rust 來重寫核心服務。

3 兩年後

Core Backend 團隊成績斐然,如今同時負責現代化的 Rust 核心服務和仍在重寫中的舊版 PHP 服務。同時,其他一些團隊已經成功應用了 Rust:Kraken 的期貨團隊加入了我們的行列,他們獨立地將所有後端堆棧遷移到了 Rust 上;Cryptowatch 選擇了 Rust 用於桌面應用程序;Kraken 將冷存儲系統遷移到 Rust;Kraken Digital Asset Bank 也在用 Rust 構建。這種語言本身也有了顯著改進,讓異步網絡服務編寫起來更容易了。

從策略上講,我們決定在 Rust 中重寫完全相同的功能:由於所有 PHP 服務都是無狀態的,因此可以輕鬆地將邏輯(逐個端點地)移植到 Rust。這樣一來,新招募的團隊就可以獲取更多有關底層系統的知識,並可以進行增量部署或輕鬆回滾。我們已經構建了一個全面的集成測試套件,PHP 和 Rust 服務都需要通過它的測試以確保行爲是相似的。將功能移植到 Rust 後,可以更輕鬆、更安全地擴展。

儘管性能提升不是重寫的主要目標,但我們很高興看到 Rust 提供了開箱即用的驚人速度。我們 Tokio 驅動的 RPC 服務器並未做過特別優化(儘管我們通常對內存使用模式非常謹慎),結果每個實例可以支持 150k 請求 / 秒的吞吐量,同時將 p99.9 延遲保持在 3ms 以下。系統的運行速度取決於最慢的部分,雖然我們的 PHP 核心服務不是 Kraken 的唯一瓶頸,但它們的 IO 性能要比 Rust 的低一些,並且對負載更敏感。在將整個端到端路徑遷移到 Rust 並消除瓶頸之後,我們的客戶應該能看到巨大的性能提升。同時,我們會將端點遷移至 Rust、重新設計數據庫和擴展服務,盡一切努力來提高性能和可靠性。

這是將一個端點移植到 Rust 時響應時間的變化

4 用於應用程序服務的 Rust

Rust 通常被宣傳爲一種出色的系統編程語言,非常適合底層任務、命令行實用程序和網絡服務(例如負載均衡器)。許多人認爲 Rust 的複雜性對於一般的業務邏輯來說是很大的劣勢,Rust 的就業市場也太小了,以至於公司很難使用這種語言來完成諸如構建用戶管理系統或 REST API 之類的常見任務。

Rust 非常適合系統編程,但我們也一直用它來做一些通常用更高級別語言(例如 Java、Ruby 或 TypeScript)實現的應用程序服務。正確性在 Kraken 中絕對至關重要,而 Rust 的現代語言結構讓我們更容易編寫正確而健壯的代碼。Rust 缺少垃圾收集的特性在編寫不需要 “關心” 內存管理的通用邏輯時往往被認爲是一種劣勢,但在實踐中這並不是問題,因爲我們正在構建的是無狀態服務,而存儲循環數據從來都不是問題。

但 Rust 需要精確度,我想說的是這是這種語言最大的好處:它的顯式性(受其強大的類型系統支持)帶來了容易審查且運行時可靠的表達性代碼。在這方面,我認爲 Rust 與 Java 和其他同類語言比起來既有更低級別的優勢,也有更高級別的好處。Core Backend 團隊還開發了其他一些技術服務,例如負載均衡器或服務監視流,它們需要良好的性能,而且使用 Rust 讓我們不必在系統和應用程序邏輯語言之間來回切換,還可以重用庫和模式,實踐中這非常方便。

隨着團隊和代碼庫的成長,有效審查代碼的能力變得至關重要。Rust 可以讓行爲清晰且隔離地表現出來,這意味着我們無需過多考慮系統的其他部分——只研究當前函數往往就足夠了。在審查代碼時,我們會看到一個 diff(更改的行和周圍的上下文),雖然可能需要更多時間來深入研究更改,但更快的審覈可以讓開發人員迅速獲得反饋,這是很好的驅動力。在 Rust 中可以肯定的是,編譯後的更改不會出現數據爭用(併發錯誤的主要來源之一)和內存安全問題(我們的大多數代碼都用的是 safe Rust)。我可以很容易地發現可能導致問題的函數(當沒有其他選擇時,Rust 會中止執行)、發現無用的內存副本,並收集開發人員的意圖。Rust 的 linter、Clippy 有助於統一代碼樣式,帶來了更符合習慣、更一致的代碼庫。最近兩年來我審查了成千上萬的合併請求,Rust 爲我帶來了比其他主流編程語言都更高的信心。

Rust 是一種大型而複雜的語言,開發人員很容易在細節上迷失方向。還好我們沒必要爲了保持效率而瞭解所有的細節。根據我們的經驗,Rust 是一種非常有生產力的語言:它具有出色的工具鏈,可以迫使我們徹底建模問題、節省寶貴的調試時間、解決潛在的生產問題,並且非常便於代碼重用(這是生產力的倍增器)。

5 建立一個 Rust 團隊

在這兩年中,我們已經構建了現代化的 Kraken 後端技術棧的基礎、將現有功能重寫爲 Rust、構建了新的 Rust 服務和功能,還組建了一支由 30 多名工程師組成的 Core Backend 團隊。一些開發人員一開始應聘的是 PHP 開發,但加入團隊後學會了 Rust。值得一提的是,Kraken 是一家全球化的遠程優先公司,Core Backend 團隊的工程師來自 15 個國家,工作地點分佈在 12 國境內。

Rust 吸引了很多熱情的開發人員,他們通常對系統編程、分佈式系統或加密技術感興趣。我們目前的 Core Backend 工程師中有很大一部分是 Rust 愛好者,他們通過各種 Rust 在線資源發現了我們的招聘機會,包括 Reddit 和 This Week In Rust(它們多次推薦了我們的招聘信息,謝謝!)。因爲這些社區,人們很久以前就知道 Kraken 在招聘 Rust 開發人員了。

我們的 Core Backend 團隊成員需要在競爭激烈的市場中應對具有挑戰性的技術和業務問題,在世界各地進行遠程工作,而我們提供了與地理位置無關的高額基本薪酬和慷慨的期權。此外,團隊成員幾乎所有時間都在編寫 Rust。這兩年來應聘的衆多候選人很滿意這樣的條件,使我們得以組建世界一流的工程團隊。

我相信使用 Rust 可以幫助人們成爲更好的開發人員,因爲它推動人們重視簡潔的設計和精確度。但光是瞭解 Rust 並不能讓我們成爲出色的工程師。我們看到許多候選人對 Rust 都很滿意,但他們在構建後端系統方面經驗有限。我們僱用了許多有着巨大潛力的初級開發人員,因爲在組建團隊時,平衡是成功的關鍵。經驗豐富的開發人員往往是出色的導師:他們通常擁有簡化事物的智慧,知道不應該過於信任自己,也明白該如何最大限度地發揮自身對業務線的影響。

我們希望隨着越來越多的公司(從 Discord 和 Deliveroo 到亞馬遜和微軟)在 Rust 上加大籌碼,我們可以幫助業界發出一個信號,告訴大家 Rust 的工作機會有很多,並且花時間學習這種語言不會浪費精力。許多經驗豐富的開發人員更願意留在他們擅長的技術棧中,但是有些人可能還是喜歡嘗試擺脫自己的舒適區並挑戰自我。

6Rust 很偉大,但不是完美的!

Rust 讓我們能夠構建許多運行良好的高性能生產代碼。我們有一個龐大的團隊,成員分別負責後端中各種差異巨大的部分。大部分代碼都非常健壯:我們還沒有經歷過 Rust 核心服務的崩潰或恐慌(大致相當於運行時異常)。

總體而言,我可以說我們只遇到過業務邏輯問題、配置錯誤問題,並且遇到了一個一般性的性能問題,其與在 musl libc 上運行的,具有特定內核配置的 Tokio 相關,不過我們用 perf 工具定位後就輕鬆修復了。

儘管這種語言的確很棒,但也有一些衆所周知的侷限困擾着我們。

我們一直在搭配使用 async Rust 與 Future 組合器,並在 async/await 支持進入 nightly 版本後立刻開始使用它。這項功能非常棒,使我們能夠使用 Tokio 構建大規模的併發應用程序。我們並不需要花費太多時間來讓我們的服務器處理超過 10K 的併發連接或實現背壓。但它還是有一些改進餘地:

在工具鏈方面,Cargo 和 Rustup 大大簡化了設置和編譯項目的工作。RustAnalyzer 帶來了顯著的改進,並提供了很棒的 IDE 體驗。編譯時間總體變短了:想要更短也是可以的,但考慮到增量構建和 sccache,現在這樣也不錯了。優化構建確實速度很慢,但總體來說爲了性能和安全性這是很小的代價。具有許可支持的私有 Cargo 註冊表顯然會助力 Rust 的企業應用。我們一直在使用 git 依賴項,但缺乏語義版本控制的支持讓更新過程變得很痛苦。市面上有一些開源的 Cargo 註冊表可用,但 Cargo 本身不支持訪問令牌或憑證。我們很樂意贊助這項工作。

7Kraken 熱愛 Rust

總的來說,Rust 非常成熟,並且它的大多數痛點在其他主流語言也多多少少會存在。Rust 讓代碼重用起來非常輕鬆,並使我們能夠在不犧牲性能的情況下安全地應對快速變化的大型代碼庫。

對我們而言,使用 Rust 不再是一項實驗或賭注。這是我們正在構建的可靠技術,並且 Core Backend 團隊正在尋找熟練的工程師。不得不提的是我們團隊在 Rust 之外的價值觀:Core Backend 團隊利用 Rust 的工程價值和受 Netflix 影響的高性能團隊文化,進一步擴展了 Kraken 的文化,以及對我們使命的承諾。我們相信超越代碼的工程文化,相信自主權。我們拒絕傲慢自大。我們不斷相互學習。不在一個辦公室工作讓我們面臨更大的挑戰,而積極進取的人們可以成爲優秀的工程師,能夠自我驅動、技術精湛,能夠在需求和技術解決方案之間架起橋樑,從而自主前行。我們既看重天賦也看重努力,同時在工作與生活之間保持了良好的平衡,維持健康的體魄。我們關心自己在構建的事物,並全力幫助我們的隊友取得成功。我們意識到完美是出色成果的敵人(衆所周知,Rust 開發人員都是完美主義者!😉)。最後,我們相信團隊會不斷自我完善:如果技術或組織方面出現問題,我們會解決它們。

我們的 Core Backend 團隊目前在招募中級和高級後端工程師以及站點可靠性工程師,這些人員可幫助支持和改善我們的運營、工具鏈和 CI。我們還在聘請測試工程師來幫助我們使用 Rust 和 Cucumber 測試 API。

Kraken 的其他團隊也一直在尋找優秀的工程師,他們選擇 Rust 作爲構建強大和快速響應系統的首選工具:

最後,我們想幫助 Rust 成長。我們已經通過 Kraken Grants 計劃贊助了一些開源工作(例如 iced GUI 框架)。我們很樂意贊助 Rust 項目或相關關鍵項目的個人貢獻者。如果你正在爲 Rust 生態系統做出重要貢獻並需要資金,請與我們聯繫!同時,RustAnalyzer 團隊所做的出色工作給我們留下了深刻的印象,這些工作直接讓整個社區受益,我們將爲該項目捐款 5 萬歐元!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/oIS8knDO92PgiRf9JKRNpA