Uber推出數據湖集成神器DBEvents,支持MySQL、Cassandra等

在全球市場保持Uber平臺的可靠性和實時性是一項7*24小時不能間斷的任務。當舊金山的人們進入夢鄉時,巴黎的上班族們正發送着Uber車輛訂單準備出門工作。而同一時刻在地球的另一端,孟買的居民可能正在用Uber Eats訂購晚餐。

我們在Uber的大數據平臺上促成各種互動,使用我們的Marketplace來匹配乘客和司機;食客、餐館和配送夥伴;貨車司機和運貨人。從數據的角度來洞察這些交互有助於我們爲全球用戶提供優質且有意義的產品體驗。

食客們希望食物能及時送達,乘客也希望在最短的時間內被接到,我們的數據必須儘可能快的反映出現場發生的事件。但隨着四面八方的數據匯入我們的數據湖,在這種規模下保持數據的新鮮度成爲了一項重大的挑戰。

雖然現在已經有一些爲公司提供24小時數據新鮮度的方案,但對於Uber的實時性需求來說還是過時了。此外,對於Uber的數據規模和運營規模,這種方案無法保證可靠運行。

爲了滿足我們的特殊需求,我們開發了DBEvents——一種專爲高數據質量和新鮮度而設計的變更數據獲取系統。變更數據獲取系統(CDC, Change Data Capture System)可以用來確定哪些數據發生了變更,以便採取一些操作,比如獲取或是複製。DBEvents有助於引導、獲取已有表格的快照,以及增量的流式更新。

作爲Uber其他軟件(例如MarmarayHudi)的補充,DBEvents從MySQL、Apache Cassandra和Schemaless中獲取數據,以更新我們的Hadoop數據湖。這個解決方案可管理PB級的數據並可在全球範圍內運營,幫助我們爲內部數據客戶提供最好的服務。

快照數據獲取

從歷史上看,Uber的數據獲取一般會先確認要獲取的數據集,然後使用MapReduce或是Apache Spark運行一個大型處理作業,從源數據庫或表中高併發地讀取數據。接下來,我們會將這個作業的輸出發送到離線的數據湖,如HDFS或Apache Hive。我們把這個過程稱爲快照,根據數據集大小的不同,一般會花費幾分鐘到幾小時的時間,這對於我們內部客戶的需求來說還不夠快。

當一個作業開始獲取數據時,它會分散成多個並行任務,與上游的表格(如MySQL)建立並行的連接並拉取數據。從MySQL讀取大量數據會對其實時應用流量施加很大的壓力。我們可以使用專用的服務器執行ETL操作來減輕壓力,但這會影響到數據的完整性,也會因爲這個備份的數據庫服務器增加額外的硬件成本。

獲取數據庫或表的時間會隨着數據量的增加而延長,並在某些時刻無法再滿足業務的需求。由於大多數的數據庫每天僅更新部分數據,只有極少量的新紀錄會被添加,而整個快照過程會一遍又一遍地讀取和寫入整張表的數據,包括未修改的行,這會導致計算和存儲資源無法被有效利用。

DBEvents的要求

爲了Uber對更新鮮更快速的數據洞察需求,我們需要設計一種更好的方式來將數據提取到數據湖中。當我們開始設計DBEvents時,爲最終的解決方案定義了三個業務要求:新鮮度、質量和效率。

新鮮度

數據的新鮮度指它更新得有多頻繁。假設在時間t1更新MySQL表中的一行。數據提取作業在時間t1+1時刻開始運行,並消耗N個單位時間完成作業。則用戶可以在t1+1+N時刻獲得數據。這裏,數據的新鮮度延遲是N+1,即數據實際更新到數據湖中並可被獲取的時間延遲。

Uber有很多用例都要求N+1儘可能的小,最好是幾分鐘。這些用例包括欺詐檢測,因爲即使是最輕微的延遲也會影響到客戶的體驗。出於這些原因,我們在DBEvents中將數據新鮮度的要求排在了最高優先級。

質量

如果我們無法描述或理解數據湖中的數據,它們是無法發揮出價值的。設想不同的上游服務對不同的表有不同的schema。儘管這些表在創建時都有一個schema,但這些schema會隨着用例的變化而變化。如果對於攝入的數據沒有一個一致的方法來定義和更新schema,數據湖很快就會變成一個數據沼澤——一大堆難以被理解使用的數據。

另外,隨着表schema的演進,搞明白爲什麼要增加字段或是棄用原有字段很重要。如果不瞭解每列數據代表的含義,就很難理解數據的意義。因此,確保數據的高質量是DBEvents另一個優先考慮的要求。

效率

