分佈式 Key-Value 存儲系統:Cassandra 入門

簡介: Apache Cassandra 是一套開源分佈式 Key-Value 存儲系統。它最初由 Facebook 開發,用於儲存特別大的數據。 Cassandra 不是一個數據庫,它是一個混合型的非關係的數據庫,類似於 Google 的 BigTable。本文主要從以下五個方面來介紹 Cassandra:Cassandra 的數據模型、安裝和配製 Cassandra、常用編程語言使用 Cassandra 來存儲數據、Cassandra 集羣搭建。


 

Cassandra 的數據存儲結構

Cassandra 的數據模型是基於列族(Column Family)的四維或五維模型。它借鑑了 Amazon 的 Dynamo 和 Google's BigTable 的數據結構和功能特點,採用 Memtable 和 SSTable 的方式進行存儲。在 Cassandra 寫入數據之前,需要先記錄日誌 ( CommitLog ),然後數據開始寫入到 Column Family 對應的 Memtable 中,Memtable 是一種按照 key 排序數據的內存結構,在滿足一定條件時,再把 Memtable 的數據批量的刷新到磁盤上,存儲爲 SSTable 。

圖 1. Cassandra 的數據模型圖:

  1. Cassandra 的數據模型的基本概念:
  2. 1. Cluster : Cassandra 的節點實例,它可以包含多個 Keyspace
    2. Keyspace : 用於存放 ColumnFamily 的容器,相當於關係數據庫中的 Schema 或 database3. ColumnFamily : 用於存放 Column 的容器,類似關係數據庫中的 table 的概念 4. SuperColumn :它是一個特列殊的 Column, 它的 Value 值可以包函多個 Column5. Columns:Cassandra 的最基本單位。由 name , value , timestamp 組成

下面是關於數據模型實例分析 :


圖 2. 數據模型實例分析
圖 2. 數據模型實例分析 

Cassandra 節點的安裝和配置

獲取 Cassandra

 # wget  http://labs.renren.com/apache-mirror/cassandra/0.6.0/apache- 
 cassandra-0.6.0-rc1-bin.tar.gz 
 # tar -zxvf apache-cassandra-0.6.0-rc1-bin.tar.gz 
 # mv  apache-cassandra-0.6.0-rc1 cassandra 
 # ls Cassandra 


Cassandra 的目錄說明
bin 存放與 Cassandra 操作的相關腳本
conf 存放配置文件的目錄
interface Cassandra 的 Thrift 接口定義文件,可以用於生成各種編程語言的接口代碼
Javadoc 源代碼的 javadoc
lib Cassandra 運行時所需的 jar 包

配製 Cassandra 節點的數據存儲目錄和日誌目錄

修改配製文件 storage-conf.xml:


默認的內容
				
 <CommitLogDirectory>/var/lib/cassandra/commitlog</CommitLogDirectory> 
	 <DataFileDirectories> 
 <DataFileDirectory>/var/lib/cassandra/data</DataFileDirectory> 
	 </DataFileDirectories> 


配置後的內容
				
	 <CommitLogDirectory>/data3/db/lib/cassandra/commitlog</CommitLogDirectory> 
	 <DataFileDirectories> 
 <DataFileDirectory>/data3/db/lib/cassandra/data</DataFileDirectory> 
	 </DataFileDirectories> 


修改日誌配製文件 log4j.properties:


log4j.properties 配置
				
 # 日誌路徑
 #log4j.appender.R.File=/var/log/cassandra/system.log 
 # 配置後的日誌路徑 : 
 log4j.appender.R.File=/data3/db/log/cassandra/system.log 


創建文件存放數據和日誌的目錄

 # mkdir – p /data3/db/lib/cassandra 
 # mkdir – p /data3/db/log/Cassandra 

配製完成後,啓動 Cassandra

 # bin/Cassandra 


顯示信息

 INFO 09:29:12,888 Starting up server gossip 
 INFO 09:29:12,992 Binding thrift service to localhost/127.0.0.1:9160 

看到這兩行啓動回顯信息時,說明 Cassandra 已啓動成功。

連接到 Cassandra 並添加、獲取數據

Cassandra 的 bin 目錄已自帶了命令行連接工具 cassandra-cli,可使用它連接到 Cassandra,並添加、讀取數據。


