您的位置:首頁>科技>正文

如何達到facebook發版速度:Dropbox灰度發佈平臺系統架構

像 Dropbox 這樣的 SaaS 公司需要持續升級反覆運算他們的系統, 並且這會涉及整個架構棧的所有層。 當需要調整某些基礎架構, 發佈一個新功能, 或是設置 A/B 測試時, 最重要的是我們如何快速變更並體現在產品中。

對我們的代碼進行更改, 然後“簡單地”推送出去不是一個好選擇:因為推送變更代碼到 Web 伺服器這個流程可能需要數小時, 而發佈新的移動版或桌上出版用戶端則需更長時間。 同時在任何情況下, 完整的代碼部署是非常危險的, 因為它可能會引入新的代碼缺陷:

我們想要把一些可配置的 “開關” 添加到我們的產品中,

並且根據我們的需要來動態打開或關閉。 這能帶給我們很好的靈活性, 以及安全的即時的調整能力。

為了滿足這種需要, 我們構建了一個名為 Stormcrow的平臺, 它允許我們編輯和部署 “feature gates”(可以稱作功能門或開關)。 它是一個可配置的代碼路徑, 通過請求 Stormcrow 來確定如何執行。 典型的代碼使用看起來像這樣:

可在更改後10分鐘內於生產環境生效。

可用於所有 Dropbox 系統, 從底層的基礎架構到桌上出版或移動版產品。

提供高級的使用者投放功能, 比如可以根據我們資料倉庫中的使用者畫像來細分用戶。

構建一個像這樣通用的功能開關系統並不容易, 因為它需要具有足夠的表達或描述能力來處理不同的用例, 同時要足夠健壯來應對 Dropbox 巨大的流量。

接下來就讓我一一描述系統的工作原理和我們的一些經驗教訓。

示例

假設我們想執行一項 A/B 測試, 看看德國使用者喜歡什麼顏色的按鈕。 進一步, 假設我們已經知道英語為母語的使用者喜歡藍色按鈕。 那麼在 Stormcrow UI 中, 我們可以這樣配置功能:

這表明我們將對“德語區域的使用者”分別以 33% 和 33% 的比例顯示紅色按鈕(RED_BUTTON)和藍色按鈕(BLUE_BUTTON), 剩餘 34% 的使用者則不顯示該按鈕。 同時, 使用英語會話(譯者注, 指流覽器語言或用戶端語言)的使用者則 100% 顯示藍色按鈕(BLUE_BUTTON)。 除此之外的所有剩餘用戶並不參與這項 A/B 測試。

需要注意的是, 對於給定的任意特性/功能, 可以使用不同的群體(population)類型:上面例子分別使用“用戶”和“會話”來定義群體, 前者僅代表已登錄的用戶, 後者代表對我們網站的任何訪問者。

在 Stormcow 中, 有一系列的群體, 這些群體按照自頂向下的方式進行匹配:首先, 我們將嘗試匹配群體1, 如果失敗了, 我們在匹配群體2, 依此類推。 一旦我們匹配一個群體, 我們就選擇一個變數(variant)應用到這個群體上。

通過用戶的 ID 與種子(圖中右上角的小灰色框)hash 而實現隨機。 Stormcrow 用戶也可以通過種子來實現特殊的行為。 例如, 如果想要兩個不同的特性/功能分配給完全相同的用戶們, 那可以指定相同的種子。

如何定義群體?

要瞭解群體是如何定義的, 我們需要瞭解兩個概念:

選擇器(selector)是一個代碼物件, 它被傳遞進 Stormcrow 以幫助它做出判斷。 例如, usersession

資料欄位(datafield)是一段代碼, 它接收一個或多個選擇器, 並提取指定類型的值:布林值、日期、數位、集合或字串。 然後將它們通過簡單的規則引擎(使用這些值執行邏輯計算)組合成資料欄位。

這裡是一個真實的資料欄位例子, user_email:

@dataField裝飾器(decorator)指定該資料欄位需要一個USER物件, 並且將產生一個STRING。 它還包括一段説明文本,

由此我們可以自動生成文檔。 函數的實際主體只是將使用者的電子郵件從物件中取出。

