HBaseは、GoogleのBigTableを元にしたオープンソースの列指向分散データベース。(リレーショナルデータベース(RDB)ではない)
Javaで作られており、Apache HadoopのHDFSを使用して稼動する。その為、Hadoopとの親和性が高いと思われる。
|
|
|
|
Windowsへのインストール方法は、TakaoさんのCygwinを利用してWindowsにHBaseをインストールに詳しく書かれています。
HBaseはUNIXを想定しているようなので、Windowsで試す為にはCygwinが必要。
sshが必要なので、CygwinのOpenSSHもダウンロードして設定しておく。
HBaseはJavaで動くので、JDK1.6もインストールしておく。
※単独環境ではHadoopのインストールは不要(hbase-0.20.3のアーカイブの中にhadoopも含まれている)。
作業内容 | 設定内容・実施コマンド | 備考 | |
---|---|---|---|
1 | HBASE_HOME\conf\hbase-env.sh を編集する。 |
export JAVA_HOME="C:/Program Files/Java/jdk1.6.0_13"
|
環境変数JAVA_HOMEの設定。 |
export HBASE_IDENT_STRING=$HOSTNAME
|
|||
2 | HBASE_HOME\conf\hbase-site.xml を編集する。 (hbase-default.xmlから必要なプロパティーをコピーして、 値を書き換える。[2010-08-07]) |
<property> <name>hbase.rootdir</name> <value>file:///C:/cygwin/tmp/hbase/data</value> </property> |
Windows単独環境用の設定。 HBase0.89では単独環境でZooKeeperが使われなくなったので、 hbase.zookeeper.quorumは設定不要。[2010-07-06] |
<property> <name>hbase.tmp.dir</name> <value>C:/cygwin/tmp/hbase/tmp</value> </property> |
|||
<property> <name>hbase.zookeeper.quorum</name> <value>127.0.0.1</value> </property> |
|||
3 | 上記で指定したディレクトリーを作っておく。 |
mkdir -p /tmp/hbase/data mkdir -p /tmp/hbase/tmp chmod 777 /tmp/hbase/data chmod 777 /tmp/hbase/tmp |
Cygwin(bash)から実行。 |
作業内容 | 実施コマンド | 備考 | |
---|---|---|---|
1 | HBaseを起動する。 |
/usr/local/hbase-0.20.3/bin/start-hbase.sh cygpath: cannot create short name of
\\?\C:\cygwin\usr\local\hbase-0.20.3\logs |
初回起動時は色々警告が出るが、 とりあえず大丈夫なようだ。 (最初はlogsディレクトリーが無いので cygpathがエラーを出すが 次回以降は作られているので問題ない) (最初はRSAの鍵が無いので、作成する) 起動にはちょっと時間がかかる。 |
2 | HBase Shellを実行する。 |
/usr/local/hbase-0.20.3/bin/hbase shell HBase Shell; enter 'help<RETURN>' for list of supported commands. |
HBaseの対話型ツール。 「help」と入力してEnterキーを押すと ヘルプが表示される。 |
→HBase Shellで色々入力してみるサンプル | |||
3 | HBase Shellを終了する。 |
exit |
|
4 | HBaseを停止する。 |
/usr/local/hbase-0.20.3/bin/stop-hbase.sh
|
停止もちょっと時間がかかる。 (「 . 」の個数は毎回違う) |
HBaseの起動時にsshサービス(sshd)が動いていないとエラーになる。
port 22: Connection refused.0.1
starting master, logging to
/usr/local/hbase-0.20.3/bin/../logs/hbase-host-master-host.out
localhost: ssh: connect to host localhost port 22: Connection
refused
HBase Shellでテーブルを作って表示してみる例。(HBase0.20)
太字が入力したコマンド。
$ /usr/local/hbase-0.20.3/bin/hbase shell ←HBase Shellの起動 HBase Shell; enter 'help<RETURN>' for list of supported commands. Version: 0.20.3, r902334, Mon Jan 25 13:13:08 PST 2010
hbase(main):001:0>
create 'test','data' ←testという名前のテーブルを作成
0 row(s) in 2.5940 seconds
hbase(main):002:0>
list ←テーブル一覧を表示
test
1 row(s) in 0.0310 seconds
hbase(main):003:0>
describe 'test' ←テーブル定義を表示
DESCRIPTION ENABLED
{NAME => 'test', FAMILIES => [{NAME => 'data', COMPRESSION => 'NONE', true
VERSIONS => '3', TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY
=> 'false', BLOCKCACHE => 'true'}]}
1 row(s) in 0.0310 seconds
hbase(main):004:0>
put 'test','row1','data:1','value1' ←テーブルにデータを入れてみる 0 row(s) in 0.0160 secondshbase(main):005:0>
put 'test','row2','data:1','value2' 0 row(s) in 0.0160 seconds
hbase(main):006:0>
scan 'test' ←テーブルのデータを表示してみる
ROW COLUMN+CELL
row1 column=data:1, timestamp=1266505167015, value=value1
row2 column=data:1, timestamp=1266505217843, value=value2
1 row(s) in 0.0310 seconds
hbase(main):007:0>
disable 'test' ←テーブルを使用不可にする(削除する為には事前に使用不可にする必要がある) 0 row(s) in 2.0620 secondshbase(main):008:0>
drop 'test' ←テーブルを削除する 0 row(s) in 0.0160 seconds 0 row(s) in 0.0000 seconds 0 row(s) in 0.0470 seconds
hbase(main):009:0>
exit ←HBase Shellの終了
$
HBaseもテーブルを作ってデータを入れるのだが、RDBとはテーブルの構造が異なる。
(これについては豊月さんのSQLとHbaseの操作比較が分かりやすいです。
これだけあれば他の説明は要らない!くらいの感じですが、勉強も兼ねて自分でもメモしておきます。)
まず、テーブル名があるのはRDBもHBaseも同じ。
(RDBでは基本的にテーブル名は大文字小文字の区別が無いが、HBaseでは区別される)
RDBでいう「項目(カラム・列)」は、HBaseでは列ファミリー(ColumnFamily、family)と言う。
で、RDBのデータ型に当たるものは、列ファミリーではマップしか無い。
マップにはキーと値が必要なわけだが、このキーをqualifier(column qualifierあるいはlabel)、値はセル(Cell)と呼ぶ。
セルの内容は値そのものと思っていいと思うが、履歴管理される。(普通にgetすれば最新の値が取れる)
(HBaseでは、familyとqualifierをコロン「:
」で結んだものをcolumnと呼ぶ)
また、行(レコード・データ)を識別する為に、暗黙に「ROW」というプライマリキー相当の項目が存在する。
以上を踏まえて例示的にRDBのSQLとHBaseを対比させてみると、以下のような感じになると思う。
(テーブル名や項目名・データは豊月さんのHbase解説用Data図を真似てみた)
(RDBのデータ型にマップは無いので、その辺りはJava(や他の言語)風に表現してみた)
RDB(SQL) | HBase | 備考 | |
---|---|---|---|
テーブル作成 |
create table TEST_TYPE ( ROW String primary key, ColumnFamilyA Map<String, Cell>, ColumnFamilyB Map<String, Cell>, ColumnFamilyC Map<String, Cell>, ColumnFamilyD Map<String, Cell> ); |
create 'TEST_TYPE', 'ColumnFamilyA', 'ColumnFamilyB', 'ColumnFamilyC', 'ColumnFamilyD' |
|
データ挿入 |
insert into TEST_TYPE ( ROW, ColumnFamilyA, ColumnFamilyB, ColumnFamilyC, ColumnFamilyD ) values ( 'KEY00001', { null => 'k1_cfA0', 'Column1' => 'k1_cfA_c1', 'Column2' => 'k1_cfA_c2', 'Column3' => 'k1_cfA_c3' }, { null => 'k1_cfB0', 〜 'Column3' => 'k1_cfC_c3' } ); |
put 'TEST_TYPE','KEY00001','ColumnFamilyA','k1_cfA0' put 'TEST_TYPE','KEY00001','ColumnFamilyA:Column1','k1_cfA_c1' put 'TEST_TYPE','KEY00001','ColumnFamilyA:Column2','k1_cfA_c2' put 'TEST_TYPE','KEY00001','ColumnFamilyA:Column3','k1_cfA_c3' put 'TEST_TYPE','KEY00001','ColumnFamilyB','k1_cfB0' put 'TEST_TYPE','KEY00001','ColumnFamilyB:Column1','k1_cfB_c1' put 'TEST_TYPE','KEY00001','ColumnFamilyB:Column2','k1_cfB_c2' put 'TEST_TYPE','KEY00001','ColumnFamilyB:Column3','k1_cfB_c3' put 'TEST_TYPE','KEY00001','ColumnFamilyC','k1_cfC0' put 'TEST_TYPE','KEY00001','ColumnFamilyC:Column1','k1_cfC_c1' put 'TEST_TYPE','KEY00001','ColumnFamilyC:Column2','k1_cfC_c2' put 'TEST_TYPE','KEY00001','ColumnFamilyC:Column3','k1_cfC_c3' |
columnはマップのようなものなので、 全てを入れる必要は無い。 (左の例では全部入れているが) 入れなかったデータは 領域も確保されないので 効率が良い。 |
データ更新 |
update TEST_TYPE set ColumnFamilyB.Column1 = 'B1' where ROW = 'KEY00001' ; |
put 'TEST_TYPE','KEY00001','ColumnFamilyB:Column1','B1' |
|
データ削除 |
update TEST_TYPE set ColumnFamilyC.Column2 = null where ROW = 'KEY00001' ; |
deleteall 'TEST_TYPE','KEY00001','ColumnFamilyC:Column2' |
columnはマップのようなものなので、 nullを入れるというよりは キーを削除するイメージ。 |
レコード削除 |
delete from TEST_TYPE where ROW = 'KEY00003' ; |
deleteall 'TEST_TYPE','KEY00003' |
[2010-03-06] |
ROW | 'ColumnFamilyA' | 'ColumnFamilyB' | 'ColumnFamilyC' | 'ColumnFamilyD' | |||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
レコード1 | KEY00001 |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||
レコード2 | KEY00002 |
|
|
|
|
ついでにJavaのクラスで表すとどのようになるか考えてみた。
(HBaseで提供されているAPIではなく、HBaseのテーブルの概念をクラスで表現してみたもの)
public final class TEST_TYPE { /* コンストラクター */ private TEST_TYPE() { } private static final TEST_TYPE INSTANCE = new TEST_TYPE(); // 唯一のインスタンスを返す public static TEST_TYPE getInstance() { return INSTANCE; } /** TEST_TYPEの項目名の定義 */ public static enum ColumnFamily { ColumnFamilyA, ColumnFamilyB, ColumnFamilyC, ColumnFamilyD } /** 一行分のデータを保持するクラス */ public static class Record extends EnumMap<ColumnFamily, Map<String, Cell>> { //コンストラクター private Record() { super(ColumnFamily.class); } } /** キー(ROW)毎の行を保持する */ private Map<String, Record> recMap = new TreeMap<String, Record>(); // データをセットする public void put(String row, ColumnFamily family, String qualifier, String value) { Record record = recMap.get(row); if (record == null) { record = new Record(); recMap.put(row, record); } Map<String, Cell> colMap = record.get(family); if (colMap == null) { colMap = new TreeMap<String, Cell>(); record.put(family, colMap); } Cell cell = colMap.get(qualifier); if (cell == null) { cell = new Cell(); colMap.put(qualifier, cell); } cell.setValue(value); } // データを取得する public String get(String row, ColumnFamily family, String qualifier) { Record record = recMap.get(row); if (record == null) { return null; } Map<String, Cell> colMap = record.get(family); if (colMap == null) { return null; } Cell cell = colMap.get(qualifier); if (cell == null) { return null; } return cell.getValue(); } // データを削除する public void delete(String row, ColumnFamily family, String qualifier) { Record record = recMap.get(row); if (record == null) { return; } Map<String, Cell> colMap = record.get(family); if (colMap == null) { return; } colMap.remove(qualifier); } // 全データをキー順に返す public Iterable<Entry<String, Record>> scan() { return recMap.entrySet(); } }
public final class Cell { private List<Value> history = new ArrayList<Value>(); // 値をセットする public void setValue(String value) { Value data = new Value(value, System.currentTimeMillis()); history.add(data); } // 最新の値を取得する public String getValue() { int last = history.size() - 1; Value data = history.get(last); return data.getValue(); } @Override public String toString() { return getValue(); } }
public final class Value { private String value; private long timestamp; public Value(String value, long timestamp) { this.value = value; this.timestamp = timestamp; } public String getValue() { return value; } public long getTimestamp() { return timestamp; } }
public static void main(String[] args) { TEST_TYPE table = TEST_TYPE.getInstance(); table.put("KEY00001", ColumnFamilyA, null, "k1_cfA0" ); table.put("KEY00001", ColumnFamilyA, "Column1", "k1_cfA_c1"); table.put("KEY00002", ColumnFamilyA, null, "k2_cfA0" ); System.out.println(table.get("KEY00001", ColumnFamilyA, "Column1")); }
うーん、Map使いまくり(苦笑)(HBaseはキー順にソートされるらしいので、TreeMapを使ってみた)
こうして見ると、ROW以外を条件としてデータを取得するのは面倒だというのがよく分かる。
(列指向DBは項目の値を条件とした検索が苦手らしい。複数項目にまたがった条件指定の方法は提供されていない)
HBaseへアクセスする為の実際のJavaのコーディングを見ると、テーブル名や列名や値は、文字列String
ではなく全てバイト列byte[]
で扱っている。