分享 | 滴滴分佈式 NoSQL 數據庫 Fusion 的演進之路

出品 | 滴滴技術
作者 | 餘汶龍

圖片描述

前言:Fusion 是滴滴自研的分佈式 NoSQL 數據庫,完全兼容 Redis 協議,支持超大規模數據持久化和高性能讀寫。在滴滴內部支撐了數百個業務,具有 PB 級別的數據存儲量,是使用最廣泛的主存儲服務之一。在支持滴滴業務高速發展過程中,積累了很多分佈式存儲領域的經驗,孵化了離線到在線的高速數據導入方案、NewSQL 方案、跨機房同步等,一路解決了 Redis 容量限制、 離線數據在線打通、數據庫擴展性差、異地多活容災等問題。

本文來自滴滴的技術專家、Fusion 的負責人餘汶龍在 2018 年北京 ArchSummit 全球架構師峯會上的演講內容,重點介紹了 Fusion 的核心設計以及架構演進過程。

內容框架

  • 誕生背景:滴滴業務發展簡介
  • 演進過程:如何滿足業務需求

    海量存儲
    FastLoad
    NewSQL
    跨機房多活

  • 總結 & 展望

誕生背景

業務 & 架構演進過程

滴滴出行成立於 2012 年,剛開始創業階段技術主要靠外包解決,沒太多技術沉澱;發展到了 2014 年,乘客司機和單量都有不錯的增長,我們開始構建自己的系統架構,這個時候業務對於存儲的需求很單純,簡單用用 MySQL 基本能解決我們的問題。

到了 2015 年前後,我們的業務線多了起來,專車快車等開始上線,這個時候我們一方面做了中臺系統的重構,另一方面感受到了不小的存儲壓力,即業務數據量和請求量劇增;到了 2016 年,合併優步前後,日訂單量逼近 2000 萬,進一步挑戰我們的存儲系統,於是我們按照不同的業務,對存儲進行拆分,因爲不同的業務對於存儲的需求是不一樣的,不同的業務對於吞吐、延遲、容量、數據請求大小等都有不同的需求,分庫分表也只是緩兵之計。

如何有效應對這些個性化需求呢?於是在這個時候,我們就開始孵化滴滴自己的 NoSQL 數據庫 Fusion 了,用它來豐富我們滴滴的存儲生態,爲業務提供更多的存儲選擇。

圖片描述

Fusion 是什麼?

前面我們不斷提到 Fusion 的關鍵字,那麼是時候正式介紹下 Fusion。Fusion 是一個兼容 Redis 協議的分佈式 NoSQL 數據庫。定位介於 Redis 與 MySQL 之間的主存儲數據庫。怎麼理解這個定位呢?也就是性能方面我們向 Redis 看齊,即低延遲;持久化能力方面我們向 MySQL 看齊,即 MySQL 具備的多副本、高可用、ACID 事務,我們都是支持的,同時定位爲服務打車訂單這樣的主流程在線業務。

圖片描述

它如何實現的呢?大家都知道 Redis 的數據是存放於內存中,雖然性能很好,但是容量極小,且每 GB 存儲成本很高(大概是我們 Fusion 的 10 倍以上)。於是我們就基於 SSD 磁盤實現了一套分佈式的存儲系統,在 SSD 磁盤上實現了 Redis 的數據結構,對外通過 proxy 屏蔽內部細節,讓用戶像訪問 Redis 一樣訪問 Fusion。當前我們已經支持 StringHashBitmapSetSorted SetList 這些主流的 Redis 數據結構。

演進過程

我們 Fusion 的發展總共經歷了 4 個階段,分別解決了 4 類業務問題,我們接下來重點看下具體過程。

海量存儲

首先來看如何解決海量存儲的問題。

圖片描述

Redis 是一款非常優秀的內存數據庫,但它也有這樣一些已知問題存在:容量受限於內存、擴容遷移和大 key 過期、刪除過程是阻塞的、宕機恢復慢等問題。我們 Fusion 設計之初,就避免了這些問題。具體是如何實現的呢?我們從需求分析出發。

需求分析

Fusion 誕生初期,主要解決 2 個業務需求:

一是滴滴的歷史訂單,按照前面提到的每日千萬級別訂單量,很快就能達到幾百億的訂單,這麼龐大的數據量,存 MySQL 顯然是不夠靈活的,修改字段、修改索引都比較困難,存 Redis 就更加不可能,因此他們有新型存儲的需求;

二是地圖團隊的司機行程軌跡,每產生一條打車訂單就會產生一條司機行程軌跡,每一條行程軌跡由多個點組成,行程越長軌跡數據越大,這是一個比歷史訂單的數據量還要大的業務,存儲的困難可想而知。

圖片描述

因此,我們對上述兩個業務的需求做了提煉和優先級排定:

  1. 剛需是海量存儲。
  2. 具備基本的在線故障處理能力。
  3. 穩定性很重要!
  4. 性能要足夠好,以司機行程軌跡爲例,每天 300 億級別寫入,他們對性能的追求當然是越高越好。
  5. 接入要求簡單,這裏我們選擇了 Redis 協議。
  6. 打通其他存儲系統。

滿足了這些需求後,就誕生了存儲系統 Fusion 的雛形。

架構設計

軟件結構

下圖左邊是數據流部分,從下往上看,即 Fusion 是構建在 SSD 磁盤上的存儲服務,我們引用優秀的存儲引擎 RocksDB 來做磁盤 IO 操作,然後在磁盤之上,我們增加一層 cache 來提升性能,然後封裝一層網絡框架並支持 Redis RPC,就實現了單機版本的 Fusion 存儲節點,然後在單機的基礎上加上我們的集羣路由管理,Fusion 的集羣就搭建好了,當然對外提供服務的時候,還有一層負載均衡。

下圖右邊是控制流部分,即我們在 SaltStack 平臺基礎上,構建了用戶系統、運維繫統、統計、監控、計費等系統,方便用戶以及運維人員使用。

圖片描述

集羣架構

集羣架構上,我們採用 hash 分片的方式來做數據 sharding。從上往下看,用戶通過 Redis 協議的客戶端(jedis、redigo、hiredis 等)就可以訪問 Fusion,首先會經過 VIP 做負載均衡,然後轉發到具體 proxy,再由 proxy 轉發數據到後端 Fusion 的數據節點。
proxy 到後端數據節點的轉發,是根據請求的 key 計算 hash 值,然後對 slot 分片數取餘,得到一個固定的 slotid,每個 slotid 會固定的映射到一個存儲節點,以此解決數據路由問題。

此外,我們還做了存儲生態的打通。支持 Hadoop、MySQL、Redis 的數據同步到 Fusion,也支持 Fusion 數據同步到 MQ,供下游消費。
圖片描述

小結

接下來就對 Fusion 做個小結,拿 Redis 來做個簡單對比。

圖片描述

FastLoad

我們演進過程中,解決的第二個問題是,離線數據到在線系統的快速打通。因此我們做了一個叫 FastLoad 的系統。

圖片描述

需求分析

首先,FastLoad 誕生初期主要支持兩個業務:標籤平臺和特徵平臺。標籤平臺是指對每個乘客和司機,都打上 N 個標籤,然後後續的打車流程會依賴這部分標籤,比如優惠券的發放;然後特徵平臺呢,會收集創建各類特徵,對每個對象用某個特徵庫做一次判斷,即可確定某種行爲。接下來我們對需求進行提取。

  1. 高性能。由於這部分數據是在離線計算平臺 Hadoop 上加工完成的,業務很容易想到就近存放在 Hive 上,但 Hive
    的查詢性能實在不能滿足在線查詢的高吞吐、低延遲要求。因此對於新的存儲系統,他們第一個要求就是性能!
  2. 定時更新。像特徵數據,一般只需要小時級別甚至天級別的更新,所以業務需要有快捷的定時更新功能。
  3. 快速更新。特徵數據還有一個特點,就是數據量特別大,以乘客特徵爲例,動輒上億條數據,約 TB 級別數據量。這麼大的數據量通過 SDK

    寫入肯定是不行的。剛開始業務方也確實是這麼玩的,直接通過 Hadoop 任務調用 Redis SDK,然後一條條的寫入
    Fusion,一般是每天凌晨開始寫數據,等到早高峯 8 點時大量讀取。但是這種方法實踐下來,經常導致 Fusion
    各類超時,在早高峯打車已經來臨 時還在寫凌晨的數據,非常影響穩定性。因此第 3 個需求是必須快速更新。

  4. 穩定性。這個是毋容置疑的。
  5. 多表隔離。有些業務有很多類特徵數據,他們有隔離存儲的需求,也有分類更新、分類查找的需求,因此需要多表來支持邏輯到物理的隔離。

