滴滴從KV存儲到NewSQL實戰

0.導讀

本文講訴滴滴在分佈式Nosql存儲Fusion之上構建NewSQL的實踐之路。詳細描述Fusion-NewSQL的特性,應用場景,設計方案。sql

1.背景

Fusion-NewSQL是由滴滴自研的在分佈式KV存儲基礎上構建的NewSQL存儲系統。Fusion-NewSQ兼容了MySQL協議,支持二級索引功能,提供超大規模數據持久化存儲和高性能讀寫。數據庫

▍咱們的問題

滴滴的業務快速持續發展,數據量和請求量急劇增加,對存儲系統等壓力與日俱增。雖然分庫分表在必定程度上能夠解決數據量和請求增長的需求,可是因爲滴滴多條業務線(快車,專車,兩輪車等)的業務快速變化,數據庫加字段加索引的需求很是頻繁,分庫分表方案對於頻繁的Schema變動操做並不友好,會致使DBA任務繁重,變動週期長,而且對巨大的表操做還會對線上有必定影響。同時,分庫分表方案對二級索引支持不友好或者根本不支持。鑑於上述狀況,NewSQL數據庫方案就成爲咱們解決業務問題的一個方向。後端

▍開源產品調研

最開始,咱們調研了開源的分佈式NewSQL方案:TIDB。雖然TIDB是很是優秀的NewSQL產品,可是對於咱們的業務場景來講,TIDB並非很是適合,緣由以下:安全

咱們須要一款高吞吐,低延遲的數據庫解決方案,可是TIDB因爲要知足事務,2pc方案自然沒法知足低延遲(100ms之內的99rt,甚至50ms內的99rt) 咱們的多數業務,並不真正須要分佈式事務,或者說能夠經過其餘補償機制,繞過度布式事務。這是因爲業務場景決定的。 TIDB三副本的存儲空間成本相對比較高。 咱們內部一些離線數據導入在線系統的場景,不能直接和TIDB打通。網絡

基於以上緣由,咱們開啓了自研符合本身業務需求的NewSQL之路。數據結構

▍咱們的基礎

咱們並無打算從0開發一個完備的NewSQL系統,而是在自研的分佈式KV存儲Fusion的基礎上構建一個能知足咱們業務場景的NewSQL。Fusion是採用了Codis架構,兼容Redis協議和數據結構,使用Rocksdb做爲存儲引擎的NoSQL數據庫。Fusion在滴滴內部已經有幾百個業務在使用,是滴滴主要的在線存儲之一。架構

Fusion的架構圖以下:併發

咱們採用hash分片的方式來作數據sharding。從上往下看,用戶經過Redis協議的客戶端就能夠訪問Fusion,用戶的訪問請求發到proxy,再由proxy 轉發數據到後端 Fusion 的數據節點。proxy 到後端數據節點的轉發,是根據請求的key計算hash值,而後對slot分片數取餘,獲得一個固定的slotid,每一個slotid會固定的映射到一個存儲節點,以此解決數據路由問題。異步

有了一個高併發,低延遲,大容量的存儲層後,咱們要作的就是在之上構建MySQL協議以及二級索引。那麼如何將MySQL的數據格式轉成Redis的數據結構存儲就是咱們必須面臨的問題,後面會詳細說。nosql

2.需求

綜合考慮大多數用戶對需求,咱們整理了咱們的NewSQL須要提供的幾個核心能力:

高吞吐,低延遲,大容量。 兼容MySQL協議及下游生態。 支持主鍵查詢和二級索引查詢。 Schema變動靈活,不影響線上服務穩定性。

3.架構設計

Fusion-NewSQL由下面幾個部分組成:

解析MySQL協議的DiseServer 存儲數據的Fusion集羣-Data集羣 存儲索引信息的Fusion集羣-Index集羣 負責Schema的管理配置中心-ConfigServer 異步構建索引程序-Consumer負責消費Data集羣寫到MQ中的MySQL-Binlog格式數據,根據schema信息,生成索引數據寫入Index集羣。 外部依賴,MQ,Zookeeper

架構圖以下:

4.詳細設計

▍存儲結構

MySQL的表結構數據如何轉成Redis的數據結構是咱們面臨的第一個問題。

以下圖:

咱們將MySQL表的一行記錄轉成Redis的一個Hashmap結構。Hashmap的key由表名+主鍵值組成,知足了全局惟一的特性。下圖展現了MySQL經過主鍵查詢轉換爲Redis協議的方式:

除了數據,索引也須要存儲在Fusion-NewSQL中,和數據存成hashmap不一樣,索引存儲成key-value結構。根據索引類型不一樣,組成key-value的格式還有一點細微的差異(下面的格式爲了看起來直觀,實際上分隔符,indexname都是作過編碼的):

惟一索引: Key: table_indexname_indexColumnsValue Value: Rowkey

非惟一索引: Key: table_indexname_indexColumnsValue_Rowkey Value:null

