こんにちは、t_endoです。
今回はCassandraクラスタをAmazon EC2上に作成したいと思います。
以前何か良さそうなKVSがないものか探していたところ、
「Cassandraは、非常に高いスケーラビリティーを持ち、イベンチュアルコンシステントな分散システム構造のKVS(Key Value Store)です。 」
※Cassandra Wikiより抜粋
との事で
・PHPのクライアントがあること
・key:valueを1対多で取得できること
・クラスタ構成が組めること
こんな感じの条件で探していたので選んでみました。
各ソフトウェアのバージョンは
Amazon Linux : 2014.03
Cassandra : 2.0.6
phpcassa : 1.1.0
PHP : 5.3.0
です。
Cassandraインストール
インストールと言っても、公式ページからダウンロードしてきて、解凍してあげるだけでインストール完了です。
Cassandra公式ページ
Cassandra設定
解凍したディレクトリに
「conf/cassandra.yaml」
という設定ファイルがあるので、編集していきます。
変更前 cluster_name: 'Test Cluster' ↓ 変更後 cluster_name: 'Blog Cluster'
所属するクラスタ名を指定します
変更前 seed_provider: # Addresses of hosts that are deemed contact points. # Cassandra nodes use this list of hosts to find each other and learn # the topology of the ring. You must change this if you are running # multiple nodes! - class_name: org.apache.cassandra.locator.SimpleSeedProvider parameters: # seeds is actually a comma-delimited list of addresses. # Ex: "<ip1>,<ip2>,<ip3>" - seeds: "127.0.0.1" ↓ 変更後(seedノードの場合) seed_provider: # Addresses of hosts that are deemed contact points. # Cassandra nodes use this list of hosts to find each other and learn # the topology of the ring. You must change this if you are running # multiple nodes! - class_name: org.apache.cassandra.locator.SimpleSeedProvider parameters: # seeds is actually a comma-delimited list of addresses. # Ex: "<ip1>,<ip2>,<ip3>" - seeds: "10.0.250.151"
seedノードのIPアドレスを指定します。
seedノードとして設定する場合は自分のIPアドレスを指定します。
※seedノードとは
各ノードはseedノードから他のノードの状態を取得したりします。
Cassandraクラスタ内のハブみたいなものになります。
Cassandra Wiki参照
変更前 listen_address: localhost ↓ 変更後 listen_address: 10.0.250.151
自分のローカルIPアドレスを指定します。
変更前 endpoint_snitch: SimpleSnitch ↓ 変更後 endpoint_snitch: Ec2Snitch
Cassandraは各ノードがどこのデータセンター、ラックにあるかというのを設定するのですがその設定の仕方を指定します。
今回はEC2上の「ap-northeast-1」リージョンのみに構成するので、「Ec2Snitch」を指定しました。
今回設定した項目は以上になります。
その他詳しい設定等はDATASTAX Documentを参照してください。
DATASTAX Document
Cassandra起動~column familyを作成
・Cassandraの起動は「bin/cassandra」で起動する事ができます。
※「-p ファイル名」を付けるとプロセスIDを出力する事ができます。
sudo /opt/cassandra/bin/cassandra -p /var/run/cassandra.pid INFO 19:06:33,103 Logging initialized INFO 19:06:33,232 Loading settings from file:/opt/apache-cassandra-2.0.6/conf/cassandra.yaml |大量のログが流れていきます INFO 19:11:11,030 No gossip backlog; proceeding
・Cassandraの停止はプロセスをkillする事で停止できます。
sudo kill `cat /var/run/cassandra.pid` sudo rm -f /var/run/cassandra.pid
・全てのノードを起動したら「bin/nodetool status」を実行してみましょう。
※コマンドを実行するのは、ノードが起動している所ならどこでも大丈夫です。
/opt/cassandra/bin/nodetool status Datacenter: ap-northeast ======================== Status=Up/Down |/ State=Normal/Leaving/Joining/Moving -- Address Load Tokens Owns Host ID Rack UN 10.0.250.151 7.16 GB 256 24.9% xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 1a UN 10.0.251.152 6.5 GB 256 25.2% xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 1c UN 10.0.250.152 6.3 GB 256 23.7% xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 1a UN 10.0.251.151 6.74 GB 256 26.2% xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 1c
ちゃんと全てのノードが起動して同じクラスタに所属している事が確認できました。
※データセンターやラックもちゃんと自動で判別してくれています
・無事起動できたので早速Cassandraへ接続して遊んでみます。
接続のコマンドは「bin/cassandra-cli」です。
/opt/cassandra/bin/cassandra-cli Connected to: "Blog Cluster" on 127.0.0.1/9160 Welcome to Cassandra CLI version 2.0.6 The CLI is deprecated and will be removed in Cassandra 3.0. Consider migrating to cqlsh. CQL is fully backwards compatible with Thrift data; see http://www.datastax.com/dev/blog/thrift-to-cql3 Type 'help;' or '?' for help. Type 'quit;' or 'exit;' to quit. [default@unknown]
・まずkeyspaceを作成します。
※RDBのdbと同じようなものです
create keyspace blog_keyspace with placement_strategy='org.apache.cassandra.locator.NetworkTopologyStrategy' and strategy_options={ap-northeast:2};
blog_keyspace:keyspace名です。好きな名前を入力してください。
placement_strategy:レプリカの保存方法を指定します。(データセンターは1つですが、ラックがわかれているのでNetworkTopologyStrategyを指定しました)
strategy_options:レプリカの保存先と個数を指定します。(ap-northeastのデータセンターへ2個作成するようにしました)
※placement_strategyの保存方法
SimpleStrategy:単一のデータセンターで、ラックを考慮せずに保存します
NetworkTopologyStrategy:複数のデータセンターで、ラックを分けて保存します
・keyspaceが作成できたので、そのkeyspaceを使います
use blog_keyspace;
・column familyを作成します。
※RDBのtableと同じような物です
create column family blog_column_family with comparator='UTF8Type' and key_validation_class='UTF8Type' and default_validation_class='UTF8Type';
blog_column_family:column family名です。好きな名前を入力してください。
以下3つは文字列を入れようとしたらエラーがでてしまったので、UTF8Typeを指定しました。
comparator、key_validation_class、default_validation_class
※デフォルトだと16進数でしか入れられないみたいです。
set blog_column_family['test1']['test2'] = 'test3'; org.apache.cassandra.serializers.MarshalException: cannot parse 'test2' as hex bytes set blog_column_family['01']['02'] = '03'; Value inserted. Elapsed time: 2.05 msec(s).
基本コマンド
・キーにデータセット
set COLUMN_FAMILY_NAME['キー名']['カラム名'] = '値';
例:
[default@blog_keyspace] set blog_column_family ['1']['service'] = 'nend'; Value inserted. Elapsed time: 134 msec(s). [default@blog_keyspace] set blog_column_family ['2']['service'] = 'nex8'; Value inserted. Elapsed time: 2.32 msec(s).
・キー名のデータ取得
get COLUMN_FAMILY_NAME['キー名'];
例:
[default@blog_keyspace] get blog_column_family ['2'];
=> (name=service, value=nex8, timestamp=1425023634514000)
Returned 1 results.
Elapsed time: 57 msec(s).
・100件データ表示
list COLUMN_FAMILY_NAME;
例:
[default@blog_keyspace] list blog_column_family ; Using default limit of 100 Using default cell limit of 100 ------------------- RowKey: 2 => (name=service, value=nex8, timestamp=1425023775107000) ------------------- RowKey: 1 => (name=service, value=nend, timestamp=1425023768572000) 2 Rows Returned. Elapsed time: 1686 msec(s).
・キーのvalue数取得
count COLUMN_FAMILY_NAME ['キー名'];
例:
[default@blog_keyspace] count blog_column_family ['1']; 1 cells
・データ削除
del COLUMN_FAMILY_NAME ['キー名'];
例:
[default@blog_keyspace] del blog_column_family ['1'];
row removed.
Elapsed time: 1.4 msec(s).
[default@blog_keyspace] list blog_column_family ;
Using default limit of 100
Using default cell limit of 100
-------------------
RowKey: 2
=> (name=service, value=nex8, timestamp=1425023775107000)
-------------------
RowKey: 1
2 Rows Returned.
Elapsed time: 1547 msec(s).
※キーは残ります
・全データ削除
truncate COLUMN_FAMILY_NAME;
例:
[default@blog_keyspace] truncate blog_column_family ; blog_column_family truncated. [default@blog_keyspace] list blog_column_family ; Using default limit of 100 Using default cell limit of 100 0 Row Returned. Elapsed time: 1683 msec(s).
※コマンドの最後は必ず「;」を付けます。
付けないと
[default@blog_keyspace] set blog_column_family ['test']['test'] = 1 ...
こんな感じで改行しただけになります。
もちろんこの後「;」を打ってあげれば進むのですが、
初めてさわった時、処理中かと思って10分ぐらい待っていました…
PHPから操作
phpcassaを使用してPHPからCassandraの操作をします。
GitHubからダウンロードしてきて、lib以下のファイルをコピーしてあげれば動きます。
phpcassa
・Cの拡張モジュール追加
phpcassaの作成者がパフォーマンスアップの為にCの拡張モジュールを用意してくれています。
phpcassaをダウンロードしてきたページの「Using the C Extension」に書いてある通りに追加していきます。
解凍したディレクトリへ移動後下記コマンド実行
cd ext/thrift_protocol phpize ./configure make sudo make install
php.iniへ下記項目追加
extension=thrift_protocol.so
apacheの再起動でCの拡張モジュールを追加する事ができます。
※Cの拡張モジュールは入れなくても動作します
※追加して頂いたインフラチームの検証では、50万回の値取得で3倍近く早くなったみたいですので、どうしても入れられない場合を除いて入れておいた方がいいと思います
サンプルプログラム
<?php // ライブラリ読み込み require_once ('./phpcassa/lib/autoload.php'); use phpcassa\ColumnFamily; use cassandra\ConsistencyLevel; use phpcassa\Connection\ConnectionPool; use phpcassa\SystemManager; use phpcassa\Schema\StrategyClass; define("TTL", (60*60)); /** * keyspace接続 * @param string keyspace名 * @param array ホスト名を配列で渡します * @return object keyspace接続オブジェクト */ $connection_pool = new ConnectionPool('blog_keyspace', array( // ip :port '10.0.250.151:9160' , '10.0.250.152:9160' , '10.0.251.151:9160' , '10.0.251.152:9160' )); /** * column family接続 * @param object keyspace接続オブジェクト * @param string column family名 * @return object column family接続オブジェクト */ $column_family = new ColumnFamily($connection_pool, 'blog_column_family'); /** * データ登録(既に存在する場合は更新) * @param string キー名 * @param array array(カラム名 => 値) * @param int timestamp * @param int 有効期間(キーは残ります) */ $column_family->insert('1', array(10 => date("Y/m/d")), null, TTL); /** * データ取得 * 取得するキーがないと例外が発生してしまうので、try-catchした方がいいです * @param string キー名 * @return array array(カラム名 => 値) */ $column_family->get('1'); /** * データ件数取得 * @param string キー名 * @return int 件数 */ $column_family->get_count('1'); // 一括データ作成 for ($i = 0; $i < 10; $i++) { $key = sprintf("%d", $i); $data[$key] = array('rand' => rand(), 'access_data' => date("Y/m/d H:i:s")); } /** * 一括データ登録 or 更新 * @param array array(キー名 => array(カラム名 => 値)) * @param int timestamp * @param int 有効期間(キーは残ります) */ $column_family->batch_insert($data, null, TTL); /** * データ削除(キーは残ります) * @param string キー名 */ $column_family->remove('1'); /** * 全データ削除 */ $column_family->truncate(); ?>
終わりに
以上でEC2上にCassandraクラスタを作成しPHPから操作をする事ができました。
まだ作成しただけで運用していくとなると課題は沢山ありますが、作成する上で参考になれば幸いです。