最后我們決定構(gòu)建一個(gè)鍵值存儲(chǔ),允許存儲(chǔ)任何JSON數(shù)據(jù)而不需要嚴(yán)格的格式驗(yàn)證,一個(gè)非結(jié)構(gòu)化的模式(命名的由來)。這是一個(gè)只擴(kuò)展分片的MySQL, master節(jié)點(diǎn)都帶有寫緩沖在應(yīng)對(duì)MySQL宕機(jī),數(shù)據(jù)變更通知是一個(gè)訂閱-發(fā)布的功能,我們稱之為trigger。最后,Schemaless支持全局?jǐn)?shù)據(jù)索引。下面我們將討論一下數(shù)據(jù)模型概覽以及一些關(guān)鍵特性,包括剖析Uber的一份行程數(shù)據(jù),更深入的例子保留在接下來的文章中。
Schemaless的數(shù)據(jù)模型
Schemaless是一個(gè)只擴(kuò)展的稀疏三維持久化哈希表,非常類似Google的 Bigtable。Schemaless中的最小數(shù)據(jù)被稱作cell,不可更改;一次寫入后不可被覆寫或刪除。Cell是一個(gè)JSON blob通過一個(gè)rowkey和一個(gè)columnname引用,還有一個(gè)referencekey叫做ref key。rowkey是一個(gè)UUID,column name是一個(gè)字符串,reference key是一個(gè)整型。
你可以將row key看作是關(guān)系型數(shù)據(jù)庫的主鍵,column name看作是關(guān)系型數(shù)據(jù)庫的列。無論如何,在Schemaless中沒有預(yù)定義或強(qiáng)制模式而且每行不需要共享column name;事實(shí)上,columnname完全由應(yīng)用層定義。ref key用于給一個(gè)指定row key和列加版本。因此如果一個(gè)cell需要更新,你只需寫一個(gè)新的cell附帶一個(gè)更大的ref key (最新的cell是那個(gè)有最大的ref key的)。ref key也可以用作標(biāo)記一個(gè)列表中的實(shí)體,但主要用作標(biāo)記版本。具體哪種形式由應(yīng)用本身決定。
應(yīng)用通常把相關(guān)的數(shù)據(jù)組織進(jìn)同一列,然后每列的所有cell在應(yīng)用側(cè)的結(jié)構(gòu)都大致相同。這種分組方式很好的把一起修改的數(shù)據(jù)很好的組織到了一起,這樣應(yīng)用程序就可以在數(shù)據(jù)庫不停機(jī)的情況下迅速修改結(jié)構(gòu)。下面的例子進(jìn)行了更詳細(xì)的敘述。
實(shí)例:Schemaless中的行程數(shù)據(jù)存儲(chǔ)
在深入了解我們?nèi)绾卧赟chemaless中對(duì)行程數(shù)據(jù)建模之前,讓我們先剖析一下一個(gè)Uber的行程。行程數(shù)據(jù)在不同的時(shí)間點(diǎn)產(chǎn)生,從上車下車到付費(fèi),這許多信息伴隨著用戶在行程中的反饋以及后臺(tái)進(jìn)程處理異步到達(dá)。下圖簡要說明了一個(gè)Uber行程的不同階段是何時(shí)發(fā)生的:
這個(gè)圖表展示了一個(gè)我們行程流的簡化版。*標(biāo)志的部分是可選的且可能發(fā)生多次。
一個(gè)行程是由乘客發(fā)起,由司機(jī)結(jié)束,包含開始與結(jié)束的時(shí)間戳。這些信息構(gòu)成了行程的基礎(chǔ),我們據(jù)此計(jì)算出該次行程的費(fèi)用,由司機(jī)來收費(fèi)。行程結(jié)束后,我們可能要調(diào)整跟收取或發(fā)放的費(fèi)用。我們也可能給行程數(shù)據(jù)添加備注,從乘客或司機(jī)出發(fā)出反饋(上圖中星號(hào)部分標(biāo)出)。在第一張信用卡超期或禁用的情況下,我們不得不嘗試用多張信用卡付款。Uber行程流是一個(gè)數(shù)據(jù)驅(qū)動(dòng)的過程。隨著數(shù)據(jù)變得有效或添加,特定的一組處理會(huì)在該行程上執(zhí)行。這些信息中的一部分,比如乘客或司機(jī)的評(píng)級(jí)(上圖中note部分),可能在行程結(jié)束后幾天處理。
好了,那我們?nèi)绾伟焉鲜龅男谐棠P陀成涞絊chemaless?
行程數(shù)據(jù)模型
使用 斜體字 標(biāo)注UUID,大寫字母表示column name,下表展示了我們行程數(shù)據(jù)存儲(chǔ)的簡化版的數(shù)據(jù)模型。我們有兩個(gè)行程(UUIDstrip_uuid1 和 trip_uuid2) 以及四列(BASE, STATUS, NOTES, and FARE ADJUSTMENT)。一個(gè)格子表示一個(gè)cell,帶有一個(gè)數(shù)字以及一個(gè)JSON的 (以{…}縮寫)。格子的覆蓋代表不同版本 (也就是不同的ref keys)。
trip_uuid1 有三個(gè)cell:一個(gè)在BASE列,兩個(gè)在STATUS列,F(xiàn)ARE ADJUSTMENT列沒有內(nèi)容。trip_uuid2的BASE列有兩個(gè)格子,NOTES列有一個(gè),同樣的FARE ADJUSTMENTS列也沒有內(nèi)容。在Schemaless中,列沒什么不同;每列的語義都由應(yīng)用層定義,本例中是 Mezzanine。
在Mezzanine中,BASE列的cell包含了行程的基礎(chǔ)信息,比如司機(jī)的UUID和行程的時(shí)間。STATUS列包含行程現(xiàn)在的支付狀態(tài),每次我們嘗試對(duì)行程支付的時(shí)候都會(huì)插入一個(gè)cell (由于信用卡額度不足或者逾期等問題嘗試可能失敗)。如果司機(jī)或者Uber的DOps(司機(jī)調(diào)度員)有行程相關(guān)的備注,會(huì)在NOTES列添加一個(gè)cell。最后的FARE ADJUSTMENT列的cell記錄了行程價(jià)格的調(diào)整。
我們?nèi)绱藙澐至惺菫榱吮苊鈹?shù)據(jù)沖突 而且最小化更新時(shí)需要寫的數(shù)據(jù)量。BASE列在行程結(jié)束時(shí)寫入,基本只會(huì)寫一次。當(dāng)行程開始嘗試支付的時(shí)候開始嘗試寫STATUS列,此時(shí)BASE已經(jīng)寫好了,如果支付失敗可能會(huì)寫多次。相似的NOTES列在BASE列寫過后的一些節(jié)點(diǎn)可能會(huì)寫多次,但是與STATUS列的寫操作完全獨(dú)立。類似的FARE ADJUSTMENTS列只在行程費(fèi)用變更時(shí)會(huì)寫,例如路況不好等原因。