天天看點

曆時四年,Dropbox 用 Rust 重寫同步引擎核心代碼

雲栖号資訊:【 點選檢視更多行業資訊

在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!

曆時四年,Dropbox 用 Rust 重寫同步引擎核心代碼

Dropbox 花費四年完成重構

過去四年,我們一直在努力重構 Dropbox 桌面用戶端同步引擎,這是 Dropbox 檔案夾背後的重要技術,也是 Dropbox 最古老、最重要的代碼之一。經過四年努力,我們已經向所有 Dropbox 使用者釋出了用 Rust 寫的新同步引擎(代号為 “Nucleus” )。

重寫同步引擎很難,我們也并不想盲目慶祝新版同步引擎的釋出,因為在很多情景下,重寫是一個糟糕的想法。不過,事實證明,重寫對于 Dropbox 來說是一個好想法,但這隻是因為我們對整個過程考慮得非常周全。我們将在本文分享關于如何考慮一個重要軟體的重寫問題,并強調使該項目取得成功的關鍵舉措,比如,擁有一個非常幹淨的資料模型。

重構實屬無奈:問題太多

2008 年,Dropbox 同步首次進入測試階段。使用者安裝 Dropbox 應用程式,Dropbox 就會在他們電腦上建立一個檔案夾,隻要将檔案儲存在這個檔案夾中,就可以把它們同步到 Dropbox 伺服器和使用者的其他裝置上。Dropbox 伺服器可以持久而安全地存儲檔案,并且這些檔案還可以通過網際網路連接配接在任何地點通路。

曆時四年,Dropbox 用 Rust 重寫同步引擎核心代碼

簡單地說,同步引擎駐留在計算機上,負責對使用者檔案上傳和下載下傳到遠端檔案系統進行協調。

  • 大規模同步很難

    我們的第一個同步引擎稱之為 “Sync Engine Classic” (意即 “經典同步引擎” ),它的資料模型存在一些基本問題,這些問題隻有在大規模的情況下才會表現出來,使得漸進式改進變得不可能。

  • 分布式系統很難

    單就 Dropbox 的規模而言,建構分布式系統本身就是一項艱巨的任務。撇開原始規模不談,檔案同步就是一個獨特的分布式系統問題,因為允許用戶端長時間離線,并在重新上線時協調它們的更改。對許多分布式系統算法來說,網絡分區是異常情況,但對我們來說卻是标準操作。

正确處理這一點很重要:使用者信任 Dropbox,并将自己最珍貴的内容托付給 Dropbox,是以,Dropbox 必須保證這些内容的安全,這沒有商量餘地。但是,雙向同步有許多極端情況,持久性比僅僅確定不删除或破壞伺服器上的資料要難得多。例如,Sync Engine Classic 将 “移動” 表示為一對操作:在舊位置上進行 “删除” 操作,并在新位置上進行 “添加” 操作。如果發生網絡中斷,删除操作會進行,但對應的添加操作卻沒有進行。然後,使用者将會發現伺服器和其他裝置上出現檔案丢失,即使他們隻是在本地對檔案進行移動操作也是如此。

  • 保持持久性很難

    Dropbox 的目标是:無論使用者的計算機配置如何,都能 “正常工作” 。我們支援 Windows、macOS 和 Linux,這些平台都有各種各樣的檔案系統,且所有這些檔案系統的行為略有不同。在作業系統下,硬體有很大差異,何況使用者還會安裝不同的核心擴充或驅動程式來改變作業系統行為。而在 Dropbox 之上,應用程式以不同的方式使用檔案系統,并且依賴的行為可能實際上并不是其規範的一部分。

要保證特定環境的持久性,就需要了解其實作,有時甚至需要在調試生産問題時,對其進行逆向工程。這些問題通常會影響大量使用者,而一個罕見的檔案系統錯誤可能隻影響很小一部分使用者。是以,從規模上來看,在大部分環境下能夠“正常工作”,并提供強大的持久性保證,從根本上是對立的。

  • 測試檔案同步很難

    有了足夠大的使用者群,幾乎所有理論上可能發生的事情都會在生産環境中發生。生産環境中的調試問題比開發環境中的調試問題要貴得多,特别是對于在使用者裝置上運作的軟體而言。是以,在大規模生産之前,通過自動化測試來捕捉回歸至關重要。