定義資料欄位後, 您可以在定義群體中使用它。 這裡有一個例子, 它要麼匹配 Gmail 和 Yahoo 用戶並且顯式排出兩個特定的用戶, 要麼匹配 tomm@dropbox.com這個用戶:

因為可以運行任意邏輯計算, 所以資料欄位十分強大。在 Dropbox 內部定義了很多資料欄位,來支援我們所有的用例,不通的團隊如果需要他們可以不斷得添加新的資料欄位。

基於 Hive 的群體分類:連接我們的分析資料倉庫

即使具有創建任意資料欄位的能力,我們也面臨一個限制:我們只能依賴我們伺服器代碼可訪問的資訊(來定義群體),也就是已經載入的模型或資料庫中。但是 Dropbox 還有另一個大資料來源:我們基於 Hive 的分析資料倉庫。有時候 Dropboxer 想要通過寫一個 HiveQL 來查詢一組任意的用戶,這就可以利用各種歷史日誌和分析資料。

為了使 Stormcrow 可以利用通過 Hive 查詢獲得的群體,我們需要將其從分析倉庫移到可擴展的,同時可被生產代碼檢索的資料存儲中。為此我們構建了一個每天運行的資料管道,它將當天的所有基於 Hive 查詢的群體匯出到生產環境中。

這種方法的主要缺點是資料滯後。與資料欄位不同(資料欄位總是生成最新的資料),基於 Hive 匯出的資料庫僅僅每天更新一次。雖然這對於某些類型的開關是不可接受的,但它對於群體變化緩慢的場景來說很有用。

基於 Hive 的群體分類體現了表達力和資料新鮮度之間的權衡:對複雜分析資料執行特徵開關比對常用資料進行開關具有更多的滯後和資料工程工作。

派生群體:從簡單的群體定義中構建複雜群體

Stormcrow 最強大的功能之一是定義群體的能力。比如派生群體,下面示例是一個群體,它匹配 a)“Android設備”使用者和 b)功能 recents_web_comments值為 OFF。

這個功能幫我們避免了有些複雜匹配規則不斷得被複製和粘貼。相反,Dropbox 的功能開關旨在構建一組核心的基本群體,可以混合和匹配以滿足任意複雜的匹配需求。我們在實踐中發現,設計派生群體層次結構與重構代碼非常相似。

實際上,可以將派生群體視為替換代碼中的 “if” 語句,以便在實驗之間進行選擇。而不是寫形如“如果用戶匹配在實驗 A 中就顯示東西 A,否則如果匹配在實驗 B 中就顯示東西 B”這樣的邏輯。

選擇器推斷:通過推斷附加資訊使得 API 更易於使用

像其他複雜的軟體系統一樣,我們的代碼中使用了很多 Dropbox 內部模型。例如,user模型表示單個使用者帳戶,team模型表示 Dropbox 業務團隊。identity模型代表配對的帳戶:它將個人和商業使用者模型綁定到單個物件中。我們所有的模型都通過各種一對多和多對一的關係連接。

在 Dropbox 產品代碼中,我們通常可以訪問一個或多個這類模型。為了開發人員的方便,如果 Stormcrow 理解我們的模型關係就可以自動推斷額外的選擇器。例如,開發者可以訪問使用者物件 u並且希望查詢針對團隊而開關的功能。 他們可以這樣寫:

Stormcrow 自動推斷來獲取更多的資訊,所以開發人員只需要寫:

在 Stormcrow 中,我們將 Dropbox 的模型關係表示成一個圖,我們稱之為選擇器推斷圖。在這個圖中,每個節點都是模型類型,從節點 A 到節點 B 的邊意味著我們可以從模型 A 推斷除模型 B。當 Stormcrow 調用時,我們做的第一件事是獲取指定的選擇器,接著在圖中計算其傳遞閉包(transitive closure)。

當然,推斷可能因為額外的計算或網路調用而造成性能損耗。為了使它更高效,推斷會產生 thunk,它們會被延遲求解(evaluate),這樣我們只有在實際需要選擇器來作出開關決策時才計算它們。 請參閱下面的“性能風險”

這是我們真實的選擇器推斷圖。每個節點表示 Stormcrow 中的選擇器類型。例如,viewer是一個非常方便的模型,因為我們可以使用它來推斷session,team,user和identity。

