PHP+Thrift+HBaseを試してみた
使ってる人にとっては何周目かの今さら感漂いますが、ひょんなことから調べる必要が出てきたのでざっくりまとめてみました。
環境
- CentOS6.3
- PHP5.5.3
- JDK1.6.0-45
- HBase0.94.11
- Thrift0.9.1
HBaseとは
HBaseはKVS(Key-Value Store)の1つで、ASF(Apache Software Foundation)のHadoopプロジェクトの一環として作られたオープンソースソフトウェアです。本家はApache HBase – Apache HBase™ Homeです。
とりあえず、以下の記事をざっと読むと良いかと。
- いまさら聞けないKVSの常識をHbaseで身につける (1/3):ビッグデータ処理の常識をJavaで身につける(3) - @IT
- HBaseを触ってみよう (1/5):CodeZine(コードジン)
- HBaseとはどんなNoSQLデータベースなのか? 日本語で読める情報を集めてみた - Publickey
HBaseの論理データモデルは「多次元ソートマップ」で、テーブルやカラムといった概念がありますので、RDBMSを触ったことがある人は馴染みやすいかも。また、カラムをグルーピングした"カラムファミリー"という概念があります。カラムファミリーはテーブルに1つ以上存在し、カラムはいずれかのカラムファミリーに属します。
HBaseのインストール
"インストール"と言っても、ダウンロードして展開するだけです。
$ wget http://ftp.riken.jp/net/apache/hbase/stable/hbase-0.94.11.tar.gz
$ tar zxf hbase-0.94.11.tar.gz
$ cd hbase-0.94.11/
$
HBaseを起動・停止してみる
起動と停止は、直下のbinディレクトリにあるstart-hbase.shとstop-hbase.shを使います。環境変数JAVA_HOMEの設定を忘れずに。
$ export JAVA_HOME=/path/to/java_home $ bin/start-hbase.sh starting master, logging to /path/to/hbase-0.94.11/bin/../logs/hbase-hoge-master-mobylog64.out $ $ bin/stop-hbase.sh stopping hbase.................. $
HBase shell
HBaseにはJRuby製のshellが用意されていて、テーブルの作成やデータの登録などが可能です。helpを表示させると、大体どんなことができそうか分かると思います。
$ bin/hbase shell HBase Shell; enter 'help<RETURN>' for list of supported commands. Type "exit<RETURN>" to leave the HBase Shell Version 0.94.11, r1513697, Wed Aug 14 04:54:46 UTC 2013 hbase(main):001:0> help HBase Shell, version 0.94.11, r1513697, Wed Aug 14 04:54:46 UTC 2013 Type 'help "COMMAND"', (e.g. 'help "get"' -- the quotes are necessary) for help on a specific command. Commands are grouped. Type 'help "COMMAND_GROUP"', (e.g. 'help "general"') for help on a command group. COMMAND GROUPS: Group name: general Commands: status, version, whoami Group name: ddl Commands: alter, alter_async, alter_status, create, describe, disable, disable_all, drop, drop_all, enable, enable_all, exists, is_disabled, is_enabled, list, show_filters Group name: dml Commands: count, delete, deleteall, get, get_counter, incr, put, scan, truncate Group name: tools Commands: assign, balance_switch, balancer, close_region, compact, flush, hlog_roll, major_compact, move, split, unassign, zk_dump Group name: replication Commands: add_peer, disable_peer, enable_peer, list_peers, list_replicated_tables, remove_peer, start_replication, stop_replication Group name: snapshot Commands: clone_snapshot, delete_snapshot, list_snapshots, restore_snapshot, snapshot Group name: security Commands: grant, revoke, user_permission SHELL USAGE: Quote all names in HBase Shell such as table and column names. Commas delimit command parameters. Type <RETURN> after entering a command to run it. Dictionaries of configuration used in the creation and alteration of tables are Ruby Hashes. They look like this: {'key1' => 'value1', 'key2' => 'value2', ...} and are opened and closed with curley-braces. Key/values are delimited by the '=>' character combination. Usually keys are predefined constants such as NAME, VERSIONS, COMPRESSION, etc. Constants do not need to be quoted. Type 'Object.constants' to see a (messy) list of all constants in the environment. If you are using binary keys or values and need to enter them in the shell, use double-quote'd hexadecimal representation. For example: hbase> get 't1', "key\x03\x3f\xcd" hbase> get 't1', "key\003\023\011" hbase> put 't1', "test\xef\xff", 'f1:', "\x01\x33\x40" The HBase shell is the (J)Ruby IRB with the above HBase-specific commands added. For more on the HBase Shell, see http://hbase.apache.org/docs/current/book.html hbase(main):002:0> exit $
ここで、接続テストに使うためのテーブル"tbl"を作ってデータを突っ込んでおきます。定義は唯一のカラムファミリー"family"にカラム"column1"〜"column3"を所属させる感じ。
以下のコマンドで、putはSQLで言うところのINSERT文で、カラム単位にデータを登録します。scanはSELECT文に相当します。
$ bin/hbase shell HBase Shell; enter 'help<RETURN>' for list of supported commands. Type "exit<RETURN>" to leave the HBase Shell Version 0.94.11, r1513697, Wed Aug 14 04:54:46 UTC 2013 hbase(main):001:0> create "tbl", "family", {NAME => "column1"}, {NAME=>"column2"}, {NAME=>"column3"} 0 row(s) in 1.2370 seconds hbase(main):002:0> put 'tbl', 'test1', 'family:column1', 'value1' 0 row(s) in 3.1850 seconds hbase(main):003:0> put 'tbl', 'test1', 'family:column2', 'value2' 0 row(s) in 0.0200 seconds hbase(main):004:0> put 'tbl', 'test1', 'family:column3', 'value3' 0 row(s) in 0.0370 seconds hbase(main):011:0> put 'tbl', 'test2', 'family:column1', 'value1' 0 row(s) in 0.0500 seconds hbase(main):012:0> put 'tbl', 'test3', 'family:column1', 'value1' 0 row(s) in 0.0100 seconds hbase(main):005:0> scan 'tbl' ROW COLUMN+CELL test1 column=family:column1, timestamp=1377683232461, value=value1 test1 column=family:column2, timestamp=1377683293228, value=value2 test1 column=family:column3, timestamp=1377683298505, value=value3 test2 column=family:column1, timestamp=1377683807442, value=value1 test3 column=family:column1, timestamp=1377683810364, value=value1 3 row(s) in 0.0930 seconds hbase(main):006:0> exit $
RDBMSとは異なり、カラムごとに1行ずつ出力されます。
Thriftとは
ThriftはFacebookにて開発されたRPCフレームワークで、本家はApache Thrift - Homeです。また、PHPとHBaseを仲介するProxyサーバーとして起動することで、PHPとHBase間でデータをやりとりすることができます。
この時、.thriftファイルと呼ばれるファイルをThriftが持っているコード生成エンジンに食わせることで、PHPを含む様々な言語でのソースコード(クライアント、サーバー)を出力することができます。SOAPで言うwsdlファイルと生成されるProxyコードみたいな感じですかね。
Thriftのインストール
Thriftのインストールの前に事前準備。必要となるパッケージ郡をインストールします。
$ sudo yum install -y automake libtool flex bison pkgconfig gcc-c++ boost-devel libevent-devel zlib-devel python-devel ruby-devel
$
Thriftのアーカイブをダウンロードし、configure・make・make installすればOK。。。と言いたいところですが、Thriftにはthrift_protocolというPHP拡張モジュールが含まれていますが、buildに必要なファイルが含まれていないっぽいです。。。このため、1つ前のバージョンのThrift0.9.0から拝借してきます。
$ wget http://ftp.riken.jp/net/apache/thrift/0.9.0/thrift-0.9.0.tar.gz $ tar zxf thrift-0.9.0.tar.gz $ wget http://ftp.riken.jp/net/apache/thrift/0.9.1/thrift-0.9.1.tar.gz $ tar zxf thrift-0.9.1.tar.gz $ cp -rp thrift-0.9.0/lib/php/src/ext/thrift_protocol thrift-0.9.1/lib/php/src/ext/ $ tar zxf thrift-0.9.1.tar.gz # 再度展開 $ ./configure $ make $ sudo make install $
インストールすると、PHPからの接続に必要となるライブラリもPEARディレクトリにコピーされます。
なお、PHP拡張のインストール先などはRPM版PHPのものを基準としているようなので、ソースからインストールした場合でconfigure時に--prefixを指定したりデフォルトのままの場合、そのインストール先ディレクトリを環境変数PATHに含めておく必要があります。また、必要に応じてPHP_CONFIG_PREFIX()を指定してください。さらに、make install時にもPATHが通っている必要があります。
$ ./configure PHP_CONFIG_PREFIX=/usr/local/lib/php/lib/ $ make $ su # make install # exit $
Thriftを起動・停止してみる
Thriftをサーバーとして起動するためには、HBaseのbinディレクトリにあるhbaseコマンド、もしくは、hbase-daemon.shを使います。いずれも環境変数JAVA_HOMEの設定を忘れずに。
まずは、hbaseコマンドの場合の例。
$ export JAVA_HOME=/path/to/java_home $ cd ../hbase-0.94.11/ $ bin/hbase thrift start
なお、ログが標準エラー出力にそのまま出力されますので、適宜リダイレクトやteeコマンドを使いましょう。停止はCTRL-Cで。
次にhbase-daemon.shコマンドの場合の例。
$ export JAVA_HOME=/path/to/java_home $ cd ../hbase-0.94.11/ $ bin/hbase-daemon.sh start thrift starting thrift, logging to /path/to/hbase-0.94.11/bin/../logs/hbase-hoge-thrift-mobylog64.out $ $ bin/hbase-daemon.sh stop thrift stopping thrift. $
PHP+Thrift+HBase
まずは、HBaseの.thriftファイルからPHP用のコードを作成します。
$ cd ../ $ thrift --gen php hbase-0.94.11/src/main/resources/org/apache/hadoop/hbase/thrift/Hbase.thrift $
が作成されていることを確認します。ただし、namespaceがイケてないので、ここではコメントアウトしておきます。
$ perl -i -p -s -e "s#^namespace ;#//namespace ;#g" gen-php/*.php $
次に、gen-phpディレクトリにテスト用スクリプト(test.php)を作成します。
<?php function autoload($className) { $className = ltrim($className, '\\'); $fileName = ''; $namespace = ''; if($lastNsPos = strrpos($className, '\\')) { $namespace = substr($className, 0, $lastNsPos); $className = substr($className, $lastNsPos + 1); $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; } $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; require $fileName; } spl_autoload_register('autoload'); include 'Hbase.php'; include 'Types.php'; use \Thrift\Transport\TSocket; use \Thrift\Transport\TBufferedTransport; use \Thrift\Protocol\TBinaryProtocol; $server = 'localhost'; $port = 9090; try { $socket = new TSocket($server, $port); $socket->setRecvTimeout(5000); $transport = new TBufferedTransport($socket); $protocol = new TBinaryProtocol($transport); $client = new HbaseClient($protocol); $transport->open(); /** * 利用可能なテーブルの一覧を取得 */ var_dump($client->getTableNames()); /** * SQLで言うと"SELECT * FROM tbl LIMIT 2"に相当 */ $scan = new TScan(); $scan = $client->scannerOpenWithScan('tbl', $scan, null); var_dump($client->scannerGetList($scan, 2)); } catch (TException $e) { error_log('TException'); error_log($e); } catch (Exception $e) { error_log('Exception'); error_log($e); }
で、実行。データが無いカラムは出力されないことに注意です。
$ export JAVA_HOME=/path/to/java_home $ cd ../hbase-0.94.11/ $ bin/start-hbase.sh $ bin/hbase-daemon.sh start thrift $ php -v PHP 5.5.3 (cli) (built: Aug 23 2013 19:04:46) Copyright (c) 1997-2013 The PHP Group Zend Engine v2.5.0, Copyright (c) 1998-2013 Zend Technologies $ php test.php $ php test.php array(1) { [0]=> string(3) "tbl" } array(2) { [0]=> object(TRowResult)#7 (3) { ["row"]=> string(5) "test1" ["columns"]=> array(3) { ["family:column1"]=> object(TCell)#9 (2) { ["value"]=> string(6) "value1" ["timestamp"]=> int(1377683232461) } ["family:column2"]=> object(TCell)#10 (2) { ["value"]=> string(6) "value2" ["timestamp"]=> int(1377683293228) } ["family:column3"]=> object(TCell)#11 (2) { ["value"]=> string(6) "value3" ["timestamp"]=> int(1377683298505) } } ["sortedColumns"]=> NULL } [1]=> object(TRowResult)#8 (3) { ["row"]=> string(5) "test2" ["columns"]=> array(1) { ["family:column1"]=> object(TCell)#13 (2) { ["value"]=> string(6) "value1" ["timestamp"]=> int(1377683807442) } } ["sortedColumns"]=> NULL } } $
SQLで言うところのWHERE句
"フィルター"と呼ばれるものがそれに相当します。色々なフィルターが用意されていますので、詳細は以下のURLを読んでみてください。
フィルターはPHP側でも利用可能で、以下はcolumn1の値を正規表現マッチしたカラムを取得する例です。PostgreSQLだと
SELECT column1 FROM tbl WHERE column1 ~ 'e[13]$'
な感じですかね?
<?php : $scan = new TScan(); $scan->columns = array('family:column1'); $scan->filterString = "ValueFilter(=,'regexstring:e[13]$')"; $scan = $client->scannerOpenWithScan($tablename, $scan, null); :
その他メソッドについて
thriftコマンドで生成されたHbase.phpの先頭で定義されているHbaseIfインターフェースを眺めてもいいんですが、大元のHbase.thriftファイルを見たほうが型定義がハッキリするので、こちらの方がオススメです。