Grail:Uber是如何管理大規模基礎設施的

AdolphLWQ發表於2019-08-21

易於獲取當前系統狀態對於規模化構建、維護基礎設施至關重要。由於Uber的商業持續擴張,我們的基礎設施在規模和複雜性上不斷增加,使得我們必要時獲取所需資訊變得很困難。

為了解決這個問題,我們開發了Grail,一個聚合狀態資訊並在一個全域性檢視中展示、橫跨多個資料中心和區域的平臺。有了Grail,我們可以更容易地開發快速、健壯的運維工具。

繼續閱讀以瞭解Grail如何通過圖模型,根本性地改變Uber工程部門操作儲存的方式,使團隊更容易縫合不同源頭的資料。

設計簡單的管理方式

2016年末,為了支撐不斷增加的負載,我們把所有資料庫主機從旋轉式硬碟更新到固態硬碟。有一步很重要,就是依然能夠鑑別和追蹤使用舊硬體的成千上萬資料庫。

那時候我們沒有容易的方式獲取裝置的當前狀態,並且還要追蹤大量指令碼和任務。這驅使我們尋找不同的方法來開發大規模運維工具,需求如下:

  1. 持續收集整個基礎設施的狀態。
  2. 唯一的全域性檢視。
  3. 低延遲從所有資料來源獲取所有資料。
  4. 關聯所有資料來源的資料。
  5. 簡單新增和刪除資料來源。

Grail簡介

不像Metricbeatosquery等類似資訊收集系統,Grail不收集特定領域的資訊,它的角色是一個平臺,以高可用和響應式的方式聚合、連結和查詢來自不同資料來源的資料,例如主機、資料庫、部署和所有權等資訊。它高效隱藏了實現細節。

此外,你可以接近實時的方式,快速獲取下面問題的答案:

  1. 哪些主機當前空閒空間超過4TB?
  2. 某個團隊的資料庫使用多少磁碟空間?
  3. 哪些資料庫執行在旋轉磁碟上?

如果你的服務和主機很少,這些問題就不重要。你只需要寫一個指令碼,在需要的時候直接收集資訊就行了。但是以Uber的規模,當你有一堆服務和數十萬主機時,這種方法就失效了。節點太多響應就會慢,查詢完後資料關聯會出錯,結果也不能反映真實情況了。大規模場景下很難及時收集狀態。

一個關鍵結論是“不存在唯一的真理來源”。資料中心和系統的資訊總是分佈在多個地方,只有把它們關聯起來才能做決策。更復雜的是這些狀態一直在變:主機的空閒磁碟空間在變、供應新的儲存叢集、並行發生的其它事件。整個系統的狀態不可能實時獲取,只能接近它。

規模化維護

Uber的儲存平臺團隊開發維護的儲存系統支撐了拍位元組的關鍵任務資料,我們的運維工具有一套標準的自我修復正規化,有三個簡單步驟:首先我們收集系統狀態,然後和正常狀態比較,最後處理異常資料。

我們構建的所有操作工具都經過這個迴圈,直到系統狀態收斂於正常狀態

如前文所述,在大規模場景下,不使用Grail這樣的聚合平臺是很難收集狀態。舉個例子,當我們想獲取所有執行主機當前狀態時,比如trips資料。首先我們要先找出哪些主機包含這個資料。接下來我們要連線到這些主機並收集當前狀態。最後轉換並展示結果。

有了Grail,我們只需要執行一條查詢語句,就可以獲得需要的資訊:

TRAVERSE datastore:trips ( SCAN cluster ( SCAN db ( SCAN host ( FIELD HostInfo ) ) ) )

結果以json文件的形式返回,與查詢結構非常相似,對程式碼友好。下面的程式碼片段展示了執行上面查詢語句的精簡版結果:

json { "__id": "datastore:trips", "cluster": [{ "__id": "cluster-trips-us1-44", "db": [{ "__id": "cluster-trips-us1-44-db26", "host": [{ "__id": "host:database862-sic1", "HostInfo": { "cpuCount": 24, "puppetRole": "database", "memory": { "freeBytes": 1323212425, "totalBytes": 137438953472 }, "disk": { "freeBytes": 48289601723, "totalBytes": 1598689906787 } } }] }] }] }

拼接資料

Grail圍繞對Uber基礎設施的兩項觀察進行設計。第一,基礎設施中節點和節點間的聯絡可以很自然地建模為圖。

有了Grail,基礎設施被表示成相互連線的節點圖

模型圖中的節點通過唯一的鍵進行標識,鍵由型別和名字以type:name的形式構成。資料來源使用節點鍵將包括屬性和連線的資料附加到節點上,因此資料就好像被節點鍵標識一樣。節點的鍵空間是全域性的,而屬性和連線的鍵空間相對於節點是區域性的。

Grail的物件模型是這樣的,建模圖中的節點由資料來源生成的屬性和連線隱式定義,這意味著下面條件至少滿足一條節點A存在: 1. 資料來源產生的資料有A的屬性。 2. 資料來源將節點A與至少一個其它節點關聯。 3. 資料來源至少將其它一個節點與A關聯。

第二點是單個基礎設施概念,比如主機或資料庫的資訊是去中心化的。這意味著獲取完整資料檢視需要結合不同系統的資訊。

Grail的方法是讓每個資料來源提供自己所屬子圖來解決去中心化問題。這些子圖可能會有重疊,因為資料來源可能把屬性和連線附加到同一個節點。

圖4:每個資料來源提供它們自己的子圖,並且將資料附加到節點鍵上。查詢時將這些子圖結合起來,提供整個基礎設施的檢視