我們發現選擇器推斷為開發人員帶來了巨大便利,同時易於理解。當然我們也有檢查工具來確保開發人員不會傳遞錯誤的選擇器。請參閱後面的“審核挑戰”

部署:Web 和內部基礎設施

如果你有大量生產伺服器,如何將功能門控的配置部署到它們上面呢?很顯然,我們需要將功能門控相關的資料保存在資料庫中,但是那麼就需要一次網路調用來檢索。dropbox.com 上一次典型的頁面載入中可能會涉及大量的功能門控,這會導致對資料庫的大量讀取。即使使用精心設計的緩存系統(例如使用本地緩存+ memcached)緩解這些問題,資料庫也會成為系統的單點故障。

相反,我們將一個名為 stormcrow_config.json的 JSON 檔部署到所有的生產伺服器上。這個部署僅僅使用我們的內部推送系統,並且在每次對 Stormcrow 配置進行更改時推送。

我們所有的伺服器都運行一個稱為“Stormcrow 載入器”的後臺執行緒,它監視磁片上的 stormcrow_config.json副本,當它改變時就重新載入它。這讓 Stormcrow 不用中斷伺服器就可以重新載入。

如果由於某種原因找不到設定檔,那麼 Stormcrow 也能夠回退到直接訪問資料庫,但是對於任何大流量的系統來說,這是非常危險和不推薦的做法。

部署:桌面端和移動端

對桌上出版和移動版的功能開關稍有不同。對於這些用戶端,它們通常是批量請求開關相關的資訊。比如從後端獲得的 Stormcrow:

這兩種平臺上的用戶端還會傳遞一個或多個包含平臺特定資訊的特殊選擇器。移動用戶端傳遞一個選擇器,提供關於正在使用的 App 和設備本身的資訊。桌面用戶端則傳遞一個帶有桌面主機資訊的選擇器。與其他選擇器一樣,Stormcrow 有可根據這些平臺特殊的選擇器來定義規則的資料欄位。

監控

Stormcrow 中所有被開關的功能的每次分配和曝光,都會記錄到我們的即時監控系統 Vortex 中。 Stormcrow UI 中嵌入了圖表,使用者可以利用其來跟蹤分配和曝光的速率。例如,下圖顯示了三種不同的變數(黃色,藍色和綠色),以及每個變數隨時間曝光給使用者的數量。 每次修改功能(或功能所依賴的群體)時,圖表中會用垂直線注釋。這使我們很容易地看到變更的影響。在該圖中,我們可以看到綠色和藍色變數在第一次修改之後收斂(垂直線),同時黃色變數上升。

用戶還可以按一下底部的連結,使用我們的 Vortex 或其他資料工具更詳細地挖掘資料。

性能風險

由於 Stormcrow 的模組化資料欄位設計,Dropbox 的開發人員可能直接或間接得編寫嚴重影響性能的資料欄位。比如:有人創建一個新的資料欄位,對於他們的小型業務來說是非常安全的,但這個資料欄位也可被其他人使用,這就可能造成小型業務系統無法承載大量的請求流量。

這告訴我們一個重要的教訓:在功能開關中要避免資料庫調用或其他 I/O!

相反,調用方應該傳遞進來盡可能多的資訊。這樣避免 Stormcrow 因為推斷而產生的隱式 I/O ,同時調用方的顯式 I/O 也使得調用方更好評估性能的損耗。

理想情況下,Stormcrow 應是完全“純粹的”(以函數式程式設計而言),並不會執行任何 I/O。目前我們還沒有能夠做到這一點,因為一些實際的原因:有時為調用方提供一個便捷的 API 意味著 Stormcrow 需要做更多並付出性能代價。

審計挑戰

功能開關有一方面比較棘手,因為它們沒有被納入版本控制中:它們可以獨立于使用方代碼進行更改。通過我們的“每日推送”系統(對於我們的後端)或通過桌上出版或移動版用戶端的發佈過程,Dropbox 中的代碼上線以可預測的方式進行。 但對於功能開關的變更,由於它們的性質,可以發生在白天或晚上的任何時間。因此,擁有完善的審計工具很重要,因為我們可以盡可能快地跟蹤功能開關相關的回歸。