連接到 Cassandra,並添加、讀取數據
				
 # bin/cassandra-cli --host localhost --port 9160 
 Connected to: "Test Cluster" on localhost/9160 
 Welcome to cassandra CLI. 
 Type 'help' or '?' for help. Type 'quit' or 'exit' to quit. 
 cassandra> 
 cassandra> set Keyspace1.Standard2['studentA']['age'] = '18'
 Value inserted 
 cassandra> get Keyspace1.Standard2['studentA'] 
 => (column=age, value=18, timestamp=1272357045192000) 
 Returned 1 results   

停止 Cassandra 服務


查出 Cassandra 的 pid:16328
				
 # ps -ef | grep cassandra 
 # kill 16328 

Cassandra 配製文件 storage-conf.xml 相關配製介紹


清單 1. storage-conf.xml 節點配製說明清單
				
 <!-- 集羣時顯示的節點名稱 --> 
 <ClusterName>Test Cluster</ClusterName> 
 <!-- 節點啓動時,是否自動加入到集羣中,默認爲 false --> 
 <AutoBootstrap>false</AutoBootstrap> 
 <!-- 集羣的節點配製 --> 
 <Seeds> 
 <Seed>127.0.0.1</Seed> 
 </Seeds> 
 <!-- 節點之間通迅的監聽地址 --> 
 <ListenAddress>localhost</ListenAddress> 
 <!-- 
      基於 Thrift 的 cassandra 客戶端監聽地址,
集羣時設爲:0.0.0.0 表示偵聽所有客戶端 , 默認爲:localhost 
 --> 
 <ThriftAddress>localhost</ThriftAddress> 
 <!-- 客戶端連接的端口 --> 
 <ThriftPort>9160</ThriftPort> 
 <!-- 
   FlushDataBufferSizeInMB   將 memtables 上的數據寫入在 Disk 上,
超過設定好的限制大小時 ( 默認 32M),則將數據寫入磁盤,
   FlushIndexBufferSizeInMB  超過設定的時長(默認 8 分鐘)後,
將 memtables 由的數據寫入磁盤中
 --> 
 <FlushDataBufferSizeInMB>32</FlushDataBufferSizeInMB> 
 <FlushIndexBufferSizeInMB>8</FlushIndexBufferSizeInMB> 
 <!-- 
 節點之間的日誌記錄同步模式。
 默認:periodic, 對應配製 CommitLogSyncPeriodInMS 
 啓動 batch 時,則對應的配製 CommitLogSyncBatchWindowInMS 
 --> 
 <CommitLogSync>periodic</CommitLogSync> 
 <!-- 	默認爲每 10 秒同步一次日誌記錄 --> 
 <CommitLogSyncPeriodInMS>10000</CommitLogSyncPeriodInMS> 
 <!-- 
 <CommitLogSyncBatchWindowInMS>1</CommitLogSyncBatchWindowInMS> --> 


常用編程語言使用 Cassandra 來存儲數據

在使用 Cassandra 時,通常情況下都需要使用第三方插件 Thrift 來生成與 Cassandra 相關的庫文件 , 您可以在 http://incubator.apache.org/thrift 下載此插件,並學習它的使用方法。以下是分別在 Java、PHP、Python、C#、Ruby 五種常用編程語言中使用 Cassandra:

Java 程序使用 Cassandra

把 libthrift-r917130.jar,apache-cassandra-0.6.0-rc1.jar 加入到 Eclipse 的編譯路徑中。

建立數據庫連接:使用 libthrift-r917130.jar 的 TTransport 的 open 方法建立起與 Cassandra 服務端 (IP:192.168.10.2 端口:9160) 的連接。

數據庫操作:使用 Cassandra.Client 創建一個客戶端實例。調用 Client 實例的 insert 方法寫入數據,通過 get 方法獲取數據。

關閉數據庫連接:使用 TTransport 的 close 方法斷開與 Cassandra 服務端的連接。


