本文是Cassandra數據模型設計第一篇(全兩篇),該系列文章包含了eBay使用Cassandra數據模型設計的一些實踐。其中一些最佳實踐咱們是經過社區學到的,有些對咱們來講也是新知識,還有一些仍然具備爭議性,可能在要經過進一步的實踐才能從中獲益。 數據庫
本文中,我將會講解一些基本的實踐以及一個詳細的例子。即便你不瞭解Cassandra,也應該能理解下面大多數內容。 數據結構
咱們嘗試使用Cassandra已經超過1年時間了。Cassandra如今正在服務一些用例,涉及到的業務從大量寫操做的日誌記錄和跟蹤,到一些混合工做。其中一項服務是咱們的「Social Signal」項目,支撐着ebay的pruduct pages裏like/own/want特性。咱們開發的一些用例已經上線運行,但更多的仍是處於開發階段。 分佈式
咱們的Cassandra集羣規模並不龐大,但正在穩步的增加中。在過去幾個月裏,咱們共部署了幾十個節點,它們分佈在幾個跨機房的小型集羣中。你可能會問,爲何要多個集羣?咱們經過的職能部門和業務來劃分集羣。相同職能部門的相同業務的用例共享一個集羣,但它們存在於不一樣的keyspaces中。 ide
RedLaser, Hunch和其它ebay的合做夥伴也在嘗試cassandra解決現實中各類問題。除了Cassandra,咱們也在使用MongoDB和Hbase,本文中我不會討論它們,但我相信它們都有各自的優勢。 性能
我相信此時你必定有不少問題,在這篇文章裏暫時不會一一說明。在即將到來的Cassandra Summit大會,我將更詳細的講解咱們每一個用例場景,數據模型和多數據中心部署,以及經驗教訓和其它知識。 優化
本文重點講述咱們在ebay應用的Cassandra數據模型設計最佳實踐。下面讓咱們先看看這系列文章會用到的一些術語。 網站
術語「Column Name」 和 「Column Key」被認爲是同樣的。一樣的,「Super Column Name」 和 「Super Column Key」也認爲是相同的。 ui
下圖表示一個 Column Family (簡稱CF)中的一個row spa
下圖表示一個 Super Column Family (簡稱SCF)中的一個row 設計
下圖表示一個Column Family中一個row,它包含Composite Columns。Composite Columns的屬性經過分隔符’|’鏈接。請注意,這裏看到的只是數據的表現形式,Cassandra內置了Composite Column,它是一個對象,並非使用’|’做爲屬性分隔符的字符串。(順便說下,本文不要求你掌握Super Column和Composite Column方面知識。)
基於上面的內容,讓咱們開始第一個實踐吧!
取而代之,應該把它想象成事一個有序的map結構。
對於一個新手來講,下面關係型數據庫術語經常被對應到Cassandra模型
這種對比能夠幫助咱們從關係型數據庫轉換到非關係型數據庫。可是當設計Cassandra column famiy的時候請不要這樣去類比。取而代之,考慮它是一個map中嵌入另外一個map:外部map的key爲row key,內部map的key爲column key,兩個map的key都是有序的。以下:
1
|
SortedMap<RowKey, SortedMap<ColumnKey, ColumnValue>>
|
將column family想象成嵌套的並排序的map比關係型數據庫table描述的更爲準確,它將幫助你正確的進行Cassandra模型設計。
Map能夠進行高效查詢,同時排序的特性能夠進行高效column掃描。在Cassandra中,咱們可使用row key和column key作高效查找和範圍掃描
Column key的數量是很龐大的(譯者注:目前譯者所使用的Cassandra1.2.5版本,每一個row支持最多20億個columns)。換句話說你,你能夠擁有一個wide rows。
Column key自身能夠存儲值,即你能夠擁有一個沒有值的column。
若是集羣使用Order Preserving Partitioner (OOP)策略進行數據存儲,就能夠對row key進行範圍查詢。可是OOP大多數狀況都不推薦使用(譯者注:將rowkey按照順序存儲到節點上,若是分區不均勻,將致使數據讀寫不均衡),因此你能夠認爲外部的map是不排序的,以下:
1
|
Map<RowKey, SortedMap<ColumnKey, ColumnValue>>
|
上面提到的」Super Column」,認爲它們是一組column,這樣的話,兩級嵌套map就會像下面展現的同樣變爲三級嵌套map:
1
|
Map<RowKey, SortedMap<SuperColumnKey,
|
1
|
SortedMap<ColumnKey, ColumnValue>>>
|
注意:
你須要傳遞timestamp給每一個column value,由於Cassandra使用它作內部的衝突處理機制。但在建模過程當中你能夠忽略它(譯者注:在操做column的時候timestamp信息會自動添加到column)。同時,不要考慮在你的程序中使用column的timestamp,由於它不是爲你設計的,與Hbase不一樣,它們不會生成新的version數據(譯者注:在Hbase中相同rowkey和column key的數據會保存多個version,而Cassandra會將相同數據覆蓋,timestamp只保存最後一次更新的時間)。
由於Super Column的性能問題和缺少二級索引支持問題,Cassandra社區對它的使用曾有過強烈爭議。因此,推薦使用Composite Columns代替Super Column實現功能。(譯者注:使用Super Column,若是你要獲取其中一個columnvalue,則要掃描整個Super Column,這會致使查詢性能很糟糕)
建模儘可能從實體和它們的關係開始
與關係型數據庫不一樣,在Cassandra中經過建立二級索引或者編寫複雜SQL(使用joins, order by, group by)來新建或修改查詢不是件容易的事情。由於Cassandra具備很高的分佈式特性,因此要先考慮查詢模式,而後再設計column family。
牢記前面提到的嵌入排序map數據結構,在考慮如何組織你的數據到map,以知足快速查詢/排序/分組/過濾/聚合的要求。
在大部分狀況下,實體和它們的關係是很重要的(特殊用例除外,如日誌存儲或者其它時間序列數據)。若是我給你一個查詢模式,用於爲一個電子商務網站建立Cassandra模型,但不告訴你任何實體和它們的關係。你會有意或者無心的從查詢模式或者從你以前領域對象的理解找出實體和它們之間的關係(由於咱們是經過實體和關係來描述真實世界)。在設計數據模型時最好從實體和關係開始,而後使用反範式化和冗餘的方式繼續圍繞查詢模式建模。若是這聽起來有些讓人困惑,經過後面的詳細例子就能夠理解。
注意:在建模的時候考慮如下幾點會頗有幫助。區分頻次大的查詢和頻次小的查詢,有些查詢可能只被查詢幾千次,其它可能被查詢數十億次;還要考慮哪些查詢對數據延遲是敏感的。確保你的模型優先知足查詢頻次大的查詢和重要查詢。
根據實際狀況,若是不須要就不要反範式化。
在關係型數據庫的世界裏,範式化的優勢是顯而易見的:較少的數據冗餘,較少的數據修改異常,概念更清晰,更容易維護等等;一樣,它的缺點也十分明顯:多表join查詢會很慢等等。這兩方面也會體如今Cassandra中,可是缺點會更明顯,由於Cassandra數據是分佈式存儲,固然它也並不支持join操做。因此,對於一個徹底範式化的schema,Cassandra讀操做性能可能比RDBMS更糟糕,因此咱們一般經過反範式化來提高查詢性能。(譯者注:Cassandra一次查詢可能會請求多個節點並將結果彙總到客戶端,而RDBMS查詢只需從本地查詢便可)。
這個實踐和上一個查詢建模實踐是很是重要的,我會在餘下的文章中經過一個詳細的例子作進一步說明。
注意:下面咱們要討論的例子只是個演示,它不表明eBayCassandra項目的數據模型。
實戰:User和Item中間的’Like’關係
這個示例是關於電子商務系統的一個功能,一個user能夠喜歡多個item,同時一個item能夠被多個user所喜好,在關係型數據庫中這個關係是經過many-to-many實現的,以下圖所示:
經過上面的模型,咱們能夠進行以下查詢:
經過user id獲取user
經過item id獲取item
獲取指定user喜歡的全部item
查看指定item被那些user所喜好
下面將介紹幾個經過Cassandra建模解決上面問題的現方案,反範式的順序從低到高。你會發現最佳方案依賴於查詢模式。
這個模型支持經過user id查詢user和經過item id查詢item。但沒法簡單查詢某個user喜好的全部item或者某個item被那些user所喜好。
對於這個用例來講,這是最糟糕的設計,主要是由於User_Item_Like沒有設計好。
注意:爲了簡單起見,關係型數據模型中的timestamp字段沒有體現到Cassandra模型中(這個字段用於存儲user什麼時候喜好某個item),我會在後面介紹它。
這個模型中User和Item是範式化實體,user id 和item id被映射存儲兩次,第一次是經過item id存儲user id(User_By_Item),第二次經過item id存儲user id(Item_By_User)。
這樣,咱們很容易能夠經過Item_By_User查詢某個user喜歡的所有item,還能夠經過User_By_Item查詢某個item被哪些user所喜好。這裏咱們使用了,Item_By_User和User_By_Item這兩個column family做爲自定義二級索引。(譯者注:Cassandra column family也有二級索引功能,它的做用是經過建立column key索引快速查詢到column value)。
有這樣一個場景,咱們老是但願經過指定user查詢其喜好的item,同時要獲取item title信息。在當前模型下,咱們首先要經過Item_By_User獲取指定user關聯的item id,而後根據這些item id依次查詢Item模型獲取title信息,反之亦然。一個item有可能被幾百個user所喜好,或者一個活躍user可能喜好許多item,基於當前的模型設計,將會致使不少額外的查詢。因此,最好經過反範式‘Item_by_User’ 中的itemtitle和’ User_by_Item’中的username信息來優化查詢,方案3將會向你們展現。
注意:即便你能夠批量讀取(譯者注:在Cassandra Java客戶端hector中能夠MultigetSliceQuery類實現一次查詢傳入多個rowkey),但它們將仍然很慢,由於Cassandra底層仍然會單獨查詢每一個rowkey,而後經過Coordinator 節點(譯者注:Coordinator 節點爲Cassandra客戶端直接請求的節點,能夠理解爲它是一個代理)彙總到客戶端。批量讀取能夠避免請求的往返耗時,它是個不錯的選擇,你能夠去嘗試它。
在這個模型中,title和username被分別反範式到User_By_Item和Item_By_User。這樣將容許咱們高效查詢指定user喜好的全部item,以及喜好指定item全部的user。這樣咱們就爲整個用例作了很大的反範式化工做。
問題又來了,如何獲取指定user喜好item的具體信息(title,desc,price等等)?首先咱們要問問本身咱們是否真的須要這個查詢。仍是上面的例子,當用戶但願獲取item額外信息的時候,咱們能夠在頁面上展現全部的item title,當點擊item title時,在打開的新頁面顯示這個item的具體信息。因此,在這個用例中咱們最好不要極端反範式化。(item title列表中一般還會顯示title和price信息,這也很容易實現,這個就留給你們作練習)
讓咱們考慮下面兩個查詢:
經過所給item id,獲取具體item信息(title, desc等等),並一同查詢喜歡這個item的user name
經過所給的user id,獲取具體user信息,並一同查詢user喜歡的全部item titile
上面兩個查詢出如今查詢item和user的詳情頁面是很正常的,這些在當前模型中能夠很好的實現。二者都須要兩次查詢,一次查詢item(或者user)信息,另外一次查詢user name(或者item title)。User變得更加活躍的(喜歡上千個items)或者item變得很熱門(被幾百萬user喜好),查詢的次數不會隨之增長,仍然爲兩次。這很好,當咱們從方案2到方案3,反範式化並無讓咱們變糟糕。讓咱們看看方案4如何作更進一步的優化。
很明顯,方案4看起來有些凌亂。在數據存儲結構上,它與方案3也不一樣。
若是User和Item之間是高度關聯的實體(相似ebay),相比當前方案我將更傾向於方案3。
由於咱們不打算反範式化全部item屬性到User實體或者反範式化全部user屬性到Item實體,因此這裏咱們使用了部分範式化。我不會打算進行極限反範式化(讓全部time屬性到User實體和全部user屬性到Item實體),由於在這個用例中那樣作是沒有意義的。
注意:這裏我使用Super Column只是爲了給展現。大多狀況,應該傾向於使用composite columns,而不是Super Column。
在本文的用例中方案3是優勝者。上面的方案中咱們忽略了timestamp信息,下面咱們將把它以timeuuid(type-1 uuid)形式添加到最終模型上。注意,在User_By_Item實體中timeuuid和userid合併爲一個composite column key,在Item_By_user實體中timeuuid和item id合併爲一個composite column key。
回想一下,column key是有序存儲的。這裏咱們的User_By_Item 和 Item_By_User兩個實體的column keys經過timeuid排序後被存儲到磁盤,這使得基於時間的範圍查詢很是高效。在這個模型中,咱們不須要讀取一個row中全部column,就能夠高效的查詢某個item最近被哪些user所喜好,以及某個用戶最近喜歡了哪些item。
最終模型以下:
咱們經過一些基本的實踐和詳細例子幫你開啓Cassandra數據建模之旅。下面是一些關鍵點:
當設計Cassandra列族時,不要把它想成是關係表,要把它想成是嵌套的、排序的map數據結構。
要圍繞着查詢來設計列族,從設計實體及其關係開始。
在須要的時候,經過反範式化和冗餘來提高讀性能。
記住有多種方式建立模型,最佳的方式依賴於你的用例和查詢模式。
這裏我沒有提到其它經常使用的用例,如日誌記錄、監控、實時分析(rollups, counters),或者時間序列。可是,咱們討論的實踐也適用於它們。此外,有些衆所周知的技術和模式用於時間序列的模型設計。在eBay,咱們也使用這些技術,也樂於在後續的文章中分享這些。關於時間序列數據建模,我推薦你閱讀 Advanced time series with Cassandra 和 Metric collection and storage,若是你是Cassandra新手,請先閱讀DataStax documentation。
1.2文檔
http://www.datastax.com/documentation/cassandra/1.2/pdf/cassandra12.pdf
原文連接:http://www.ebaytechblog.com/2012/07/16/cassandra-data-modeling-best-practices-part-1/