圖片描述

架構設計

滿足上述需求後,就誕生了我們的 FastLoad 系統。接下來就來看下我們的架構是如何設計的。我們給用戶提供兩種接入方式:控制檯和 OpenAPI。用戶通過任一一種方式提交 FastLoad 任務時,都會在我們的 FastLoad 服務器上,創建一個 DTS 任務,該任務會在 Hadoop 配置中心註冊一個調度任務(週期性或一次性,由用戶決定),然後 FastLoad 服務器根據用戶上傳的數據存儲路徑或 Hive 表(我們支持的數據源有:HDFS 上的 JSON 文件和 Hive 結構的數據),按照用戶提交的拼 key 方式,我們啓動 map/reduce 任務直接構造 Fusion 底層存儲在文件系統上的文件 SST,並把它們構造好相互之間的排序,避免重複,構造好後通知 Fusion 存儲節點,下載 SST 文件,然後 load 到 Fusion 數據庫中。此後,用戶就可以通過 Redis-Client 訪問我們幫它加載的數據了。

圖片描述

小結

總結一下我們的 FastLoad 一站式 DTS 平臺,有如下優勢:

  1. 減少 N 次網絡交互。相比調用 Redis SDK 的方式寫入,我們減少非常多的網絡交互,傳輸的是壓縮格式文件,節省了網絡帶寬。
  2. 對用戶請求 0 影響。我們利用 map/reduce 的計算能力,做了 SST 的全局排序,讓 SST 進入 Fusion
    的時候,不經由 L0,直接到達最終 level,避免了 LSM 的 compact 影響,因此對用戶可以說沒有影響。
  3. 接入簡單,用戶 0 感知細節。用戶既不需要關心 Hadoop 使用、任務調度,也不需要自己寫 Redis SDK
    的代碼,只需要告訴我們,在什麼時間點需要什麼樣的數據即可!
  4. 提供了 OpenAPI,方便用戶的自動化流程打通。
  5. 提供全量覆蓋和增量導入兩種方式。

圖片描述

NewSQL

在演進過程的第 3 個階段,我們主要是針對 MySQL 的。大家都知道 MySQL 的擴展性比較差,面對百億級存儲,有幾個問題,一個是數據存不下,一個是擴展不靈活,比如修改字段、修改索引等。接着就來討論下,我們是如何解決這類問題的。
圖片描述

需求分析

同樣的,我們先來分析下業務的需求是什麼?簡單理解下,我們認爲有 3 點剛需:

  1. 輕鬆改字段。即需要足夠的擴展性。
  2. 存儲不限量。即需要一個容量儘可能大的存儲。
  3. 省成本。既然需要存 MySQL 都存不下的數據,那麼成本一定要考慮清楚。
  4. 至於事務、穩定性、高性能、二級索引,我們認爲都是基本需求。

圖片描述

背景問題

如何實現 shema 到 key/value 的轉換?

前面的介紹我們知道,Fusion 是支持 Redis 協議的,那麼 schema 轉換成 key/value,就可以用 Redis 的 hash 結構來實現,下圖我們就以 student 表爲例,轉換了 2 行數據。
圖片描述

如何做主鍵查詢呢?

下面的圖片給出了一個例子,即查詢 ID 爲 1 的學生的全部信息或年齡。

圖片描述

如何實現二級索引呢?

我們還是以 student 表爲例,分別構建如下 agesex 索引,其編碼規則如下可見。

圖片描述

如何做非主鍵查詢和範圍查詢呢?

在上圖構建好索引後,就很容易實現下面的兩個例子,即查詢年齡在某個範圍的學生,和查詢某種性別的所有學生。

圖片描述

背景問題

