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