Stormcrow 通過提供完整的審核歷史記錄,以及靜態分析代碼庫中的功能來解決此問題。

審核歷史記錄很簡單:我們對給定功能和群體的所有歷史修改顯示一個類似 “News Feed” 的視圖。

對代碼庫的靜態分析更有趣。我們運行一個名為“Stormcrow 靜態分析器”的特殊服務。 它會拉取我們的代碼並掃描它,搜索 Stormcrow 相關功能的使用。 對於給定的需要開關的功能,它會生成:

一個當前 master branch 中所有出現此功能的列表。

一個“歷史視圖”,展示了此功能相關的所有提交記錄。

例如,下面是靜態分析器對一個名為 can_see_weathervane功能的輸出:

靜態分析器還會執行另一項重要的任務,那就是,我們的生產代碼中找到的與單元測試正在測試相匹配的變數。 它會發送“nag”郵件給該功能的所有者告知相關問題,比如已經棄用的功能應該從代碼庫中刪除。

品質保障和測試

針對功能的手工測試,Stormcrow 支援“重覆蓋”(override)。重覆蓋允許 Dropboxers 臨時將自己放入任一群體中。 我們還有一個“資料欄位重覆蓋”(datafields override)的概念,可以臨時更改單個資料欄位的值。 比如,臨時將語言區域設置為德語,來測試德語下的體驗。

對於單元測試,我們運行一個”模擬的“(mocked)Stormcrow,並對每個開關功能都返回一個“預設”變數用於測試。

總結

在 Dropbox 當前系統的體量和規模下構建出這樣一個統一的功能開關服務需要方方面面的仔細考慮,從基礎設施層面,到資料獲取和配置管理,再到 UI 設計和相關工具鏈。我們希望這篇文章對於正在打造自己的功能開關系統的同行有所説明。

為什麼新的平臺稱為 Stormcrow?因為這個系統取代了我們以前的功能開關系統 Gandalf(譯者注,甘道夫會說“你不能通過!”) ,同時指環王的粉絲們會將“風暴”(Stormcrow)視為甘道夫的名字之一。

英文原文

https://blogs.dropbox.com/tech/2017/03/introducing-stormcrow/

所以資料欄位十分強大。在 Dropbox 內部定義了很多資料欄位,來支援我們所有的用例,不通的團隊如果需要他們可以不斷得添加新的資料欄位。

基於 Hive 的群體分類:連接我們的分析資料倉庫

即使具有創建任意資料欄位的能力,我們也面臨一個限制:我們只能依賴我們伺服器代碼可訪問的資訊(來定義群體),也就是已經載入的模型或資料庫中。但是 Dropbox 還有另一個大資料來源:我們基於 Hive 的分析資料倉庫。有時候 Dropboxer 想要通過寫一個 HiveQL 來查詢一組任意的用戶,這就可以利用各種歷史日誌和分析資料。

為了使 Stormcrow 可以利用通過 Hive 查詢獲得的群體,我們需要將其從分析倉庫移到可擴展的,同時可被生產代碼檢索的資料存儲中。為此我們構建了一個每天運行的資料管道,它將當天的所有基於 Hive 查詢的群體匯出到生產環境中。

這種方法的主要缺點是資料滯後。與資料欄位不同(資料欄位總是生成最新的資料),基於 Hive 匯出的資料庫僅僅每天更新一次。雖然這對於某些類型的開關是不可接受的,但它對於群體變化緩慢的場景來說很有用。

基於 Hive 的群體分類體現了表達力和資料新鮮度之間的權衡:對複雜分析資料執行特徵開關比對常用資料進行開關具有更多的滯後和資料工程工作。

派生群體:從簡單的群體定義中構建複雜群體

Stormcrow 最強大的功能之一是定義群體的能力。比如派生群體,下面示例是一個群體,它匹配 a)“Android設備”使用者和 b)功能 recents_web_comments值為 OFF。

這個功能幫我們避免了有些複雜匹配規則不斷得被複製和粘貼。相反,Dropbox 的功能開關旨在構建一組核心的基本群體,可以混合和匹配以滿足任意複雜的匹配需求。我們在實踐中發現,設計派生群體層次結構與重構代碼非常相似。