清單 2. Java 連接 Cassandra,寫入並讀取數據。
				
 package com.test.cassandra;| 
 import java.io.UnsupportedEncodingException; 
 import org.apache.thrift.transport.TTransport; 
 import org.apache.thrift.transport.TSocket; 
 import org.apache.thrift.protocol.TProtocol; 
 import org.apache.thrift.protocol.TBinaryProtocol; 
 import org.apache.thrift.TException; 
 import org.apache.cassandra.thrift.Cassandra; 
 import org.apache.cassandra.thrift.Column; 
 import org.apache.cassandra.thrift.ColumnOrSuperColumn; 
 import org.apache.cassandra.thrift.ColumnPath; 
 import org.apache.cassandra.thrift.ConsistencyLevel; 
 import org.apache.cassandra.thrift.InvalidRequestException; 
 import org.apache.cassandra.thrift.NotFoundException; 
 import org.apache.cassandra.thrift.TimedOutException; 
 import org.apache.cassandra.thrift.UnavailableException; 
 /* 
 * 使 Java 客戶端連接 Cassandra 並進行讀寫操作
 * @author jimmy 
 * @date 2010-04-10 
 */ 
 public class JCassandraClient{ 
 public static void main(String[] args) throws InvalidRequestException, 
 NotFoundException, UnavailableException, TimedOutException, 
 TException, UnsupportedEncodingException { 

 // 建立數據庫連接
 TTransport tr = new TSocket("192.168.10.2", 9160); 
 TProtocol proto = new TBinaryProtocol(tr); 
 Cassandra.Client client = new Cassandra.Client(proto); 
 tr.open(); 
 String keyspace = "Keyspace1"; 
 String cf = "Standard2"; 
 String key = "studentA"; 
 // 插入數據
 long timestamp = System.currentTimeMillis(); 
 ColumnPath path = new ColumnPath(cf); 
 path.setColumn("age".getBytes("UTF-8")); 
 client.insert(keyspace,key,path,"18".getBytes("UTF-8"), 
 timestamp,ConsistencyLevel.ONE); 
 path.setColumn("height".getBytes("UTF-8")); 
 client.insert(keyspace,key,path,"172cm".getBytes("UTF-8"), 
 timestamp,ConsistencyLevel.ONE); 
 // 讀取數據
 path.setColumn("height".getBytes("UTF-8")); 
 ColumnOrSuperColumn cc = client.get(keyspace, key, path, ConsistencyLevel.ONE); 
 Column c = cc.getColumn(); 
 String v = new String(c.value, "UTF-8"); 
        // 關閉數據庫連接
 tr.close(); 
 } 
 } 

PHP 程序使用 Cassandra

在 PHP 代碼中使用 Cassandra,需要藉助 Thrift 來生成需要的 PHP 文件,通過使用 thrift --gen php interface/cassandra.thrift 生成所需要的 PHP 文件,生成的 PHP 文件中提供了與 Cassandra 建立連接、讀寫數據時所需要的函數。


清單 3. PHP 連接 Cassandra,寫入並讀取數據。
				
 <?php 
 $GLOBALS['THRIFT_ROOT'] = '/usr/share/php/Thrift'; 
 require_once 
 $GLOBALS['THRIFT_ROOT'].'/packages/cassandra/Cassandra.php'; 
 require_once 
 $GLOBALS['THRIFT_ROOT'].'/packages/cassandra/cassandra_types.php'; 
 require_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocket.php'; 
 require_once $GLOBALS['THRIFT_ROOT'].'/protocol/TBinaryProtocol.php'; 
 require_once 
 $GLOBALS['THRIFT_ROOT'].'/transport/TFramedTransport.php'; 
 require_once 
 $GLOBALS['THRIFT_ROOT'].'/transport/TBufferedTransport.php'; 
 try { 
 // 建立 Cassandra 連接
 $socket = new TSocket('192.168.10.2', 9160); 
 $transport = new TBufferedTransport($socket, 1024, 1024); 
 $protocol = new TBinaryProtocolAccelerated($transport); 
 $client = new CassandraClient($protocol); 
 $transport->open(); 
 $keyspace = 'Keyspace1'; 
 $keyUser = "studentA"; 
 $columnPath = new cassandra_ColumnPath(); 
 $columnPath->column_family = 'Standard1'; 
 $columnPath->super_column = null; 
 $columnPath->column = 'age'; 
 $consistency_level = cassandra_ConsistencyLevel::ZERO; 
 $timestamp = time(); 
 $value = "18"; 
 // 寫入數據
 $client->insert($keyspace, $keyUser, $columnPath, $value, 
                            $timestamp, $consistency_level); 
 $columnParent = new cassandra_ColumnParent(); 
 $columnParent->column_family = "Standard1"; 
 $columnParent->super_column = NULL; 
 $sliceRange = new cassandra_SliceRange(); 
 $sliceRange->start = ""; 
 $sliceRange->finish = ""; 
 $predicate = new cassandra_SlicePredicate(); 
 list() = $predicate->column_names; 
 $predicate->slice_range = $sliceRange; 
 $consistency_level = cassandra_ConsistencyLevel::ONE; 
 $keyUser = studentA; 
 // 查詢數據
 $result = $client->get_slice($keyspace, $keyUser, $columnParent, 
             $predicate, $consistency_level); 
 // 關閉連接
 $transport->close(); 
 } catch (TException $tx) { 
 }?> 