架構設計上分成接入層和數據存儲層,在接入層(DISE)我們提供控制檯來管理用戶的字段,用戶可以在這裏定義自己的 schema、字段、索引,並做相應的修改。然後用戶通過我們提供的類 SQL 的 SDK 接入,由我們的 SchemaServer 做 schema 轉換,接着插入數據到存儲層。然後數據存儲層吐出 binlog,由 IndexServer 異步消費 binlog 並構建索引。查詢時候,用戶的請求經由 SDK 到達 SchemaServer,SchemaServer 先查詢索引服務器,拿到對應的主鍵信息,然後根據命中的主鍵查詢詳細信息,最後返回給用戶。

圖片描述

小結

NewSQL 解決的問題是針對 MySQL 的特殊場景的,我們就拿 MySQL 來跟 Fusion 做個對比,可以看到 Fusion 只是在部分場景上解決了 MySQL 的容量限制以及擴展問題,但還是有很多場景並不能支持的。

圖片描述

跨機房多活建設

最後一個演進我們講的是如何支持業務的跨機房容災問題。

圖片描述

背景介紹

滴滴多活的業務架構如下圖,可以看到用戶層接入層和業務層都是無狀態的,因此如圖中的白色虛線所描述的,他們的請求可以在兩個機房間來回路由,而不影響業務請求的正確性。那麼是如何做到這一點的呢?必然得有一個地方維護着狀態的一致性,才能讓業務自由切換。因此跨機房多活容災最複雜的部分就在底層數據同步層,這裏的數據同步涉及到很多中間件,我們這裏只關心 Fusion 的跨機房多活。

圖片描述

架構設計

下圖是 Fusion 的跨機房同步架構,不依賴任何外部中間件,也不依賴內部 proxy。當用戶數據通過 A 機房寫入時,落地到某個存儲節點上,該存儲節點會 cache 一份對端節點的路由表,並異步的將剛纔寫入的數據轉發到對端集羣。

我們這裏的轉發採用了兩個異步特性:1. 跟用戶寫入主流程完全異步,即不影響用戶正常請求;2. A 機房到 B 機房的數據同步,採用了異步、批量、應答的方式高效同步。既保證了用戶請求主機房的吞吐和延遲,也大幅降低了備機房數據讀取的延遲。

圖片描述

小結

到此總結下我們的多活方案:

  1. 異步數據複製。在追求性能的同時,放棄了一段時間的不一致。如果在數據未達成一致的時候,主機房宕機,備機房的數據將缺失,但這個缺失不會是永久,等到主機房恢復後,我們會把這部分數據自動補齊到備機房,這個過程我們會根據時間戳去重。
  2. 自適應感知集羣狀態變更,比如切主、擴容等。在運行過程中,兩個機房的集羣難免會發生各類路由變化,我們在設計時考慮到了這一點,針對路由變化,我們會及時更新路由表,以把數據同步到正確的節點上。
  3. 數據可靠同步。我們的數據同步是依賴滑動窗口的應答機制,因此實現了一種可靠的數據同步。
  4. 支持雙寫,解決秒級衝突。由第一點提到,在某些場景是存在雙寫的,如果雙寫的是不同 key,自然不需要解決衝突,如果雙寫的是針對同一個
    key,那麼我們會根據時間戳做衝突檢測。
  5. 自動數據補償。也就是在發生主機房宕機後,寫入備機房的增量數據,可以自動的補償到主機房;原先滯留在主機房的數據,在主機房恢復後,也可以補償到備機房。即可以達到最終一致性。

圖片描述

總結&展望

總結

在伴隨滴滴業務發展的過程中,Fusion 經歷了 4 個發展階段,我們堅持”好東西是用出來“,因此在每個階段,都儘量避免”過度設計“,只解決特定的業務問題。這給了我們很多認真打磨產品的時間和精力,讓我們贏得了用戶口碑。

圖片描述

展望

通過前面的分享我們知道,Fusion 雖然能做的事情很多,但不能做的事情更多,所以我們的目標是持續發展持續演進,把解決業務問題當做己任。未來我們將往分佈式數據庫方向前進,解決更多的業務問題。

圖片描述

END

圖片描述

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