Cassandra簡介

 在前面的一篇文章《圖形數據庫Neo4J簡介》中,咱們介紹了一種很是流行的圖形數據庫Neo4J的使用方法。而在本文中,咱們將對另一種類型的NoSQL數據庫——Cassandra進行簡單地介紹。html

  接觸Cassandra的緣由與接觸Neo4J的緣由相同:咱們的產品須要可以記錄一系列關係型數據庫所沒法快速處理的大量數據。 Cassandra,以及後面將要介紹的MongoDB,都是咱們在技術選型過程當中的一個備選方。雖說最後咱們並無選擇Cassandra,可是在 整個技術選型過程當中所接觸到的一系列內部機制,思方式等都是很是有趣的。並且在整個選型過程當中也借鑑了CAM(Cloud Availability Manager)組在實際使用過程當中所獲得的一些經驗。所以我在這裏把本身的筆記總結成一篇文章,分享出來。java

 

技術選型git

  技術選型經常是一個很是嚴謹的過程。因爲一個項目一般是由數十位甚至上百位開發人員協同開發的,所以一個精準的技術選型經常可以大幅提升整個項 目的開發效率。在嘗試爲某一類需求設計解決方時,咱們經常會有不少種能夠選擇的技術。爲了可以精準地選擇一個適合於這些需求的技術,咱們就須要慮一系 列有關學習曲線,開發,維護等衆多方面的因素。這些因素主要包括:github

  • 該技術所提供的功能是否可以完整地解決問題。算法

  • 該技術的擴展性如何。是否容許用戶添加自定義組成來知足特殊的需求。數據庫

  • 該技術是否有豐富完整的文檔,而且可以以避免費甚至付費的形式獲得專業的支持。數組

  • 該技術是否有不少人使用,尤爲是一些大型企業在使用,並存在着成功的例。緩存

  在該過程當中,咱們會逐漸篩選市面上所能找到的各類技術,並最終肯定適合咱們需求的那一種。服務器

  針對咱們剛剛所提到的需求——記錄並處理系統自動生成的大量數據,咱們在技術選型的初始階段會有不少種選擇:Key-Value數據庫,如 Redis,Document-based數據庫,如MongoDB,Column-based數據庫,如Cassandra等。並且在實現特定功能時, 咱們經常能夠經過以上所列的任何一種數據庫來搭建一個解決方。能夠說,如何在這三種數據庫之間選擇經常是NoSQL數據庫初學者所最爲頭疼的問題。致使 這種現象的一個緣由就是,Key-Value,Document-based以及Column-based其實是對NoSQL數據庫的一種較爲泛泛的分 類。不一樣的數據庫提供商所提供的NoSQL數據庫經常具備略爲不一樣的實現方式,並提供了不一樣的功能集合,進而會致使這些數據庫類型之間的邊界並非那麼清 晰。網絡

  恰如其名所示,Key-Value數據庫會以鍵值對的方式來對數據進行存儲。其內部經常經過哈希表這種結構來記錄數據。在使用時,用戶只須要通 過Key來讀取或寫入相應的數據便可。所以其在對單條數據進行CRUD操做時速度很是快。而其缺陷也同樣明顯:咱們只能經過鍵來訪問數據。除此以外,數據 庫並不知道有關數據的其它信息。所以若是咱們須要根據特定模式對數據進行篩選,那麼Key-Value數據庫的運行效率將很是低下,這是由於此時Key- Value數據庫經常須要掃描全部存在於Key-Value數據庫中的數據。

  所以在一個服務中,Key-Value數據庫經常用來做爲服務端緩存使用,以記錄一系列經由較爲耗時的複雜計算所獲得的計算結果。最著名的就是Redis。固然,爲Memcached添加了持久化功能的MemcacheDB也是一種Key-Value數據庫。

  Document-based數據庫和Key-Value數據庫之間的不一樣主要在於,其所存儲的數據將再也不是一些字符串,而是具備特定格式的文檔,如XML或JSON等。這些文檔能夠記錄一系列鍵值對,數組,甚至是內嵌的文檔。如:

複製代碼

 1 { 2     Name: "Jefferson", 3     Children: [{ 4         Name:"Hillary", 5         Age: 14 6     }, { 7         Name:"Todd", 8         Age: 12 9     }],10     Age: 45,11     Address: {12         number: 1234,13         street: "Fake road",14         City: "Fake City",15         state: "NY",16         Country: "USA"17     }18 }

複製代碼

  有些讀者可能會有疑問,咱們一樣也能夠經過Key-Value數據庫來存儲JSON或XML格式的數據,不是麼?就是Document- based數據庫經常會支持索引。咱們剛剛提到過,Key-Value數據庫在執行數據的查找及篩選時效率很是差。而在索引的幫助下,Document- based數據庫則可以很好地支持這些操做了。有些Document-based數據庫甚至容許執行像關係型數據庫那樣的JOIN操做。並且相較於關係型 數據庫,Document-based數據庫也將Key-Value數據庫的靈活性得以保留。

  而Column-based數據庫則與前面兩種數據庫很是不一樣。咱們知道,一個關係型數據庫中所記錄的數據經常是按照行來組織的。每一行中包含 了表示不一樣意義的多個列,並被順序地記錄在持久化文件中。咱們知道,關係型數據庫中的一個常見操做就是對具備特定特徵的數據進行篩選及操做,並且該操做常 常是經過WHERE子句來完成的:

1 SELECT * FROM customers WHERE country='Mexico';

  在一個傳統的關係型數據庫中,該語句所操做的表可能以下所示:

  而在該表所對應的數據庫文件中,每一行中的各個數值將被順序記錄,從而造成了以下圖所示的數據文件:

  所以在執行上面的SQL語句時,關係型數據庫並不能連續操做文件中所記錄的數據:

  這大大下降了關係型數據庫的性能:爲了運行該SQL語句,關係型數據庫須要讀取每一行中的id域和name域。這將致使關係型數據庫所要讀取的 數據量顯著增長,也須要在訪問所需數據時執行一系列偏移量計算。何況上面所舉的例子僅僅是一個最簡單的表。若是表中包含了幾十列,那麼數據讀取量將增大幾 十倍,偏移量計算也會變得更爲複雜。

  那麼咱們應該如何解決這個問題呢?就是將一列中的數據連續地存在一塊兒:

  而這就是Column-based數據庫的核心思想:按照列來在數據文件中記錄數據,以得到更好的請求及遍歷效率。這裏有兩點須要注意:首 先,Column-based數據庫並不表示會將全部的數據按列進行組織,也沒有那個必要。對某些須要執行請求的數據進行按列存儲便可。另一點則 是,Cassandra對Query的支持其實是與其所使用的數據模型關聯在一塊兒的。也就是說,對Query的支持頗有限。咱們立刻就會在下面的章節中 對該限制進行介紹。

  至此爲止,您應該可以根據各類數據庫所具備的特性來爲您的需求選擇一個合適的NoSQL數據庫了。

 

Cassandra初體驗

  OK,在簡單地介紹了Key-Value,Document-based以及Column-based三種不一樣類型的NoSQL數據庫以後,我 們就要開始嘗試着使用Cassandra了。鑑於我我的在使用一系列NoSQL數據庫時經常遇到它們的版本更新缺少API後向兼容性這一狀況,我在這裏直 接使用了Datastax Java Driver的樣例。這樣讀者也能從該頁面中查閱針對最新版本客戶端的示例代碼。

  一段最簡單的讀取一條記錄的Java代碼以下所示:

複製代碼

Cluster cluster = null;try {    // 建立鏈接到Cassandra的客戶端
    cluster = Cluster.builder()
            .addContactPoint("127.0.0.1")
            .build();    // 建立用戶會話
    Session session = cluster.connect();    // 執行CQL語句
    ResultSet rs = session.execute("select release_version from system.local");    // 從返回結果中取出第一條結果
    Row row = rs.one();
    System.out.println(row.getString("release_version"));
} finally {    // 調用cluster變量的close()函數並關閉全部與之關聯的連接
    if (cluster != null) {
        cluster.close();
    }
}