實際上,可以將派生群體視為替換代碼中的 “if” 語句,以便在實驗之間進行選擇。而不是寫形如“如果用戶匹配在實驗 A 中就顯示東西 A,否則如果匹配在實驗 B 中就顯示東西 B”這樣的邏輯。

選擇器推斷:通過推斷附加資訊使得 API 更易於使用

像其他複雜的軟體系統一樣,我們的代碼中使用了很多 Dropbox 內部模型。例如,user模型表示單個使用者帳戶,team模型表示 Dropbox 業務團隊。identity模型代表配對的帳戶:它將個人和商業使用者模型綁定到單個物件中。我們所有的模型都通過各種一對多和多對一的關係連接。

在 Dropbox 產品代碼中,我們通常可以訪問一個或多個這類模型。為了開發人員的方便,如果 Stormcrow 理解我們的模型關係就可以自動推斷額外的選擇器。例如,開發者可以訪問使用者物件 u並且希望查詢針對團隊而開關的功能。 他們可以這樣寫:

Stormcrow 自動推斷來獲取更多的資訊,所以開發人員只需要寫:

在 Stormcrow 中,我們將 Dropbox 的模型關係表示成一個圖,我們稱之為選擇器推斷圖。在這個圖中,每個節點都是模型類型,從節點 A 到節點 B 的邊意味著我們可以從模型 A 推斷除模型 B。當 Stormcrow 調用時,我們做的第一件事是獲取指定的選擇器,接著在圖中計算其傳遞閉包(transitive closure)。

當然,推斷可能因為額外的計算或網路調用而造成性能損耗。為了使它更高效,推斷會產生 thunk,它們會被延遲求解(evaluate),這樣我們只有在實際需要選擇器來作出開關決策時才計算它們。 請參閱下面的“性能風險”

這是我們真實的選擇器推斷圖。每個節點表示 Stormcrow 中的選擇器類型。例如,viewer是一個非常方便的模型,因為我們可以使用它來推斷session,team,user和identity。

我們發現選擇器推斷為開發人員帶來了巨大便利,同時易於理解。當然我們也有檢查工具來確保開發人員不會傳遞錯誤的選擇器。請參閱後面的“審核挑戰”

部署:Web 和內部基礎設施

如果你有大量生產伺服器,如何將功能門控的配置部署到它們上面呢?很顯然,我們需要將功能門控相關的資料保存在資料庫中,但是那麼就需要一次網路調用來檢索。dropbox.com 上一次典型的頁面載入中可能會涉及大量的功能門控,這會導致對資料庫的大量讀取。即使使用精心設計的緩存系統(例如使用本地緩存+ memcached)緩解這些問題,資料庫也會成為系統的單點故障。

相反,我們將一個名為 stormcrow_config.json的 JSON 檔部署到所有的生產伺服器上。這個部署僅僅使用我們的內部推送系統,並且在每次對 Stormcrow 配置進行更改時推送。

我們所有的伺服器都運行一個稱為“Stormcrow 載入器”的後臺執行緒,它監視磁片上的 stormcrow_config.json副本,當它改變時就重新載入它。這讓 Stormcrow 不用中斷伺服器就可以重新載入。

如果由於某種原因找不到設定檔,那麼 Stormcrow 也能夠回退到直接訪問資料庫,但是對於任何大流量的系統來說,這是非常危險和不推薦的做法。

部署:桌面端和移動端

對桌上出版和移動版的功能開關稍有不同。對於這些用戶端,它們通常是批量請求開關相關的資訊。比如從後端獲得的 Stormcrow:

這兩種平臺上的用戶端還會傳遞一個或多個包含平臺特定資訊的特殊選擇器。移動用戶端傳遞一個選擇器,提供關於正在使用的 App 和設備本身的資訊。桌面用戶端則傳遞一個帶有桌面主機資訊的選擇器。與其他選擇器一樣,Stormcrow 有可根據這些平臺特殊的選擇器來定義規則的資料欄位。

監控