Python 程序使用 Cassandra

在 Python 中使用 Cassandra 需要 Thrift 來生成第三方 Python 庫,生成方式: thrift --gen py interface/cassandra.thrift, 然後在 Python 代碼中引入所需的 Python 庫,生成的 Python 庫提供了與 Cassandra 建立連接、讀寫數據時所需要的方法。


清單 4. Python 連接 Cassandra,寫入並讀取數據。
				
 from thrift import Thrift 
 from thrift.transport import TTransport 
 from thrift.transport import TSocket 
 from thrift.protocol.TBinaryProtocol import 
 TBinaryProtocolAccelerated 
 from cassandra import Cassandra 
 from cassandra.ttypes import * 
 import time 
 import pprint 
 def main(): 
 socket = TSocket.TSocket("192.168.10.2", 9160) 
 transport = TTransport.TBufferedTransport(socket) 
 protocol = TBinaryProtocol.TBinaryProtocolAccelerated(transport) 
 client = Cassandra.Client(protocol) 
 pp = pprint.PrettyPrinter(indent=2) 
 keyspace = "Keyspace1"
 column_path = ColumnPath(column_family="Standard1", column="age") 
 key = "studentA"
 value = "18 "
 timestamp = time.time() 
 try: 
 # 打開數據庫連接
 transport.open() 
 # 寫入數據
 client.insert(keyspace,key,column_path, 
 value,timestamp,ConsistencyLevel.ZERO) 
 # 查詢數據
 column_parent = ColumnParent(column_family="Standard1") 
 slice_range = SliceRange(start="", finish="") 
 predicate = SlicePredicate(slice_range=slice_range) 
 result = client.get_slice(keyspace,key,column_parent, 
 predicate,ConsistencyLevel.ONE) 
 pp.pprint(result) 
 except Thrift.TException, tx: 
 print 'Thrift: %s' % tx.message 
 finally: 
 # 關閉連接
 transport.close() 
 if __name__ == '__main__': 
 main() 


C# 使用 Cassandra

在 C# 中使用 Cassandra 需要 Thrift.exe 來生成動態鏈接庫,使用 ./thrift.exe --gen csharp interface/cassandra.thrift 生成所需要的 DLL 文件,生成的 DLL 提供了與 Cassandra 建立連接,讀寫數據等所需的類和方法,在編程環境中引入生成的 DLL,即可使用。


清單 5. C# 連接 Cassandra,寫入並讀取數據。
				
 namespace CshareCassandra{ 
 using System; 
 using System.Collections.Generic; 
 using System.Diagnostics; 
 using Apache.Cassandra; 
 using Thrift.Protocol; 
 using Thrift.Transport; 
 class CassandraClient{ 
 static void Main(string[] args){ 
 // 建立數據庫連接
 TTransport transport = new TSocket("192.168.10.2", 9160); 
 TProtocol protocol = new TBinaryProtocol(transport); 
 Cassandra.Client client = new Cassandra.Client(protocol); 
 transport.Open(); 
 System.Text.Encoding utf8Encoding = System.Text.Encoding.UTF8; 
 long timeStamp = DateTime.Now.Millisecond; 
 ColumnPath nameColumnPath = new ColumnPath(){ 
 Column_family = "Standard1", 
 Column = utf8Encoding.GetBytes("age")}; 
   // 寫入數據
 client.insert("Keyspace1","studentA",nameColumnPath, 
 utf8Encoding.GetBytes("18"),timeStamp, ConsistencyLevel.ONE); 
 // 讀取數據
 ColumnOrSuperColumn returnedColumn = client.get("Keyspace1", 
            "studentA", nameColumnPath, ConsistencyLevel.ONE); 
        Console.WriteLine("Keyspace1/Standard1: age: {0}, value: {1}", 
                     utf8Encoding.GetString(returnedColumn.Column.Name), 
                     utf8Encoding.GetString(returnedColumn.Column.Value)); 
 // 關閉連接
 transport.Close(); 
 } 
 }} 


Ruby 使用 Cassandra

在 Ruby 中使用 Cassandra 需要先安裝 gem,安裝命令:gem install cassandra

安裝完成後,打開 Ruby 的 irb 開始使用 Cassandra。


