iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 28
0
Big Data

Spark 2.0 in Scala系列 第 28

[Spark-Day28](Spark好友篇)一次搞定Cassandra安裝與基礎查詢操作

趁最後幾天衝一下我很愛的分散式資料庫Cassandra。如果要用30秒的電梯演講說明Cassandra的優點,我會說:

Cassandra是個開放源碼、分散式、去中心式、高擴展性、高可用、容錯、可調式一致性、列導向資料庫。其分散式設計與資料模型分別奠基於Amazon Dynamo與Google Bigtable,現有許多大型網站採用。

分散式去中心式高擴展性高可用容錯可調式一致性這每一項在Cassandra背後都有其對應的實現理論與技術。若你曾經聽過有人或文章在Cassandra的架構,那你可能有聽到例如GossipToken RingVirtua NodePartitionerReplication StrategyConsistency LevelMemTableSSTableQuery-DrivenData Modeling等一堆相關技術名詞。但這次主題不是Cassandra 30天鐵人賽XD,有興趣進一步研究的,我推薦這本:

http://ithelp.ithome.com.tw/upload/images/20170114/20103839jrXuk9ukvn.png

O'REILLY出版社大家都不陌生吧。這本基於Cassandra最新主板號(3.0)2016年6月才出版的Definitive Guide,內容相當完整。
中文譯版準時的話估計今年年中之前吧(....就看我有沒有拖稿了XD)。

廢話不多說,先來玩看看吧:
下載目前最新的3.9版本:apache-cassandra-3.9-bin.tar.gz。Cassandra的版本號是用Intel那套Tick-Tock準則,而且每個月幾乎都會更新版本號(即便打開release note沒做啥事)。所以內容與3.0差異並沒有想像中那麼大。下載完後,可以在資料夾內透過:

$bin/cassandra

直接啟動服務,此時螢幕上會開始刷訊息...如果一切順利,應該會看到如下畫面:

http://ithelp.ithome.com.tw/upload/images/20170114/20103839Lx8Pbpmq8n.png
並且再最後一行看到:JUMP TO NORMAL。

此prompt接著會被釋放(如果沒有就自己按一下ENTER試試),不像zookeeper或kafka會咬死XD,而初步探索Cassandra最好用的就是透過python實作的cqlsh(Cassandra Query Language shell)來玩啦,透過:bin/cqlsh直接連接吧:

joechh@joechh:/opt/apache-cassandra-3.9$ bin/cqlsh
Connected to Test Cluster at 127.0.0.1:9042.
[cqlsh 5.0.1 | Cassandra 3.9 | CQL spec 3.4.2 | Native protocol v4]
Use HELP for help.
cqlsh> 

注意如果你有修改cassandra.yaml檔更改IP或是Client端的port,那在cqlsh後面就要指定對應的IP跟port,都沒指定預設會走127.0.0.1:9042。

動手玩CQL(Cassandra Query Language)

進到shell可以先用help看一下可以用的指令總表
http://ithelp.ithome.com.tw/upload/images/20170114/20103839dZxHZFVmfE.png
若對某個指令的細部參數想進一步了解可以用help COMMAND,會跳出對應的網頁參考資料。

描述一下叢集(也可用於keyspace與table)

cqlsh:testspace> DESCRIBE CLUSTER 

Cluster: Test Cluster
Partitioner: Murmur3Partitioner

Range ownership:
'testspace'

我們可以看到這座測試叢集的一些相關資訊,裏面還有一個很重要的Partitioner資訊(預設採用Murmur3Partitioner),繼續看下去吧。

keyspace勒:

cqlsh:testspace> DESCRIBE KEYSPACES;
system_schema  system_auth  system  system_distributed  system_traces

可以將keyspace假想成RDB中的資料庫等級的階層,內含多個tables。

想知道版本號也沒問題:

cqlsh:testspace> show version
[cqlsh 5.0.1 | Cassandra 3.9 | CQL spec 3.4.2 | Native protocol v4]

其實這個就是shell登入畫面的一部份啦~注意的是Cassandra中有許多小元件,每個重要元件都有其版本,像是cqlsh、native protocol等,而最重要的就是Cassandra版本啦(3.9版)


[Snippet.62] Create Keyspace/Table in Cassandra
玩資料庫當然要會建keyspace(DB)跟表阿,看看怎麼做,

cqlsh:testspace> create KEYSPACE my_keyspace with replication = {'class': 'SimpleStrategy', 'replication_factor': 1 };

建立過程中請愛用TAB,cassandra有支援syntax complete的補齊功能,就像Linux下指令一樣。你會愛上他的。用describe KEYSPACE my_keyspace看一下剛建好的keyspace吧:

cqlsh:>use my_keyspace;
cqlsh:my_keyspace> CREATE TABLE user (first_name text, last_name text, primary key(first_name));

[Snippet.63] CURD in Cassandra
先來放筆資料進去,怎麼放?Insert into

cqlsh:my_keyspace> INSERT INTO user (first_name, last_name) VALUES ( 'Joe','Hsu');

