0.導讀
本文講訴滴滴在分散式Nosql儲存Fusion之上構建NewSQL的實踐之路。詳細描述Fusion-NewSQL的特性,應用場景,設計方案。
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的資料結構儲存就是我們必須面臨的問題,後面會詳細說。
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協議和功能支援。
-
1 #
-
2 #
說到底還是leveldb….
我的電腦總是息屏,需要重新輸入開機密碼方可繼續操作,很煩,很痛苦。請問專家如何設定就不會出現上述情況