複製代碼

  看起來很簡單,是麼?其實在客戶端的幫助下,操做Cassandra實際上並非很是困難的一件事。反過來,如何爲Cassandra所記錄的 數據設計模型纔是最須要讀者仔細慮的。與你們所最爲熟悉的關係型數據庫建模方式不一樣,Cassandra中的數據模型設計須要是Join-less的。 簡單地說,那就是因爲這些數據分佈在Cassandra的不一樣結點上,所以這些數據的Join操做並不能被高效地執行。

  那麼咱們應該如何爲這些數據定義模型呢?首先咱們要了解Cassandra所支持的基本數據模型。這些基本數據模型有:Column,Super Column,Column Family以及Keyspace。下面咱們就對它們進行簡單地介紹。

  Column是Cassandra所支持的最基礎的數據模型。該模型中能夠包含一系列鍵值對:

1 {2     "name": "Auther Name",3     "value": "Sam",4     "timestamp": 1234567895 }

  Super Column則包含了一系列Column。在一個Super Column中的屬性能夠是一個Column的集合:

複製代碼

1 {2     "name": "Cassandra Introduction",3     "value": {4         "auther": { "name": "Auther Name", "value": "Sam", "timestamp": 123456789},5         "publisher": { "name": "Publisher", "value": "China Press", "timestamp": 234567890}6     }7 }

複製代碼

  這裏須要注意的是,Cassandra文檔已經再也不建議過多的使用Super Column,而緣由卻沒有直接說明。聽說這和Super Column經常須要在數據訪問時執行反序列化相關。一個最爲常見的證據就是,網絡上經常會有一些開發人員在Super Column中添加了過多的數據,並進而致使和這些Super Column相關的請求運行緩慢。固然這只是猜想。只不過既然官方文檔都已經開始對Super Column持謹慎意見,那麼咱們也須要在平常使用過程當中儘可能避免使用Super Column。

  而一個Column Family則是一系列Column的集合。在該集合中,每一個Column都會有一個與之相關聯的鍵:

複製代碼

 1 Authers = { 2     「1332」: { 3         "name": "Auther Name", 4         "value": "Sam", 5         "timestamp": 123456789 6     }, 7     「1452」: { 8         「name」: 「Auther Name」, 9         「value」: 「Lucy」,10         「timestamp」: 01234343711     }12 }

複製代碼

  上面的Column Family示例中所包含的是一系列Column。除此以外,Column Family還能夠包含一系列Super Column(請謹慎使用)。

  最後,Keyspace則是一系列Column Family的集合。

  發現了麼?上面沒有任何一種方法可以經過一個Column(Super Column)引用另外一個Column(Super Column),而只能經過Super Column包含其它Column的方式來完成這種信息的包含。這與咱們在關係數據庫設計過程當中經過外鍵與其它記錄相關聯的使用方法很是不一樣。還記得以前 咱們經過外鍵來建立數據關聯這一方法的名稱麼?對的,Normalization。該方法能夠經過外鍵所指示的關聯關係有效地消除在關係型數據庫中的冗餘 數據。而在Cassandra中,咱們要使用的方法就是Denormalization,也便是容許能夠接受的必定程度的數據冗餘。也就是說,這些關聯的 數據將直接記錄在當前數據類型之中。

  在使用Cassandra時,哪些不應抽象爲Cassandra數據模型,而哪些數據應該有一個獨立的抽象呢?這一切決定於咱們的應用所經常執 行的讀取及寫入請求。想一想咱們爲何使用Cassandra,或者說Cassandra相較於關係型數據庫的優點:快速地執行在海量數據上的讀取或寫入請 求。若是咱們僅僅根據所操做的事物抽象數據模型,而不去理會Cassandra在這些模型之上的執行效率,甚至致使這些數據模型沒法支持相應的業務邏輯, 那麼咱們對Cassandra的使用也就沒有實際的意義了。所以一個較爲正確的作法就是:首先根據應用的需求來定義一個抽象概念,並開始針對該抽象概念以 及應用的業務邏輯設計在該抽象概念上運行的請求。接下來,軟件開發人員就能夠根據這些請求來決定如何爲這些抽象概念設計模型了。

  在抽象設計模型時,咱們經常須要面對另一個問題,那就是如何指定各Column Family所使用的各類鍵。在Cassandra相關的各種文檔中,咱們經常會遇到如下一系列關鍵的名詞:Partition Key,Clustering Key,Primary Key以及Composite Key。那麼它們指的都是什麼呢?

  Primary Key其實是一個很是通用的概念。在Cassandra中,其表示用來從Cassandra中取得數據的一個或多個列:

1 create table sample (2     key text PRIMARY KEY,3     data text4 );

  在上面的示例中,咱們指定了key域做爲sample的PRIMARY KEY。而在須要的狀況下,一個Primary Key也能夠由多個列共同組成:

1 create table sample {2     key_one text,3     key_two text,4     data text,5     PRIMARY KEY(key_one, key_two)6 };

  在上面的示例中,咱們所建立的Primary Key就是一個由兩個列key_one和key_two組成的Composite Key。其中該Composite Key的第一個組成被稱爲是Partition Key,然後面的各組成則被稱爲是Clustering Key。Partition Key用來決定Cassandra會使用集羣中的哪一個結點來記錄該數據,每一個Partition Key對應着一個特定的Partition。而Clustering Key則用來在Partition內部排序。若是一個Primary Key只包含一個域,那麼其將只擁有Partition Key而沒有Clustering Key。

  Partition Key和Clustering Key一樣也能夠由多個列組成:

複製代碼

1 create table sample {2     key_primary_one text,3     key_primary_two text,4     key_cluster_one text,5     key_cluster_two text,6     data text,7     PRIMARY KEY((key_primary_one, key_primary_two), key_cluster_one, key_cluster_two)8 };

複製代碼

  而在一個CQL語句中,WHERE等子句所標示的條件只能使用在Primary Key中所使用的列。您須要根據您的數據分佈決定到底哪些應該是Partition Key,哪些應該做爲Clustering Key,以對其中的數據進行排序。

  一個好的Partition Key設計經常會大幅提升程序的運行性能。首先,因爲Partition Key用來控制哪一個結點記錄數據,所以Partition Key能夠決定是否數據可以較爲均勻地分佈在Cassandra的各個結點上,以充分利用這些結點。同時在Partition Key的幫助下,您的讀請求應儘可能使用較少數量的結點。這是由於在執行讀請求時,Cassandra須要協調處理從各個結點中所獲得的數據集。所以在響應 一個讀操做時,較少的結點可以提供較高的性能。所以在模型設計中,如何根據所須要運行的各個請求指定模型的Partition Key是整個設計過程當中的一個關鍵。一個取值均勻分佈的,卻經常在請求中做爲輸入條件的域,經常是一個能夠慮的Partition Key。

  除此以外,咱們也應該好好地慮如何設置模型的Clustering Key。因爲Clustering Key能夠用來在Partition內部排序,所以其對於包含範圍篩選的各類請求的支持較好。

 

Cassandra內部機制

  在本節中,咱們將對Cassandra的一系列內部機制進行簡單地介紹。這些內部機制不少都是業界所經常使用的解決方。所以在瞭解了Cassandra是如何使用它們的以後,您就能夠很是容易地理解其它類庫對這些機制的使用,甚至在您本身的項目中借鑑及使用它們。

  這些常見的內部機制有:Log-Structured Merge-Tree,Consistent Hash,Virtual Node等。

 