在Uber,我們有數以千計的微服務來負責業務邏輯的不同部分以及不同的業務線。在大多數情況下,每個微服務都有一個或多個備用數據庫來存儲長期的數據。可以想象,這會導致成百上千的表格都需要被讀取,這需要大量的計算和存儲資源。

因此,DBEvents的第三個設計目標是使系統更高效。通過對存儲和計算資源的使用優化,我們最終降低了數據中心使用和工程時間的成本,並能在未來加入更多的數據源。

設計DBEvents

結合這三個需求,我們構建了Uber的變更數據獲取系統DBEvents,用於增量地感知並獲取數據的變更,從而改善我們平臺的使用體驗。

數據集的獲取可以分爲兩個步驟:

  1. 引導:一個表格在某個時間點快照的表現

  2. 增量獲取:對錶格進行增量的獲取並實施變更(上游發生的)

引導(Bootstrap)

我們開發了一個可插拔數據源的庫,來引導例如Cassandra,Schemaless和MySQL等外部數據源通過我們的攝取平臺Marmaray將數據導入數據湖。這個庫提供了有效引導數據集所需的語意,同時提供了能夠添加任何數據源的可插拔架構。每個外部數據源都會將其原始數據的快照備份至HDFS。

快照備份完成後,Marmaray會調用庫,依次讀取備份數據,並將其作爲Marmaray可用的Spark RDD來提供。在執行可選的去重、部分行合併及其他操作後,Marmaray會將RDD持久化到Apache Hive。

爲了提高獲取超大表格時的效率和可靠性,引導的過程是增量的。你可以爲數據集設計批次的大小,也可以增量式(可能是並行的)地進行引導,從而避免過大的作業。

image

圖1:我們的可插拔源引導庫從HDFS備份中讀取,爲取數平臺Marmaray準備數據集

MySQL 引導案例

爲MySQL數據庫創建備份一般會先在文件系統中爲數據創建一個副本,然後使用本地文件格式將其存儲在另一個存儲引擎中。這種逐位複製文件的方式被稱爲物理備份。由於存在索引,被複制的物理文件通常包含重複數據,這會使磁盤上數據集的大小明顯增加。

作爲DBEvents體系的一部分,我們開發並開源了一個名爲StorageTapper的服務,它從MySQL數據庫讀取數據,將其轉換爲模式化的版本,並將事件發佈到不同的目的地,例如HDFS或者Apache Kafka。這種在目標存儲系統上生成事件的方法使我們能夠創建邏輯備份。邏輯備份依賴StorageTapper基於原始數據庫創建的事件從而在目標系統上重新創建數據集,而不是使用數據集的直接備份。

除了比物理備份具有更高的效率外,邏輯備份還有以下優點:

  • 它們很容易被原始存儲服務以外的系統處理,因爲數據格式是標準、可用的。
  • 它們不依賴特定版本的MySQL,從而能提供更好的數據完整性。
  • 它們非常緊湊,不會複製重複的數據。

image

圖2:StorageTapper從MySQL讀取二進制變更日誌,對Apache Avro中的時間進行編碼,並將它們發送至Apache Kafka或是在HDFS中備份。可以用這些事件在其他系統(例如Apache Hive)中重構數據集。

實現新鮮度

爲了使我們的數據足夠新鮮,我們需要以小批量的方式來增量地消費和修改數據集。我們的數據湖使用的是HDFS(一種append-only系統)來存儲PB級的數據。而大部分的分析數據都是用Apache Parquet文件格式編寫的,這種方式適用於大規模的列掃描,但是無法更新。遺憾的是,由於HDFS是append-only模式而Apache Parquet是不可修改的,用戶如果想更新數據集就必須要批量重寫整個數據集(在使用Hive時,是重寫數據集的大量分區)。

爲了快速獲取數據,我們使用了Apache Hudi——一個由Uber設計的用於管理HDFS中所有原始數據集的開源庫,它能減少對不可變的數據湖執行upsert操作所花費的時間。Apache Hudi能在數據集上提供原子的upsert操作和增量數據流。

MySQL 增量提取案例

除了引導,我們還能使用StorageTapper從MySQL源執行增量提取。在我們的用例中,StorageTapper從MySQL的二進制日誌中讀取事件,其中記錄了數據庫所做的變更。二進制日誌中包含了所有INSERT、UPDATE、DELETE和DDL這些我們稱之爲二進制日誌事件的操作。這些事件會按照數據庫變更發生的順序依次寫入日誌中。

StorageTapper讀取這些事件,用Apache Avro的格式對其進行編碼,並將它們發送至Apache Kafka。每條二進制日誌事件都是Kafka中的一條消息,每條消息對應一整行表格的數據。由於發送到Apache Kafka的事件能反映出對原始數據庫做變更的順序,當我們將Kafka中的消息應用到另一個數據庫時,就會獲得與原始數據完全一致的副本。這個方法會比直接從MySQL轉發數據到另一個數據庫使用更少的計算資源。

