原文: http://www.rackspacecloud.com/blog/2010/05/12/cassandra-by-example/#
原作者:Eric Evan
原文發佈日期:May 12, 2010
譯者:王旭(http://wangxu.me/blog/ , @gnawux)
翻譯時間:2010年5月15,25,26日
近來 Cassandra 備受矚目,很多人正在評估是否可以應用 Cassandra。由於這些人更多的追求速度,相應的,我們的文檔就過於粗淺了。這些文章中,最差的是爲有關係數據庫基礎的人解釋Cassandra數據模型的那些。
Cassandra 數據模型實際和傳統的數據庫差異非常大,足夠讓人眩暈,而且很多誤解都需要修正。
有些人把這個數據模型描述成存放map的map,或對於super column的場景,是存放map的map的map。這些解釋經常用類似 JSON 標記的視覺輔助展示方法來進行佐證。其他人則把列族看做是係數表,還有人把列族看作是存放列對象的集合容器。甚至有人有時把列看走勢三元組。我覺得所有這些解釋都不夠好。
問題在於很難去用類比的方法來確切解釋一個新的東西,而且如果比較的不準確的話常常把人搞糊塗。我仍然期望有人能解釋清楚這個數據模型,但同時我覺得確切的例子可能更容易說明白一些。
Twitter
儘管 Twitter 本身就是 Cassandra 的一個實際的應用場景,它仍然是一個不錯的教學實例,因爲它衆所周知而且易於抽象。在例子中,和很多站點一樣,每個用戶都有一份用戶數據(顯示名稱、密碼、email等),這些信息鏈接到朋友(譯註:用戶follow的人)和 follower(譯註:follow用戶的人)。此外,如果沒有那些短 tweets 的話也就不是 twitter 了,tweet每條140個字符,它們都關聯着諸如時間戳和惟一的id這樣的元數據,這個id我們可以從URL裏看到。
現在我們在一個關係數據庫裏來直接進行建模,我們首先需要一個表來存放用戶。
2 |
id INTEGER PRIMARY KEY , |
我們還需要兩張表來存儲一對多的follow關係。
01 |
CREATE TABLE followers ( |
03 |
user INTEGER REFERENCES user (id), |
05 |
follower INTEGER REFERENCES user (id) |
09 |
CREATE TABLE following ( |
11 |
user INTEGER REFERENCES user (id), |
13 |
followed INTEGER REFERENCES user (id) |
顯然,我們還需要表來存儲tweets。
3 |
user INTEGER REFERENCES user (id), |
由於僅僅是個例子,我已經極大簡化了情況,但僅僅是這個極度簡化的模型,也還有很多需要做的工作。例如,要以可行的方法達到達到數據歸一化就需要一個外部鍵值約束,而因爲我們需要從多張表join信息,我們需要對任意值建索引,以保證高效。
但是讓一個分佈式系統正常工作相當有挑戰性,幾乎不可能不做任何折衷。對Cassandra來說也是如此,而且這也是爲什麼上述數據模型對我們來說是無法工作的的原因。對於入門者,沒有可供參考的完整性,缺乏次索引使得join很難進行,所以,你必須反歸一化。另一方面,你被迫思考你要進行的查詢的方式和期望結果,因爲這差不多就是數據模型看起來的樣子。
Twissandra
那麼如何把上述模型翻譯到Cassandra中呢?十分幸運,我們只需要看看 Twissandra,這是 Eric Florenzano 寫的一個 Twitter 的簡化版克隆,用作例子。那麼讓我們來使用 Twitter 和 Twissandra 作爲例子來看看 Cassandra 的數據模型是如何的。
Schema
Cassandra 是一種無 schema 的數據存儲方式,但爲你的應用做一些特定的配置還是必要的。Twissandra 給出了一個可以工作的 Cassandra 配置,不過研究一下關於數據模型方面的配置還是物有所值的。
Keyspaces
Keyspaces 是 Cassandra 中最頂層的命名空間。在未來版本的 Cassandra 中,將可以動態創建 keyspace,正如在 RDBMS 中創建數據庫一樣,但是對於 0.6 和以前的版本,這些都在主配置文件中定義,如:
2 |
<keyspace name = "Twissandra" > |
Column Families
對於每個 keyspace,都可以有一個或多個列族。列族是用於關聯類型相近的記錄的命名空間。Cassandra 在寫操作時,在一個列族內部允許有記錄級的原子性,對它們進行查詢非常高效。這些特性十分重要,在進行你的數據建模前必須記牢,它們會在下面討論到。
和keyspace類似,列族也在主配置文件中定義,雖然在將來的版本中你將可以在運行時創建列族,正像在RDBMS中創建表一樣。
02 |
<keyspace name = "Twissandra" > |
03 |
<columnfamily comparewith= "UTF8Type" name = "User" > |
04 |
<columnfamily comparewith= "BytesType" name = "Username" > |
05 |
<columnfamily comparewith= "BytesType" name = "Friends" > |
06 |
<columnfamily comparewith= "BytesType" name = "Followers" > |
07 |
<columnfamily comparewith= "UTF8Type" name = "Tweet" > |
08 |
<columnfamily comparewith= "LongType" name = "Userline" > |
09 |
<columnfamily comparewith= "LongType" name = "Timeline" > |
10 |
</columnfamily></columnfamily></columnfamily></columnfamily></columnfamily></columnfamily></columnfamily></keyspace> |
需要指出的是,上面的配置片段中,指定名字的時候同時指定了一個比較者類型。這凸顯了 Cassandra 和傳統數據庫的又一個重大不同,記錄按照設計的順序存儲,在之後不能輕易改變。
這些列族都是什麼?
一下子看所有的七個Twissandra列族是幹什麼的可能不那麼直觀,所以,我們來逐個仔細看一下:
User
User用於存儲用戶信息,大致相當於上面描述的用戶表。列族中的每條記錄以UUID爲鍵值,幷包含用戶名和密碼列。
Username
在User列族中查詢一個用戶需要知道用戶的鍵值,但從用戶名怎麼找到這個UUID鍵值呢?在上面描述的SQL關係數據庫裏的話,我們就在User 表裏來一個匹配用戶名的SELECT語句(WHERE username = ‘jericevans’)就行了。但這對於Cassandra來說卻不可能。
首先,關係數據庫可以順序地掃描全表來進行這樣一個 SELECT,但由於記錄是基於鍵值分佈在 Cassandra 集羣中的,這個匹配將可能會在多個節點上進行,可能是很多節點。而且,即使是數據就在一個節點上,仍然有一個原因會讓這一操作遠沒有關係數據庫效率高,因爲關係數據庫可以對username列有索引。前面提到過,Cassandra是不支持第二索引的。
解決方案就是,建立一個我們自己的反向索引,進行用戶名到UUID鍵值的映射,這就是Username列族的用途。
Friends
Followers
Friends 和 Follower 列族可以回答這些問題:用戶X follow了哪些人?誰follow了用戶X?這兩個列族的鍵值都是這個唯一的用戶ID,其中包含了哪些有follow關係的用戶以及它們創建的時間。
Tweet
Tweet 列族用於存放所有的tweets。這個列族以每個 tweet 的 UUID爲鍵值,還包含了用戶id,tweet內容以及tweet時間這些列。
Userline
這是屬於每個用戶的時間線。記錄的鍵值是用戶的ID,其他的列中,包含有一個數字時間戳到Tweet列族中的tweet ID的映射。
Timeline
最後,Timeline列族類似於Userline,只是這裏存儲着每個用戶的朋友的tweet的時間線視圖。
有了上面這些列祖,現在我們可以看一些常用的操作都是如何發生的。
把這些列族放在一起來試一下
添加一個新用戶
首先,新用戶需要一個方法來註冊一個賬戶,當他們註冊的時候,組要將他們添加到Cassandra數據庫中去。對於Twissandra,我們來看看裏面的內容:
1 |
username = 'jericevans' |
2 |
password = '**********' |
5 |
columns = { 'id' : useruuid, 'username' : username, 'password' : password } |
7 |
USER . insert (useruuid, columns) |
8 |
USERNAME. insert (username, { 'id' : useruuid}) |
Twissandra是用Python寫成的,使用 Pycassa 作爲訪問 Cassandra的客戶端,上述大寫的 USER 和 USERNAME 是 pycassa.ColumnFamily 的實例,它們需要在使用之前的某個位置被分別初始化。
這裏說明一下,這不是從 Twissandra 裏原樣摘出來的。我讓他們更加簡單而且是自包含的。比如,在上面的例子中,如果沒有對用戶名和密碼的賦值的話,可能不那麼好理解,不過一個 web 應用只能從用戶註冊表單裏得到這些內容。
從這個例子中回來,有兩個不同的 Cassandra 寫操作(insert()),第一個創建了一個用戶列族,另一個更新了用戶名到用戶 UUID 鍵值的反向映射表。在兩個例子中,參數都是用於查找記錄的鍵值,以及包含列名和值的map。
Following 一個朋友
1 |
frienduuid = 'a4a70900-24e1-11df-8924-001ff3591711' |
3 |
FRIENDS. insert (useruuid, {frienduuid: time . time ()}) |
4 |
FOLLOWERS. insert (frienduuid, {useruuid: time . time ()}) |
這裏我們再來兩個不同的insert()操作,這次是加入一個用戶到我們的朋友列表,並加入反向關係:給被 follow 用戶添加一個 follower。
發出Tweet
01 |
tweetuuid = str(uuid()) |
02 |
body = '@ericflo thanks for Twissandra, it helps!' |
03 |
timestamp = long( time . time () * 1e6) |
05 |
columns = { 'id' : tweetuuid, 'user_id' : useruuid, 'body' : body, '_ts' : timestamp } |
06 |
TWEET. insert (tweetuuid, columns) |
08 |
columns = {struct.pack( '>d' , timestamp : tweetuuid} |
09 |
USERLINE. insert (useruuid, columns) |
11 |
TIMELINE. insert (useruuid, columns) |
12 |
for otheruuid in FOLLOWERS.get(useruuid, 5000): |
13 |
TIMELINE. insert (otheruuid, columns) |
要存儲一條新的tweet,我們需要使用一個新的UUID作爲鍵值,在 Tweet列族創建一個記錄,其中的列包含作者的用戶ID,創建的時間,當然還有tweet的文本內容本身。
此外,用戶的 Userline 中也要加入tweet的時間和它的id。如果這是用戶的第一條tweet的話,這個insert()會產生一條新的紀錄,後面的只是爲這條記錄添加新列。
最後要給發出tweet的用戶和其他follower的 timeline 列族添加這條tweet的ID和時間。
值得注意的一件事是,這裏,時間戳使用的是64位長整型變量,而當它成爲一個列的名字的時候,它會被打包爲網絡字節序的二進制值。這是因爲 Userline和Timeline列族使用了一個LongType Comparator,允許我們使用數值區間指定查找指定範圍,所以它們被按照數值來存放起來。
接收一個用戶的 tweets
1 |
timeline = USERLINE.get(useruuid, column_reversed= True ) |
2 |
tweets = TWEET.multiget(timeline. values ()) |
接收一個用戶的tweet,首先從Userline獲取tweet ID的一個列表,然後從Tweet列族通過multiget()方法萊讀取這些tweet。得到的結果將是通過着數值表示的時間戳逆序排列的,因爲Userline使用了LongTyper comparator,並且reversed設置爲了True。
獲取一個用戶的時間線
1 |
start = request.GET.get( 'start' ) |
4 |
timeline = TIMELINE.get(useruuid, column_start=start, |
5 |
column_count=limit, column_reversed= True ) |
6 |
tweets = TWEET.multiget(timeline. values ()) |
和上一個例子類似,這次是從 Timeline 讀取 tweet ID,不過這次我們還使用了 start 和 limit 來控制讀取列的範圍。這樣有助於輸出結果的分頁。
那麼,下一步呢?
希望這足夠提供給你一個大致的概念。重複一下,我從代碼中提取了一些例子,爲了簡明起見,略去了一些操作,所以現在可能是 check out 出Twissandra 的源代碼並進行下一步深入研究的好時候了。有很多功能,諸如 retweet 和 lists,都還空着沒有實現,可以作爲一個練習的起點。如果你已經熟悉 Python 和 Django 的話,那你可以考慮實現一下這些方法。
Cassandra 的 wiki 包含了大量的信息,而且還在不斷增多,還包括一個實時更新的其他人貢獻的文章與幻燈片的列表。
如果你喜歡IRC的話,你可以加入 irc.freenode.net 的 #cassandra 頻道,來和那裏的人聊天,他們總是熱衷於提供幫助和回答問題。如果你更青睞 email 的話,cassandra-user 郵件列表上也有很多可以提供幫助的人。
No related posts.