從司機與乘客的位置和目的地,到餐館的訂單和支付交易,Uber 的運輸平臺上的每次互動都是由資料驅動的。資料賦能了 Uber 的全球市場,使我們面向全球乘客、司機和食客的產品得以具備更可靠、更無縫的使用者體驗,並使我們自己的員工能夠更有效地完成工作。
憑藉系統的複雜性和資料的廣泛性,Uber 將資料驅動提升到了一個新的水平:每天處理萬億級的 Kafka 訊息,橫跨多個資料中心的 HDFS 中儲存著數百 PB 的資料,並支援每週上百萬的分析查詢。
然而大資料本身並不足以形成見解。Uber 規模的資料要想被有效且高效地運用,還需要結合背景資訊來做業務決策,這才能形成見解。為此我們建立了 Uber 的內部平臺 Databook,該平臺用於展示和管理具體資料集的有關內部位置和所有者的後設資料,從而使我們能夠將資料轉化為知識。
業務(和資料)的指數增長
自 2016 年以來,Uber 的平臺上已增加了多項新業務,包括 Uber Eats、Uber Freight,以及 Jump Bikes。如今,我們每天至少完成 1500 萬次行程,每月活躍有超過 7500 萬乘客。在過去的八年中,Uber 已從一家小型創業公司發展到在全球擁有 18,000 名員工。
隨著這種增長,資料系統和工程架構的複雜性也增加了。例如有數萬張表分散在多個在役的分析引擎中,包括 Hive、Presto 和 Vertica。這種分散導致我們急需瞭解資訊的全貌,特別是我們還在不斷新增新業務和新員工。 2015 年的時候,Uber 開始手動維護一些靜態 HTML 檔案來對錶進行索引。
隨著公司發展,要更新的表和相關後設資料的量也在增長。為確保我們的資料分析能跟上,我們需要一種更加簡便快捷的方式來做更新。在這種規模和增長速度下,有個能發現所有資料集及其相關後設資料的強大系統簡直不要太贊:它絕對是讓 Uber 資料能利用起來的必備品。
圖 1:Databook 是 Uber 的內部平臺,可以展示和管理資料有關內部位置和所有者的後設資料。
為使資料集的發現和探索更容易,我們建立了 Databook。 Databook 平臺管理和展示著 Uber 中豐富的資料集後設資料,使我們員工得以探索、發現和有效利用 Uber 的資料。 Databook 確保資料的背景資訊 —— 它的意義、質量等 —— 能被成千上萬試圖分析它的人接觸到。簡而言之,Databook 的後設資料幫助 Uber 的工程師、資料科學家和運營團隊將此前只能幹看的原始資料轉變為可用的知識。
通過 Databook,我們摒棄了手動更新,轉為使用一種先進的自動化後設資料庫來採集各種經常重新整理的後設資料。Databook 具有以下特性:
- 擴充性: 易於新增新的後設資料、儲存和記錄。
- 訪問性: 所有後設資料可被服務以程式方式獲取。
- 擴充套件性: 支援高通量讀取。
- 功能性: 跨資料中心讀寫。
Databook 提供了各種各樣的後設資料,這些後設資料來自 Hive、Vertica、MySQL、Postgres、Cassandra 和其他幾個內部儲存系統,包括:
- 表模式
- 表/列的說明
- 樣本資料
- 統計資料
- 上下游關係
- 表的新鮮度、SLA 和所有者
- 個人資料分類
所有的後設資料都可以通過一箇中心化的 UI 和 RESTful API 來訪問到。 UI 使使用者可以輕鬆訪問到後設資料,而 API 則使 Databook 中的後設資料能被 Uber 的其他服務和用例使用。
雖說當時已經有了像 LinkedIn 的 WhereHows 這種開源解決方案,但在 Databook 的開發期間,Uber 還沒有采用 Play 框架和 Gradle(譯者注:兩者為 WhereHows 的依賴)。 且 WhereHows 缺乏跨資料中心讀寫的支援,而這對滿足我們的效能需求至關重要。因此,利用 Java 本身強大的功能和成熟的生態系統,我們建立了自己內部的解決方案。
接下來,我們將向您介紹我們建立 Databook 的過程以及在此過程中我們遇到的挑戰。
Databook 的架構
Databook 的架構可以分為三個部分:採集後設資料、儲存後設資料以及展示後設資料。下面圖 2 描繪的是該工具的整體架構:
圖 2:Databook 架構:後設資料從 Vertica、Hive 和其他儲存系統中獲取,儲存到後端資料庫,通過 RESTful API 輸出。
Databook 引入多個資料來源作為輸入,儲存相關後設資料並通過 RESTful API 輸出(Databook 的 UI 會使用這些 API)。
在初次設計 Databook 時,我們就必須做出一個重大的決定,是事先採集後設資料存起來,還是等到要用時現去獲取?我們的服務需要支援高通量和低延遲的讀取,如果我們將此需求託付給後設資料源,則所有後設資料源都得支援高通量和低延遲的讀取,這會帶來複雜性和風險。比如,獲取表模式的 Vertica 查詢通常要處理好幾秒,這並不適合用來做視覺化。同樣,我們的 Hive metastore 管理著所有 Hive 的後設資料,令其支援高通量讀取請求會有風險。既然 Databook 支援許多不同的後設資料源,我們就決定將後設資料儲存在 Databook 自身的架構中。此外,大多數用例雖然需要新鮮的後設資料,但並不要求實時看到後設資料的更改,因此定期爬取是可行的。
我們還將請求服務層與資料採集層分開,以使兩者能執行在獨立的程式中,如下面的圖 3 所示:
圖 3:Databook 由兩個不同的應用層組成:資料採集爬蟲和請求服務層。
兩層隔離開可減少附帶影響。例如,資料採集爬蟲作業可能佔用較多的系統資源,沒隔離就會影響到請求服務層上 API 的 SLA。另外,與 Databook 的請求服務層相比,資料採集層對中斷不太敏感,如果資料採集層掛掉,可確保仍有之前的後設資料能提供,從而最大限度地減少對使用者的影響。
事件驅動採集 vs 定時採集
我們的下一個挑戰是確定如何最且成效且最高效地從多種不同資料來源採集後設資料。我們考慮過多種方案,包括建立一個分散式的容錯框架,利用基於事件的資料流來近乎實時地檢測和除錯問題。
我們先建立了爬蟲來定期採集各種資料來源和微服務生成的有關資料集的後設資料資訊,例如表的使用資料統計,它由我們用於解析和分析 SQL 的強大開源工具 Queryparser 生成。(順帶一提:Queryparser 也由我們的“資料知識平臺”團隊建立)。
我們需要以可擴充套件的方式頻繁採集後設資料資訊,還不能阻塞到其他的爬蟲任務。為此,我們將爬蟲部署到了不同的機器,這就要求分散式的爬蟲之間能進行有效協調。我們考慮配置 Quartz 的叢集模式(由 MySQL 支援)來做分散式排程。但是,卻又面臨兩個實現上的障礙:首先,在多臺機器上以叢集模式執行 Quartz 需要石英鐘的定期同步,這增加了外部依賴,其次,在啟動排程程式後我們的 MySQL 連線就一直不穩定。最後,我們排除了執行 Quartz 叢集的方案。
但是,我們仍然決定使用 Quartz,以利用其強大的記憶體中排程功能來更輕鬆、更高效地向我們的任務佇列釋出任務。對於 Databook 的任務佇列,我們用的是 Uber 的開源任務執行框架 Cherami。這個開源工具讓我們能在分散式系統中將消費程式解耦,使其能跨多個消費者群組進行非同步通訊。有了 Cherami,我們將 Docker 容器中的爬蟲部署到了不同的主機和多個資料中心。使用 Cherami 使得從多個不同來源採集各種後設資料時不會阻塞任何任務,同時讓 CPU 和記憶體的消耗保持在理想水平並限制在單個主機中。
儘管我們的爬蟲適用於大多數後設資料型別,但有一些後設資料還需要近乎實時地獲取,所以我們決定過渡到基於 Kafka 的事件驅動架構。有了這個,我們就能及時檢測和除錯資料中斷。我們的系統還可以捕獲後設資料的重大變動,例如資料集上下游關係和新鮮度,如下面的圖 4 所示:
圖 4:在 Databook 中,對每個表採集上下游關係/新鮮度後設資料。
這種架構使我們的系統能夠以程式方式觸發其他微服務並近乎實時地向資料使用者傳送資訊。但我們仍需使用我們的爬蟲執行諸如採集/重新整理樣本資料的任務,以控制對目標資源的請求頻率,而對於在事件發生時不一定需要採集的後設資料(比如資料集使用情況統計)則自動觸發其他系統。
除了近乎實時地輪詢和採集後設資料之外,Databook UI 還從使用者和生產者處採集資料集的說明、語義,例如表和列的描述。
我們如何儲存後設資料
在 Uber,我們的大多數資料管道都執行在多個叢集中,以實現故障轉移。因此,同一張表的某些型別的後設資料的值(比如延遲和使用率)可能因叢集的不同而不同,這種資料被定義為叢集相關。相反,從使用者處採集的說明後設資料與叢集無關:描述和所有權資訊適用於所有叢集中的同一張表。 為了正確關聯這兩種型別的後設資料,例如將列描述與所有叢集中的表列相關聯,可以採用兩種方案:寫時關聯或讀時關聯。
寫時關聯
將叢集相關的後設資料與叢集無關的後設資料相關聯時,最直接的策略是在寫入期間將後設資料關聯在一起。例如,當使用者給某個表列新增描述時,我們就將資訊儲存到所有叢集的表中,如下面的圖 5 所示:
圖 5:Databook 將叢集無關的後設資料持久化儲存到所有表中。
這方案可確保持久化資料保持整潔。例如在圖 5 中,如果“列 1”不存在,該叢集就會拒絕該請求。但這存在一個重要的問題:在寫入時將叢集無關的後設資料關聯到叢集相關的後設資料,所有叢集相關的後設資料必須已經存在,這在時間上只有一次機會。例如,當在圖 5 中改動表列描述時,還只有叢集 1 有此“列 1”,則叢集 2 的寫入失敗。之後,叢集 2 中同一個表的模式被更新,但已錯失機會,除非我們定期重試寫入,否則此描述將永遠不可用,這導致系統複雜化。下面圖 6 描述了這種情況:
圖 6:Databook 將叢集無關的後設資料持久儲存到所有表中。
讀時關聯
實現目標的另一種方案是在讀取時關聯叢集無關和叢集相關的後設資料。由於這兩種後設資料是在讀取時嘗試關聯,無所謂叢集相關的後設資料一時是否存在,因此這方案能解決寫時關聯中丟失後設資料的問題。當表模式更新後顯示“列 1”時,其描述將在使用者讀取時被合併,如下面圖 7 所示:
圖 7:Databook 在讀取時關聯叢集相關和叢集無關的後設資料。
儲存選擇
Databook 後端最初是使用 MySQL,因為它開發速度快,可以通過 Uber 的基礎設施自動配置。但是,當涉及多資料中心支援時,共享 MySQL 叢集並不理想,原因有三:
- 單個主節點:首先,Uber 僅支援單個主節點,導致其他資料中心的寫入時間較慢(我們這情況每次寫入增加約 70ms)。
- 手動提權:其次,當時不支援自動提權。因此,如果主節點掛掉,要花數小時才能提升一個新的主節點。
- 資料量:我們棄用 MySQL 的另一個原因是 Uber 所產生的大量資料。我們打算保留所有歷史變更,並希望我們的系統支援未來擴充套件,而無需在叢集維護上花費太多時間。
出於這些原因,我們選擇 Cassandra 來取代 MySQL,因為它具有強大的 XDC 複製支援,允許我們從多個資料中心寫入資料而不會增加延遲。而且由於 Cassandra 具有線性可擴充套件性,我們不再需要擔心適應 Uber 不斷增長的資料量。
我們如何展示資料
Databook 提供了兩種訪問後設資料的主要方法:RESTful API 和視覺化 UI。Databook 的 RESTful API 用 Dropwizard(一個用於高效能 RESTful Web 服務的 Java 框架)開發,並部署在多臺計算機上,由 Uber 的內部請求轉發服務做負載平衡。
在 Uber,Databook 主要用於其他服務以程式方式訪問資料。例如,我們的內部查詢解析/重寫服務依賴於 Databook 中的表模式資訊。API 可以支援高通量讀取,並且可以水平擴充套件,當前的每秒查詢峰值約為 1,500。視覺化 UI 由 React.js 和 Redux 以及 D3.js 編寫,主要服務於整個公司的工程師、資料科學家、資料分析師和運營團隊,用以分流資料質量問題並識別和探索相關資料集。
搜尋
搜尋是 Databook UI 的一項重要功能,它使使用者能夠輕鬆訪問和導航表後設資料。我們使用 Elasticsearch 作為我們的全索引搜尋引擎,它從 Cassandra 同步資料。如下面圖 8 所示,使用 Databook,使用者可結合多個維度搜尋,例如名稱、所有者、列和巢狀列,從而實現更及時、更準確的資料分析:
圖 8:Databook 允許使用者按不同的維度進行搜尋,包括名稱、所有者和列。
Databook 的新篇章
通過 Databook,Uber 現在的後設資料比以往更具可操作性和實用性,但我們仍在努力通過建造新的、更強大的功能來擴充套件我們的影響力。我們希望為 Databook 開發的一些功能包括利用機器學習模型生成資料見解的能力,以及建立高階的問題檢測、預防和緩解機制。
如果結合內部和開源解決方案建立可擴充套件的智慧服務並開發有創意的複雜技術對您來說有吸引力,請聯絡 Zoe Abrams(za@uber.com)或申請我們團隊的職位!
如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。