Apache Thriftを始める

概要

ここでは、ブロッキング、ノンブロッキング、非同期といった複数のモードでThriftサービスとクライアントを作成する方法を説明する。例ではApache ThriftとJavaを使う。

事前準備

Apache Thriftをあらかじめインストールしておく必要がある。インストール方法はApache Thriftの概要を参照する。

Apache Thriftを使った開発

Thriftスクリプトでサービスを定義する

インストールが完了したらThrift IDLファイルを作成する。IDLを一度書けば、複数の言語向けのコードを生成できる。

namespace java com.devkuma.thrift.tutorial

typedef i64 long
typedef i32 int

service ArithmeticService {
  long add(1:int num1, 2:int num2),
  long multiply(1:int num1, 2:int num2),
}

namespaceはJavaのパッケージ構造、たとえばcom.devkuma.thrift.tutorialになる。Thriftでは32ビットと64ビットの整数型を使え、typedefで別名を付けられる。serviceは、サーバー間またはサーバー・クライアント間通信で公開するメソッドを持つクラスのように考えればよい。

ThriftコンパイラでJavaコードを生成する。

thrift --gen <language> <Thrift filename>
% thrift -r --gen java arithmetic.thrift

成功すると、gen-java/com/devkuma/thrift/tutorial/配下にArithmeticService.javaが生成される。

Javaプロジェクトを作成する

GradleでJavaプロジェクトを作成し、TSocketTTransportなどThrift通信に必要なライブラリを追加する。

dependencies {
    implementation 'org.apache.thrift:libthrift:0.18.1'
    implementation 'javax.annotation:javax.annotation-api:1.3.2'
    implementation 'ch.qos.logback:logback-classic:1.4.7'
    implementation 'org.slf4j:slf4j-api:2.0.7'
}

サービス実装を作成する

生成されたスケルトンインターフェースArithmeticService.Ifaceを実装する。

public class ArithmeticServiceImpl implements ArithmeticService.Iface {
    public long add(int num1, int num2) throws TException {
        return num1 + num2;
    }

    public long multiply(int num1, int num2) throws TException {
        return num1 * num2;
    }
}

Blocking Mode

ブロッキングモードでは、I/O処理中にサーバースレッドが待機する。例では、受信したリクエストをスレッドプールで処理するTThreadPoolServerを使う。

TServerSocket serverTransport = new TServerSocket(7911);
ArithmeticService.Processor processor =
    new ArithmeticService.Processor(new ArithmeticServiceImpl());
TServer server = new TThreadPoolServer(
    new TThreadPoolServer.Args(serverTransport).processor(processor));
server.serve();

クライアントはソケットを開き、プロトコルを作成し、ArithmeticService.Client経由でメソッドを呼び出す。

TTransport transport = new TSocket("localhost", 7911);
TProtocol protocol = new TBinaryProtocol(transport);
ArithmeticService.Client client = new ArithmeticService.Client(protocol);
transport.open();

long addResult = client.add(100, 200);
long multiplyResult = client.multiply(20, 40);

サーバーとクライアントを実行すると、次のような結果になる。

Add result: 300
Multiply result: 800

TBinaryProtocolは、サーバーとクライアント間で交換されるデータをエンコードする。ほかにTCompactProtocolTDebugProtocolTDenseProtocolTJSONProtocolなどを利用できる。

Non-blocking Mode

ノンブロッキングサーバーではTNonblockingServerSocketTNonblockingServerを使う。先に来たリクエストを処理している間も、別のリクエストを受け付けられる。

TNonblockingServerTransport serverTransport =
    new TNonblockingServerSocket(7911);
ArithmeticService.Processor processor =
    new ArithmeticService.Processor(new ArithmeticServiceImpl());
TServer server = new TNonblockingServer(
    new TNonblockingServer.Args(serverTransport).processor(processor));
server.serve();

クライアントは通常のTSocketTFramedTransportでラップする。ノンブロッキングサーバーでは、送信データを正しく構造化するため、クライアント側もフレーム付きトランスポートを使う必要がある。

TTransport transport = new TFramedTransport(new TSocket("localhost", 7911));
TProtocol protocol = new TBinaryProtocol(transport);
ArithmeticService.Client client = new ArithmeticService.Client(protocol);

ノンブロッキングサーバーとクライアントを実行しても、ブロッキング例と同じ計算結果を確認できる。

Asynchronous Mode

非同期モードでは、リクエスト完了時に呼ばれるコールバックをクライアントに登録する。非同期クライアントはブロッキングサーバーとは正しく通信できない。TNonblockingSocketと生成されたArithmeticService.AsyncClientを使う。

ArithmeticService.AsyncClient client =
    new ArithmeticService.AsyncClient(
        new TBinaryProtocol.Factory(),
        new TAsyncClientManager(),
        new TNonblockingSocket("localhost", 7911));

client.add(200, 400, new AddMethodCallback());

同時に複数の非同期処理を実行する場合は、それぞれ別のクライアントインスタンスが必要になる。1つのクライアントが実行中に別メソッドを実行しようとすると、ThriftはIllegalStateExceptionを発生させる。

参考