保證質量

爲了確保數據高質量,我們需要先使用schema來爲數據湖中的數據集定義結構。Uber使用了一種內部的schema管理服務Schema-Service,它能保證數據湖中的每個數據集都有關聯的schema,並且使schema的任何變更都遵從變更規則。這些變更規則保證了schema後向的兼容性,以避免影響到這類數據集的消費者。

Schema-Service使用Apache Avro的格式來存儲schema並執行schema的變更。此schema通常是上游表schema的1:1展現。只要變更被接受爲向後兼容,自助服務工具就能允許內部用戶修改schema。一旦schema以Apache Avro的格式變更,一個DDL語句就會作用到表來改變實際的schema。

我們通過Schema編碼過程將數據模式化(schematized)。schema執行庫(heatpipe)會將數據模式化或編碼,就像能對數據進行schema檢查的瘦客戶端。schema執行庫還會向每個變更日誌中添加元數據,使其全局標準化,不用考慮數據從哪兒來或是要寫到哪裏去。確保數據都有schema且schema都是最新的,意味着我們可以找到並使用數據湖中所有的數據。

image

圖3:DBEvents的heatpipe庫對數據進行編碼,Schema-Service是所有schema的網關。這是實現將所有數據模式化的方法。

MySQL schema 執行案例

如上所述,用戶可以通過Schema-Service請求變更MySQL的schema,這能使變更生效並保證它們是向後兼容的。如果請求成功,就可以使用新版本的schema。每當StorageTapper在MySQL二進制日誌中讀取ALERT TABLE語句時,它都會檢測到這些schema的變更。這會觸發StorageTapper開始用新的schema去處理未來的事件。

有效的資源利用

我們發現在較早的pipeline中有一些低效率的問題:

  • 計算利用率:大型作業快照整個表並以一定頻率重新加載的操作是非常低效的,尤其是在只有少數記錄更新的情況下。

  • 上游穩定性:由於經常要加載整個表,作業會對源施加壓力,例如大量讀取MySQL表。

  • 數據準確性:不預先檢查數據質量,會導致數據質量變低並且無法給數據湖用戶帶來良好的體驗。

  • 延遲:源表中發生變化的時間到數據湖中可查詢的時間之間的延遲很大,這會降低數據的新鮮度。

Hudi僅消費和應用上游表中更新的行和變更日誌來提升我們用DBEvents使能的pipeline的效率。Hudi的設計使用增量更新來替代快照,會用更少的計算資源,從而可以改善許多低效率的問題。同時通過讀取變更日誌,Hudi不再需要加載整張表,因此可以減輕對上游數據源的壓力。

圖4清晰地描述了這些解決方案是如何在DBEvents的增量架構中協同工作的。在Uber,我們從不同的數據源中拉取數據。每個源都有一個自定義的實現去讀取變更日誌時間並提供增量變更。舉個例子,MySQL的變更日誌通過StorageTapper拉取並推送至Apache Kafka,而如前面提到的,Cassandra的變更日誌則是通過Cassandra的變更數據捕獲(CDC)功能結合Uber特有的集成能力來實現的。

image

圖4:在DBEvents中,每種源類型都以統一的消息格式向Kafka中發送變更日誌事件。

Marmaray 是Uber開源的、通用的數據獲取和分發庫。在較高的層面上,Marmaray爲我們的DBEvents pipeline提供了下列功能,以提高整個架構的效率:

  • 通過我們的schema管理庫和服務,提供高質量的、模式化的數據。
  • 從各類數據存儲提取數據至我們的Apache Hadoop數據湖。
  • 使用Uber內部的工作流編排服務構建pipeline來消化和處理獲取到的數據,並基於HDFS和Apache Hive中的數據存儲和計算各類業務指標。

無論數據源是什麼,單個攝取pipeline都會執行相同的有向無環圖作業(DAG)。這個過程會依據特定的源來確定運行時的攝取行爲,類似於策略設計模式

標準化變更日誌事件

我們的目標之一是以一種能被其他內部數據消費者使用的方式(如流式作業和自定義的pipeline)來標準化變更日誌事件。

在標準化DBEvents中的變更日誌之前,我們需要先解決一些問題:

  • 在模式化負載時如何發現錯誤,以及我們應該如何處理?
  • 負載有可能很複雜,只有全量負載的一小部分能被更新。我們如何準確的知道更新了什麼?
  • 如果這個負載是上游數據庫或表中行變更的日誌,那麼該行的主鍵是什麼?
  • 由於我們使用Apache Kafka作爲消息總線來發送和接收變更日誌,我們如何強制事件使用單調遞增的時間戳?