然而,同步引擎的測試很難,因為檔案狀态和使用者動作的可能組合是一個天文數字。一個共享檔案夾可能有數千個成員,每個成員都有一個同步引擎,該引擎具有不同的連接配接性,以及不同的 Dropbox 檔案系統的過期視圖。每個使用者可能有不同的本地更改等待上傳,并且他們從伺服器下載下傳檔案的部分進度也可能有所不同。是以,系統有許多可能的 “快照” ,是以,所有這些都必須進行測試。

從系統狀态中采取的有效操作的數量也非常龐大。同步檔案是一個高度并發的過程,使用者可能會同時上傳和下載下傳許多檔案。單個檔案的同步可能涉及并行傳輸内容塊、将内容寫入磁盤或從本地檔案系統讀取内容。全面測試需要嘗試這些操作的不同順序,以確定我們的系統不存在并發錯誤。

  • 指定同步行為很難

    最後,通常很難精确定義同步引擎的正确行為。例如,考慮這樣一種情況:假設我們有三個檔案夾,其中一個檔案夾嵌套在另一個檔案夾中。

曆時四年,Dropbox 用 Rust 重寫同步引擎核心代碼

假設兩個使用者 Alberto 和 Beatrice,他們脫機使用這個檔案夾。Alberto 将 “Archives” 檔案夾移到 “January” 檔案夾;而 Beatrice 将 “Drafts” 檔案夾移到 “Archives” 檔案夾。

曆時四年,Dropbox 用 Rust 重寫同步引擎核心代碼

當他們重新聯網後會發生什麼情況呢?如果直接應用這些步驟,那麼,我們的檔案系統圖中将會出現一個循環:“Archives” 檔案夾是 “Drafts” 檔案夾的父目錄,“Drafts” 檔案夾是 “January” 檔案夾的父目錄,而 “January” 檔案夾是 “Archives” 檔案夾的父目錄。

曆時四年,Dropbox 用 Rust 重寫同步引擎核心代碼

在這種情況下,正确的最終系統狀态是什麼?Sync Engine Classic 複制每個檔案夾,合并 Alberto 和 Bratrice 的目錄樹。使用 Nuclues,我們保留原始目錄,最終的順序取決于哪個同步引擎先上傳它們的移動操作。

曆時四年,Dropbox 用 Rust 重寫同步引擎核心代碼

在這種三個檔案夾和兩個動作的簡單情況下,Nucleus 有一個令人滿意的最後狀态。但是,我們該如何在一般情況下指定同步行為,而不會淹沒在一系列的邊角案例中呢?

譯注:邊角案例(Corner case),或病态案例(pathological case)是指其操作參數在正常範圍以外的問題或是情形,而且多半是幾個環境變量或是條件都在極端值的情形,即使這些極端值都還在參數規格範圍内(或是邊界),也算是邊角案例。

例如有某個揚音器會扭曲聲音,但隻有在音量最大、低音最大及高濕度的環境下才會出現。或者伺服器會有不穩定的情形,但條件是在最多 64 個輔助微處理器、記憶體為最大值是 512 Gigabyte,同時一萬個使用者上線時才會不穩定,這些都是邊角案例。

邊角案例和邊緣案例不同,邊緣條件隻是單一個變量為最大值或最小值。若某個揚音器隻要音量最大,不論其他條件是否正常或是極端,聲音都會扭曲,這是邊緣案例。

如何解決這些問題?

大規模同步檔案很難。在 2016 年,我們已經很好地解決了這一問題。我們有數以億計的使用者,像 Smart Sync 這樣的新産品特性正在開發中,還有一支強大的同步專家團隊。Sync Engine Classic 經過多年生産強化,花了很多時間來尋找并修複最罕見的錯誤。