有筆資料了,先來個RDB最愛的起手式select * from大法XD:

cqlsh:my_keyspace> SELECT * from user ;

 first_name | last_name
------------+-----------
        Joe |       Hsu

常見的Aggregation函式當然也沒問題啦:

cqlsh:my_keyspace> SELECT COUNT(*) FROM user ;

 count
-------
     1

Where過慮器一定要有的:

cqlsh:my_keyspace> SELECT * from user WHERE first_name='Joe';

 first_name | last_name
------------+-----------
        Joe |       Hsu

刪除某個欄位的值:

cqlsh:my_keyspace> DELETE  last_name  from user WHERE first_name='Joe';
cqlsh:my_keyspace> SELECT * from user WHERE first_name='Joe';

 first_name | last_name
------------+-----------
        Joe |      null

清掉整張表的內容:

cqlsh:my_keyspace> TRUNCATE user;
cqlsh:my_keyspace> SELECT * from user WHERE first_name='Joe';

 first_name | last_name
------------+-----------

刪除表:

cqlsh:my_keyspace> drop table user;
cqlsh:my_keyspace> desc TABLEs;

<empty>

接著我們將表跟值建回來以利後續使用,知道怎麼建了吧

接下來加個欄位:

cqlsh:my_keyspace> ALTER TABLE user ADD title text;
cqlsh:my_keyspace> SELECT * from user;

 first_name | last_name | title
------------+-----------+-------
        Joe |       Hsu |  null

多放幾筆資料進來:

cqlsh:my_keyspace> INSERT INTO user(first_name , last_name , title ) VALUES ( 'Chang', 'Wen-Ting', 'Mrs.');
cqlsh:my_keyspace> INSERT INTO user(first_name , last_name) VALUES ( 'Mary','Walejake');
cqlsh:my_keyspace> SELECT * from user;

 first_name | last_name | title
------------+-----------+-------
        Joe |       Hsu |  null
       Mary |  Walejake |  null
      Chang |  Wen-Ting |  Mrs.

此外,Cassandra這種每個值都有個Key與之對應的資料庫,每個欄位都有各別的寫入時間(這個時間其實會被拿來做很多事情,例如是節點間資料副本版本比對看哪份較新之類的):

cqlsh:my_keyspace> SELECT first_name, last_name, writetime(last_name) from user;

 first_name | last_name | writetime(last_name)
------------+-----------+----------------------
        Joe |       Hsu |     1484343504750490
       Mary |  Walejake |     1484343738096185
      Chang |  Wen-Ting |     1484343690394195

但Cassandra目前尚不允許對主鍵查TS....

cqlsh:my_keyspace> SELECT first_name, last_name, writetime(first_name) from user;
InvalidRequest: Error from server: code=2200 [Invalid query] message="Cannot use selection function writeTime on PRIMARY KEY part first_name"

我們可以對欄位設定TTL(Time-to-Live),讓他自動過時消失,而這在HouseKeeping時相當有用,不用自己手動維護程式或寫script來砍資料,該怎麼設定呢?

cqlsh:my_keyspace> SELECT first_name, last_name, TTL(last_name) from user where first_name = 'Joe' ;

 first_name | last_name | ttl(last_name)
------------+-----------+----------------
        Joe |       Hsu |           null

預設null代表沒有設定,用UPDATE設上去吧(單位是秒)

cqlsh:my_keyspace> UPDATE user USING TTL 3600 SET last_name= 'Hue' WHERE first_name = 'Joe' ;
cqlsh:my_keyspace> SELECT first_name, last_name, TTL(last_name) from user where first_name = 'Joe' ;

 first_name | last_name | ttl(last_name)
------------+-----------+----------------
        Joe |       Hue |           3597

設上去的一瞬間就開始倒數哩。

Cassandra Date Type

Cassandra主要有下列幾種不同的資料型別:

  • 數字類別:int、big/small/tiny/var + int、float/double、decimal
  • 文字類別: text、varchar、ascii
  • 時間類別: timestamp、date/time
  • 唯一值類別: uuid、timeuuid
  • 集合類別:set、map、list
  • 其他內建類別:boolean、blob、inet、counter
  • 使用者自定義類別(User-Defined Type):UDT

Cassandra裏面沒有固定大小的字串(RDB的char),絕大多數情境可以直接使用text(就例如Java的String一般)
我們會選一些比較特別的來玩看看,包含uuidsetlistmapUDT

先玩玩uuid,uuid全名是universally unique identifier,因為cassandra是一個分散式資料庫,他會用一些取名技巧保證此identifier即便是整座叢集中都是唯一的,那我們該如何設定uuid呢?Cassandra已經幫我們準備好函式了:

cqlsh:my_keyspace> ALTERT TABLE user ADD id uuid;
cqlsh:my_keyspace> UPDATE user SET id = uuid() WHERE first_name='Mary';
cqlsh:my_keyspace> SELECT first_name,id FROM user WHERE first_name='Mary';

 first_name | id
