ConfigBus:Twitter 的動態配置實踐
動態配置能夠在不重新啟動應用程式的情況下更改正在執行的系統的行為和功能。理想的動態配置系統使服務開發人員和管理員能夠方便地檢視和更新配置,並高效可靠地嚮應用程式提供配置更新。它使組織能夠快速、大膽地迭代新特性,並提供工具,減少與更改現有系統相關的風險。
在 Twitter 的早期,應用程式管理並分發自己的配置,通常儲存在 ZooKeeper 中。但是,我們以前 ofollow,noindex">使用 ZooKeeper 的經驗 表明,它在用作通用鍵值儲存時不能伸縮。其他團隊轉而使用 Git 進行儲存,並結合自定義的工具來更新、分發和重新載入配置。隨著 Twitter 的發展,很明顯需要一個標準的解決方案來提供可伸縮的基礎設施、可重用的庫和有效的監控。
本文將介紹 Twitter 的動態配置系統 ConfigBus。ConfigBus 包括儲存配置的資料庫、將配置分發到 Twitter 資料中心中的機器的管道、讀取和更新配置的 API 和工具。
架構
在一個較高的層次上,你可以將 ConfigBus 看作一個 Git 儲存庫,它的內容被推送到 Twitter 資料中心的所有機器上。配置更改經過一系列步驟到達目的地:
- 開發人員向 Git 儲存庫提交更改。經過身份驗證的應用程式也可以通過 ConfigBus 服務提交到儲存庫。
- 預接收鉤子驗證更改。如果驗證通過,則在伺服器上接受並提交推送。(我們將在“特點”部分更詳細地討論這種驗證。)
- 一旦伺服器上的提交過程完成,Git 接收後鉤子就會使用提交資訊(SHA、時間戳等)更新 ZooKeeper 中的特定節點。
- 接下來會觸發 ZooKeeper watches ,引發下游過程的動作:
a. 在作為配置準備區的“ConfigStore”機器上,watch 事件會呼叫一個回撥,從 Git 伺服器獲取最新提交。它還用當前的 SHA 更新 ZooKeeper 中的條目。
b. 在目標機器上,watch 事件觸發映象任務,該任務輪詢 ZooKeeper,找出與最新提交具有相同 SHA 的 ConfigStore 機器。一旦找到源機器,映象任務將執行 rsync 把更改同步到本地機器上。 - 最後,使用這些配置檔案的應用程式將看到檔案系統上的更改(通過 ConfigBus 客戶端庫),並啟動程序內重新載入。
最後,系統停止工作,以便把第 1 步中更改的檔案同步到所有目標機器,並由依賴這些機器的所有客戶端應用程式重新載入。
特點
配置即程式碼
使用 Git 允許開發人員重用源儲存庫提供的許多相同的命令和工作流。Git 及其周邊的生態系統主要提供了以下功能:
- 具有更改日誌的原始碼控制 :能夠檢查過去的配置更改以檢視更改了什麼(以及由誰更改、何時更改或為什麼更改)是非常有價值的。Git 自然允許這樣做。開發人員完全有信心,當前和過去的版本在版本控制中是安全的。
- 自動部署 :ConfigBus 中的配置檔案會自動複製到所有目標機器。目前,配置傳播的平均延遲為 80-100 秒,而 p99 延遲大約為 300 秒。
- 分析驗證 :ConfigBus 使用 預接收鉤子 來執行驗證程式,它們可以檢查配置檔案中的語法錯誤,進行模式驗證,以及執行任何型別的自定義驗證。它針對 JSON 等流行配置格式為 Twitter 開發人員提供了開箱即用的語法驗證。它還提供了指定模式和驗證相容性的能力。使用者還可以編寫自定義驗證,並在 Git 中新增和更新配置時執行這些驗證。
- 程式碼稽核 :讓配置更改通過程式碼評審有助於減少錯誤,並在將其投入生產環境之前發現問題。
- ACL :我們在 Git 儲存庫中強制執行配置所有權,以確保配置檔案只被應該管理它們的團隊和應用程式修改。當推送一個更改時,一個預接收鉤子會驗證是否允許執行該推送的使用者對這些檔案進行更改。
- 程式設計訪問 :ConfigBus 服務支援對 ConfigBus 的程式設計訪問。該服務實現了 Git“智慧”HTTP 協議 ,並充當配置儲存庫的前端。它通過 HTTP 和 Thrift API 提供讀取和“compare-and-set”寫入功能。這使得編寫多使用者應用程式向單個檔案推送更改變得簡單了,不需要它們具有儲存庫的本地克隆。該服務還內建了樂觀併發控制,當推送由於對儲存庫的併發更新而失敗時會自動重試。
大規模持續交付
一旦配置被安全儲存,我們需要一種方法把它們提供給執行在 Twitter 基礎設施 上的軟體,包括執行在 Mesos 雲以及直接執行在裸機上的服務。這是通過 rsync 將檔案推送到所有的機器上來實現的。需要訪問配置的應用程式只需從本地檔案系統讀取。這樣做的好處是:
- 簡單 :使用檔案系統作為 API 使得任何語言編寫的應用程式都可以使用 ConfigBus。可以在本地檔案系統讀取配置還有助於減少服務啟動時間,特別是在雲環境中,應用程式例項可以在一個節點上宕掉,然後在另一個節點上執行。
- 可容錯 :將配置資料推送到每臺機器上的本地檔案系統中,這樣,即使 ConfigBus 管道的某些部分失敗,應用程式也可以繼續執行。例如,如果 Git 伺服器宕機,團隊將無法進行新的配置更改,但是執行中的應用程式不會受到太大影響。同樣,如果 ZooKeeper 出現故障,分發管道也會受到影響,但機器上現有的配置仍然可用。相反,如果配置獲取服務宕掉,需要按需獲取配置資料的系統將會失敗。
- 可擴充套件 :ConfigBus 的多層架構允許系統根據需求的增加進行伸縮。ConfigStore 層將 Git 伺服器與直接流量隔離開來。在這一層增加容量以適應 Twitter 不斷增長的機器數量所帶來的需求增加,這在操作上是非常簡單的。
“熱”過載
動態配置系統的主要優點之一是能夠獨立於使用它的軟體部署重新載入配置更改。此外,一個完全動態的配置系統應該能夠在不重新啟動應用程式程序的情況下重新載入更改,從而最小化對整個應用程式的影響。ConfigBus 提供了一些庫,允許客戶端訂閱特定的檔案,並在這些檔案更改時呼叫回撥。雖然應用程式也可以直接從檔案系統讀取,但是使用經過良好測試和封裝的客戶端庫具有以下優點:
- 避免檢測配置更改並觸發重新載入的程式碼重複;
- 允許嵌入釋出配置新鮮度指標用於問題檢測的程式碼。
監控
ConfigBus 是一個複雜的分散式系統,有許多活動部件。我們在各個層級對系統進行監控,以便收集統計資訊,並在發現異常行為時發出警報。
- 獨立部件 :我們監控各個子系統(如 Git 和 ZooKeeper 伺服器)的健康狀況。例如,我們收集與 Git 儲存庫的功能和效能相關的統計資訊(包大小、提交延遲、鉤子延遲等等),併發出警報。
- 版本跟蹤 :Git 伺服器將期望的配置版本釋出到 ZooKeeper。下游消費者使用此版本監控配置資料的新鮮度。
- 端到端監控 :執行在客戶端機器上的監控應用程式每隔幾分鐘就會更新配置儲存庫中的特定檔案,並等待更新傳播到其本地機器。這有助於度量 ConfigBus 管道的關鍵特徵,例如提交成功率、提交延遲和配置同步延遲。
用例
流量路由:在 Twitter,ConfigBus 用於儲存服務路由引數。這可以用於控制請求路由邏輯(例如,如果開發人員希望將 1% 的服務請求路由到執行軟體自定義版本的一組例項)。
元服務發現:在 Twitter,服務是通過一個服務發現服務發現彼此。但是,它們必須首先發現服務發現服務本身。這是通過 ConfigBus 實現的。使用 ConfigBus 與類似於 ZooKeeper 這樣的東西,其優點是,在每臺機器的本地檔案系統上都有可用的資訊,這使得系統更能抵禦故障(也就是說,如果 ConfigBus 或 ZooKeeper 出現故障,服務發現仍然可以執行)。
Decider:在 Twitter,Decider 是服務使用的特性標識系統,用於在執行時動態啟用和禁用單個特性。該系統位於 ConfigBus 之上。Decider 是面向 鍵 - 值 的(“cool_new_feature 的值是多少?”),而 ConfigBus 是面向 檔案 的(“檔案 application/config.json 的內容是什麼?”)。單個特性的標識稱為“決策器(decider)”。一旦嵌入到程式碼中,決策器就可以更改正在執行的應用程式的行為,而不需要更改程式碼或重新部署。除此之外,決策器可以用來:
- 有選擇性地啟用程式碼 :可以在程式碼中使用決策鍵來選擇性地啟用程式碼塊。Decider 提供的主要方法是“isAvailable”,它接受一個決策鍵引數,如果針對當前的呼叫開啟特性,則返回“true”;如果針對當前的呼叫關閉特性,則返回“false” 。“isAvailable”方法使得開發人員可以像下面這樣切換程式碼路徑:
- 切換後臺儲存系統 :有些應用程式使用決策器選擇寫入或讀取的後臺儲存系統。例如,應用程式從舊資料庫遷移到新資料庫,它可能會臨時將資料寫入兩個系統,並在遷移完成後動態關閉舊資料庫。或者,如果新系統有問題,應用程式可能會選擇關閉它。決策器使得這些更改安全而快速。
- 作為止血帶斷開過載系統 :在 Twitter,監控系統有時會在檢測到負載過重時更新決策器,以便禁用某些程式碼路徑。這可以防止系統過載,並允許它們進行恢復。
- 區域間故障轉移 :Decider 用於儲存某些路由引數,這些引數控制著 Twitter 服務跨區域的流量分佈。其中許多是通過監控軟體自動更新的,後者可以觀察每個區域的故障率。
“特性切換(Feature Switches)”:在 Twitter,特性切換為控制應用程式的行為提供了一個複雜而強大的基於規則的系統。特性切換控制特性在初步開發、團隊測試、內部測試、Alpha 測試、Beta 測試、釋出以及最終淘汰的過程中的可用性。與 Decider 的配置一樣,特性切換配置儲存在 ConfigBus 中。不過,在移動裝置上,最終配置會有一個關鍵區別。在釋出的最後一個階段,移動應用程式會通過執行在 Twitter 資料中心的服務定期 拉取 這些配置更新。與 Decider 相比,特性切換還提供更細粒度的控制。典型的 Decider 配置很簡單,例如,“允許資料中心 X 中 70% 的請求寫入新資料庫”。特性切換配置更高階,也更復雜,例如,“為 X 團隊中的所有人以及這個平臺上的這些特定使用者啟用這個新特性。”
“庫切換(Library toggles)”:特性切換和 Decider 是為了幫助應用程式開發人員安全釋出特性而設計的。庫開發人員在推出更改時有時需要類似的開關機制。 Finagle 是 Twitter 面向 JVM 的開源 RPC 框架,它提供了一種 切換 機制, 庫開發人員 可以使用該機制安全地釋出更改,同時也為服務所有者提供了某種程度的控制。Twitter 在內部實現這個 API 時使用 ConfigBus 來動態控制這些切換。
執行 A/B 測試:要有效地執行產品試驗需要快速迭代和易於調整的功能。Twitter 的試驗框架使用了 ConfigBus,允許應用程式開發人員輕鬆地設定和擴充套件試驗,並在需要時快速關閉它們。
一般應用程式配置:ConfigBus 最典型的用法是儲存一般應用程式配置檔案,並在更改提交時動態地重新載入它們。
經驗教訓
我們在生產環境中執行 ConfigBus 已經將近四年了。以下是我們學到的一些東西。
近實時分發
儘管 ConfigBus 的目標是近實時分發,但是,簽入儲存庫的糟糕配置更改會迅速傳播到任何地方。為了最小化這種更改的影響,ConfigBus 最近添加了一個可選特性,提供了“分階段(staged )”滾動功能,從而可以增量地滾動推出更改。這是通過同時推送新舊版本的配置以及一些關於推出階段的元資料來實現的。然後,各個應用程式例項使用階段元資料來動態地載入合適版本的配置。
Git 儲存庫規模
隨著 Git 儲存庫年齡的增長,它的規模也在增大。較大的儲存庫會減慢諸如“git clone”和“git add”之類的操作。儲存庫大小不僅受檢入的大檔案影響,而且還受到重大更改的影響。下面是我們用來解決這個問題的一些策略。
- “淺推送(Shallow pushes)” :我們升級到一個比較新的 Git 版本,該版本允許開發人員從儲存庫的淺克隆版本推送更改。這意味著最初的克隆操作要快得多,因為它只傳輸 HEAD 提交以及提交和推送更改所需的最小元資料。
- 歸檔 :在極少數情況下,我們會對 Git 儲存庫進行歸檔,將所有歷史記錄移動到一個歸檔中,然後重新開始。這使得我們可以刪除舊的、潛在的大檔案,減少儲存庫的大小。我們會避免經常這樣做,因為這會迫使開發人員重新克隆儲存庫。
- 延長 delta 鏈 :我們目前正在研究,是否要積極地對 Git 物件進行重新打包以延長 delta 鏈,從而幫助減少儲存庫的大小,同時又保持更改提交效能不變。
分割槽
我們不允許 Git 儲存庫上的“非快進(non-fast-forward)”推送,以保護主分支中的提交不會被強制推送覆蓋。這項設定的效果是要求對儲存庫的任何推送都必須使用儲存庫的最新副本。如果兩個提交者在將資料推送到儲存庫時產生競爭,那麼其中一個將勝出,另一個將不得不拉取最新的更改並重試。這會增加配置更新操作的延遲。對於頻繁提交者來說,延遲的增加帶來了一個巨大的問題。我們是通過在後臺將頻繁更新的名稱空間劃分到單獨的專用儲存庫來解決這個問題。對於使用 API 進行配置更新的客戶端來說,這沒有任何差異。
檔案級線性化
實際上,不允許非快進推送意味著 ConfigBus 在儲存庫級上是線性化的。如果兩個開發人員因同時推送更改而發生競爭,則其中一個將“獲勝”,另一個必須拉取最新的更改並重試。即使這兩個開發人員正在更新完全不同的檔案,情況也是如此。對於不斷更新的儲存庫,這會給客戶端帶來不必要的負擔。因此,我們把 ConfigBus 服務設計成自動拉取更新,並在失敗時重試推送。這提供了表面上的檔案級線性化,確保客戶端只在檔案級更新衝突時才看到失敗。
git fetch
與 git pull
“git pull”實際上是“git fetch”+“git merge”。如果 ConfigStore 機器上的克隆站點損壞或與遠端伺服器不同步,合併步驟可能會失敗。以自動方式從伺服器獲取更新最安全、最簡潔的方法是執行“git fetch”+“git reset——hard FETCH_HEAD”,以便覆蓋克隆站點上存在的任何本地狀態。
rsync 導致的緩慢
我們選擇讓少量的 ConfigStore 機器從 Git 上獲取資訊,並將它們作為其他機器通過 rsync 進行同步的源。我們使用 -c 選項執行 rsync,迫使它忽略時間戳,併為大小相同的檔案計算校驗和。這是 CPU 密集型的,因此限制了每臺 ConfigStore 機器可以提供的併發 rsync 操作的數量。反過來,這又增加了整個端到端傳播的延遲。將名稱空間分割槽成單獨的儲存庫可以減少 rsync 在每次提交時需要比較的檔案數量。另一種可能的選擇是在每臺 ConfigStore 機器上執行一個 Git 伺服器,並讓所有的目標機器執行“Git fetch”,這隻需要下載最新的“HEAD”,而不需要任何比較開銷(因為 Git 伺服器確切地知道發生了什麼變化)。
非原子同步
ConfigBus 使用 rsync,這意味著檔案可以單個地同步到目標機器上。因此,如果提交碰巧更改了多個檔案,那麼目標機器上的檔案系統可能會暫時包含新舊檔案的混合。一個可能的解決方案是同步到一個臨時位置,然後使用原子重新命名操作來完成更改。但是,由於需要以向後相容的方式支援分割槽名稱空間,部署位置上存在符號連結,這使得問題變得複雜。一個更可行的解決方案是繼續像現在這樣分發主 Git 儲存庫,但是把將來的分割槽儲存庫切換到原子部署。
未來展望
在 Twitter,我們構建 ConfigBus 是為了提供一個健壯的動態配置平臺。隨著現有用例的發展和新用例的出現,ConfigBus 必須進行更改以適應它們。以下是我們特別關注的幾個領域。
Git
對於終端使用者而言,Git 有很多優點,但是,它代表了一個持續的操作挑戰。我們對它今後是否仍然是正確的解決辦法持開放態度。替代方法包括鍵 - 值儲存,如 Consul,但我們必須解決歷史太少這個 相反的問題 。
重新設計分發
使用 rsync 從一個很小的 ConfigStore 機器池進行分發限制了分發管道的速度。探索點對點分發模型將是一件很有趣的事情,在這種模型中,每臺機器在擁有部分或全部資料後都充當進一步傳輸的源。
支援大物件
目前,我們不鼓勵使用 ConfigBus 處理大型 Blob,這主要是因為 Git,也因為在每臺機器上儲存大型 Blob 的效率很低。一個潛在的解決方案是將 Blob 儲存在一個常規的 Blob 儲存中,並將活動版本儲存在 ConfigBus 中,然後根據需要下載它們。
檢視英文原文: Dynamic configuration at Twitter