Stormcrow 中所有被開關的功能的每次分配和曝光,都會記錄到我們的即時監控系統 Vortex 中。 Stormcrow UI 中嵌入了圖表,使用者可以利用其來跟蹤分配和曝光的速率。例如,下圖顯示了三種不同的變數(黃色,藍色和綠色),以及每個變數隨時間曝光給使用者的數量。 每次修改功能(或功能所依賴的群體)時,圖表中會用垂直線注釋。這使我們很容易地看到變更的影響。在該圖中,我們可以看到綠色和藍色變數在第一次修改之後收斂(垂直線),同時黃色變數上升。

用戶還可以按一下底部的連結,使用我們的 Vortex 或其他資料工具更詳細地挖掘資料。

性能風險

由於 Stormcrow 的模組化資料欄位設計,Dropbox 的開發人員可能直接或間接得編寫嚴重影響性能的資料欄位。比如:有人創建一個新的資料欄位,對於他們的小型業務來說是非常安全的,但這個資料欄位也可被其他人使用,這就可能造成小型業務系統無法承載大量的請求流量。

這告訴我們一個重要的教訓:在功能開關中要避免資料庫調用或其他 I/O!

相反,調用方應該傳遞進來盡可能多的資訊。這樣避免 Stormcrow 因為推斷而產生的隱式 I/O ,同時調用方的顯式 I/O 也使得調用方更好評估性能的損耗。

理想情況下,Stormcrow 應是完全“純粹的”(以函數式程式設計而言),並不會執行任何 I/O。目前我們還沒有能夠做到這一點,因為一些實際的原因:有時為調用方提供一個便捷的 API 意味著 Stormcrow 需要做更多並付出性能代價。

審計挑戰

功能開關有一方面比較棘手,因為它們沒有被納入版本控制中:它們可以獨立于使用方代碼進行更改。通過我們的“每日推送”系統(對於我們的後端)或通過桌上出版或移動版用戶端的發佈過程,Dropbox 中的代碼上線以可預測的方式進行。 但對於功能開關的變更,由於它們的性質,可以發生在白天或晚上的任何時間。因此,擁有完善的審計工具很重要,因為我們可以盡可能快地跟蹤功能開關相關的回歸。

Stormcrow 通過提供完整的審核歷史記錄,以及靜態分析代碼庫中的功能來解決此問題。

審核歷史記錄很簡單:我們對給定功能和群體的所有歷史修改顯示一個類似 “News Feed” 的視圖。

對代碼庫的靜態分析更有趣。我們運行一個名為“Stormcrow 靜態分析器”的特殊服務。 它會拉取我們的代碼並掃描它,搜索 Stormcrow 相關功能的使用。 對於給定的需要開關的功能,它會生成:

一個當前 master branch 中所有出現此功能的列表。

一個“歷史視圖”,展示了此功能相關的所有提交記錄。

例如,下面是靜態分析器對一個名為 can_see_weathervane功能的輸出:

靜態分析器還會執行另一項重要的任務,那就是,我們的生產代碼中找到的與單元測試正在測試相匹配的變數。 它會發送“nag”郵件給該功能的所有者告知相關問題,比如已經棄用的功能應該從代碼庫中刪除。

品質保障和測試

針對功能的手工測試,Stormcrow 支援“重覆蓋”(override)。重覆蓋允許 Dropboxers 臨時將自己放入任一群體中。 我們還有一個“資料欄位重覆蓋”(datafields override)的概念,可以臨時更改單個資料欄位的值。 比如,臨時將語言區域設置為德語,來測試德語下的體驗。

對於單元測試,我們運行一個”模擬的“(mocked)Stormcrow,並對每個開關功能都返回一個“預設”變數用於測試。

總結

在 Dropbox 當前系統的體量和規模下構建出這樣一個統一的功能開關服務需要方方面面的仔細考慮,從基礎設施層面,到資料獲取和配置管理,再到 UI 設計和相關工具鏈。我們希望這篇文章對於正在打造自己的功能開關系統的同行有所説明。

為什麼新的平臺稱為 Stormcrow?因為這個系統取代了我們以前的功能開關系統 Gandalf(譯者注,甘道夫會說“你不能通過!”) ,同時指環王的粉絲們會將“風暴”(Stormcrow)視為甘道夫的名字之一。

英文原文

https://blogs.dropbox.com/tech/2017/03/introducing-stormcrow/

Next Article
喜欢就按个赞吧!!!
点击关闭提示