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[]で扱っている。