易於獲取當前系統狀態對於規模化構建、維護基礎設施相當重要。因爲Uber的商業持續擴張,咱們的基礎設施在規模和複雜性上不斷增長,使得咱們必要時獲取所需信息變得很困難。node
爲了解決這個問題,咱們開發了Grail,一個聚合狀態信息並在一個全局視圖中展現、橫跨多個數據中心和區域的平臺。有了Grail,咱們能夠更容易地開發快速、健壯的運維工具。git
繼續閱讀以瞭解Grail如何經過圖模型,根本性地改變Uber工程部門操做存儲的方式,使團隊更容易縫合不一樣源頭的數據。github
2016年底,爲了支撐不斷增長的負載,咱們把全部數據庫主機從旋轉式硬盤更新到固態硬盤。有一步很重要,就是依然可以鑑別和追蹤使用舊硬件的成千上萬數據庫。redis
那時候咱們沒有容易的方式獲取設備的當前狀態,而且還要追蹤大量腳本和任務。這驅使咱們尋找不一樣的方法來開發大規模運維工具,需求以下:數據庫
不像Metricbeat和osquery等相似信息收集系統,Grail不收集特定領域的信息,它的角色是一個平臺,以高可用和響應式的方式聚合、連接和查詢來自不一樣數據源的數據,例如主機、數據庫、部署和全部權等信息。它高效隱藏了實現細節。json
此外,你能夠接近實時的方式,快速獲取下面問題的答案:bash
若是你的服務和主機不多,這些問題就不重要。你只須要寫一個腳本,在須要的時候直接收集信息就好了。可是以Uber的規模,當你有一堆服務和數十萬主機時,這種方法就失效了。節點太多響應就會慢,查詢完後數據關聯會出錯,結果也不能反映真實狀況了。大規模場景下很難及時收集狀態。架構
一個關鍵結論是「不存在惟一的真理來源」。數據中心和系統的信息老是分佈在多個地方,只有把它們關聯起來才能作決策。更復雜的是這些狀態一直在變:主機的空閒磁盤空間在變、供應新的存儲集羣、並行發生的其它事件。整個系統的狀態不可能實時獲取,只能接近它。運維
Uber的存儲平臺團隊開發維護的存儲系統支撐了拍字節的關鍵任務數據,咱們的運維工具備一套標準的自我修復範式,有三個簡單步驟:首先咱們收集系統狀態,而後和正常狀態比較,最後處理異常數據。分佈式
如前文所述,在大規模場景下,不使用Grail這樣的聚合平臺是很難收集狀態。舉個例子,當咱們想獲取全部運行主機當前狀態時,好比trips數據。首先咱們要先找出哪些主機包含這個數據。接下來咱們要鏈接到這些主機並收集當前狀態。最後轉換並展現結果。
有了Grail,咱們只須要運行一條查詢語句,就能夠得到須要的信息:
TRAVERSE datastore:trips (
SCAN cluster (
SCAN db (
SCAN host (
FIELD HostInfo
)
)
)
)
複製代碼
結果以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基礎設施的兩項觀察進行設計。第一,基礎設施中節點和節點間的聯繫能夠很天然地建模爲圖。
模型圖中的節點經過惟一的鍵進行標識,鍵由類型和名字以type:name
的形式構成。數據源使用節點鍵將包括屬性和鏈接的數據附加到節點上,所以數據就好像被節點鍵標識同樣。節點的鍵空間是全局的,而屬性和鏈接的鍵空間相對於節點是局部的。
Grail的對象模型是這樣的,建模圖中的節點由數據源生成的屬性和鏈接隱式定義,這意味着下面條件至少知足一條節點A存在:
第二點是單個基礎設施概念,好比主機或數據庫的信息是去中心化的。這意味着獲取完整數據視圖須要結合不一樣系統的信息。
Grail的方法是讓每一個數據源提供本身所屬子圖來解決去中心化問題。這些子圖可能會有重疊,由於數據源可能把屬性和鏈接附加到同一個節點。
上圖最上面展現了三個子圖。實線和顏色表示子圖由哪些數據源提供,虛線表示整個圖。下面的圖表示從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的高層架構包含三個組件:
Ingesters週期性從預配置的數據源中收集數據,而後經過Coordination集羣傳輸,最後存儲到每個查詢節點上的datastore中。Coordination由定製的內存Raft集羣實現,基於etcd Raft庫開發。Raft協議確保數據更新和被存儲到datastore的順序,同時確保重啓後數據一致。Coordination Nodes
和Query Nodes
都包含了存儲在datastore中的每一個數據源最新數據更新。當Raft-logs被截斷時,Coordination Nodes只使用datastore中的數據來建立當前數據的快照。
datastore是一個簡單的鍵/值抽象,數據源的名字做爲鍵,不一樣的鍵下面存儲每一個數據源最新的數據更新。全部數據源的數據分開存儲,只有在執行查詢時才聚合起來。
Grail經過在每一個區域運行各自的實例,爲咱們提供基礎設施的全局視圖。每一個實例負責從本地主機和服務收集數據。查詢節點根據配置追蹤本地和遠程區域上的raft-log。當執行查詢時,查詢引擎把本地和遠程信息結合起來。
爲了擴展Grail,咱們能夠部署多個coordination集羣、擴展查詢引擎來支撐分佈式查詢,以便未來能夠增長數據吞吐量和大小。
在與分佈式系統交互時,考慮到信息不許確很是重要。無論數據如何提供,來自聚合平臺或直接來自源頭,在系統變化時不可避免會有延遲。分佈式系統不是事務的,你不能用一致的快照獲取它。無論基礎設施規模如何,這些條件都是對的。
咱們的運維工具使用Grail的信息作決策。當這些決策須要改變系統時,在應用改變以前,咱們老是確保雙重檢查源頭的信息。舉個例子,當主機端的代理程序被分配任務時,代理程序在執行任務前會先檢查先決條件是否知足,好比判斷主機是否有足夠的磁盤空間。
正如前面所討論的,高效基礎設施管理須要深入洞察系統狀態。當規模很小時這很簡單,你只須要按需查詢數據便可。可是這個方法不適用大規模系統,這時你要將信息聚合到一處。正如咱們在實踐中學到的,當有數十萬主機和許多系統時,快速獲取合理且最新的系統狀態很重要。
最後Grail的優點能夠總結爲三點:
目前,Grail服務咱們存儲方案的大多運維工具,而且對基礎設施的各個方面都有幾乎無數的使用案例。事實上隨着信息範圍不斷增長,會有更多的使用案例。
Grail has made it easy for us to build tooling that quickly and robustly performs complex operational tasks.
這句話很容易意會,但用準確、精煉的語言表達出來還很難。我是這麼翻譯的有了Grail,咱們能夠更容易地開發快速、健壯的運維工具。
In figure 5, Data Source 1 attaches a property under the HostInfo key, while Data Source 2 attaches a property under the ServiceInfo key and associates the node with a range of other service nodes in the graph under the association type Service.
這句話後一句很差理解、很差翻譯
lets users specify a starting collection of nodes and then traverse the graph by following associations while interacting with the fields stored in the properties along the way
很差翻譯,借鑑工具後翻譯以下讓用戶指定一個節點集,而後經過後面的關聯遍歷圖,同時與沿圖屬性中的字段交互
。
Over time, however, this solution also lacked the scalability and latency we needed for fast, efficient ingestion and querying.
可是隨着時間的推移,咱們發現這個方案在快速、有效攝取和查詢所需信息時,缺乏伸縮性和低延遲。
Distributed systems are not transactional—you cannot capture it with a consistent snapshot.
本文介紹Uber存儲平臺團隊是如何管理本身的存儲基礎設施的。它們須要收集狀態,而後根據這些信息執行運維任務、制定決策。
他們開發了Grail來管理,不斷迭代來提高性能。將基礎設施抽象成圖模型,而且本身開發了工具來快速遍歷圖。他們面臨多個挑戰:分佈式系統的數據延遲、不許確問題,查詢性能低等。最後都很好的解決了,這些方法對咱們有借鑑意義。
公衆號[QuanTalk],專一於計算機科學與技術、獨立思考、閱讀分享。歡迎關注交流