[github]Github Pagesを作りました
Octopressとgithub Pagesを使ったブログ - 0xff.toBlog()
この記事を読んで、Github Pagesでblogを書いてみたくなり、作ってみました。
http://masayuki038.github.com/
以前からOctopressが気になっていたので、ちょうど良いタイミングでした。
ということで、Github Pagesの方に引っ越します。
mvn assembly:assemblyとjava.lang.IncompatibleClassChangeError
mvn assembly:assemblyで作成したファイルをjava -jarで実行すると以下の例外が出る、という事象に悩まされてました。
Exception in thread "main" java.lang.IncompatibleClassChangeError: Implementing class at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(Unknown Source) at java.security.SecureClassLoader.defineClass(Unknown Source) at java.net.URLClassLoader.defineClass(Unknown Source) at java.net.URLClassLoader.access$000(Unknown Source) at java.net.URLClassLoader$1.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClassInternal(Unknown Source) at net.wrap_trap.utils.EntityService.<init>(EntityService.java:47) at net.wrap_trap.utils.BsonStore.initialize(BsonStore.java:276) at net.wrap_trap.utils.BsonStore.<init>(BsonStore.java:33) at net.wrap_trap.utils.FileStoredMap.<init>(FileStoredMap.java:26) at net.wrap_trap.collections.fsm.bench.Benchmark1.main(Benchmark1.java:9 )
IncompatibleClassChangeError?これまで遭遇したことがない例外だったので調べてみました。
IncompatibleClassChangeError (Java Platform SE 6)
Thrown when an incompatible class change has occurred to some class definition. The definition of some class, on which the currently executing method depends, has since changed.
クラス定義ファイル間の不整合が発生しているようですが、そんな変なクラスの変更したかな…。一旦cleanしてからリトライしましたが、事象が変わらず。
コンパイルからちょっと離れて、クラス間の定義で不整合が出るという点に着目してみたところ、2つ思い当たる事がありました。今回のモジュールはorg.mongodb.bsonに直接依存しており、mongo-java-driverに間接依存していました。mongo-java-driverのjarファイルの中にorg.bsonのクラスファイルが含まれており、それが直接依存しているorg.mongodb.bsonのクラスファイルとバージョンが合わない為、この例外が出ているのではないかと。
そしてもう1つ、mvn assembly:assemblyする際に、依存モジュールをunpackするようにしていました。
<assembly> <id>distribution</id> <formats> <format>zip</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <dependencySets> <dependencySet> <unpack>true</unpack> <scope>runtime</scope> <outputDirectory>/</outputDirectory> </dependencySet> </dependencySets> </assembly>
unpackを指定すると、assembly時に依存モジュールのjarを展開してクラスファイルとしてモジュールに取り込みます。
今回の問題は、2つのモジュールにバージョンの異なるorg.bsonのクラスファイルがそれぞれ入っていており、展開される際にどちらかが上書きされてしまう為、不整合が起きていました。
依存モジュールでmongo-java-driverを使っているので、org.bsonを直接参照するのをやめ、代わりにmongo-java-driverを参照するようにしたところ、この例外は起こらなくなりました。dependencySetのunpackをtrueにする場合、この点に気をつける必要があります。
JDK6をソースコードからビルド
jvmのgcまわりの挙動を確認する為、CentOS上でJDK6のソースコードを取得してビルドしてみました。基本的な流れは以下のページを参考にしています。
「Compile Hotspot JDK from Source Code」
http://konpairu.net/blog/archives/139
ビルドするのに色々躓きましたが、その度ググって何とかなりました。躓いた箇所と対応を、備忘録として記載しておきます。
依存パッケージのインストール
# yum install alsa-lib-devel -y # yum install cups-devel -y
Missing ./../src/share/lib/fonts/LucidaTypewriterRegular.ttf
ERROR: Missing ./../src/share/lib/fonts/LucidaTypewriterRegular.ttf. Verify you have downloaded and overlayed on the source area all the binary files.
フォントのコピーが必要。以下のページを参考にしました。
「Java SE 6(Mustang)をソースからビルドする」
http://d.hatena.ne.jp/torutk/20050825/1124985836
# cp -pir /opt/jdk1.6.0_16/jre/lib/fonts/* ../../j2se/src/share/lib/fonts/
No rule to make target `/usr/X11R6/lib/X11/config/Imake.tmpl'
make[3]: *** No rule to make target `/usr/X11R6/lib/X11/config/Imake.tmpl', needed by `xmkmf'. Stop.
X11のフォルダ構成の問題らしい。別の所にあるファイルだったのでsymlinkを貼りました。
# cd /usr/X11R6/lib/ # ls -l X11 (何も無かった) # rm -r X11 # ln -s /usr/share/X11 X11
error: static declaration of 'sigignore' follows non-static declaration
../../../../src/solaris/hpi/native_threads/src/interrupt_md.c:115: error: static declaration of 'sigignore' follows non-static declaration
/usr/include/signal.h:377: error: previous declaration of 'sigignore' was here
ヘッダではnon-staticで宣言されているものの、実装ではstaticになっていることが原因。
以下のページを参考にしました。
「jdk1.6のコンパイル」
http://www.melange.co.jp/blog/?p=779
# pushd ../../j2se/src/solaris/hpi/native_threads/src/ # cp -pi interrupt_md.c interrupt_md.org # vi interrupt_md.c # diff -u interrupt_md.org interrupt_md.c --- interrupt_md.org 2012-02-27 18:26:56.000000000 +0900 +++ interrupt_md.c 2012-03-03 20:09:06.000000000 +0900 @@ -110,7 +110,7 @@ } #ifndef HAVE_SIGIGNORE -static int +int sigignore(int sig) { struct sigaction action; # popd
No rule to make target `/usr/src/jvms/jdk16u16/control/build/linux-amd64/classes/com/sun/java/swing/plaf/windows/icons/Computer.gif'
make[5]: *** No rule to make target `/usr/src/jvms/jdk16u16/control/build/linux-amd64/classes/com/sun/java/swing/plaf/windows/icons/Computer.gif', needed by `other_files'. Stop.
Computer.gif等の画像ファイルをコピーして取り込もうとしているようです。この画像を探してみたのですが、bootstrap jdkに含まれてなかった為、コピーするタスク自体を消しました。
# pushd ../../j2se/make/javax/swing/plaf # cp -pi FILES.gmk FILES.org # vi FILES.gmk # popd
No rule to make target `/usr/src/jvms/jdk16u16/control/build/linux-amd64/lib/audio/soundbank.gm'
make[4]: *** No rule to make target `/usr/src/jvms/jdk16u16/control/build/linux-amd64/lib/audio/soundbank.gm', needed by `copy-files'. Stop.
soundbank.gmはbootstrap jdkのjre/libの下にあったので、それをコピーして先に進めました。
# cp -pi /home/masayuki/install/soundbank.gm ../build/linux-amd64/lib/audio/
(諸事情によりコピー元のパスがjre/libではありませんが)
../../../src/solaris/native/sun/awt/img_util_md.h:14: error: expected specifier-qualifier-list before 'XID'
../../../src/solaris/native/sun/awt/img_util_md.h:14: error: expected specifier-qualifier-list before 'XID'
以下のページを参考にして、libXt-develをインストール。
「Building OpenJDK on Ubuntu」
http://d.hatena.ne.jp/dolduke/20080420
yum install libXt-devel -y
/usr/bin/ld: cannot find -lXext
/usr/bin/ld: cannot find -lXext
libXext-develをインストール。
# yum install libXext-devel -y
/usr/bin/ld: cannot find -lXtst
/usr/bin/ld: cannot find -lXtst
libXtst-develをインストール。
# yum install libXtst-devel -y
../../../src/solaris/native/sun/awt/awt_dnd.c:172: error: static declaration of 'xerror_code' follows non-static declaration
../../../src/solaris/native/sun/awt/awt_dnd.c:172: error: static declaration of 'xerror_code' follows non-static declaration
awt_dnd.cのxerror_codeのstaticを外しました。
# pushd ../../j2se/src/solaris/native/sun/awt/ # cp -pi awt_dnd.c awt_dnd.org # vi awt_dnd.c # diff -u awt_dnd.org awt_dnd.c --- awt_dnd.org 2012-02-27 18:26:56.000000000 +0900 +++ awt_dnd.c 2012-03-03 21:24:11.000000000 +0900 @@ -169,7 +169,7 @@ return awt_root_window; } -static unsigned char xerror_code = Success; +unsigned char xerror_code = Success; static int xerror_handler(Display *dpy, XErrorEvent *err) { # popd
No rule to make target `../../../src/share/lib/cmm/sRGB.pf', needed by `/usr/src/jvms/jdk16u16/control/build/linux-amd64/lib/cmm/sRGB.pf'. Stop.
make[4]: *** No rule to make target `../../../src/share/lib/cmm/sRGB.pf', needed by `/usr/src/jvms/jdk16u16/control/build/linux-amd64/lib/cmm/sRGB.pf'. Stop.
これもjre/libの下にあったのでコピーしています。
# mkdir ../../j2se/src/share/lib/cmm # cp -pi /home/masayuki/install/cmm/* ../../j2se/src/share/lib/cmm/
relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC
make[4]: Entering directory `/usr/src/jvms/jdk16u16/j2se/make/sun/jdbc'
/usr/bin/gcc -shared -o /usr/src/jvms/jdk16u16/control/build/linux-amd64/tmp/sun/sun.jdbc.odbc/JdbcOdbc/libodbcinst.so dummyodbc.c
/usr/bin/ld: /tmp/cccOEmCj.o: relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC
jdbcのところで上記エラーが出ました。何故ここだけ…。CFLAGSを見てないようなのでMakefileに「-fPIC」をハードコード。
# diff -u ../../j2se/make/sun/jdbc/Makefile.org ../../j2se/make/sun/jdbc/Makefile --- ../../j2se/make/sun/jdbc/Makefile.org 2012-02-27 18:26:50.000000000 +0900 +++ ../../j2se/make/sun/jdbc/Makefile 2012-03-03 22:11:06.000000000 +0900 @@ -85,10 +85,10 @@ make_libs: $(TEMPDIR)/libodbcinst.so $(TEMPDIR)/libodbc.so $(TEMPDIR)/libodbcinst.so: dummyodbc.c $(TEMPDIR) - $(CC) -shared -o $@ $< + $(CC) -fPIC -shared -o $@ $< $(TEMPDIR)/libodbc.so: dummyodbc.c $(TEMPDIR) - $(CC) -shared -o $@ $< + $(CC) -fPIC -shared -o $@ $< clean:: $(RM) -f $(TEMPDIR)/libodbcinst.so $(TEMPDIR)/libodbc.so
No rule to make target `/usr/src/jvms/jdk16u16/control/build/linux-amd64-fastdebug/lib/audio/soundbank.gm'
make[6]: *** No rule to make target `/usr/src/jvms/jdk16u16/control/build/linux-amd64-fastdebug/lib/audio/soundbank.gm', needed by `copy-files'. Stop.
またsoundbank.gmが。fastbugというモジュール?も作られていて、そちら側でエラーとなっています。前回と同様にコピー。
# mkdir ../build/linux-amd64-fastdebug/lib/audio # cp -ip /home/masayuki/install/soundbank.gm ../build/linux-amd64-fastdebug/lib/audio
Mockito+PowerMockでStringのmockを作る
文字列のハッシュ衝突時のテストケースを書くことになりましたが、いつも使っているMockitoはfinal classのmockを作ることができません。
調べてみたところ、PowerMockというモジュールを使うことによって、MockitoのAPIを用いてfinal classのmockを作ることができることが分かり、試してみました。
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertThat; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import static org.powermock.api.mockito.PowerMockito.*; @RunWith(PowerMockRunner.class) @PrepareForTest(String.class) public class HashCollisionTest { @Test public void testHashCodeEquivalent(){ String foo = "foo"; String bar = "bar"; assertThat(foo.hashCode(), is(not(bar.hashCode()))); bar = spy(bar); when(bar.hashCode()).thenReturn(foo.hashCode()); assertThat(bar, is("bar")); assertThat(foo.hashCode(), is(bar.hashCode())); } }
Mockitoのみを用いた場合と異なるのは以下の3点です。
- static importの変更
- 「org.mockito.Mockito.*」から「org.powermock.api.mockito.PowerMockito.*」に
- @RunWith(PowerMockRunner.class)を追加
- @PrepareForTest([mockの対象となるclass])を追加
PowerMockは既存のmockモジュールを拡張するモジュールで、EasyMockとMockitoに対応しています。final classのmockを作成する為に別のmockモジュールを導入しても良いですが、既にEasyMockやMockitoを使ってテストコードを書いているのであれば、PowerMockを導入してみることをお勧めします。
[java]new Semaphore(0)
とあるコードの中に、以下のような記述がありました。
import java.util.concurrent.Semaphore; ... Semaphore permit = new Semaphore(0);
これまで、ミューテックスは1つの、セマフォは任意の数のクリティカルセクションへの進入を許可するもの、と覚えていました。ですので、初期値に「0」が指定されたセマフォがどういった意味を持つのか理解できず調べてみました。その中で、意外にセマフォ自体を理解できていないことが分かり、認識の弱かった部分を纏めてみました。
所有(アカウント)という概念がない
ミューテックスの場合は獲得したタスク(スレッド)がそのミューテックスを所有しますが、セマフォには所有されるという概念がありません。以下のコードを実行し、スレッドダンプを取ってみました。
import java.util.concurrent.Semaphore; import org.junit.Test; public class SemaphoreTest { @Test public void trySemaphore() throws InterruptedException{ final Semaphore permit = new Semaphore(2); Runnable task = new Runnable(){ public void run() { try { permit.acquire(); Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); }finally{ permit.release(); } } }; Thread t1 = new Thread(task, "t1"); Thread t2 = new Thread(task, "t2"); Thread t3 = new Thread(task, "t3"); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); } }
上記コードの実行中にスレッドダンプを取ってみると、以下のようになります。
Full thread dump Java HotSpot(TM) Client VM (21.0-b17 mixed mode, sharing): "t3" prio=6 tid=0x046cfc00 nid=0xb00 waiting on condition [0x04c9f000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x24b57978> (a java.util.concurrent.Semaphore$NonfairSync) at java.util.concurrent.locks.LockSupport.park(Unknown Source) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(Unknown Source) at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(Unknown Source) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(Unknown Source) at java.util.concurrent.Semaphore.acquire(Unknown Source) at net.wrap_trap.test.concurrent.SemaphoreTest$1.run(SemaphoreTest.java:15) at java.lang.Thread.run(Unknown Source) Locked ownable synchronizers: - None "t2" prio=6 tid=0x046cf400 nid=0x308 waiting on condition [0x0489f000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at net.wrap_trap.test.concurrent.SemaphoreTest$1.run(SemaphoreTest.java:16) at java.lang.Thread.run(Unknown Source) Locked ownable synchronizers: - None "t1" prio=6 tid=0x046bf000 nid=0x860 waiting on condition [0x04c1f000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at net.wrap_trap.test.concurrent.SemaphoreTest$1.run(SemaphoreTest.java:16) at java.lang.Thread.run(Unknown Source) Locked ownable synchronizers: - None ...
スレッドt1,t2はセマフォを獲得し、t3はセマフォを待っている状態ですが、「Locked ownable synchronizers:」は何れもNoneとなっています。
同じことをReentrantLockで試してみます。
Full thread dump Java HotSpot(TM) Client VM (21.0-b17 mixed mode, sharing): "t3" prio=6 tid=0x047e6800 nid=0xc2c waiting on condition [0x04aef000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x24b57f50> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) at java.util.concurrent.locks.LockSupport.park(Unknown Source) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(Unknown Source) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(Unknown Source) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Unknown Source) at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(Unknown Source) at java.util.concurrent.locks.ReentrantLock.lock(Unknown Source) at net.wrap_trap.test.concurrent.ReentrantLockTest$1.run(ReentrantLockTest.java:15) at java.lang.Thread.run(Unknown Source) Locked ownable synchronizers: - None "t2" prio=6 tid=0x047e3400 nid=0x1204 waiting on condition [0x0466f000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x24b57f50> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) at java.util.concurrent.locks.LockSupport.park(Unknown Source) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(Unknown Source) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(Unknown Source) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Unknown Source) at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(Unknown Source) at java.util.concurrent.locks.ReentrantLock.lock(Unknown Source) at net.wrap_trap.test.concurrent.ReentrantLockTest$1.run(ReentrantLockTest.java:15) at java.lang.Thread.run(Unknown Source) Locked ownable synchronizers: - None "t1" prio=6 tid=0x0478ec00 nid=0x5d4 waiting on condition [0x04c0f000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at net.wrap_trap.test.concurrent.ReentrantLockTest$1.run(ReentrantLockTest.java:16) at java.lang.Thread.run(Unknown Source) Locked ownable synchronizers: - <0x24b57f50> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) ...
スレッドt1はロックを獲得しており、「Locked ownable synchronizers:」には獲得しているロックの情報が表示されます。セマフォは獲得したスレッドには関連せず、獲得できる数を管理しているだけです。
どのスレッドからも解放できる
セマフォは特定のスレッドと関連しない為、どのスレッドからでもセマフォを解放することができます。例えば以下のようなコード。
import java.util.Random; import java.util.concurrent.Semaphore; import org.junit.Test; public class ConsumerProducer { static class Value { public Object value; } @Test public void tryProvide() throws InterruptedException { final Semaphore s = new Semaphore(0); final Value shared = new Value(); final Random rand = new Random(); class Consumer implements Runnable { public void run() { while(true) { try { s.acquire(); System.out.println("get: " + shared.value); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Producer implements Runnable { public void run() { while(true) { shared.value = rand.nextInt(); System.out.println("put: " + shared.value); s.release(); try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } } } } Thread t1 = new Thread(new Consumer()); t1.start(); Thread t2 = new Thread(new Producer()); t2.start(); t1.join(); t2.join(); } }
Customerのスレッドはセマフォを獲得するだけ、Producerのスレッドはセマフォを解放するだけです。つまり、セマフォを獲得したスレッドとは異なるスレッドでセマフォを解放しています。上記コードでは「new Semaphore(0);」としていて、最初にCustomerのスレッドがセマフォを獲得しようとするのですが、Producerが値を準備してセマフォを解放するまでセマフォを獲得することができません。
もちろんObject#wait、Object#notifyを使って同じようなことはできますが、synchronizedブロックを必要としないセマフォの方が書き方としてはスマートだと思います。但し、前記のとおりセマフォには所有という考え方が無い為、例えばセマフォの獲得で長い時間待たされた場合、どのスレッドがセマフォを解放できていないのかスレッドダンプで確認しようとしても、セマフォを獲得しているスレッドを特定することはできません。この点は注意が必要です。
[java][bson]POJOをBSON形式でシリアライズする
JavaのオブジェクトをBSON形式でファイルに保存する為、いくつかの実装を試してみました。
BSONのバイナリに変換できるライブラリ
BSONのページのImplementationにJava実装が列挙されている為、今回はこれらを試してみました。
mongo-java-driver
名前にあるとおりMongoDBのドライバですが、BasicBSONEncoderを使って、Javaのオブジェクト(BSONObjct)を直接BSONのバイナリに変換することもできます。バイナリからBSONObjctに戻すには、BasicBSONDecoderを使います。
bson4jasckson
JSONを扱うJacksonのFactoryを拡張してBSONを扱えるようにしたのがbson4jascksonです。Jacksonと同様、ObjectMapperを使うとPOJOを直接BSONのバイナリに変換することができます。
@Test public void testSerialize() throws JsonGenerationException, JsonMappingException, IOException{ Employee emp = new Employee(); emp.setA(76); emp.setName("babb"); emp.setSal(Integer.MAX_VALUE-2); //serialize ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectMapper mapper = new ObjectMapper(new BsonFactory()); mapper.writeValue(baos, emp); byte[] buffer = baos.toByteArray(); // deserialize ByteArrayInputStream bais = new ByteArrayInputStream(buffer); Employee dst = mapper.readValue(bais, Employee.class); assertThat(dst.getA(), is(76)); assertThat(dst.getName(), is("babb")); assertThat(dst.getSal(), is(Integer.MAX_VALUE-2)); }
また、Jacksonは複数のシリアライズ方法を提供しており、ObjectMapperに任せると都合が悪い場合はmongo-java-driverのBSONObjectのような汎用型を扱うTreeModel、さらに細かく調整する場合にはStreamingAPIを使うこともできます。詳細は以下のURLに記載されています。
http://wiki.fasterxml.com/JacksonFAQ#Processing_Models
ebson
これはスクラッチで作成されたBSON実装です。サマリには「Extensible BSON encoder/decoder library written in Java with pluggable Java-to-BSON type mappings.」とあり、独自のマッピングを記述しやすいのが特徴です。
残念なのは、APIのインターフェースにByteBufferが使用されていること。ByteBufferは途中でcapacityを変更できない為、ちょっと使いづらかったというのが正直な感想です。
代わりにコードはかなり整理されており、参考になる点が多数ありました。
まとめ
POJOを直接BSONバイナリに変更したい場合はbson4jascksonのObjectMapperが便利ですが、特殊なマッピングを行うのであればebsonも面白いと思います。MongoDB絡みで連携可能な既存資産があれば、mongo-java-driverかな。
hbaserb
アプリケーションのログをparseしてHBaseに格納する為に、hbaserbというライブラリを使ってみました。hbaserbからHBaseを操作するのはThrift経由となる為、先にThriftの受け口を起動しておきます。
hbase-daemon.sh start thrift
tableもhbaserbで作成することはできますが、今回は予めhbase shellでtableを作っておき、データを保存してみました。
hbase(main):004:0> create 'hbaserb_test', 'data'
HBaseを操作するRubyのコードは以下です。
require 'rubygems' require 'hbaserb' client = HBaseRb::Client.new '127.0.1.1' table = client.get_table 'hbaserb_test' table.mutate_row 'row1', {'data:key1' => 'value1', 'data:key2' => 'value2'} table.mutate_row 'row2', {'data:key1' => 'hoge', 'data:key2' => 'foobar'} table.create_scanner('row1') do |row| row.columns.keys.each do |key| puts "#{key} => #{row.columns[key].value}" end end
上記コードを実行すると、以下のように表示されます。
masayuki@ubuntu-vm:~/work/ruby/hbaserb$ ruby test.rb data:key1 => value1 data:key2 => value2 data:key1 => hoge data:key2 => foobar
hbase shellからも保存したデータを確認することができます。
hbase(main):005:0> scan 'hbaserb_test' ROW COLUMN+CELL row1 column=data:key1, timestamp=1312999209128, value=value1 row1 column=data:key2, timestamp=1312999209128, value=value2 row2 column=data:key1, timestamp=1312999209130, value=hoge row2 column=data:key2, timestamp=1312999209130, value=foobar 2 row(s) in 0.2240 seconds