Log-Structured Merge-Tree

  最有意思的一個數據結構莫過於Log-Structured Merge-Tree。Cassandra內部使用相似的結構來提升服務實例的運行效率。那它是如何工做的呢?

  簡單地說,一個Log-Structured Merge-Tree主要由兩個樹形結構的數據組成:存在於內存中的C0,以及主要存在於磁盤中的C1

  在添加一個新的結點時,Log-Structured Merge-Tree會首先在日誌文件中添加一條有關該結點插入的記錄,而後再將該結點插入到樹C0中。添加到日誌文件中的記錄主要是基於數據恢復的慮。畢竟C0樹處於內存中,很是容易受到系統宕機等因素的影響。而在讀取數據時,Log-Structured Merge-Tree會首先嚐試從C0樹中查找數據,而後再在C1樹中查找。

  在C0樹知足必定條件以後,如其所佔用的內存過大,那麼它所包含的數據將被遷移到C1中。在Log-Structured Merge-Tree這個數據結構中,該操做被稱爲是rolling merge。其會把C0樹中的一系列記錄歸併到C1樹中。歸併的結果將會寫入到新的連續的磁盤空間。

幾乎是論文中的原圖

  就單個樹來看,C1和咱們所熟悉的B樹或者B+樹有點像,是不?

  不知道您注意到沒有。上面的介紹突出了一個詞:連續的。這是由於C1樹中同一層次的各個結點在磁盤中是連續記錄的。這樣磁盤就能夠經過連續讀取來避免在磁盤上的過多尋道,從而大大地提升了運行效率。

 

MemtableSSTable

  好,剛剛咱們已經提到了Cassandra內部使用和Log-Structured Merge-Tree相似的數據結構。那麼在本節中,咱們就將對Cassandra的一些主要數據結構及操做流程進行介紹。能夠說,若是您大體理解了上一 節對Log-Structured Merge-Tree的講解,那麼理解這些數據結構也將是很是容易的事情。

  在Cassandra中有三個很是重要的數據結構:記錄在內存中的Memtable,以及保存在磁盤中的Commit Log和SSTable。Memtable在內存中記錄着最近所作的修改,而SSTable則在磁盤上記錄着Cassandra所承載的絕大部分數據。在 SSTable內部記錄着一系列根據鍵排列的一系列鍵值對。一般狀況下,一個Cassandra表會對應着一個Memtable和多個SSTable。除 此以外,爲了提升對數據進行搜索和訪問的速度,Cassandra還容許軟件開發人員在特定的列上建立索引。

  鑑於數據可能存儲於Memtable,也可能已經被持久化到SSTable中,所以Cassandra在讀取數據時須要合併從Memtable 和SSTable所取得的數據。同時爲了提升運行速度,減小沒必要要的對SSTable的訪問,Cassandra提供了一種被稱爲是Bloom Filter的組成:每一個SSTable都有一個Bloom Filter,以用來判斷與其關聯的SSTable是否包含當前查詢所請求的一條或多條數據。若是是,Cassandra將嘗試從該SSTable中取出 數據;若是不是,Cassandra則會忽略該SSTable,以減小沒必要要的磁盤訪問。

  在經由Bloom Filter判斷出與其關聯的SSTable包含了請求所須要的數據以後,Cassandra就會開始嘗試從該SSTable中取出數據了。首 先,Cassandra會檢查Partition Key Cache是否緩存了所要求數據的索引項Index Entry。若是存在,那麼Cassandra會直接從Compression Offset Map中查詢該數據所在的地址,並從該地址取回所須要的數據;若是Partition Key Cache並無緩存該Index Entry,那麼Cassandra首先會從Partition Summary中找到Index Entry所在的大體位置,並進而從該位置開始搜索Partition Index,以找到該數據的Index Entry。在找到Index Entry以後,Cassandra就能夠從Compression Offset Map找到相應的條目,並根據條目中所記錄的數據的位移取得所須要的數據:

