前言
表格存儲Tablestore是阿里云自研的面向海量結(jié)構(gòu)化數(shù)據(jù)存儲的Serverless NoSQL多模型數(shù)據(jù)庫。Tablestore在阿里云官網(wǎng)上有各種文檔介紹,也發(fā)布了很多場景案例文章,這些文章收錄在這個合集中《表格存儲Tablestore權(quán)威指南》。值得一提的是,Tablestore可以支撐海量的數(shù)據(jù)規(guī)模,也提供了多種索引來支持豐富的查詢模式,同時作為一個多模型數(shù)據(jù)庫,提供了多種模型的抽象和特有接口。本文主要對Tablestore的存儲和索引引擎進(jìn)行介紹和解讀,讓大家對Tablestore引擎層的原理和能力,索引的作用和使用方式等有一個認(rèn)識。
基本架構(gòu)
Tablestore是一款云上的Serverless的分布式NoSQL多模型數(shù)據(jù)庫,提供了豐富的功能。假設(shè)用戶可以采用各種開源組件搭建一套類似服務(wù),可以說是成本非常高昂,而使用Tablestore僅需在控制臺上創(chuàng)建一個實例即可享受全部功能,而且是完全按量計費(fèi),可以說是0門檻。
整體架構(gòu)如下圖所示,本文不展開敘述每個模塊的功能。
在服務(wù)端引擎層中,存在兩個引擎:存儲引擎和索引引擎。這兩個引擎的數(shù)據(jù)結(jié)構(gòu)和原理不同,為了方便讀者理解,本文將這兩個引擎稱為表引擎(Table)和多元索引引擎(Searchindex)。整體來說,引擎層是基于LSM架構(gòu)和共享存儲(盤古),支持自動的Sharding和存儲計算分離。
表引擎
表引擎的整體架構(gòu)類似于Google的BigTable,在開源領(lǐng)域的實現(xiàn)有HBase等。
數(shù)據(jù)模型可以定義為寬行模型,如下圖所示。其中不同的分區(qū)可以加載到不同的機(jī)器上,實現(xiàn)水平擴(kuò)展:
首先說明一下為什么Tablestore的主鍵可以包含多個主鍵列,而像HBase只有一個RowKey。這里有幾點:
多列主鍵列按照順序共同構(gòu)成一個主鍵,類似MySQL的聯(lián)合主鍵。如果使用過HBase,可以把這里的多列主鍵列,拼接起來看作一個RowKey,每一列其實都只是整體主鍵的一部分。
第一列主鍵列是分區(qū)鍵,使用分區(qū)鍵的范圍進(jìn)行分區(qū)劃分,保證了分區(qū)鍵相同的行,一定在同一個分區(qū)(Partition)上。一些功能依賴這一特性,比如分區(qū)內(nèi)事務(wù)(Transection),本地二級索引(LocalIndex, 待發(fā)布),分區(qū)內(nèi)自增列等。
業(yè)務(wù)上常需要多個字段來構(gòu)成主鍵,如果只支持一個主鍵列,業(yè)務(wù)需要進(jìn)行拼接,多列主鍵列避免了業(yè)務(wù)層做主鍵拼接和拆解。
許多用戶第一次看到多列主鍵列時,常會有誤解,認(rèn)為主鍵的范圍查詢(GetRange接口)可以針對每一列單獨進(jìn)行,實際上這里的主鍵范圍指的是整體主鍵的范圍,而非單獨某一列的范圍。
這個模型具有這樣的一些優(yōu)勢:
完全水平擴(kuò)展,因此可支撐的讀寫并發(fā)和數(shù)據(jù)規(guī)模幾乎無上限。Tablestore線上也有一些業(yè)務(wù)在幾千萬級的tps/qps,以及10PB級的存儲量??梢哉f一般業(yè)務(wù)達(dá)不到這樣的上限,實際的上限僅取決于集群目前的機(jī)器資源,當(dāng)業(yè)務(wù)數(shù)據(jù)量大量上漲時,只要增加機(jī)器資源即可。同時,基于共享存儲的架構(gòu)也很方便的實現(xiàn)了動態(tài)負(fù)載均衡,不需要數(shù)據(jù)庫層進(jìn)行副本數(shù)據(jù)復(fù)制。
提供了表模型,相比純粹的KeyValue數(shù)據(jù)庫而言,具有列和多版本的概念,可以單獨對某列進(jìn)行讀寫。表模型也是一種比較通用的模型,可以方便與其他系統(tǒng)進(jìn)行數(shù)據(jù)模型映射。
表模型中,按照主鍵有序存儲,而非Hash映射,因此支持主鍵的范圍掃描。類似于HashMap與SortedMap的區(qū)別,這個模型中為SortedMap。
Schema Free, 即每行可以有不同的屬性列,數(shù)據(jù)列個數(shù)也不限制。這很適合存儲半結(jié)構(gòu)化的數(shù)據(jù),同時業(yè)務(wù)在運(yùn)行過程中,也可以進(jìn)行任意的屬性列變更。
支持?jǐn)?shù)據(jù)自動過期和多版本。每列都可以存儲多個版本的值,每個值會有一個版本號,同時也是一個時間戳,如果設(shè)置了數(shù)據(jù)自動過期,就會按照這個時間戳來判斷數(shù)據(jù)是否過期,后臺對過期數(shù)據(jù)自動清理。
這個模型也有一些劣勢:
數(shù)據(jù)查詢依賴主鍵??梢园堰@個數(shù)據(jù)模型理解為SortedMap,大家知道,在SortedMap上只能做點查和順/逆序掃描,比如以下查詢方式:
主鍵點查:通過已知主鍵,精確讀取表上的一行。
主鍵范圍查:按照順序從開始主鍵(StartPrimaryKey)掃描到結(jié)束主鍵(EndPrimaryKey),或者逆序掃描。即對Table進(jìn)行順序或逆序遍歷,支持指定起始位置和結(jié)束位置。
主鍵前綴范圍查:其實等價于主鍵范圍查,這里只是說明,主鍵前綴的一個范圍,其實可以轉(zhuǎn)換成主鍵的一個范圍,在表上進(jìn)行順序掃描即可。
針對屬性列的查詢需要使用Filter,F(xiàn)ilter模式在過濾大量數(shù)據(jù)時效率不高,甚至變成全表掃描。通常來說,數(shù)據(jù)查詢的效率與底層掃描的數(shù)據(jù)量正相關(guān),而底層掃描的數(shù)據(jù)量取決于數(shù)據(jù)分布和結(jié)構(gòu)。數(shù)據(jù)默認(rèn)僅按照主鍵有序存儲,那么要按照某一屬性列查詢,符合條件的數(shù)據(jù)必然分布于全表的范圍內(nèi),需要掃描后篩選。全表數(shù)據(jù)越多,掃描的數(shù)據(jù)量也就越大,效率也就越低。
那么在實際業(yè)務(wù)中,主鍵查詢常常不能滿足需求,而使用Filter在數(shù)據(jù)規(guī)模大的情況下效率很低,怎么解決這一問題呢?
上面提到,數(shù)據(jù)查詢的效率與底層掃描的數(shù)據(jù)量正相關(guān),而Filter模式慢在符合條件的數(shù)據(jù)太分散,必須掃描大量的數(shù)據(jù)并從中篩選。那么解決這一問題也就有兩種思路:
讓符合條件的數(shù)據(jù)不再分散分布:使用全局二級索引,將某列或某幾列作為二級索引的主鍵。相當(dāng)于通過數(shù)據(jù)冗余,直接把符合條件的數(shù)據(jù)預(yù)先排在一起,查詢時直接精確定位和掃描,效率極高。
**加快篩選的速度: **使用多元索引,多元索引底層提供了倒排索引,BKD-Tree等數(shù)據(jù)結(jié)構(gòu)。以上面查詢某屬性列值為例,我們給這一列建立多元索引后,就會給這一列的值建立倒排索引,倒排索引實際上記錄了某個值對應(yīng)的所有主鍵的集合,即Value -> List, 那么要查詢屬性列為某個Value的所有記錄時,直接通過倒排索引獲取所有符合條件的主鍵,進(jìn)行讀取即可。本質(zhì)上是加快了從海量數(shù)據(jù)中篩選數(shù)據(jù)的效率。
全局二級索引
全局二級索引采用的仍然是表引擎,給主表建立了全局二級索引后,相當(dāng)于多了一張索引表。這張索引表相當(dāng)于給主表提供了另外一種排序的方式,即針對查詢條件預(yù)先設(shè)計了一種數(shù)據(jù)分布,來加快數(shù)據(jù)查詢的效率。索引的使用方式與主表類似,主要的查詢方式仍然是上面講的主鍵點查,主鍵范圍查,主鍵前綴范圍查。常見的關(guān)系型數(shù)據(jù)庫的二級索引也是類似的原理。
列舉一個最簡單的例子,比如我們有一張表存儲文件的MD5和SHA1值,表結(jié)構(gòu)如下:
通過這張表,我們可以查詢文件對應(yīng)的MD5和SHA1值,但是通過MD5或SHA1反查文件名卻不容易。我們可以給這張表建立兩張全局二級索引表,表結(jié)構(gòu)分別為:
索引1:
索引2:
為了確保主鍵的唯一性,全局二級索引中,會將原主鍵的主鍵列也放到主鍵列中,比如上面的FilePath列。有了上面兩張索引表,就可以通過主鍵前綴范圍查的方式里精確定位某個MD5/SHA1對應(yīng)的文件名了。
多元索引引擎
多元索引引擎相比于表引擎,底層增加了倒排索引,多維空間索引等,支持多條件組合查詢、模糊查詢、地理空間查詢,以及全文索引等,還提供一些統(tǒng)計聚合能力(統(tǒng)計聚合功能待發(fā)布)。因為功能較單純的二級索引更加豐富,而且一個索引就可以滿足多種維度的查詢,因此命名為多元索引。
上面在講解決Filter模式查詢慢的問題時,提到倒排索引加快了數(shù)據(jù)篩選的速度,因為記錄了某列的Value到符合條件的行的映射,Value -> List 。實際上,倒排索引這一方式,不僅可以解決單列值的檢索問題,也可以解決多條件組合查詢的問題。
我們舉一個訂單場景的例子,比如下表為一個訂單記錄:
上面一共16個字段,我們希望按照任意多個字段組合查詢,比如查詢某一售貨員、某一產(chǎn)品類型、單價在xx元之上的所有記錄??梢韵氲?,這樣的排列組合會有非常多種,因此我們不太可能預(yù)先將任何一種查詢條件的數(shù)據(jù)放到一起,來加快查詢的效率,這需要建立很多的全局二級索引。而如果采用Filter模型,又很可能需要掃描全表,效率不高。折中的方式是,可以先對某個字段建立二級索引,縮小數(shù)據(jù)范圍,再對其中數(shù)據(jù)進(jìn)行Filter。那么有沒有更好的方式呢?
多元索引可以很好的解決這一問題,而且只需要建立一個多元索引,將所有可能查詢的列加入到這個多元索引中即可,加入的順序也沒有要求。多元索引中的每一列默認(rèn)都會建立倒排,倒排就記錄了Value到List的映射。針對多列的多個條件,在每列的倒排表中找到對應(yīng)的List,這個稱為一個倒排鏈,而篩選符合多個條件的數(shù)據(jù)即為計算多個倒排鏈的交并集,這里底層有著大量的優(yōu)化,可以高效的實現(xiàn)這一操作。因此多元索引在處理多條件組合查詢方面效率很高。
此外,多元索引還支持全文索引、模糊查詢、地理空間查詢等,以地理空間查詢?yōu)槔嘣饕ㄟ^底層的BKD-Tree結(jié)構(gòu),支持高效的查詢一個地理多邊形內(nèi)的點,也支持按照地理位置排序、聚合統(tǒng)計等。
索引選擇
不是一定需要索引
如果基于主鍵和主鍵范圍查詢的功能已經(jīng)可以滿足業(yè)務(wù)需求,那么不需要建立索引。
如果對某個范圍內(nèi)進(jìn)行篩選,范圍內(nèi)數(shù)據(jù)量不大或者查詢頻率不高,可以使用Filter,不需要建立索引。
如果是某種復(fù)雜查詢,執(zhí)行頻率較低,對延遲不敏感,可以考慮通過DLA(數(shù)據(jù)湖分析)服務(wù)訪問Tablestore,使用SQL進(jìn)行查詢。
全局二級索引還是多元索引
一個全局二級索引是一個索引表,類似于主表,其提供了另一種數(shù)據(jù)分布方式,或者認(rèn)為是另一種主鍵排序方式。一個索引對應(yīng)一種查詢條件,預(yù)先將符合查詢條件的數(shù)據(jù)排列在一起,查詢效率很高。索引表可支撐的數(shù)據(jù)規(guī)模與主表相同,另一方面,全局二級索引的主鍵設(shè)計也同樣需要考慮散列問題。
一個多元索引是一系列數(shù)據(jù)結(jié)構(gòu)的組合,其中的每一列都支持建立倒排索引等結(jié)構(gòu),查詢時可以按照其中任意一列進(jìn)行排序。一個多元索引可以支持多種查詢條件,不需要對不同查詢條件建立多個多元索引。相比全局二級索引,也支持多條件組合查詢、模糊查詢、全文索引、地理位置查詢等。多元索引本質(zhì)上是通過各種數(shù)據(jù)結(jié)構(gòu)加快了數(shù)據(jù)的篩選過程,功能非常豐富,但在數(shù)據(jù)按照某種固定順序讀取這種場景上,效率不如全局二級索引。多元索引的查詢效率與倒排鏈長度等因素相關(guān),即查詢性能與整個表的全量數(shù)據(jù)規(guī)模有關(guān),在數(shù)據(jù)規(guī)模達(dá)到百億行以上時,建議使用RoutingKey對數(shù)據(jù)進(jìn)行分片,查詢時也通過指定RoutingKey查詢來減少查詢涉及到的數(shù)據(jù)量。簡而言之,查詢靈活度和數(shù)據(jù)規(guī)模不可兼得。
關(guān)于使用多元索引還是全局二級索引,也有另外一篇文章描述:《Tablestore索引功能詳解》。
除了全局二級索引之外,后續(xù)還會推出本地二級索引(LocalIndex),推出后再進(jìn)行詳細(xì)介紹。
常見組合方案
豐富的查詢功能當(dāng)然是業(yè)務(wù)都希望具備的,但是在數(shù)據(jù)規(guī)模很大的情況下,靈活的查詢意味著成本。比如萬億行數(shù)據(jù)的規(guī)模,對于表引擎來說,因為水平擴(kuò)展能力很強(qiáng),成本也很低,問題不大,但是建立多元索引,費(fèi)用就會非常高昂。全局二級索引成本較低,但是只適合固定維度的查詢。
常見的超大規(guī)模數(shù)據(jù),都帶有一些時間屬性,比如大量設(shè)備產(chǎn)生的數(shù)據(jù)(監(jiān)控數(shù)據(jù)),或者人產(chǎn)生的數(shù)據(jù)(消息、行為數(shù)據(jù)等),這類數(shù)據(jù)非常適合采用Tablestore存儲。對這類數(shù)據(jù)建立索引,會有一些組合方案:
對元數(shù)據(jù)表建立多元索引,全量數(shù)據(jù)表不建立索引或采用全局二級索引。
元數(shù)據(jù)表可以是產(chǎn)生數(shù)據(jù)的主體表,比如設(shè)備信息表,用戶信息表等。在時序模型中,產(chǎn)生數(shù)據(jù)的主體也可以認(rèn)為是一個時間線,這條線會不斷的產(chǎn)生新的點。
Tablestore的時序數(shù)據(jù)模型(Timestream)采用的也是類似的方式,對時序數(shù)據(jù)中的時間線建立一張表,專門用來記錄時間線的元數(shù)據(jù),每個時間線一行。時間線表建立多元索引,用來做時間線檢索,而全量數(shù)據(jù)則不建立索引。在檢索到時間線后,對某個時間線下的數(shù)據(jù)進(jìn)行范圍掃描,來讀取這個時間線的數(shù)據(jù)。
熱數(shù)據(jù)建立多元索引,老數(shù)據(jù)不建立索引或者采用全局二級索引:
很多情況下僅需要對非常熱的數(shù)據(jù)進(jìn)行多種維度查詢,對冷數(shù)據(jù)采取固定維度查詢即可。因此冷熱分離可以給業(yè)務(wù)提供更高的性價比。
目前多元索引還不支持TTL(后續(xù)會支持),需要業(yè)務(wù)層區(qū)分熱數(shù)據(jù)和冷數(shù)據(jù)。
總結(jié)
本文對Tablestore的存儲和索引引擎進(jìn)行了介紹和解讀,并在如何選擇和應(yīng)用索引方面給了一些參考,目的是加深大家對Tablestore的認(rèn)識和理解,更好的應(yīng)用Tablestore來解決業(yè)務(wù)需求。