Joel Spolsky 稱從頭開始重寫代碼是 “任何軟體公司都可能會犯的最嚴重的戰略錯誤” 。要想成功地完成重寫通常需要減緩特性開發速度,因為在舊系統上取得的進展需要移植到新系統上。當然,還有很多面向使用者的項目,我們的同步工程師可以進行。

盡管 Sync Engine Classic 取得了成功,但卻非常不健康。在建構 Smart Sync 的過程中,我們對系統做了許多漸進式改進,清理了低劣代碼,并重構了接口,甚至還添加了 Python 類型注釋。我們添加了大量遙測技術,并建立了流程,以確定維護既安全又簡單。但是,這些漸進的改進還是遠遠不夠。

傳遞任何更改以同步行為都需要進行艱苦的部署,而且我們仍然會在生産中發現複雜的不一緻性。團隊必須放下一切,診斷問題,解決問題,然後花時間讓他們的應用程式恢複到良好的狀态。盡管我們有一支強大的專家團隊,但要讓新工程師融入這個系統還是需要花費數年時間。最後,我們投入了大量時間來提高性能,但未能明顯擴充同步引擎可以管理的檔案總數。

這些問題有幾個根本原因,但最重要的是 Sync Engine Classic 的資料模型。資料模型是為一個沒有共享的、更簡單的世界而設計的,并且檔案缺少穩定的辨別符,這個辨別符可以在移動的過程中保持。我們會花費數個小時來調試理論上可能但 “極不可能” 出現在生産環境中的問題。改變一個系統的基本名詞通常不可能一蹴而就,很快我們就沒有有效的漸進式改進的方法了。

其次,該系統并不是為可測試性而設計的。我們依賴于緩慢的釋出和現場調試問題,而不是自動化的預釋出測試。Sync Engine Classic 容許資料模型意味着我們不能在壓力測試中進行太多檢查,因為有大量不受歡迎但仍然合法的結果而我們無法斷言。擁有一個具有限制不變量(tight invariant)的強大資料模型對于測試非常有價值,因為檢查系統是否處于有效狀态總是一件很容易做到的事情。

我們在前文讨論了為什麼同步是一個并發問題,并且測試和調試并發代碼出了名的難。Sync Engine Classic 的基于線程的架構根本一點用都沒有。它将所有排程決策都交給了作業系統,使得內建測試變得不可重制。在實踐中,我們最終使用的是長時間持有的非常粗粒度的鎖。雖然這種架構犧牲了并行性的優點,但使系統變得更易于推理。

重寫前,需要評估什麼?

讓我們将決定重寫的原因提煉成一份關于重寫的清單,它可以幫助在其他系統中進行此類決策。

  • 你是否已經用盡漸進式改進方法?

1、你是否嘗試過将代碼重構為更好的子產品?

代碼品質低劣本身并非重寫系統的重要原因。重命名變量和解開糾纏在一起的子產品都可以用漸進式改進來完成,我們在 Sync Engine Classic 中花了很多時間來完成這些工作。Python 的動态性可能會讓這一點變得困難,是以,我們添加了 MyPy 注釋,以便在編譯時逐漸捕獲更多的錯誤。但是,系統的核心原語仍然保持不變,因為僅靠重構并不能改變基本資料模型。

2、你是否嘗試通過優化來提高性能?

軟體通常把大部分時間花在很少的代碼上。許多性能問題都不是根本問題,優化分析器識别的熱點是一種漸進式改進性能的好方法。幾個月以來,團隊一直緻力于性能和規模方面的工作,他們在提高檔案内容傳輸性能方面取得了巨大成果。但是,對記憶體占用的改進(比如增加系統可以管理的檔案數量)仍然難以實作。

3、你能提供更多價值嗎?

即使你決定重寫,你能通過提高價值來降低風險嗎?這樣做可以驗證早期的技術決策,幫助項目保持發展勢頭,并減輕緩慢的特性開發帶來的痛苦。

  • 你能進行重寫嗎?

1、你是否深刻了解并尊重目前的系統?