較文檔中原圖略做調整

  發現了麼?實際上SSTable中所記錄的數據仍然是順序記錄的各個域,可是不一樣的是,它的查找首先經由了Partition Key Cache以及Compression Offset Map等一系列組成。這些組成僅僅包含了一系列對應關係,也就是至關於連續地記錄了請求所須要的數據,進而提升了數據搜索的運行速度,不是麼?

  Cassandra的寫入流程也與Log-Structured Merge-Tree的寫入流程很是相似:Log-Structured Merge-Tree中的日誌對應着Commit Log,C0樹對應着Memtable,而C1樹 則對應着SSTable的集合。在寫入時,Cassandra會首先將數據寫入到Memtable中,同時在Commit Log的末尾添加該寫入所對應的記錄。這樣在機器斷電等異常狀況下,Cassandra仍能經過Commit Log來恢復Memtable中的數據。

  在持續地寫入數據後,Memtable的大小將逐漸增加。在其大小到達某個閾值時,Cassandra的數據遷移流程就將被觸發。該流程一方面會將Memtable中的數據添加到相應的SSTable的末尾,另外一方面則會將Commit Log中的寫入記錄移除。

  這也就會形成一個容易讓讀者困惑的問題:若是是將新的數據寫入到SSTable的末尾,那麼數據遷移的過程該如何執行對數據的更新?就是: 在須要對數據進行更新時,Cassandra會在SSTable的末尾添加一條具備當前時間戳的記錄,以使得其可以標明自身爲最新的記錄。而原有的在 SSTable中的記錄隨即宣告失效。

  這會致使一個問題,那就是對數據的大量更新會致使SSTable所佔用的磁盤空間迅速增加,並且其中所記錄的數據不少都已是過時數據。所以在一段時間以後,磁盤的空間利用率會大幅降低。此時咱們就須要經過壓縮SSTable的方式釋放這些過時數據所佔用的空間:

  如今有一個問題,那就是咱們能夠根據重複數據的時間戳來判斷哪條是最新的數據,可是咱們應該如何處理數據的刪除呢?在Cassandra中,對 數據的刪除是經過一個被稱爲tombstone的組成來完成的。若是一條數據被添加了一個tombstone,那麼其在下次壓縮時就被認爲是一條已經被刪 除的數據,從而不會添加到壓縮後的SSTable中。

  在壓縮過程當中,原有的SSTable和新的SSTable同時存在於磁盤上。這些原有的SSTable用來完成對數據讀取的支持。一旦新的SSTable建立完畢,那麼老的SSTable就將被刪除。

  在這裏咱們要提幾點在平常使用Cassandra的過程當中須要注意的問題。首先是,因爲經過Commit Log來重建Memtable是一個較爲耗時的過程,所以咱們在須要重建Memtable的一系列操做前須要嘗試手動觸發歸併邏輯,以將該結點上 Memtable中的數據持久化到SSTable中。最多見的一種須要重建Memtable的操做就是從新啓動Cassandra所在的結點。

  另外一個須要注意的地方是,不要過分地使用索引。雖說索引能夠大幅地增長數據的讀取速度,可是咱們一樣須要在數據寫入時對其進行維護,形成必定的性能損耗。在這點上,Cassandra和傳統的關係型數據庫沒有太大區別。

 

Cassandra集羣

  固然,使用單一的數據庫實例來運行Cassandra並非一個好的選擇。單一的服務器可能致使服務集羣產生單點失效的問題,也沒法充分利用 Cassandra的橫向擴展能力。所以從本節開始,咱們就將對Cassandra集羣以及集羣中所使用的各類機制進行簡單地講解。

  在一個Cassandra集羣中經常包含着如下一系列組成:結點(Node),數據中心(Data Center)以及集羣(Cluster)。結點是Cassandra集羣中用來存儲數據的最基礎結構;數據中心則是處於同一地理區域的一系列結點的集 合;而集羣則經常由多個處於不一樣區域的數據中心所組成:

  上圖所展現的Cassandra集羣由三個數據中心組成。這三個數據中心中的兩個處於同一區域內,而另外一個數據中心則處於另外一個區域中。能夠 說,兩個數據中心處於同一區域的狀況並很少見,可是Cassandra的官方文檔也沒有否認這種集羣搭建方式。每一個數據中心則包含了一系列結點,以用來存 儲Cassandra集羣所要承載的數據。

  有了集羣,咱們就須要使用一系列機制來完成集羣之間的相互協做,並慮集羣所須要的一系列非功能性需求了:結點的狀態維護,數據分發,擴展性(Scalability),高可用性,災難恢復等。

  對結點的狀態進行探測是高可用性的第一步,也是在結點間分發數據的基礎。Cassandra使用了一種被稱爲是Gossip的點對點通信方, 以在Cassandra集羣中的各個結點之間共享及傳遞各個結點的狀態。只有這樣,Cassandra才能知道到底哪些結點能夠有效地保存數據,進而將對 數據的操做分發給各結點。

  在保存數據的過程當中,Cassandra會使用一個被稱爲是Partitioner的組成來決定數據到底要分發到哪些結點上。而另外一個和數據存儲相關的組成就是Snitch。其會提供根據集羣中全部結點的性能來決定如何對數據進行讀寫。

  這些組成內部也使用了一系列業界所經常使用的方法。例如Cassandra內部經過VNode來處理各硬件的性能不一樣,從而在物理硬件層次上造成一種相似《企業級負載平衡簡介》一文所中提到過的Weighted Round Robin的解決方。再好比其內部使用了Consistent Hash,咱們也在《Memcached簡介》一文中給出過介紹。

  好了,簡介完成。在下面幾節中,咱們就將對Cassandra所使用的這些機制進行介紹。

 

