フレームワーク 「 Thrift 」 調査報告

cut070626.jpg

はじめまして。
開発本部アルバイトの山本です。

今回はアメリカの SNS サイト Facebook で開発されたオープンソースの RPC フレームワーク Thrift をご紹介します。Facebook では、検索サービス、ログ管理など随所に Thrift が活用されています。

Thrift とは?

サービスのある部分は PHP で開発し、別の部分には Java を使いたい、などという場合に、それぞれの部品を RPC で容易に接続するために開発されたフレームワークです。
C++, Java, PHP, Ruby, Python に対応しています。

Thrift は次の 2 つの部分から成ります。

コンパイラ

各モジュール間のインターフェースを独自の言語で記述してコンパイラに与えると、 RPC サーバー / クライアントのベースになるコードを自動生成してくれます。

ライブラリ

独自の RPC プロトコルの実装、通信ライブラリ、数種類のサーバー、マルチスレッド関連のライブラリが含まれています。言語によっては、実装されていないライブラリがあります。

プログラマは RPC のインターフェースを記述し、コンパイラに与えてコードを自動生成させ、それを利用してサービスの中身をコーディングするだけで RPC を用いたサービスを構築することができます。

RPC を実現する方法としては SOAP や COBRA が有名です。これらと比較すると、軽量で自由度が高いのが Thrift の特徴です。また、バージョニング ( 後述 ) に対応しています。

使いかた

Hello World!

お約束の "Hello World!" を作成してみましょう。 まず、下のようにインターフェースを記述します。

#!/usr/bin/thrift -cpp -java -php -py -rb

service Hello
{
  string hello(1: string name)
}

Java でクラスを作っているような感覚ですね。 今回は使っていませんが、サービスは他のサービスを継承することもできます。 これを Thrift コンパイラに与えると、RPC サーバー / クライアントの元になるコードが各言語で生成されます。

./hello.thrift

あとは、これと付属のライブラリを利用して以下のようにメインのコードを書くだけです。 サーバー側には Ruby, クライアント側には PHP を使ってみました。

( サーバー側 )
class HelloHandler
  include Hello::Iface

  def hello(name)
    return "Hello, " + name
  end
end

handler = HelloHandler.new
processor = Hello::Processor.new(handler)
transport = TServerSocket.new(9090)
server = TSimpleServer.new(processor, transport)
server.serve()
( クライアント側 )
$host = 'localhost';
$port = 9090;

$socket = new TSocket($host, $port);
$bufferedSocket = new TBufferedTransport($socket, 1024, 1024);
$transport = $bufferedSocket;
$protocol = new TBinaryProtocol($transport);
$testClient = new HelloClient($protocol);

$transport->open();

header('Content-type: text/plain');

$res = $testClient->hello("World");
echo "$res\n";

$transport->close();

こう書くだけでクライアントからサーバーに "World" という文字列が渡り、"Hello, World" という返事が返ってきます。初期化やコネクション確立のためのコードを除けば、普通に関数を呼び出している感覚で簡単にサーバーと通信できていることが分かると思います。

データ型

インターフェース定義で利用できるデータ型は以下のとおりです。

  • 基本データ型
    bool, byte, i16, i32, i64, double, string
  • 構造体
  • コンテナ
    list, set, map
  • 例外

各言語のネイティブの型がそのまま使えるように配慮されているので、特別な型を使ったり直列化のコードを書いたりする必要はありません。
例えば list は、 C++ の中では std::vector、Java の中では ArrayList、スクリプト言語の中では普通の配列として見えるようになっています。

メソッドの非同期 ( async ) 呼び出しもサポートされています。
ただし戻り値は void でなければなりません。
また、必ず呼び出されるという保証はないので注意が必要です。

バージョニング

仕様変更によって構造体のメンバが追加されたり、メソッドの引数が変更されたりしたときに、クライアント側とサーバー側でバージョンの不一致が起きた場合、予期せぬトラブルを引きおこします。
これを防ぐため、 Thrift のインターフェース定義では構造体のメンバやメソッドの引数に番号 ( field identifier ) を付けることができ、実行時にこの番号をチェックしてくれます。
上の例でいうと、メソッド hello の引数に付いている "1:" というのがそれです。
知らない引数が届いたときは無視され、足りない引数があったときはデフォルト値が使われます。
引数の不足をプログラマが検出できる仕組みもあります。
この機能によってバージョンアップ時の安全性が向上し、よりロバストなサービスを構築することができます。

サンプルアプリケーション

評価用に簡単な Web チャットを作ってみました。
先ほどと同じように、フロントエンドは PHP、バックエンドは Ruby という構成です。

chat.jpg

RPC の部分は意識する必要がまったくなく、Web チャットとしてのメッセージ管理や HTML 出力のコードに集中できたので、短時間で作成することができました。
困ったのはライブラリの詳しいドキュメントがないことくらいでしょうか。
しかし、この程度ならチュートリアルを真似て書けば動いてくれます。
また、PHP の場合は自動生成されたコードを特定の場所に置く必要があります。気づかないと混乱するかもしれないので、README をよく読みましょう。

問題点

ライブラリにバグはないのか?

ちょっと不安もありますが、Facebook で実際に使われているということなので、それなりに安定していると考えていいのではないかと思います。

情報不足

2007 年 4 月 1 日に公開されたばかりで、付属のドキュメントやチュートリアル以外の情報はほとんどありません。もちろん日本語の情報は皆無と言っていいほどです。しかし付属のドキュメントは比較的しっかりしているので、これだけでも何とかなるでしょう。

まとめ

Thrift を使えば、さまざまな言語を適材適所で利用し、スケーラブルなサービスをより効率よく開発することができます。
まだ公開されたばかりのフレームワークですが、一度試してみる価値はあるのではないでしょうか。

関連するエントリー