清單 6. Ruby 連接 Cassandra,寫入並讀取數據
				
 > require 'rubygems' 
 > require 'cassandra'
 # 建立數據庫連接 
 > cdb = Cassandra.new('Keyspace1',"192.168.10.1:9160", :retries => 3) 
 # 寫入數據
 > cdb.insert(:Standard1, 'studentA', {'age' => '18'}) 
 # 讀取數據
 > cdb.get(:Standard1, :studentA) 
 # 關閉連接
 > cdb.disconnect 

搭建 Cassandra 集羣環境

Cassandra 的集羣是沒有中心節點的,各個節點的地位完全相同,節點之間是通過 gossip 的協議來維護集羣的狀態。


以下是兩臺安裝了 Linux 系統的服務器,且初步設置了 Cassandra 環境和啓用了端口 7000,9160:
服務器名 端口 IP 地址
ServiceA 7000,9160 192.168.10.3
ServiceB 7000,9160 192.168.10.2

配製服務器 ServiceA、ServiceB 的 storage-conf.xml 文件


ServiceA 的配置
				
 <Seeds> 
 <Seed>192.168.10.3</Seed> 
 </Seeds> 
 <ListenAddress>192.168.10.2</ListenAddress> 
 <ThriftAddress>0.0.0.0</ThriftAddress> 



ServiceB 的配置
				
 <Seeds> 
 <Seed>192.168.10.3</Seed> 
 <Seed>192.168.10.2</Seed> 
 </Seeds> 
 <ListenAddress>192.168.10.2</ListenAddress> 
 <ThriftAddress>0.0.0.0</ThriftAddress> 


配製完成後,分別啓動 ServiceA 和 ServiceB 上的 Cassandra 服務。

查看 ServiceA 和 ServiceB 是否集羣成功,可使用 Cassandra 自帶的客戶端命令

 bin/nodetool --host 192.168.10.2 ring 


集羣成功則會返回以下類似信息:
				
 Address Status Load Range Ring 
                                       106218876142754404016344802054916108445 
 192.168.10.2  Up         2.55 KB       31730917190839729088079827277059909532     |<--| 
 192.168.10.3  Up         3.26 KB       106218876142754404016344802054916108445    |-->| 

使用 Cassandra 命令行工具進行集羣測試

從 ServiceB 連接到 ServiceA,可使用命令:

 cassandra-cli -host 192.168.10.3 -port 9160 



集羣測試一
				
寫入集羣數據
 ServiceA 連接到 ServiceA: 

 # set Keyspace1.Standard2['studentAA']['A2A'] = 'a2a'

 ServiceB 連接到 ServiceA: 

 # set Keyspace1.Standard2['studentBA']['B2A'] = 'b2a'

 ServiceA 連接到 ServiceB: 
 # set Keyspace1.Standard2['studentAB']['A2B'] = 'a2b'

獲取集羣數據:

 ServiceA 連接到 ServiceA : 
 # get Keyspace1.Standard2['studentAA'], 
   get Keyspace1.Standard2['studentBA'], 
    get Keyspace1.Standard2['studentAB'] 

 ServiceB 連接到 ServiceA : 
 # get Keyspace1.Standard2['studentAA'], 
    get Keyspace1.Standard2['studentBA'], 
    get Keyspace1.Standard2['studentAB'] 

 ServiceA 連接到 ServiceB : 
 # get Keyspace1.Standard2['studentAA'], 
  get Keyspace1.Standard2['studentBA'], 
  get Keyspace1.Standard2['studentAB'] 


清單 8. 集羣測試清單二

ServiceA 停止 Cassandra 服務,ServiceA 連接到 ServiceB 並寫入數據

 # set Keyspace1.Standard2['studentAR']['A2R'] = 'a2R'

啓動 ServiceA,並鏈接到 ServiceA 本身,讀取剛纔在 ServiceB 寫入的數據

 # bin/cassandra-cli -host 192.168.10.3 -port 9160 
 # get Keyspace1.Standard2['studentAR'] 


總結

以上我們介紹了 Cassandra 的數據模型、節點安裝和配置、常用編程語言中使用 Cassandra 以及 Cassandra 的集羣和測試。Cassandra 是一個高性能的 P2P 去中心化的非關係型數據庫,可以分佈式進行讀寫操作。在系統運行時可以隨意的添加或刪降字段,是 SNS 應用的理想數據庫。


 


參考資料

關於作者

黃俊平,擅長Java應用程序的架構、設計和實現,熱衷開源軟件的開發和應用,活躍在開源中國社區 (http://www.oschina.net),您可以通過博客(http://www.itsmile.net/blog)或者郵件([email protected])來聯繫他。

發佈了77 篇原創文章 · 獲贊 3 · 訪問量 62萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章