Gossip

  首先就是Gossip。其是用來在Cassandra集羣中的各個結點之間傳輸結點狀態的協議。它每秒都將運行一次,並將當前 Cassandra結點的狀態以及其所知的其它結點的狀態與至多三個其它結點交換。經過這種方法,Cassandra的有效結點能很快地瞭解當前集羣中其 它結點的狀態。同時這些狀態信息還包含一個時間戳,以容許Gossip判斷到底哪一個狀態是更新的狀態。

  除了在集羣中的各個結點之間交換各結點的狀態以外,Gossip還須要可以應對對集羣進行操做的一系列動做。這些操做包括結點的添加,移除,重 新加入等。爲了可以更好地處理這些狀況,Gossip提出了一個叫作Seed Node的概念。其用來爲各個新加入的結點提供一個啓動Gossip交換的入口。在加入到Cassandra集羣以後,新結點就能夠首先嚐試着跟其所記錄 的一系列Seed Node交換狀態。這一方面能夠獲得Cassandra集羣中其它結點的信息,進而容許其與這些結點進行通信,又能夠將本身加入的信息經過這些Seed Node傳遞出去。因爲一個結點所獲得的結點狀態信息經常被記錄在磁盤等持久化組成中,所以在從新啓動以後,其仍然能夠經過這些持久化後的結點信息進行通 訊,以從新加入Gossip交換。而在一個結點失效的狀況下,其它結點將會定時地向該結點發送探測消息,以嘗試與其恢復鏈接。可是這會爲咱們永久地移除一 個結點帶來麻煩:其它Cassandra結點總以爲該結點將在某一時刻從新加入集羣,所以一直向該結點發送探測信息。此時咱們就須要使用 Cassandra所提供的結點工具了。

  那麼Gossip是如何判斷是否某個結點失效了呢?若是在交換過程當中,參與交換的另外一方好久不回,那麼當前結點就會將目標結點標示爲失效,並 進而經過Gossip協議將該狀態傳遞出去。因爲Cassandra集羣的拓撲結構可能很是複雜,如跨區域等,所以其用來判斷一個結點是否失效的標準並不 是在多長時間以內沒有響應就斷定爲失效。畢竟這會致使很大的問題:兩個在同一個Lab中的結點進行狀態交換會很是快,而跨區域的交換則會比較慢。若是咱們 設置的時間較短,那麼跨區域的狀態交換經常會被誤報爲失效;若是咱們所設置的時間較長,那麼Gossip對結點失效的探測靈敏度將下降。爲了不這種情 況,Gossip使用的是一種根據以往結點間交換歷史等衆多因素綜合起來的決策邏輯。這樣對於兩個距離較遠的結點,其將擁有較大的時間窗,從而不會產生誤 報。而對於兩個距離較近的結點,Gossip將使用較小的時間窗,從而提升探測的靈敏度。

 