編寫新代碼比完全了解現有代碼要容易得多。是以,在重寫之前,你必須深刻了解并尊重 “經典” 系統。這就是你的團隊和業務存在的全部原因,它通過在生産環境中運作積累了多年的智慧。去做一番考古研究,探究為什麼這一切都是這樣的。

2、你有時間嗎?

從頭開始重寫系統是一項艱苦的工作,而且要實作完整的特性需要花費大量的時間。你有這些資源嗎?你的組織是否足夠健康,能夠維持如此大規模的項目?

3、你能接受較慢的特性開發速度嗎?

我們并沒有完全停止 Sync Engine Classic 的特性開發,但是舊系統的每一次改變都會将新系統的終點線推得更遠。我們決定傳遞一些項目,在不拖累重寫團隊的情況下,我們必須有意識地配置設定資源來指導這些項目的釋出。我們還對 Sync Engine Classic 遙測技術進行了大量投資,以将其穩态維護成本維持在最低水準。

  • 你的目标是什麼?

1、為什麼第二次會更好?

如果你已經走到這一步,那麼你已經徹底地了解了舊系統,以及需要吸取的教訓。但是,重寫也應該受到不斷變化的要求或業務需求的推動。我們在上文中闡述了檔案同步是如何變化的,但是,我們重寫的決定也是具有前瞻性的。Dropbox 了解協作使用者在工作中日益增長的需求,為這些使用者建構新特性需要一個靈活、健壯的同步引擎。

2、你對新系統的原則是什麼?

對一個團隊來說,從頭開始是一個重塑技術文化的絕佳機會。鑒于我們操作 Sync Engine Classic 的經驗,我們從一開始就非常強調測試、正确性和可調試性,将所有這些原則編碼到資料模型中。我們在項目生命周期的早期就寫出了這些原則,它們為自己帶來了一次又一次的回報。

我們用 Rust 重構核心代碼

最終,我們用 Rust 編寫了 Nucleus。對我們團隊來說,押注 Rust 是我們做出的最好決定之一。除了性能之外,它對正确性的關注幫助我們克服了同步的複雜性。我們可以在類型系統中對系統的複雜不變量進行編碼,并讓編譯器為我們檢查它們。

我們幾乎所有的代碼都在一個線程( “控制線程” )上運作,并使用 Rust 的 futures 庫在這個線程上排程許多并發操作。我們隻在需要的時候才将工作轉移給其他線程:網絡 IO 到事件循環,計算開銷大的工作,如哈希到線程池,檔案系統 IO 到專用線程。這大大降低了開發人員在添加新特性時必須考慮的範圍和複雜性。

當控制線程的輸入和排程決策是固定的時,它被設計為完全确定的。我們使用這一性質,用僞随機模拟測試對其進行模糊處理。利用随機數生成器的種子,我們可以生成随機的初始檔案系統狀态、時間表和系統擾動,并讓引擎運作到完成狀态。然後,如果我們沒有通過任何同步正确性檢查,我們總是可以從原始種子中重制錯誤。我們每天在測試基礎設施中運作數以百萬計的各種場景。

我們重新設計了用戶端 - 伺服器協定,使其具有很強的一緻性。該協定確定伺服器和用戶端在考慮更改之前具有相同的遠端檔案視圖。共享檔案夾和檔案具有全局唯一的辨別符,用戶端永遠不會在臨時複制或丢失狀态下觀察到它們。現在,我們在用戶端和伺服器的遠端檔案系統視圖之間進行了強有力的一緻性檢查,任何差異都是錯誤。

【雲栖号線上課堂】每天都有産品技術專家分享!

課程位址:

https://yqh.aliyun.com/live

立即加入社群,與專家面對面,及時了解課程最新動态!

【雲栖号線上課堂 社群】

https://c.tb.cn/F3.Z8gvnK

原文釋出時間:2020-04-12

本文作者:Sujay Jayakar

本文來自:“

InfoQ 微信公衆号

”,了解相關資訊可以關注“

InfoQ