新年おめでとうございます。
2012年を迎えて、昨年の振り返りと今年の展望を軸にご挨拶を申し上げます。
http://www.youtube.com/watch?v=2qjKUVLoDDs
新年おめでとうございます。
2012年を迎えて、昨年の振り返りと今年の展望を軸にご挨拶を申し上げます。
http://www.youtube.com/watch?v=2qjKUVLoDDs
Tokyo CabinetとTokyo Tyrantについて、社内勉強会のために作ったスライドをSlideShareに公開しました。(スライドはKeynote形式でダウンロード可能です)その解説と補足をしておきます。勉強会の目的は、Tokyo CabinetとTokyo Tyrantを大づかみに理解することと、実際にデータ操作をしてみることでした。
http://www.slideshare.net/akirakoyasu/tokyo-cabinet-tokyo-tyrant-10468880
まずはTokyo Cabinet/Tokyo Tyrantとは何なのか、端的に言えば、
ということになります。(スライド2-8)
Tokyo Cabinet(以下TC)はいわゆるkey-valueストアで、4種類のデータ構造を持つことができます。(スライド10-17)
TCはデータ操作のためのCLIを備えています。データ構造によっていくらかの違いはありますが、例えばhashDBを操作する場合は以下のようにします。(スライド18-23)
# カレントディレクトリに"casket.tch"というファイル名でhashDBを作成する tchmgr create casket.tch # key"hoge"に紐づくvalue"fuga"を挿入する tchmgr put casket.tch "hoge" "fuga" # key"hoge"に紐づくvalueを参照する tchmgr get casket.tch "hoge" # hashDBのkeyを一覧する tchmgr list casket.tch # key"hoge"を削除する tchmgr out casket.tch "hoge"
これらの他にもいくつかコマンドがありますし、多数のオプションもあります。詳細はmanページを参照してください。
TCの操作はネイティブライブラリとして提供され、また、他のプログラム言語から使用するためのAPIが提供されます。基本的に先ほどのCLI(正確にはネイティブライブラリ)に準ずるメソッド・オプションとなるよう作られています。スライドではJavaAPIの例を挙げています。(スライド25)
Tokyo Tyrant(以下TT)はTCをデータストレージとして使用するDBサーバです。これは実際にサーバを起動してデータ操作をする手順を見ていただきましょう。(スライド28-36)
まずは1つ目のコンソールでTTを起動します。以下コンソール番号をプロンプトで示します。
# TTを起動する。(デフォルトポート1978) # データストレージにはカレントディレクトリの"casket.tch"を作成/使用する。 console1:$ ttserver casket.tch
2つ目のコンソールでデータ操作を行いましょう。
# key"hoge"に紐づくvalue"fuga"を挿入する console2:$ tcrmgr put localhost "hoge" "fuga" # key"hoge2"に紐づくvalue"fuga2"を挿入する console2:$ tcrmgr put localhost "hoge2" "fuga2" # key"hoge"に紐づくvalueを参照する console2:$ tcrmgr get localhost "hoge" # hashDBのkeyを一覧する console2:$ tcrmgr list localhost # key"hoge"を削除する console2:$ tcrmgr out localhost "hoge"
このデータ操作の部分は、先ほどのTCを直接使う場合とほとんど同じですね。さて今の状態で、DBにはkey"hoge2"が残っているはずです。コンソール1に戻って、TTを停止します。
^C
ストレージにしていたTCがどうなっているか確認してみましょう。TCを直接操作するので、tcrmgrではなくtchmgrを使用します。
console1:$ tchmgr list casket.tch
TC上でも、key"hoge2"が残っていることが確認できたはずです。
公式にはPerlとRuby用のAPIが提供されています。Java用のAPIとしては、tokyotyrant-javaが有名です。TCのAPIに似たAPIで操作が可能です。(スライド38)
TTはレプリケーション機能を備えています。スライドでは大雑把にしか触れていませんが、レプリケーションについてはまとまった記述がなかなか見つからず苦労したので、ここはスライドから少し離れるところもありますが、詳しく解説しましょう。
仕組みとしてはMySQLとほぼ同じです。(マネして作った、と作者が書かれていました。)具体的には、
この動作を繰り返してレプリケーションが行われます。(スライド39-42)
データが無い初期状態からレプリケーションを行うのは、極めて簡単です。
それぞれ指定して起動するだけです。
問題はデータが既に存在する場合ですね。仮に、意識的な設定を何もしていないTTが既に稼働していて、突然レプリケーションをしようと思い立ったとしましょう。稼働中のTTをマスタにし、スレーブとなるTTを新たに追加することを考えます。(スライド43-46 ここをとても大雑把にしていますが)
一番気になるのはTTを停止する必要があるのかどうか、というところです。TT起動時のオプションを確認し、不幸にしてサーバID(-sid)と更新ログディレクトリ(-ulog)とが指定されていなかった場合は、TTを一度停止する必要があります。そして例えば以下のようなオプションを追加して再起動します。以降、マスタ/スレーブでの操作をプロンプトで示します。
# 更新ログディレクトリを作成 master:$ mkdir /path/to/ulog # TTを起動 master:$ ttserver -sid 1 -ulog /path/to/ulog /path/to/casket.tch
これでまずはマスタとなるTTが手に入りました。
次はDBファイルのスナップショットを作成します。先ほど説明したように、レプリケーションの鍵になるのはタイムスタンプです。DBファイルのコピーをとると同時に、そのDBファイルのタイムスタンプを知る必要があります。
そのためにはコピーのためのシェルスクリプトをマスタサーバに用意し、TTの実行ユーザに実行権限を与えます。
master:$ cat ttcopy.sh #!/bin/sh SRCPATH="$1" DSTPATH="$SRCPATH.$2" cp -f "$SRCPATH" "$DSTPATH"
その上で以下のコマンドを発行すると、マスタサーバ上にDBファイルのコピーが作成されます。コピー中はTT全体がロックされることに注意してください。
someconsole:$ tcrmgr copy master.host '@/path/to/ttcopy.sh'
master:$ ls -l /path/to/dbdir/ -rw-r--r-- 1 ttserver ttserver 528704 12 11 18:40 casket.tch -rw-r--r-- 1 ttserver ttserver 528704 12 11 18:40 casket.tch.1323596455089588
元のDBファイル名に数字が付いたファイルが出来ていますね。この数字がDBファイルのタイムスタンプです。
先ほどコピーしたDBファイルをスレーブサーバに移動させ、タイムスタンプファイルを作成します。
# DBファイルの末尾についたタイムスタンプを.rtsファイルに書く slave:$ echo "1323596455089588" > slave.rts # DBファイル名からタイムスタンプを除去する slave:$ mv casket.tch.1323596455089588 casket.tch
そして例えば以下のようなオプションを付けて、スレーブとなるTTを起動します。
slave:$ ttserver -sid 2 -mhost master.host -mport 1978 -rts /path/to/slave.rts /path/to/casket.tch
これでレプリケーションが開始されました。
今回はスライドを基にTokyo CabinetとTokyo Tyrantの基本と、まとまった情報の少ないTokyo Tyrantのレプリケーションについて解説・補足しました。他のkey-valueストアについても言えることですが、(あくまでRDBMSではないという意味で)限定された状況下では高いパフォーマンスを発揮します。使いどころ次第で便利に使えます。
ところで、昨年5月にTokyo Cabinetの後継となるKyoto Cabinetがリリースされています。Tokyo製品群についても継続してメンテナンスはしていくと表明されているので、まだしばらくは使っていても大丈夫でしょう。機会(と時間)があればKyoto製品群にも触れてみようと思っています。
このエントリはJava Advent Calendar 2011 第4日目のエントリです。
« 第3日目のエントリ・第5日目のエントリ »
今日はAWSについて書こうと思います。というのは、最近ノーリスクとかマッシュアップとかいって、従量コストで利用できるWebモジュールの需要が(私の中で)高まってるからなのですが・・・。
AWSと言えばEC2やS3が有名ですが、他にもいろいろなサービスが提供されています。今日はその中でもシンプルで、単体でも役立ちそうなサービスをJava用のSDKから使う方法について紹介します。
この全てのサービス名に"Simple"と付いているのは偶然です。これを基準に選んだわけではありません。本当です。
なお記載のコードを実行するためには、AWSのアカウント登録と各サービスの有効化が必要です。また一応、従量課金サービスですので使い方によって料金が発生することがあります。
AWSはRESTやSOAPで制御することができますが、SDKを通せばREST/SOAPのリクエスト・レスポンスの処理はSDKに任せて、Javaから手軽にサービスを利用できます。
SDKを使用するには、AWSのページからダウンロードしてクラスパスに含めるか、あるいはmavenを使うならpom.xmlに以下のdependencyを追加します。
<dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk</artifactId> <version>1.2.12</version> </dependency>
それぞれのサービスを利用するために、認証が必要になります。AWSのアカウント画面からアクセスキーIDとシークレットアクセスキーを確認してください。これは共通で使いますので、以下のように定数として定義しておきます。
public static final String AWS_ACCESSKEY_ID = "1234567890ABCDEFGHIJ"; public static final String AWS_SECRET_ACCESSKEY = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn";
まずはご存知S3から使ってみることにします。S3は、キーに紐づくオブジェクトを格納できるストレージで、かなり大きなオブジェクトも格納できるのが特徴です。今は5テラバイトまで格納できるようです。
例としてXMLファイルを格納し、それをまた取り出してみましょう。バケット名はS3のサービス全体で一意でなければならないことに注意してください。
// 認証オブジェクト AWSCredentials credential = new BasicAWSCredentials(AWS_ACCESSKEY_ID, AWS_SECRET_ACCESSKEY); // S3クライアントを作成 AmazonS3Client client = new AmazonS3Client(credential); // S3でユニークなバケット名 String bucketName = "xxxxxxxx"; // バケットを作成 client.createBucket(bucketName); // 格納 client.putObject(bucketName, "indian teas", new File("/path/to/indianTeas.xml")); // 取り出し S3Object s3Object = client.getObject(bucketName, "indian teas"); // 標準出力へ出力 InputStream is = s3Object.getObjectContent(); ReadableByteChannel from = Channels.newChannel(is); WritableByteChannel to = Channels.newChannel(System.out); try { ByteBuffer buf = ByteBuffer.allocate(0x1000); while (from.read(buf) != -1) { buf.flip(); while (buf.hasRemaining()) { to.write(buf); } buf.clear(); } } catch (IOException e) { e.printStackTrace(); } // 削除 client.deleteObject(bucketName, "indian teas"); // バケットを削除 client.deleteBucket(bucketName); client.shutdown();
このコードを実行すると、わらわらとAmazonHttpClientのログが出力された後、取り出したXMLの内容が出力されます。
次はSimpleDBに挑戦してみましょう。SimpleDBは、複数の属性(カラムに当たります)を持つデータセット(レコード)を格納できるデータベースです。スキーマ定義は不要で、各データセットの属性を自由に設定できます。
以下のコードは簡単なデータ操作を行うものです。
// 認証オブジェクト AWSCredentials credential = new BasicAWSCredentials(AWS_ACCESSKEY_ID, AWS_SECRET_ACCESSKEY); // SimpleDBクライアントを作成 AmazonSimpleDBClient client = new AmazonSimpleDBClient(credential); // DBドメイン名 String domain = "testDomain"; // ドメインの作成 client.createDomain(new CreateDomainRequest(domain)); // 属性の異なるデータセットを挿入 client.putAttributes(new PutAttributesRequest() .withDomainName(domain) .withItemName("Darjeeling") .withAttributes( new ReplaceableAttribute("country", "India", Boolean.FALSE), new ReplaceableAttribute("area", "north-east", Boolean.FALSE))); client.putAttributes(new PutAttributesRequest() .withDomainName(domain) .withItemName("Uva") .withAttributes( new ReplaceableAttribute("country", "Sri Lanka", Boolean.FALSE), new ReplaceableAttribute("island", "Ceylon", Boolean.FALSE), new ReplaceableAttribute("area", "south-east", Boolean.FALSE))); // 参照(非SQL) SelectResult result = client.select(new SelectRequest("select * from testDomain")); // データセットを表示 List<Item> items = result.getItems(); for (Item item : items) { System.out.printf("%s:", item.getName()); for (Attribute attr : item.getAttributes()) { System.out.printf(" %s=%s,", attr.getName(), attr.getValue()); } System.out.println(); } // DBドメインを削除 client.deleteDomain(new DeleteDomainRequest(domain)); client.shutdown();
参照するときの非SQLというのが気になりますね。これはSQLを簡略化したような構文で参照条件を記述するものなのですが、詳しくはAWSのドキュメントをご覧ください。"Amazon SimpleDB Quick Reference Card"で検索すればチートシートが見つかると思います。
さて上記のコードを実行すると、運が良ければ以下の出力が得られます。(ログは除いています)
Darjeeling: area=north-east, country=India, Uva: area=south-east, country=Sri Lanka, island=Ceylon,
たまに入れたものが全部出てこないこともあります。これはconsistency、いわゆる読み取り一貫性がオプション扱いになっているからです。SelectRequestオブジェクトのconsistentReadをtrueにすれば、参照前に挿入した全てのデータセットを参照できるようになります。
SESを使うと簡単にメールを送信できます。これには1つ準備が必要になります。AWS管理コンソールのSES画面から、送信者のメールアドレス認証を行っておいてください。同じ画面にProduction Accessの申し込みボタンがありますが、テストだけならそちらの方は必要ありません。
認証を済ませたら、早速送信してみましょう。送信者と受信者には認証したメールアドレスを設定します。
// 認証オブジェクト AWSCredentials credential = new BasicAWSCredentials(AWS_ACCESSKEY_ID, AWS_SECRET_ACCESSKEY); // SESクライアントを作成 AmazonSimpleEmailServiceClient client = new AmazonSimpleEmailServiceClient(credential); // 管理コンソールで認証済みのアドレス String verifiedAddress = "mail@example.com"; // メール送信 client.sendEmail(new SendEmailRequest() .withSource(verifiedAddress) .withDestination(new Destination() .withToAddresses(verifiedAddress)) .withMessage(new Message() .withSubject(new Content("AWS Simple Email test")) .withBody(new Body() .withText(new Content("AWS SESのテストです。")) .withHtml(new Content("<h1>AWS SES</h1><p>テストです。</p>"))))); client.shutdown();
これでしばらく待つと、メールが届くはずです。
AWSにはTomcat互換のアプリケーションコンテナ、Elastic Beanstalkというサービスもあります。そこから今回のS3・SimpleDB・SESを利用すれば、簡単にいろいろなWebサービスを構築できそうですね。
余談ですが、AWSで頻繁に使われる3文字略語はどうも苦手です。全然覚えられません・・・。
形態素解析とは、文を最小の意味を持つ単位(形態素)に分解して、それぞれの形態素の品詞等を判別することを言います。今日はJavaで形態素解析をしてみましょう。
Javaから使える形態素解析ライブラリの一つにlucene-gosenがあります。特徴として、多くの形態素解析ライブラリがJNIを使用するのに対し、lucene-gosenはJNIを使用せず、Javaで完結していることが挙げられます。JNIを使用したくない/使用できない環境、例えばGoogle App Engine(GAE/J)などでも使うことができます。
準備はごく簡単です。プロジェクトのWebページから、辞書付きのjar(naist-chasenかipadic、どちらでも構いません)をダウンロードしてクラスパスに含めるだけです。
参考までに、現行バージョンは1.2.0でした。
早速使ってみましょう。コードは以下のようになります。
StringTagger tagger = SenFactory.getStringTagger(null); try { List<Token> list = new ArrayList<Token>(); list = tagger.analyze("すもももももももものうち", list); for (Token token : list) { System.out.println("======================================"); System.out.printf("surface: %s%n", token.getSurface()); System.out.printf("start: %s, length: %s%n", token.getStart(), token.getLength()); System.out.printf("cost: %s%n", token.getCost()); Morpheme morpheme = token.getMorpheme(); System.out.printf("basicForm: %s%n", morpheme.getBasicForm()); System.out.printf("cForm: %s, cType: %s%n", morpheme.getConjugationalForm(), morpheme.getConjugationalType()); System.out.printf("partOfSpeech: %s%n", morpheme.getPartOfSpeech()); System.out.printf("pron: %s, read: %s%n", morpheme.getPronunciations(), morpheme.getReadings()); System.out.printf("additionalInfo: %s%n", morpheme.getAdditionalInformation()); } } catch (IOException e) { e.printStackTrace(); }
これを実行すると以下の出力が得られます。
====================================== surface: すもも start: 0, length: 3 cost: 4636 basicForm: * cForm: *, cType: * partOfSpeech: 名詞-一般 pron: [スモモ], read: [スモモ] additionalInfo: null ====================================== surface: も start: 3, length: 1 cost: 6038 basicForm: * cForm: *, cType: * partOfSpeech: 助詞-係助詞 pron: [モ], read: [モ] additionalInfo: null ====================================== surface: もも start: 4, length: 2 cost: 10311 basicForm: * cForm: *, cType: * partOfSpeech: 名詞-一般 pron: [モモ], read: [モモ] additionalInfo: null ====================================== surface: も start: 6, length: 1 cost: 11713 basicForm: * cForm: *, cType: * partOfSpeech: 助詞-係助詞 pron: [モ], read: [モ] additionalInfo: null ====================================== surface: もも start: 7, length: 2 cost: 15986 basicForm: * cForm: *, cType: * partOfSpeech: 名詞-一般 pron: [モモ], read: [モモ] additionalInfo: null ====================================== surface: の start: 9, length: 1 cost: 16655 basicForm: * cForm: *, cType: * partOfSpeech: 助詞-連体化 pron: [ノ], read: [ノ] additionalInfo: null ====================================== surface: うち start: 10, length: 2 cost: 18621 basicForm: * cForm: *, cType: * partOfSpeech: 名詞-非自立-副詞可能 pron: [ウチ], read: [ウチ] additionalInfo: null
おお、うまく解析できましたね!
前回はまず使ってみるということで、単純なJavaオブジェクトからXMLへ変換する方法を解説しました。
今回は、XMLからJavaオブジェクトへ変換する方法と、また一歩進んでバインディングを制御するアノテーションについて解説します。
データ構造としては前回と同じものを使いましょう。
会社(従業員*)
従業員(名前,給料)
これらを表すJavaクラスも前回と基本は同じなのですが、オブジェクトの中身を見たいのでtoString()を追加しています。
Campanyクラス
public class Company { private List<Employee> employees; public List<Employee> getEmployees() { return employees; } public void setEmployees(List<Employee> employees) { this.employees = employees; } @Override public String toString() { String str = "Company [n"; if (employees != null) { for (Employee e : employees) { str += " " + e + "n"; } } str += "]"; return str; } }
Employeeクラス
public class Employee { private String name; private int salary; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } @Override public String toString() { return "Employee [name=" + name + ", salary=" + salary + "]"; } }
では、XMLをこのクラスのオブジェクトに変換するプログラムがどうなるか見てみましょう。実際的にはXMLをファイルから読み込むなどすると思いますが、この例ではその場で文字列を作っています。
public class Sample2 {
public static void main(String[] args) {
String xml = "<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"
+ "<company>"
+ " <employees>"
+ " <name>taro</name>"
+ " <salary>1000</salary>"
+ " </employees>"
+ " <employees>"
+ " <name>hanako</name>"
+ " <salary>800</salary>"
+ " </employees>"
+ "</company>";
// アンマーシャル
Company c = JAXB.unmarshal(new StringReader(xml), Company.class);
System.out.println(c);
}
}
これも前回同様ごく簡単ですね。JAXB#unmarshall()を呼ぶだけです。
実行すると以下の出力が得られます。
Company [ Employee [name=taro, salary=1000] Employee [name=hanako, salary=800] ]
これで無事にXMLからJavaオブジェクトへ変換できました。
さてこのように特に設定をしなくても、自動的にJavaオブジェクトとXMLとが(正確にはJavaクラスとXML Schemaとが)対応付けられて相互に変換できるわけですが、このままでは都合が悪いこともあります。
前回も触れたように、XML上、従業員の要素名が「employees」になっています。これはなぜかと言うとCompanyクラスのプロパティ名が「employees」だからです。もちろんプロパティ名を「employee」に変えれば要素名も変わるのですが、いつも都合良くプログラムを変えたり、XMLを変えたりできるとは限りません。
そこで登場するのがアノテーションです。Companyクラスを以下のようにしてみましょう。
public class Company { private List<Employee> employees; @XmlElementWrapper @XmlElement(name="employee") public List<Employee> getEmployees() { return employees; } public void setEmployees(List<Employee> employees) { this.employees = employees; } @Override public String toString() { String str = "Company [n"; if (employees != null) { for (Employee e : employees) { str += " " + e + "n"; } } str += "]"; return str; } }
名前からだいたい想像がつくかもしれませんね。このクラスを使ってJavaオブジェクトからXMLへの変換(前回のSample1)を行ってみると、以下のような出力が得られます。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <company> <employees> <employee> <name>taro</name> <salary>1000</salary> </employee> <employee> <name>hanako</name> <salary>800</salary> </employee> </employees> </company>
つまりそれぞれのアノテーションは以下のような働きをします。
Employeeクラスの方も少し変えてみましょう。せっかくXMLなのですから属性を使いたいときもあるでしょう。
public class Employee { private String name; private int salary; @XmlAttribute public String getName() { return name; } public void setName(String name) { this.name = name; } @XmlAttribute public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } @Override public String toString() { return "Employee [name=" + name + ", salary=" + salary + "]"; } }
これで再び変換を実行すると、出力は以下のように変わります。
<company> <employees> <employee salary="1000" name="taro"/> <employee salary="800" name="hanako"/> </employees> </company>
ここまでで、JavaオブジェクトとXMLとの間をかなり自由に行き来できるようになりました。
次回は先延ばしにしていたXML Schemaが登場する予定です。JAXBのコマンド(CLI)を使ってJavaクラスとXML Schemaの変換を行います。