Consistent Hash

  接下來咱們要講的是Consistent Hash。在一般的哈希算法中經常包含着桶這個概念。每次哈希計算都是在決定特定數據須要存儲在哪一個桶中。而若是桶的數量發生了變化,那麼以前的哈希計算結果都將失效。而Consistent Hash則很好地解決了該問題。

  那Consistent Hash是如何工做的呢?首先請慮一個圓,在該圓上分佈了多個點,以表示整數0到1023。這些整數平均分佈在整個圓上:

  在上圖中,咱們突出地顯示了將圓六等分的六個藍點,表示用來記錄數據的六個結點。這六個結點將各自負責一個範圍。例如512這個藍點所對應的結 點就將記錄從哈希值爲512到681這個區間的數據。在Cassandra以及其它的一些領域中,這個圓被稱爲是一個Ring。接下來咱們就對當前須要存 儲的數據執行哈希計算,並獲得該數據所對應的哈希值。例如一段數據的哈希值爲900,那麼它就位於853和1024之間:

  所以該數據將被藍點853所對應的結點記錄。這樣一旦其它結點失效,該數據所在的結點也不會發生變化:

  那每段數據的哈希值究竟是如何計算出來的呢?是Partitioner。其輸入爲數據的Partition Key。而其計算結果在Ring上的位置就決定了究竟是由哪些結點來完成對數據的保存。

 

Virtual Node

  上面咱們介紹了Consistent Hash的運行原理。可是這裏還有一個問題,那就是失效的那個結點上的數據該怎麼辦?咱們就沒法訪問了麼?這取決於咱們對Cassandra集羣數據複製 方面的設置。一般狀況下,咱們都會啓用該功能,從而使得多個結點同時記錄一份數據的拷貝。那麼在其中一個結點失效的狀況下,其它結點仍然能夠用來讀取該數 據。

  這裏要處理的一個狀況就是,各個物理結點所具備的容量並不相同。簡單地說,若是一個結點所能提供的服務能力遠小於其它結點,那麼爲其分配相同的 負載將使得它不堪重負。爲了處理這種狀況,Cassandra提供了一種被稱爲VNode的解決方。在該解決方中,每一個物理結點將根據其實際容量被劃 分爲一系列具備相同容量的VNode。每一個VNode則用來負責Ring上的一段數據。例如對於剛剛所展現的具備六個結點的Ring,各個VNode和物 理機之間的關係則可能以下所示:

  在使用VNode時,咱們經常須要注意的一點就是Replication Factor的設置。從其所表示的意義來說,Cassandra中的Replication Factor和其它常見數據庫中所使用的Replication Factor沒有什麼不一樣:其所具備的數值用來表示記錄在Cassandra中的數據有多少份拷貝。例如在其被設置爲1的狀況下,Cassandra將只 會保存一份數據。若是其被設置爲2,那麼Cassandra將多保存一份這些數據的拷貝。

  在決定Cassandra集羣所須要使用的Replication Factor時,咱們須要慮如下一系列因素:

  • 物理機的數量。試想一下,若是咱們將Replication Factor設置爲超過物理機的數量,那麼必然會有物理機保存了同一份數據的兩部分拷貝。這實際上沒有太大的做用:一旦該物理機出現異常,那就會一次損失 多份數據。所以就高可用性這一點來講,Replication Factor的數值超過物理機的數量時,多出的這些數據拷貝意義並不大。

  • 物理機的異構性。物理機的異構性經常也會影響您所設Replication Factor的效果。舉一個極端的例子。若是說咱們有一個Cassandra集羣並且其由五臺物理機組成。其中一臺物理機的容量是其它物理機的4倍。那麼 將Replication Factor設置爲3時將會出現具備較大容量的物理機上存儲了一樣的數據這種問題。其並不比設置爲2好多少。

  所以在決定一個Cassandra集羣的Replication Factor時,咱們要仔細地根據集羣中物理機的數量和容量設置一個合適的數值。不然其只會致使更多的無用的數據拷貝。

 

注:這篇文章寫於15年8月。鑑於NoSQL數據庫發展很是快,並且經常具備一系列影響後向兼容性的更改(如Spring Data Neo4J已經不支持@Fetch)。所以若是您發現有什麼描述已經發生了改變,請幫留下評論,以便其它讀者。在此感激涕零

相關文章
相關標籤/搜索