爲了回答DBEvents用例的這些問題,我們定義了一組Apache Hadoop的元數據標題並添加到每個Kafka的消息中。通過這種設計,元數據和數據都能使用heatpipe(使用Apache Avro)進行編碼並通過Kafka進行傳輸。這使我們能標準化一個能被這類事件所有消費者使用的全局的元數據集。這個元數據單獨描述每個更新,以及這些更新與之前的更新有怎樣的聯繫。元數據也會遵循schema規則被寫入Apache Hive中一個名爲MetadataStruct的特殊列。之後用戶就可以輕鬆地查詢MetadataStruct來獲取有關行狀態的更詳細的信息。

下面,我們會重點介紹在事件中標準化的一些關鍵元數據字段:

Hadoop元數據字段
元數據字段 描述
Row Key Row Key字段是每個源表的唯一鍵,用於標識行,並根據結果合併部分變更日誌。
Reference Key Reference Key是收到的變更日誌的版本,這個版本必須是單調遞增的。該鍵可以用來判斷此數據是否是某個特定行最近的更新。
Changelog Columns Changelog Columns字段是一個數組<record{“name”:string, “ref_key”:long, “Hadoop_Changelog_Fields”:array}> ,它包含了column names、ref_key和all_changed_fieldnames,會在當前的消息事件中被更新。
Source Source字段反映了生成變更日誌的源表的類型。比如Apache Kafka、Schemaless、Apache Cassandra和MySQL。
Timestamp Timestamp字段以毫秒爲單位標記事件的創建時間。Timestamp有多種用途,最主要的是用於監控時延和完整性。(我們所說的事件的創建是指,當StorageTapper這類數據模式化服務在把事件推向Kafka前,實際模式化該事件的時間點。)
isDeleted [True/False]。這是一個布爾值,用於支持Hive表中row_key的刪除。
Error Exception Error Exception是一個字符串,用於捕獲在發送當前的變更日誌時遇到的異常或問題(無錯時是null)。如果源數據有任何schema的問題,Error Exception會反映出收到的異常,之後就能用來追蹤源的問題或修復/重發消息。
Error Source Data Error Source Data是一個包含了實際數據源錯誤的字符串(無錯時是null)。如果出現任何有問題的消息,我們就不能將這個字段獲取到主表中,應將它移至相應的錯誤表。可以使用這個數據來和生產者一起進行修復。
ForceUpdate [True/False]。ForceUpdate是一個布爾值,它可以確保變更日誌作用在已有數據之上。在許多情況下,比最新看到的ref_key更早的ref_key會被視爲重複的並跳過。設置這個標誌後,會忽略hadoop_ref_key字段直接應用變更日誌。
Data Center Data Center字段指的是產生事件的原始數據中心。這個字段非常有利於追蹤消息以及調試任何潛在的問題,尤其是在active-active或all-active架構下。Heatpipe會根據發佈消息的數據中心自動填充這個值。

如上表所述,標準化的元數據使我們的架構具備魯棒性和普適性。元數據提供了充足的信息使我們能完整地瞭解每個事件的狀態。舉個例子,如果事件的模式化或編碼有任何問題,定義錯誤的字段就會被填充,如圖5所示,我們就可以決定下一步採取什麼操作。在Uber,我們會將錯誤以及造成問題的實際負載都一起寫入錯誤表中。

image

圖5:所有不符合schema標準的數據都會被寫入DBEvents的錯誤表

錯誤表有很多用途:

  • 數據的生產者可以發現未通過schema檢查的數據並在之後進行修復或發佈更新。
  • 數據的操作和工具可以使用錯誤表來查找和調試丟失的數據。
  • 寫入錯誤表可以確保我們的系統沒有任何的數據丟失,因爲數據不在實際表中就在錯誤表中。

下一步工作

有了DBEvents提供的增量變更流,我們就能爲數據湖提供更快、更新、更高質量的數據。使用這些數據,才能確保Uber的服務更有效的運作,如車輛共享和Uber Eats。

在未來,我們還打算通過以下功能來強化這個項目:

  • 自助服務集成:我們希望能極大地簡化數據集加載至Apache Hive的過程。爲此,我們需要對DBEvents的架構做一些增強,這樣每個數據源都能無縫地觸發引導和增量獲取。這需要源系統、攝取系統和數據源監控框架之間的互相集成。
  • 延遲和完整性監控:雖然我們已經構建了模塊來提供這部分的信息,但只在Kafka數據源做了具體實現。我們希望能爲所有類型的數據源增加這部分的功能。

致謝

特別感謝Reza Shiftehfar,Evan Richards,Yevgeniy Firsov,Shriniket Kale,Basanth Roy,Jintao Guan以及其他團隊成員的貢獻!

更多內容,請關注AI前線

image

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