------------+--------------------------------------
       Mary | 6b8e8506-9416-48b9-baf7-1717bc5d37c8

透過uuid()函式設定值

再來看看set與list這類好用的集合型別:
先新增個email的set欄位並取值

cqlsh:my_keyspace> ALTER TABLE user ADD emails set<text>;
cqlsh:my_keyspace> UPDATE user SET emails ={'joe@example.com','joe2@mail.com'} WHERE first_name ='Joe';
cqlsh:my_keyspace> SELECT emails FROM user WHERE first_name='Joe' ;

 emails
--------------------------------------
 {'joe2@mail.com', 'joe@example.com'}

那LISTS勒:

cqlsh:my_keyspace> ALTER TABLE user ADD phone_numbers list<text>
cqlsh:my_keyspace> UPDATE user SET phone_numbers=['02-2782-1234'] where first_name ='Joe' ;
cqlsh:my_keyspace> SELECT phone_numbers FROM user WHERE first_name = 'Joe';

 phone_numbers
------------------
 ['02-2782-1234']

如果我想將一筆新的值加入集合該怎麼做?以List為例好了

cqlsh:my_keyspace> UPDATE user SET phone_numbers = phone_numbers + ['07-371-8888'] WHERE first_name ='Joe';
cqlsh:my_keyspace> SELECT phone_numbers FROM user WHERE first_name = 'Joe';

 phone_numbers
---------------------------------
 ['02-2782-1234', '07-371-8888']

可以用類似phone_numbers + ['07-371-8888']這種語法加進去,如果我想調整把新的值放到List的最前面可以嗎?當然可以阿,那就['07-371-8888'] + phone_numbers即可


[Snippet.64] Cassandra User Defined Type
建立自定義類別可以讓資料的表示方式更有彈性,就像物件導向一樣,把一個概念封裝成一個型別,那該怎麼建立勒?就像建個表一樣!

cqlsh:my_keyspace> CREATE TYPE address(
               ... street text,
               ... city text,
               ... state text,
               ... zip_code int);

有沒有發現,幾乎跟開表一樣,只是Type沒有主鍵而已。你說沒有主鍵沒啥阿~那是因為Cassandra中所有表都有primary key!!這與傳統DB有點不同。而Cassandra的primary key與RDB的作用有非常大非常大的不同,這部份就不多說了,基本上為Cassandra設計表時有很大的功夫是在設定一個符合查詢要求的primary key,這也是Query-Driven的由來。

有了UDT之後,把他放入之前的user表吧,但是一個人地址可能不一樣,那能不能把他指定為集合物件的型別勒?

cqlsh:my_keyspace> ALTER TABLE user ADD addresses map<text,address>;
InvalidRequest: Error from server: code=2200 [Invalid query] message="Non-frozen UDTs are not allowed inside collections: map<text, address>"

結果很不幸的...如果要直接定義不行XD。原因是集合物件還沒有完全支援自定義型別。所以社群就想了個方式,用freeze限制住UDT在集合內的一些能力,好讓他能先放入集合中。再未來的版本中,如果開發完成,則會有unfreeze這類的功能,來看看怎麼宣告吧:

cqlsh:my_keyspace> ALTER TABLE user ADD addresses map<text,frozen<address>>;

現在對user表執行DESCRIBE就會看到UDT型別:

cqlsh:my_keyspace> DESCRIBE TABLE user ;

CREATE TABLE my_keyspace.user (
    first_name text PRIMARY KEY,
    addresses map<text, frozen<address>>,
    emails set<text>,
    id uuid,
    last_name text,
    phone_numbers list<text>,
    title text
) 
//..以下資訊略

改完當然要放個值進去然後讀看看囉~

cqlsh:my_keyspace> UPDATE user SET addresses=
        ... addresses +{'home':{street:'7712 E. haha Road',
        ...city:'Taaa',state:'AW',zip_code:55678}} 
        ...WHERE first_name='Joe' ;

這樣可以仔細看看map與UDT寫值的方式!成功囉,看看結果(太寬用截圖的XD):
http://ithelp.ithome.com.tw/upload/images/20170114/201038391iYKlZp4VI.png

有沒有感覺少了塊啥?在RDB中很重要的??.....沒錯,就是Join!!。Cassandra與許多分散式NoSQL一樣。不提供Join操作。這時候Spark就可以來補足這塊啦:可以將資料從Cassandra讀出來,載入Spark執行一些Cassandra架構上不適合的操作!加上Cassandra持續改進Spark-connector的效能與支援度。最後就變成好朋友,雙雙成為SMACK成員啦。

基礎的Cassandra安裝與操作就到此,明天就看看怎麼透過Scala+Spark與Cassandra互動吧!


上一篇
[Spark-Day27](Spark 好友篇)SparkStreaming With Kafka
下一篇
[Spark-Day29](Spark好友篇) Cassandra with Spark長篇
系列文
Spark 2.0 in Scala30

尚未有邦友留言

立即登入留言