ConfigBus:Twitter的動態配置實踐

動態配置能夠在不重新啓動應用程序的情況下更改正在運行的系統的行爲和功能。理想的動態配置系統使服務開發人員和管理員能夠方便地查看和更新配置,並高效可靠地嚮應用程序提供配置更新。它使組織能夠快速、大膽地迭代新特性,並提供工具,減少與更改現有系統相關的風險。

在Twitter的早期,應用程序管理並分發自己的配置,通常存儲在ZooKeeper中。但是,我們以前使用ZooKeeper的經驗表明,它在用作通用鍵值存儲時不能伸縮。其他團隊轉而使用Git進行存儲,並結合自定義的工具來更新、分發和重新加載配置。隨着Twitter的發展,很明顯需要一個標準的解決方案來提供可伸縮的基礎設施、可重用的庫和有效的監控。

本文將介紹Twitter的動態配置系統ConfigBus。ConfigBus包括存儲配置的數據庫、將配置分發到Twitter數據中心中的機器的管道、讀取和更新配置的API和工具。

架構

在一個較高的層次上,你可以將ConfigBus看作一個Git存儲庫,它的內容被推送到Twitter數據中心的所有機器上。配置更改經過一系列步驟到達目的地:

  1. 開發人員向Git存儲庫提交更改。經過身份驗證的應用程序也可以通過ConfigBus服務提交到存儲庫。
  2. 預接收鉤子驗證更改。如果驗證通過,則在服務器上接受並提交推送。(我們將在“特點”部分更詳細地討論這種驗證。)
  3. 一旦服務器上的提交過程完成,Git接收後鉤子就會使用提交信息(SHA、時間戳等)更新ZooKeeper中的特定節點。
  4. 接下來會觸發ZooKeeper watches,引發下游過程的動作:
    a. 在作爲配置準備區的“ConfigStore”機器上,watch事件會調用一個回調,從Git服務器獲取最新提交。它還用當前的SHA更新ZooKeeper中的條目。
    b.在目標機器上,watch事件觸發鏡像任務,該任務輪詢ZooKeeper,找出與最新提交具有相同SHA的ConfigStore機器。一旦找到源機器,鏡像任務將運行rsync把更改同步到本地機器上。
  5. 最後,使用這些配置文件的應用程序將看到文件系統上的更改(通過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 fetchgit 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章