Java+Thriftで簡単なKVSを作る例

Thriftとは

詳しいことはググればわかる。半年ぐらい前には「もうメンテされないかも」とか言われていたのに、Cassandraで使われてるせいか開発が活発化しており、気が付いたら0.5.0までバージョンアップしてきている。

Java用のThriftモジュールのインストール

  • Apache Thriftから最新版(0.5.0)のtar.gzをダウンロードしてきて、アーカイブを展開
  • cd lib/java
  • ant

これでビルドされているはず。Thriftアプリを作る際には以下のjarを使う。

  • libthrift.jar
  • build/ivy/lib/commons-lang-2.5.jar ←不要かも
  • build/ivy/lib/log4j-1.2.14.jar
  • build/ivy/lib/slf4j-api-1.5.8.jar
  • build/ivy/lib/slf4j-log4j12-1.5.8.jar

Thriftコンパイラのインストール

Windows版のバイナリはApache Thriftで配布されている。
ソースからビルドする場合は、compiler/cppに入ってるものをビルドする。私はWindowsで作業してるのでビルドしなかったが、READMEにビルド手順が書いてあるので、詳しくはそれを参照。

Thrift IDLからRPCコード生成(コンパイル)

とりあえずKVS的なIDLを作成。名前は適当にexample.thriftとでもしておく。

namespace java example

exception AlreadyExistException {
  1: string message
}

service SimpleKVS
{
  // set だとThrift組み込みデータ型とバッティングする
  void put(1:string key, 2:string value),

  string get(1:string key) throws (1:AlreadyExistException error)
}

IDLをコンパイル。コンパイルすると、gen-javaにソースコードが生成される。

$ thrift-0.5.0 --gen java example.thrift

プログラムの実装

ビルドしたThriftのjarをビルドパスに登録したり、IDLから生成したソースコードをコピーしたら、サーバサイド、クライアントサイドを実装する。
socketのopen/close処理が怪しいので、後でコードを修正するかも。

サーバサイドプログラム
package example;

import java.util.HashMap;
import org.apache.thrift.TException;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
import org.apache.thrift.transport.TTransportException;

public class Server {
    static class SimpleKVSHandler implements SimpleKVS.Iface {
        private HashMap<String, String> data = new HashMap<String, String>();

        @Override
        public void put(String key, String value)
                throws AlreadyExistException, TException {
            if (data.containsKey(key)) throw new AlreadyExistException(key);
            data.put(key, value);
        }

        @Override
        public String get(String key) throws TException {
            return (data.containsKey(key)) ? data.get(key) : "";
        }
    };

    public static void main(String[] args) {
        try {
            SimpleKVSHandler handler = new SimpleKVSHandler();
            SimpleKVS.Processor processor = new SimpleKVS.Processor(handler);
            TServerTransport transport = new TServerSocket(9090);
            TServer server = new TThreadPoolServer(processor, transport);
            server.serve();
        } catch (TTransportException e) {
            e.printStackTrace();
        }
    }
}
クライアントサイドプログラム
package example;

import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;

public class Client {
    public static void main(String[] args) {
        TTransport transport = new TSocket("localhost", 9090);
        TProtocol protocol = new TBinaryProtocol(transport);
        try {
            transport.open();
            SimpleKVS.Client client = new SimpleKVS.Client(protocol);
            // set
            System.out.println("set(foo, bar)");
            client.put("foo", "bar");
            // get
            String result1 = client.get("foo");
            System.out.println("get(foo): " + result1);
            // 既に存在するkeyにset
            System.out.println("set(foo, bar)");
            client.put("foo", "bar");
        } catch (AlreadyExistException e) {
            e.printStackTrace();
        } catch (TTransportException e) {
            e.printStackTrace();
        } catch (TException e) {
            e.printStackTrace();
        } finally {
            transport.close();
        }
    }
}

動作確認

サーバを起動してからクライアントを起動すると以下のように出力されるはず。

set(foo, bar)
get(foo): bar
set(foo, bar)
AlreadyExistException(message:foo)
        at example.SimpleKVS$put_result.read(SimpleKVS.java:942)
        at example.SimpleKVS$Client.recv_put(SimpleKVS.java:113)
        at example.SimpleKVS$Client.put(SimpleKVS.java:87)
        at example.Client.main(Client.java:25)