BloomFilterを使ってみる
http://dev.ariel-networks.com/column/tech/boom_filter/
http://www.perl.com/pub/2004/04/08/bloom_filters.html
hadoopにBloomFilterの実装があったのでそのクラスを使ってみる。
その対象のデータに対して検索したい場合の検索インデックスとしてDBなどに保存するために、BloomFilterを使って生成したbit列をシリアライズする方法も試した。
bit列をもとに同じBloomFilterを復元することもできた。
なんかに使えそう。
/** * */ package jp.co.cyberagent.partner.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import junit.framework.Assert; import org.apache.hadoop.util.bloom.BloomFilter; import org.apache.hadoop.util.bloom.Key; import org.apache.hadoop.util.hash.Hash; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author watanabe_yusaku * */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration() public class BloomFilterTest { @Configuration static class Config{ } @Test public void test() { //Filterテスト BloomFilter b = new BloomFilter(128, 128, Hash.MURMUR_HASH); b.add(new Key(new String("yuhsaku").getBytes())); b.add(new Key(new String("hiroki").getBytes())); b.add(new Key(new String("yumi").getBytes())); b.add(new Key(new String("kunitaka").getBytes())); b.add(new Key(new String("mitsuko").getBytes())); b.add(new Key(new String("tsuyoshi").getBytes())); Assert.assertTrue(b.membershipTest(new Key("yuhsaku".getBytes()))); Assert.assertFalse(b.membershipTest(new Key("hoge".getBytes()))); //Filterのbit列をシリアライズしてから再度復元してちゃんとFilterできるか確認 BloomFilter rebuildBloomFilter = new BloomFilter(128, 128, Hash.MURMUR_HASH); ByteArrayOutputStream bytearrayOs = new ByteArrayOutputStream(); DataOutputStream s = new DataOutputStream(bytearrayOs); byte[] bytes; try { b.write(s); System.out.println("size:" + s.size()); bytes = new byte[s.size()]; bytes = bytearrayOs.toByteArray(); rebuildBloomFilter.readFields(new DataInputStream(new ByteArrayInputStream(bytes))); }catch(IOException ioe) { Assert.fail(); } Assert.assertTrue(b.membershipTest(new Key("yuhsaku".getBytes()))); Assert.assertFalse(b.membershipTest(new Key("hoge".getBytes()))); } }
HBaseの概要理解のメモ
Apache HttpComponents(旧httpClient)でssl通信時のエラー
アプリケーションからhttps通信を使って他のサーバと連携することはよくあると思います。
例えばfacebookやgoogleのOAuth認証のOPとやりとりする場合とか。
私は開発時はMac上で行なっているがその時点ではjava7はOpenJDKしかなかったので(現在はMac版のoracle jdk7が出てますね)、ほとんどの開発をOpenJDK上で行なっていたんですが、なぜかHttpComponentsを使ってhttpsの通信を行うと下記エラーが出る。
Exception in thread "main" java.lang.RuntimeException: javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated at Main.execute(Main.java:62) at Main.main(Main.java:19) Caused by: javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated at sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:397) at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128) at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:397) at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:148) at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:150) at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:121) at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:575) at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:425) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:754) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:732) at Main.execute(Main.java:54) ... 1 more
サンプルコードは以下
public class Main { public static void main(String[] args) throws Exception{ Main m = new Main(); m.execute(args); } public void execute(String[] args) throws Exception{ System.out.println(System.getProperty("java.version")); HttpClient httpClient = new DefaultHttpClient(); //ちょうどmixi developer Centerにサーバ証明書についてのFAQがあったので使わしてもらう //http://developer.mixi.co.jp/openid/faq/ HttpGet get = new HttpGet("https://mixi.jp/"); HttpResponse res = httpClient.execute(get); if(res.getStatusLine().getStatusCode() >= 300) { System.out.println(res.getStatusLine().getStatusCode()); } System.out.println(EntityUtils.toString(res.getEntity())); } }
httpComponentsのバージョンは
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.1.3</version> </dependency>
を使っています
正直あまりsslについて知識がなかったので、とりあえずHttpClientでSSL通信(HttpClient3.0-rc3) にしたがってサーバ証明書を登録して事無きを得たのだが、どうにも気持ち悪い(いちいちこんな作業をしないとhttps通信できないのか?という気持ち悪さ)
ちなみにいちいち証明書の認証とかめんどくせぇという人は以下のように書くとオレオレ証明書などでも無視して全部受けて入れるようにできます。
try { TrustStrategy trustStrategy = new TrustStrategy() { @Override public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { return true; } }; X509HostnameVerifier hostnameVerifier = new AllowAllHostnameVerifier(); SSLSocketFactory sslSf = new SSLSocketFactory(trustStrategy, hostnameVerifier); SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme("https", 8443, sslSf)); schemeRegistry.register(new Scheme("https", 443, sslSf)); ClientConnectionManager connection = new ThreadSafeClientConnManager(schemeRegistry); HttpClient httpClient = new DefaultHttpClient(connection); }catch(UnrecoverableKeyException uke) { //TODO }catch(KeyStoreException kse) { //TODO }catch(KeyManagementException kme) { //TODO }catch(NoSuchAlgorithmException nsae) { //TODO }
しかし今回はライブラリを開発しているので勝手に証明書を無視するようなコードを入れ込むわけにはいきません。
というわけで、改めてsslの仕組みを調べてjavaのssl通信の仕組みについてお勉強。
どうやらデフォルトでcacertsというファイルにいわゆるCAルート証明書がインストールされていることになっているようだ。
なので、httpComponentsを使ったアプリケーションのjavaランタイムのcacertsにCAルート証明書が入っていないと、サーバ証明書のクライアント認証が通せないのでエラーになるということみたいです。
実際にMac標準にインストールされているJDKのcacertsを確認するとかなりの量のルート証明書がインポートされている
$ keytool -v -list -keystore /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/security/cacerts | more キーストアのパスワードを入力してください: #←デフォルトパスワードはchangeitなので、それを入力 キーストアのタイプ: JKS キーストア・プロバイダ: SUN キーストアには183エントリが含まれます 別名: keychainrootca-99 作成日: 2012/03/23 エントリ・タイプ: trustedCertEntry 所有者: CN=TWCA Root Certification Authority, OU=Root CA, O=TAIWAN-CA, C=TW 発行者: CN=TWCA Root Certification Authority, OU=Root CA, O=TAIWAN-CA, C=TW シリアル番号: 1 有効期間の開始日: Thu Aug 28 16:24:33 JST 2008終了日: Wed Jan 01 00:59:59 JST 2031 証明書のフィンガプリント: MD5: AA:08:8F:F6:F9:7B:B7:F2:B1:A7:1E:9B:EA:EA:BD:79 SHA1: CF:9E:87:6D:D3:EB:FC:42:26:97:A3:B5:A3:7A:A0:76:A9:06:23:48 SHA256: BF:D8:8F:E1:10:1C:41:AE:3E:80:1B:F8:BE:56:35:0E:E9:BA:D1:A6:B9:BD:51:5E:DC:5C:6D:5B:87:11:AC:44 署名アルゴリズム名: SHA1withRSA バージョン: 3 拡張: #1: ObjectId: 2.5.29.14 Criticality=false ・ ・ ・以下略
openjdkのほうは空っぽ
$ keytool -v -list -keystore /Library/Java/JavaVirtualMachines/1.7.0u4.jdk/Contents/Home/jre/lib/security/cacerts キーストアのパスワードを入力してください: キーストアのタイプ: JKS キーストア・プロバイダ: SUN キーストアには0エントリが含まれます
これが原因でサーバ証明書の認証ができないためエラーになったみたい。
試しに同じコードでMacの標準jdkにビルドパスを変更したら問題なく通信できました。
openjdkを使う場合はCAルート証明書を自前でインポートするとかしないといけないのだろうか。
そのへんまでは調べきれてないけど、とりあえず原因がわかったのですっきりした。
必要になったらまた追記します。
javaでjavaagentとagentlibを調査してみる
http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/java.html
の-agentlib, -javaagentの項目について調べてみた
あれ、なんか俺agentlibとjavaagentを勘違いしている、、、、?
-agentlib
-agentlib:hprofや-agentlib:jdwpなどプロファイラやデバッガなどを利用するネイティブライブラリを指定する
詳しくはJVMTI(Java Virtual Machine Tools Interface)を参照することつまりCやC++を使って、VMにアクセスしてプロファイル情報などを引き抜いたり、デバッグ用に挙動を操作したりすることができるらしい
JVMTIの日本語DOC
http://java.sun.com/javase/ja/6/docs/ja/platform/jvmti/jvmti.html#starting
JVMTIを利用する
http://www.02.246.ne.jp/~torutk/javahow2/jvmti.html
-javaagent
java.lang.instrumentパッケージを使ってバイトコードをVM起動時に動的に書き換えたりできる。
下記さくらばさんの書き込みがそのままです
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=26578&forum=12
以下バイトコードを動的に操作 Instrumentationを参考にjavaagentについて色々動作を試してみる
eclipseのworkspaceにできたクラスファイルをjarにまとめる
$ cd ~/Documents/workspace/lick_up_jvm/target/classes $ jar cvf sample.jar jp/co/watanabe_yusaku/instrument/InstrmentTest.class マニフェストが追加されました InstrmentTest.classを追加中です(入=982)(出=528)(46%収縮されました)
いったんjarを展開してマニフェストファイルにPremain-Classを追記する
$ jar xvf sample.jar META-INF/が作成されました \META-INF/MANIFEST.MFが展開されました \jp/co/watanabe_yusaku/instrument/InstrmentTest.classが展開されました $ vim META-INF/MANIFEST.MF ↓追記 Premain-Class: jp.co.watanabe_yusaku.instrument.InstrmentTest $ jar cvfm sample.jar META-INF/MANIFEST.MF jp/co/watanabe_yusaku/instrument/InstrmentTest.class マニフェストが追加されました jp/co/watanabe_yusaku/instrument/InstrmentTest.classを追加中です(入=982)(出=528)(46%収縮されました)
実行してみる
$ java -javaagent:sample.jar jp/co//watanabe_yusaku/main/HelloWorld class java.util.HashMap$EntryIterator class java.lang.Boolean 略 Hello, World!
ちゃんとHelloWorldクラスのmainが実行される前にInstrmentTestクラスのpremainが実行されてるようだ。
この仕組みを使えば稼働中のアプリケーションの構造(設定ファイルなどを含む)を一切変更せずに挙動を変更することができる。
以下のようなコードをjarで固めてjavaagent指定すると全てのメソッドの実行時間をミリ秒で出力することができた。
import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; public class MethodTimer { private static ClassPool classPool; @SuppressWarnings("rawtypes") public static void premain(String agentArgs, Instrumentation inst) { classPool = ClassPool.getDefault(); inst.addTransformer(new ClassFileTransformer() { public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { try { ByteArrayInputStream stream = new ByteArrayInputStream(classfileBuffer); CtClass ctClass = classPool.makeClass(stream); CtMethod[] ctMethods = ctClass.getDeclaredMethods(); for(CtMethod ctMethod : ctMethods) { ctMethod.addLocalVariable("start", CtClass.longType); ctMethod.insertBefore("start = System.currentTimeMillis();"); ctMethod.insertAfter("System.out.println(\"" + ctClass.getName() + "." + ctMethod.getName() + "()" + ":exec time = \" + (System.currentTimeMillis() - start));"); } return ctClass.toBytecode(); }catch(CannotCompileException | IOException e) { IllegalClassFormatException ecfe = new IllegalClassFormatException(); ecfe.initCause(e); throw ecfe; } } }); } }
マニフェストファイルは
Premain-Class: MethodTimer Boot-Class-Path: javassist.jar Can-Redefine-Classes: true
mainクラスは
public class HelloWorld { public static void main(String[] args) throws Exception{ HelloWorld h = new HelloWorld(); h.execute(args); } public void execute(String[] args) throws Exception{ this.say("Hello, World0"); this.say("Hello, World1"); Thread.sleep(3000); this.say("Hello, World2"); this.say("Hello, World3"); } public void say(String str) { System.out.println(str); } }
実行ディレクトリにjavasisst.jarとsample.jarを配置してから実行すると
java -javaagent:sample.jar HelloWorld sun.launcher.LauncherHelper.makePlatformString():exec time = 0 sun.launcher.LauncherHelper.getMainMethod():exec time = 0 sun.launcher.LauncherHelper.checkAndLoadMain():exec time = 6 Hello, World0 HelloWorld.say():exec time = 1 Hello, World1 HelloWorld.say():exec time = 1 Hello, World2 HelloWorld.say():exec time = 0 Hello, World3 HelloWorld.say():exec time = 0 HelloWorld.execute():exec time = 3004 HelloWorld.main():exec time = 3004
AOPを導入するまでもないけど、ちょっと本番環境で設定ファイルなどを含む環境などを一切変更しないでプロファイリングしたい時に便利かもしれないですね
自宅サーバのRAID障害が出た日
以前構築した自宅サーバのRAID環境から障害通知メールが来た。
件名:mdadm monitoring
This is an automatically generated mail message from mdadm running on localhost.localdomain A Fail event had been detected on md device /dev/md0. It could be related to component device /dev/sdc1. Faithfully yours, etc. P.S. The /proc/mdstat file currently contains the following: Personalities : [raid1] md0 : active raid1 sdc1[2](F) sdb1[0] 976759936 blocks [2/1] [U_] unused devices:
/var/log/messagesを確認してそれっぽいエラーが出ているのがここらへん。
ar 18 05:43:04 localhost kernel: SCSI device sdc: 1953525168 512-byte hdwr sectors (1000205 MB) Mar 18 05:43:11 localhost kernel: sdc: Write Protect is off Mar 18 05:43:12 localhost kernel: SCSI device sdc: drive cache: write back Mar 18 05:44:12 localhost kernel: ata2.01: exception Emask 0x10 SAct 0x0 SErr 0x400100 action 0x6 frozen Mar 18 05:44:12 localhost kernel: ata2: SError: { UnrecovData Handshk } Mar 18 05:44:12 localhost kernel: ata2.01: cmd 35/00:00:3f:05:c3/00:04:09:00:00/f0 tag 0 dma 524288 out Mar 18 05:44:12 localhost kernel: res 40/00:cf:61:05:c3/40:03:09:00:00/f0 Emask 0x14 (ATA bus error) 169,51 34% Mar 18 05:43:04 localhost kernel: sdc: Current [descriptor]: sense key: Medium Error Mar 18 05:43:04 localhost kernel: Add. Sense: Unrecovered read error - auto reallocate failed Mar 18 05:43:04 localhost kernel: Mar 18 05:43:04 localhost kernel: Descriptor sense data with sense descriptors (in hex): Mar 18 05:43:04 localhost kernel: 72 03 11 04 00 00 00 0c 00 0a 80 00 00 00 00 00 Mar 18 05:43:04 localhost kernel: 09 c3 05 61 Mar 18 05:43:04 localhost kernel: end_request: I/O error, dev sdc, sector 163775841 Mar 18 05:43:04 localhost kernel: ata2: EH complete Mar 18 05:43:04 localhost kernel: SCSI device sdc: 1953525168 512-byte hdwr sectors (1000205 MB) Mar 18 05:43:11 localhost kernel: sdc: Write Protect is off Mar 18 05:43:12 localhost kernel: SCSI device sdc: drive cache: write back Mar 18 05:44:12 localhost kernel: ata2.01: exception Emask 0x10 SAct 0x0 SErr 0x400100 action 0x6 frozen Mar 18 05:44:12 localhost kernel: ata2: SError: { UnrecovData Handshk } Mar 18 05:44:12 localhost kernel: ata2.01: cmd 35/00:00:3f:05:c3/00:04:09:00:00/f0 tag 0 dma 524288 out Mar 18 05:44:12 localhost kernel: res 40/00:cf:61:05:c3/40:03:09:00:00/f0 Emask 0x14 (ATA bus error) 169,51 33% Mar 18 05:43:04 localhost kernel: sdc: Current [descriptor]: sense key: Medium Error Mar 18 05:43:04 localhost kernel: Add. Sense: Unrecovered read error - auto reallocate failed Mar 18 05:43:04 localhost kernel: Mar 18 05:43:04 localhost kernel: Descriptor sense data with sense descriptors (in hex): Mar 18 05:43:04 localhost kernel: 72 03 11 04 00 00 00 0c 00 0a 80 00 00 00 00 00 Mar 18 05:43:04 localhost kernel: 09 c3 05 61 Mar 18 05:43:04 localhost kernel: end_request: I/O error, dev sdc, sector 163775841 Mar 18 05:43:04 localhost kernel: ata2: EH complete Mar 18 05:43:04 localhost kernel: SCSI device sdc: 1953525168 512-byte hdwr sectors (1000205 MB) Mar 18 05:43:11 localhost kernel: sdc: Write Protect is off Mar 18 05:43:12 localhost kernel: SCSI device sdc: drive cache: write back Mar 18 05:44:12 localhost kernel: ata2.01: exception Emask 0x10 SAct 0x0 SErr 0x400100 action 0x6 frozen Mar 18 05:44:12 localhost kernel: ata2: SError: { UnrecovData Handshk } Mar 18 05:44:12 localhost kernel: ata2.01: cmd 35/00:00:3f:05:c3/00:04:09:00:00/f0 tag 0 dma 524288 out Mar 18 05:44:12 localhost kernel: res 40/00:cf:61:05:c3/40:03:09:00:00/f0 Emask 0x14 (ATA bus error) Mar 18 05:44:12 localhost kernel: ata2.01: status: { DRDY } 169,51 34% Mar 18 05:44:23 localhost kernel: ata2: hard resetting link Mar 18 05:44:24 localhost kernel: ata2: SATA link up 3.0 Gbps (SStatus 123 SControl 300) Mar 18 05:44:24 localhost kernel: ata2.01: revalidation failed (errno=-2) Mar 18 05:44:24 localhost kernel: ata2.01: disabled Mar 18 05:44:25 localhost kernel: ata2.00: failed to IDENTIFY (I/O error, err_mask=0x40) Mar 18 05:44:25 localhost kernel: ata2.00: revalidation failed (errno=-5) Mar 18 05:44:25 localhost kernel: ata2: failed to recover some devices, retrying in 5 secs Mar 18 05:44:30 localhost kernel: ata2: hard resetting link Mar 18 05:44:30 localhost kernel: ata2: SATA link up 3.0 Gbps (SStatus 123 SControl 300) Mar 18 05:44:30 localhost kernel: ata2.00: configured for UDMA/133 Mar 18 05:44:30 localhost kernel: ata2: EH complete
以下らへんを参考に復旧作業中だがだめだったらディスク買い換えるか、、、、
CentOSでソフトウェアRAIDの構築
smartd での sector error 復活作業
ext3でフォーマットする方法
HDDの不良ブロックをどうにかできないか – ubuntu badblocks mke2fs e2fsck
【追記】
badblocksコマンドで/dev/sdcのディスクについて不良セクタのチェックを行ったが特に検出されなかった(1TBのディスクなので--fullオプションをつけて実行したら一週間ぐらいかかった)
とりあえずRAID1を構築しなおして/dev/md0のRAIDデバイスについてext3でフォーマットしなおしたら復旧した。
ソフトウェアRAIDの書き込み失敗とかそういう論理的な問題だったのだろうか、、、、