[譯文]Cassandra實例

原文: 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裏看到。

現在我們在一個關係數據庫裏來直接進行建模,我們首先需要一個表來存放用戶。

1 CREATE TABLE user (
2     id INTEGER PRIMARY KEY,
3     username VARCHAR(64),
4     password VARCHAR(64)
5 );

我們還需要兩張表來存儲一對多的follow關係。

01 CREATE TABLE followers (
02  
03     user INTEGER REFERENCES user(id),
04  
05     follower INTEGER REFERENCES user(id)
06  
07 );
08  
09 CREATE TABLE following (
10  
11     user INTEGER REFERENCES user(id),
12  
13     followed INTEGER REFERENCES user(id)
14  
15 );

顯然,我們還需要表來存儲tweets。

1 CREATE TABLE tweets (
2     id INTEGER,
3     user INTEGER REFERENCES user(id),
4     body VARCHAR(140),
5     timestamp TIMESTAMP
6 );

由於僅僅是個例子,我已經極大簡化了情況,但僅僅是這個極度簡化的模型,也還有很多需要做的工作。例如,要以可行的方法達到達到數據歸一化就需要一個外部鍵值約束,而因爲我們需要從多張表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 和以前的版本,這些都在主配置文件中定義,如:

1 <keyspaces>
2   <keyspace name="Twissandra">
3   ...
4   </keyspace>
5 </keyspaces>

Column Families

對於每個 keyspace,都可以有一個或多個列族。列族是用於關聯類型相近的記錄的命名空間。Cassandra 在寫操作時,在一個列族內部允許有記錄級的原子性,對它們進行查詢非常高效。這些特性十分重要,在進行你的數據建模前必須記牢,它們會在下面討論到。

和keyspace類似,列族也在主配置文件中定義,雖然在將來的版本中你將可以在運行時創建列族,正像在RDBMS中創建表一樣。

01 <keyspaces>
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>
11 </keyspaces>

需要指出的是,上面的配置片段中,指定名字的時候同時指定了一個比較者類型。這凸顯了 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 '**********'
3 useruuid = str(uuid())  
4  
5 columns = {'id': useruuid, 'username': username, 'password'password}  
6  
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'  
2  
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)  
04  
05 columns = {'id': tweetuuid, 'user_id': useruuid, 'body': body, '_ts'timestamp}
06 TWEET.insert(tweetuuid, columns)  
07  
08 columns = {struct.pack('>d'timestamp: tweetuuid}
09 USERLINE.insert(useruuid, columns)  
10  
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')
2 limit = NUM_PER_PAGE  
3  
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.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章