形成這種差別的緣由就是非惟一索引在加入Rowkey以前的部分是有可能重複的,沒法全局惟一。另外,惟一索引不將Rowkey編碼在key中,是由於在查詢語句是單純的「=」查詢的時候直接get操做就能夠找到對應的Rowkey內容,而不須要經過scan,這樣的效率更高。

後面會在查詢流程中重點講述如何經過二級索引查詢到數據。

▍數據讀寫流程

數據寫入

用戶經過MySQL-sdk將協議發給dise-server dise-server根據schema對用戶寫入的SQL作校驗 dise-server將校驗經過的SQL轉成Redis的Hashmap結構,經過Redis協議發給Data集羣 Data集羣將數據寫入wal文件,並將數據存儲rocksdb。 Data集羣后臺線程將wal文件消費,轉成MySQL-Binlog格式。將數據發到MQ 異步索引模塊消費MQ,將MySQL-Binlog根據操做類型(insert,update,delete)配合schema信息,構建索引信息,並將索引數據寫入index集羣。 經過上面的鏈路,用戶的一條MySQL寫操做就完成了數據存儲和索引構建。因爲經過數據構建索引這一步是經過MQ異步完成,因此會存在數據和索引有必定的時間差的狀況。

查詢

下面是一個使用二級索引查詢數據的案例:

dise-server接收到SQL查詢,根據條件,選擇索引,若是沒有命中任何索引,給用戶返回錯誤(Fusion-NewSQL不能以非索引字段做爲查詢條件)。 根據選中的索引,構建查詢範圍,經過scan命令遍歷Index集羣,獲取符合條件的主鍵集合。下圖以一個SQL查詢,展現使用scan遍歷二級索引的例子:

根據主鍵,經過hgetall命令向Data集羣查詢符合條件的結果集。 將結果集構建成MySQL的結果返回給用戶。 根據上面索引數據的格式能夠看到,scan範圍的時候,前綴必須固定,映射到SQL語句到時候,意味着where到條件中,範圍查詢只能有一個字段,而不能多個字段。好比:

索引是age和name兩個字段的聯合索引。若是查詢語句以下:

select * from student where age > 20 and name >‘W’;

scan就沒有辦法肯定前綴,也就沒法經過index_age_name這個索引查詢到知足條件的數據,因此使用KV形式存儲到索引只能知足where條件中有一個字段是範圍查詢。固然能夠經過將聯合索引分開存放,屢次交互搜索取交集的方式解決,可是這就和咱們下降RPC次數,下降延遲的設計初衷相違背了。爲了解決這個問題,咱們引入了Elastic Search搜索引擎,這部分後面會詳細說明。

▍Schema變動

用戶涉及Schema變動時,會以工單形式發給管控系統。管控系統審批事後,會將變動請求推給配置中心,配置中心進行安全性檢查後,將新的Schema寫入到存儲中,並給各個節點推送變動。

字段變動:

節點接收到推送,更新本地的Schema。對於歷史數據,並不真正去修改數據,而是在查詢的時候,根據Schema信息匹配字段,若是數據比Schema缺失某些字段,就使用默認值代替;若是數據比Schema多了字段,就隱藏掉多餘字段不展現。

新增索引分爲兩步處理:

新增索引,歷史數據不處理,增量數據馬上走索引構建流程。 經過歷史索引構建工具,掃描歷史數據,構建新索引的KV,將歷史數據完成索引構建。這裏有個優化點,掃描slave而不是master,避免對線上產生影響。

5.生態構建

一個單獨的存儲產品解決全部問題的時代早已通過去,數據孤島是沒有辦法很好服務業務的,Fusion-NewSQL從設計的那天起就考慮了和其餘存儲系統的打通。

▍Fusion-NewSQL到其餘存儲系統

Fusion-NewSQL經過兼容MySQL的Binlog格式,將數據發到MQ中。下游各個系統凡是能接入MySQL數據的,均可以經過消費MQ中相同格式的Fusion-NewSQL數據,將數據存到其餘系統中。這樣的方式用最小的工做量最大程度作到了兼容。

▍Hive到Fusion-NewSQL

Fusion-NewSQL還支持將離線的Hive表中的數據經過Fusion-NewSQL提供的FastLoad(DTS)工具,將Hive表數據轉入到Fusion-NewSQL,知足離線數據到在線的數據流動。

若是用戶本身完成數據流轉,通常會掃描Hive表,而後構建MySQL的寫入語句,一條條將數據寫入到Fusion-NewSQL,流程以下面這樣:

MySQL-client將寫請求發給DiseServer。 DiseServer將MySQL寫作解析,轉成hashmap將轉換後的數據以Redis協議發給Data集羣。 Data集羣的存儲節點收到數據,將數據寫到wal文件。 Data集羣的存儲節點走Rocksdb的寫流程,這裏包括了寫memtable,還有可能memtable寫滿,發生flush以及觸發後臺的compact。 異步線程消費wal,將數據構建MySQL-Binlog格式發到MQ。 異步索引程序消費MySQL-Binlog,構建Index集羣須要的數據,向Index集羣發送寫入請求。 Index集羣的存儲節點寫wal。 Index集羣的存儲節點進入Rocksdb的寫流程。

從上面的流程能夠看出這種遷移方式有幾個痛點:

有這種Hive到Fusion-NewSQL數據導入需求的用戶都須要開發一套相同邏輯的代碼,維護成本高。 每條Hive數據都要通過較長鏈路,數據導入耗時較長。 離線平臺的數據量大,吞吐高,直接大幅提高在線系統的QPS,對在線系統的穩定性有較大影響。

基於上述的痛點,咱們設計了Fastload數據導入平臺,經過約定Hive到Fusion-NewSQL的表格式,使用Hadoop併發處理數據,並構建Rocksdb能識別的sst存儲文件,繞過複雜的DISE寫鏈路,直接將數據導入到Fusion-NewSQL中,流程以下:

用戶填寫工單,選中將指定Hive表的某些字段映射爲Fusion-NewSQL表的字段(這裏能夠Hive中多個字段組成一個Fusion-NewSQL字段)。 Hadoop遍歷Hive表,而且經過Zookeeper獲取數據應該存放在Data集羣和Index集羣的路由信息 經過上面的遍歷,計算,以後,將數據直接構建成、Rocksdb能識別的sst,而且其中存的數據已是按DISE的表結構信息組成的KV數據。 將sst文件直接發送到指定的存儲節點,存儲節點或經過Rocksdb提供的ingest功能,直接將sst文件加載到Fusion-NewSQL中,用戶能夠讀到。

這個方案避免了冗長複雜的寫鏈路,同時不會增長系統的QPS,在磁盤和網絡IO沒有達到瓶頸的狀況下對線上訪問幾乎是沒有任何影響;同時,用戶只須要填寫Hive到Fusion-NewSQL的Schema映射關係便可,沒必要再關心實現。

▍經過Elastic Search實現複雜查詢

在業務使用MySQL或Fusion-NewSQL的過程當中,咱們發現有這樣一種場景:業務的查詢條件很複雜,涉及的字段數,條件,聚合都比較多,這種場景下,業務會選擇將Elastic Search做爲MySQL或Fusion-NewSQL的下游,將數據導入Elastic Search,而後經過Elastic Search豐富的搜索能力,先從Elastic Search中獲取數據在MySQL或Fusion-NewSQL的主鍵,而後再根據主鍵獲取所有數據。

根據上面的場景,Fusion-NewSQL提供一個特殊的索引類型:ES。用戶在建立索引的時候,能夠將須要作複雜查詢的字段勾選出來,共同構建成一個ES索引,這樣既知足了業務需求,避免了每一個業務都須要開發一套和Elastic Search交互的複雜邏輯,又統一了數據庫使用接口都爲MySQL。同時,還彌補了前面提到的Fusion-NewSQL的KV二級索引不能支持多個字段範圍檢索的能力。

架構圖以下:

ES索引只是在上圖紅4處,將ES索引中包含的字段信息和主鍵寫入到Elastic Search中。在查詢時綠1若是選中了ES類型的索引,就根據where條件中涉及的字段,組裝成Elastic Search的DSL語句,從Elastic Search獲取主鍵,再從Data集羣獲取。因爲Elastic Search查詢的延遲比較慢,Fusion-NewSQL能夠支持一張表的多個索引採用KV索引和ES索引並存,對於延遲要求高,查詢條件相對簡單的使用KV索引;對於查詢條件複雜,延遲要求不高的使用ES索引。

6.總結

Fusion-NewSQL當前已經現已經接入訂單、預估、帳單、用戶中心、交易引擎等70個核心業務,總QPS超過200W,總數據超過600TB。

固然,Fusion-New不是一個通用完備的NewSQL方案,而是在已有的nosql數據庫基礎上,經過對SQL協議的支持以及組合各類組件,構建對一個對外表達的數據庫,可是這種方式,能夠以最小的開發代價,知足大多數的業務場景,具有較高的投入產出比。

7.後續工做

有限制的事物支持,好比讓業務規劃落在一個節點的數據能夠支持單機跨行事務。 實時索引替代異步索引,知足即寫即讀。目前已經有一個寫穿+補償機制的方案,在沒有分佈式事務的前提下知足正常狀態的實時索引,異常狀況下保證數據索引最終一致的方案。 更多的SQL協議和功能支持。

本文做者: ▬

李 鑫 滴滴 | 資深軟件開發工程師

多年分佈式存儲領域設計及開發經驗。曾參與Nosql/NewSQL數據庫Fusion,分佈式時序數據庫sentry,NewSQL數據庫SDB等系統的設計開發工做。

相關文章
相關標籤/搜索