上圖最上面展示了三個子圖。實線和顏色表示子圖由哪些資料來源提供,虛線表示整個圖。下面的圖表示從Grail使用者的角度看到的檢視。

通過方法,我們可以自動更新資料來源的所有資料。對不同資料來源,我們能夠以不同的速度並行更新資料。

上圖中,資料來源1在鍵HostInfo下附加屬性,資料來源2鍵ServiceInfo下附加屬性,並將此節點和關聯型別Service下的一系列服務建立聯絡。

資料導航

隨著設計的實施,我們需要一種簡單的方法,能夠在圖中執行特定遍歷。我們調研的技術中沒有能很好符合需求的。比如,GraphQL需要定義模式,且不支援對映和節點間命名關聯。Gremlin似乎可以,但實現並單獨使用它非常複雜。所以我們開發了自己的方案。

我們的查詢語言YQL,使用者只需要指定一個開始節點集,然後通過後面的條件遍歷圖,同時與沿圖屬性中的欄位互動。舉個例子,下面的查詢語句列出了所有滿足條件的主機:空閒記憶體大於40G、剩餘磁碟空間大於100G且是SSD:

TRAVERSE host:* (
    FIELD HostInfo
    WHERE HostInfo.disk.media = “SSD“
    WHERE HostInfo.disk.free > (100*1024^3)
    WHERE HostInfo.memory.free > (40*1024^3)
)

遷移到記憶體

從釋出起Grail的架構經歷多次迭代。起初,它是我們之前資料庫運維工具的內部元件。第一版迭代受TAO啟發,基於Python開發,使用redis儲存圖。當它變得低效時,我們決定把它作為一個單獨服務用Go重寫,使用共享的ElasticSearch叢集儲存。但是隨著時間的推移,我們發現這個方案在快速、有效攝取和查詢所需資訊時,缺少伸縮性和低延遲。

我們重新思考它的架構,把之前存到共享ElasticSearch叢集中的資料遷移,改為直接儲存到每個查詢節點上定製的記憶體資料庫裡。

當前Grail的高層架構包含三個元件:

  1. Ingesters,從配置的資料來源收集資料。
  2. Coordination,確保嚴格的資料更新順序。
  3. Query Nodes,為資料獲取提供水平擴充套件能力

Ingesters週期性從預配置的資料來源中收集資料,然後通過Coordination叢集傳輸,最後儲存到每一個查詢節點上的datastore中。Coordination由定製的記憶體Raft叢集實現,基於etcd Raft庫開發。Raft協議確保資料更新和被儲存到datastore的順序,同時確保重啟後資料一致。Coordination NodesQuery Nodes都包含了儲存在datastore中的每個資料來源最新資料更新。當Raft-logs被截斷時,Coordination Nodes只使用datastore中的資料來建立當前資料的快照。

datastore是一個簡單的鍵/值抽象,資料來源的名字作為鍵,不同的鍵下面儲存每個資料來源最新的資料更新。所有資料來源的資料分開儲存,只有在執行查詢時才聚合起來。

Grail通過在每個區域執行各自的例項,為我們提供基礎設施的全域性檢視。每個例項負責從本地主機和服務收集資料。查詢節點根據配置追蹤本地和遠端區域上的raft-log。當執行查詢時,查詢引擎把本地和遠端資訊結合起來。

為了擴充套件Grail,我們可以部署多個coordination叢集、擴充套件查詢引擎來支撐分散式查詢,以便將來可以增加資料吞吐量和大小。

處理精確問題

在與分散式系統互動時,考慮到資訊不準確非常重要。不管資料如何提供,來自聚合平臺或直接來自源頭,在系統變化時不可避免會有延遲。分散式系統不是事務的,你不能用一致的快照獲取它。不管基礎設施規模如何,這些條件都是對的。

我們的運維工具使用Grail的資訊做決策。當這些決策需要改變系統時,在應用改變之前,我們總是確保雙重檢查源頭的資訊。舉個例子,當主機端的代理程式被分配任務時,代理程式在執行任務前會先檢查先決條件是否滿足,比如判斷主機是否有足夠的磁碟空間。

關鍵點

正如前面所討論的,高效基礎設施管理需要深刻洞察系統狀態。當規模很小時這很簡單,你只需要按需查詢資料即可。但是這個方法不適用大規模系統,這時你要將資訊聚合到一處。正如我們在實踐中學到的,當有數十萬主機和許多系統時,快速獲取合理且最新的系統狀態很重要。

最後Grail的優勢可以總結為三點:

  1. 所有資料都被聚合到支援通用查詢API的單一共享模型。
  2. 低延遲查詢所有地區當前狀態。
  3. 團隊可以附加自己特定領域的概念,並將它們與來自其它領域的相關概念關聯起來。

目前,Grail服務我們儲存方案的大多運維工具,並且對基礎設施的各個方面都有幾乎無數的使用案例。事實上隨著資訊範圍不斷增加,會有更多的使用案例。

譯者說

本文介紹Uber儲存平臺團隊是如何管理自己的儲存基礎設施的。它們需要收集狀態,然後根據這些資訊執行運維任務、制定決策。

他們開發了Grail來管理,不斷迭代來提升效能。將基礎設施抽象成圖模型,並且自己開發了工具來快速遍歷圖。他們面臨多個挑戰:分散式系統的資料延遲、不準確問題,查詢效能低等。最後都很好的解決了,這些方法對我們有借鑑意義。

Grail:Uber是如何管理大規模基礎設施的
公眾號[QuanTalk],專注於電腦科學與技術、獨立思考、閱讀分享。歡迎關注交流

相關文章