スケーラブルなクラウド・ソフトウェアの開発を Apache Thrift で単純化する

Apache Thrift は、スケーラブルなサービスをさまざまなプログラミング言語で開発できるフレームワークであり、クラウド環境内のさまざまなコンポーネント間のやりとりを明確に規定することができます。この記事では、Thrift に関連する概念 (複数のプログラミング言語の場合のリモート・プロシージャー・コールに対するインターフェース定義) について紹介した後、複数のプログラミング言語が使用されているクライアント・サーバー・アプリケーションで Thrift を使用する例について説明します。

M. Tim Jones, Independent author, Consultant

Photo of M.Tim JonesM. Tim Jones は組み込みソフトウェアのエンジニアであり、『Artificial Intelligence: A Systems Approach』、『GNU/Linux Application Programming』(現在、第 2 版です) や『AI Application Programming』(こちらも現在、第 2 版です)、それに『BSD Sockets Programming from a Multilanguage Perspective』などの著者でもあります。技術的な経歴は静止軌道衛星用のカーネル開発から、組み込みシステム・アーキテクチャーやネットワーク・プロトコル開発まで、広範にわたっています。彼はコロラド州ロングモン在住で、Intel に勤務するプラットフォーム・アーキテクトであり、執筆活動も行っています。



2013年 12月 12日

クラウド環境でのアプリケーション開発は、分散システムを開発する場合と同じような一連の問題 (ノード間の通信、プロトコル、公開されるサービスなどに関係する問題) に突き当たる傾向があり、さらに複数のプログラミング言語が使用されることで、開発が一層複雑になる可能性があります。プログラミング言語は、それぞれが独自の型システムを持っているため、グループ内のすべてのエンティティーが同じフォーマットで通信できるように、クライアントとサーバーとの間で型の変換が必要になります。また、同じ 1 つのプログラミング言語でシステムが開発される場合であっても、オブジェクト同士が効率的かつ明確な方法で通信することが求められます。使用するプログラミング言語が追加されると、システムの複雑さや作業の範囲が劇的に増加します。

分散システムの開発では、ソフトウェア要素間の通信が重要な問題です。この種のサービスが単純化されると、ソフトウェアではプロシージャー・コールの基礎となる分散型のやり取りを抽象化できるようになります。「リモート・プロシージャー・コール (RPC)」と呼ばれるものの最も初期の実装の 1 つは、Xerox が 1981年に発表した Courier というプロトコルです。このプロトコルは、Mesa プログラミング言語で作成されたアプリケーションがネットワーク経由で通信するためのプリミティブを提供していましたが、これらのアプリケーションでは RPC のシリアライズやデシリアライズを手作業で行う必要がありました。Courier が発表されて以降、この RPC の概念は多くのアプリケーションに広く採用されています。RPC の例には、これ以外にも Common Object Request Broker Architecture、Google Protocol Buffers (protobuf) パッケージ、Apache Avro など数多くあります。

2007年、Facebook はオープンソースの分野に RPC の新しい実装を発表しました。この実装はさまざまなプログラミング言語をほぼシームレスにサポートしており、現在は Apache の Thrift というプロジェクトになっています。Thrift は (サポートのレベルはまちまちですが)、Python、Ruby、Smalltalk、Haskell、Erlang、Java、Node.js、PHP、Go、D、C/C++ など、多くのプログラミング言語をサポートしています。

Apache Thrift の起源

Apache のオープンソース・プロジェクトとなる前、Thrift は Facebook が内部で使用するソフトウェア・ライブラリーであり、一連のコード生成ツールでもありました。Thrift の目標は、さまざまなプログラミング言語間の通信をハイパフォーマンスのソフトウェア・トランスペアレント・ブリッジを使用して効率的に行えるようにすることでした。

Thrift は、さまざまなプログラミング言語によるスケーラブルなサービスを開発するためのフレームワークであり、さまざまなプログラミング言語のコードを生成できるのみならず、ネットワーク・ベースのサービスの開発を単純化するためのソフトウェア・スタックも提供しています。では、Thrift の基礎にある考え方を探り、Thrift の機能のデモンストレーションを行いましょう。


共通言語としての Apache Thrift

Apache Thrift は、複数のプログラミング言語間で通信するアプリケーションの開発を単純化するソフトウェア・スタックを実装しています。図 1 は Apache Thrift のソフトウェア・スタックを簡単に示したものです。スタックの一番下には物理 (Physical) インターフェースがあります (物理インターフェースはネットワーク (Network) インターフェースの場合もあれば、ファイル (File) のように単純なものの場合もあります)。この物理インターフェースはスタックの上位レベルに影響を及ぼします。

図 1. Thrift のソフトウェア・スタック
Thrift のソフトウェア・スタックを示す図

Thrift のトランスポート (Transport) レイヤーとプロトコル (Protocol) レイヤーは何種類かあり、それらがデータの移動方法やフォーマットを規定しています (入出力ストリームのカプセル化を行うプロセッサー (Processor) レイヤーは 1 種類です)。例えば、プロトコル・レイヤーの TBinaryProtocol は、TSocket トランスポート上でネットワークとエンドポイントの間の通信をするために使用できる効率的な通信用バイナリー・プロトコルを規定しています。あるいは、プロトコル・レイヤーに TJSONProtocol を使用すると、共有メモリーを利用した通信用の TMemoryTransport をトランスポート・レイヤーに使用して JSON (JavaScript Object Notation) フォーマットで通信することができます。

最上位レベルには、Thrift の助けを借りて実装可能なサーバー・タイプがあります。サーバー・タイプの構成には、デバッグ用のシングル・スレッド・サーバー (TSimpleServer)、REST (Representational State Transfer) のような URL を提供する HTTP ベース・サーバー (THttpServer)、各受信リクエストに対してプロセスを分岐するマルチプロセス・サーバー (TForkingServer) などがあります。Thrift は、さまざまな手法 (プロトコルやトランスポート) での通信を単純化することのみならず、さまざまなサーバー・スタイルを使用したサーバー開発を単純化することも目的として作成されています。

図 1 には示してありませんが、Thrift がサポートするさまざまな言語間での通信を可能にする型システムがあります。このシステムでは、byte 型、short 型、int double 型、string 型などの他、コンテナー (リストやマップなど) や構造体などの高度な型もサポートされています。こうした汎用の型システムによって、データ通信のための共通のベースが提供されます。


Thrift をインストールする

Thrift は、ソースからインストールすることも (これは比較的簡単なプロセスです)、aptyum などのパッケージ・マネージャーを使用してインストールすることもできます。ソースからのインストールは、gzip で圧縮された tarball をダウンロードして解凍し、それを構成して、make でビルドするだけの単純な作業です。Thrift がビルドされると、makeinstall コマンドを使用してターゲットに簡単にインストールすることができます。選択するプログラミング言語によっては、他のパッケージが必要になる可能性もあります (例えば C++ を使用するサーバーやクライアントには Boost パッケージが必要です)。

最近のバージョンの Linux ディストリビューションを実行している場合には、そのディストリビューションのパッケージ・マネージャーを使用して Thrift コンパイラーをインストールできるかもしれません。例えば、Ubuntu の場合には以下のコマンドを使用することができます。

$ sudo apt-get install thrift-compiler

詳細についてはインストールの資料を参照してください。


RPC、型システム、インターフェース定義

図 1 のソフトウェア・スタックを見ると、このソリューションは非常に複雑に思えるかもしれません。Thrift には (複数プログラミング言語による RPC スキームに見られるような) 複雑な部分がありますが、実際には Thrift の使い方は非常に単純です。

Thrift を使用して RPC アプリケーションを作成するための最初のステップは、インターフェース定義ファイルを定義することです (図 2 を参照)。このファイルにより、公開される型とサービスを定義することができます。このファイルは言語に依存せず、Thrift の型と定義を使用します。Thrift コンパイラーはこのファイルを使用することで、選択された言語による (クライアント用とサーバー用の両方の) ソース・ファイルを生成します。

図 2. Thrift コンパイラーを使用してソースを生成する
Thrift コンパイラーを使用してソースを生成するプロセスを示す図

では、この概念を説明する単純な例を見てから、実際の例に移りましょう。Thrift コンパイラーの一般的な使い方は以下のとおりです。

$ thrift --gen <language> >.thrift file>

ネットワークを利用した計算サービスという単純なアプリケーションを考えてみましょう。このサービスは単純な API を使用して基本的な数学計算サービスをリモート・クライアントに提供します。このサービスを定義するために、リスト 1 の Thrift ファイルを作成します。このファイルは、さまざまなプログラミング言語のユーザーのための名前空間を提供します (py は Python を表し、rb は Ruby を表します)。次に、公開したいサービスを定義します。Thrift の定義を使用することにし、service キーワードと、サービスを表す名前 (この場合は MyMath) を指定します。サービスの定義はプログラミング言語に依存しない形で行います。このサービスは、公開された 4 つの関数で構成されており、各関数は公開されて一連の入力値を引数に取って結果を返します。

リスト 1. インターフェースを定義する .thrift ファイルの例 (proj.thrift)
# proj.thrift

namespace py demoserver
namespace rb demoserver

/* All operands are 32-bit integers called a Value */
typedef i32 Value
typedef i32 Result

/* Math service exposes an some math function */
service MyMath
{
  Result add( 1: Value op1, 2: Value op2 ),
  Result mul( 1: Value op1, 2: Value op2 ),
  Result min( 1: Value op1, 2: Value op2 ),
  Result max( 1: Value op1, 2: Value op2 )
}

このインターフェース定義をコンパイルしてコードにするために、以下のように所望のプログラミング言語 (この場合は Python) を Thrift に対して定義します。

$ thrift --gen py proj.thrift

このプロセスより、型、定数、コードを定義するいくつかのファイルが含まれた gen-py というサブディレクトリーが作成されます。生成されたコードで構成される、このサブディレクトリーの全内容を以下に示します。興味深い点は、型用に生成されたコード (ttypes.py) とサービスのソフトウェア・スタック用に生成されたコード (MyMath.py) があることです。また、新しいサービスをテストする手段としてこの後すぐ説明する MyMath-remote にも注目してください。

$ ls gen-py/*
gen-py/__init__.py

gen-py/demoserver:
constants.py  __init__.py  __init__.pyc  MyMath.py  MyMath.pyc  MyMath-remote  
ttypes.py  ttypes.pyc
$

ただし、Thrift のすべてをデモンストレーションし、Thrift が提供する機能をよく理解するには、1 つの言語では十分ではありません。Ruby で作成されたクライアントを考えてみてください。Ruby クライアントをサポートするには、Thrift コンパイラーを使用して Ruby 用の Thrift コード・セットを生成する必要があります。

$ thrift --gen rb proj.thrift

新たに作成された gen-rb サブディレクトリーの中を見てみると、Ruby 実装のためのファイルがあることがわかります。言語によって Thrift ソフトウェア・スタックの実装は異なり、以下を見るとわかるように、生成されるファイルは異なるものの、型、定数、サービスの実装が生成されるという点は同じです。

$ ls gen-rb/
my_math.rb  proj_constants.rb  proj_types.rb
$

以上でサービス・インターフェースの定義ファイルに関する Thrift の作業は完了です。今度は Python で実装したサービスを使用するサーバーの実装に移りましょう。


Thrift サーバーを作成する

リスト 1 のサービス定義に基づいて、これらのサービスを Python で実装します。リスト 2 に示すように、この Python サーバーは極めて単純です。まず、Thrift が生成するコードに Python モジュールのパスを追加することで Python モジュールを公開し、その後でこれから使用する必要なモジュールをインポートします。これらのモジュールが図 1 に示したソフトウェア・スタックであることに注意してください。Thrift から作成された基底クラス (demoserver.MyMath.Iface) を基に、数学計算の実装の派生クラスを定義します。このクラスにより、単純に Python で構成されたサービスの実装を定義します。

リスト 2 の最後の部分はこのスクリプトの主要部分であり、Thrift スタックのコンポーネントを組み立てています。ここではプロセッサーを作成し、それを MathImpl クラスで初期化しています。また、Thrift スタックのプロトコル要素とトランスポート要素を作成し、TThreadedServer の中で Thrift スタックを組み立てています。最後にサーバー・インスタンスの serve() メソッドを呼び出し、スレッド化されたリクエストを処理できるようにしています。

リスト 2. Python で作成した Thrift サーバー (server.py)
#!/usr/bin/python

import sys

sys.path.append('./gen.py')

from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer

import demoserver.MyMath

class MathImpl( demoserver.MyMath.Iface ):
	def add( self, op1, op2 ):
		return op1 + op2
	def mul( self, op1, op2 ):
		return op1 * op2
	def max( self, op1, op2 ):
		return max([op1, op2])
	def min( self, op1, op2 ):
		return min([op1, op2])

if __name__ == '__main__':

	processor = demoserver.MyMath.Processor( MathImpl() )
	transport = TSocket.TServerSocket( port = 18181 )
	tbfactory = TTransport.TBufferedTransportFactory()
	pbfactory = TBinaryProtocol.TBinaryProtocolFactory()

	server = TServer.TThreadedServer( processor, transport, tbfactory, pbfactory )

	print 'Starting the Math Server...'

	server.serve();

Python サービス用に MyMath-remote ユーティリティーが作成されたことを思い出してください。このファイルを Thrift が生成してくれたことにより、Python サーバーをテストすることができます。このテスト・アプリケーションを使用するには、まず生成されたファイルを Python のパスに公開する必要があります。

$ export PYTHONPATH=$PYTHONPATH:./gen-py

この定義が終わると、このコマンド・ユーティリティーを使用してサーバーをテストすることができます。リスト 3 には、このユーティリティーのヘルプ情報とともに、このアプリケーションの使い方の例を示しています。このユーティリティーを使用する際には、ホストの定義 (サーバーの場所とポート番号)、呼び出し対象の関数、一連の引数を指定する必要があります。先ほど作成したサーバーは、クライアントと同じノード上で実行されるため、ここでは localhost、先ほど定義したポート番号、そして対象とするテスト関数およびその引数を指定しています。

リスト 3. 生成されたテスト・アプリケーションを使用する
$ ./gen-py/demoserver/MyMath-remote --help

Usage: ./gen-py/demoserver/MyMath-remote [-h host[:port]] 
		[-u url] [-f[ramed]] function [arg1 [arg2...]]

Functions:
  Result add(Value op1, Value op2)
  Result mul(Value op1, Value op2)
  Result min(Value op1, Value op2)
  Result max(Value op1, Value op2)

$ ./gen-py/demoserver/MyMath-remote -h localhost:18181 max 5 7
7
$ ./gen-py/demoserver/MyMath-remote -h localhost:18181 mul 9 8
72
$

この自動生成されたテスト・アプリケーションは、クライアントを準備する前に、作成したサーバーをテストする便利な手段になります。クライアントの話題が出たところで、このサーバーに対応する Thrift クライアントについて見て行きましょう。このクライアントは Ruby で実装します。


Thrift クライアントを作成する

上記で作成したサーバーの動作を確認するために、次は Thrift と Ruby 言語を使用してクライアントを作成します。このプロセスは、複数の異なるプログラミング言語 (ここでは Python と Ruby) で Thrift を使用する場合のプログラミング言語ごとの違いを説明する上で役立つとともに、複数のプログラミング言語を使用した素晴らしいテスト環境を提供することにもなります。

リスト 4 は、Ruby での単純な Thrift クライアントのソース全体を示しています。まず始めに、生成される Thrift の Ruby コードをロードするためのパスを定義することにより、Thrift モジュールが見えるようにします。Python によるサーバーの場合と同じように、この Ruby クライアントは Ruby モジュール内に生成されるコードからソフトウェア・スタックを作成しています。ここでは (サーバーのノードとポート番号を指定して初期化される) ソケットを作成し、そのソケットをトランスポート・レイヤーに接続しています。このソケットをプロトコル・インスタンスとして初期化し、さらにプロトコル・インスタンスをサービスとして初期化します。

(transport インスタンスの open メソッドを使用して) サーバーへの接続を作成すると、背後でサーバーに対してシリアライズされているサービス関数を呼び出すことができます。それが終わるとトランスポートが閉じられ、それによってサーバーのスレッドが解放されます。

リスト 4. Ruby で作成した Thrift クライアント (client.rb)
# Make thrift-generated code visible
$:.push('./gen-rb')

require 'thrift'
require 'my_math'

begin

	# Build up the Thrift stack
	transport = Thrift::BufferedTransport.new(Thrift::Socket.new('localhost', 18181))
	protocol = Thrift::BinaryProtocol.new(transport)
	client = Demoserver::MyMath::Client.new(protocol)

	transport.open()

	# Try an add operation
	result = client.add( 1, 5 )
	puts result.inspect

	# Try a max operation
	result = client.max( 9, 7 )
	puts result.inspect

	transport.close()

end

稼働中の Python サーバーに対してこのサンプル・クライアントを実行すると、リスト 5 に示す出力が得られます。ここまで紹介してきた、言語によって異なる抽象化のコード量はわずかです。Thrift が生成したコードを見ると、スタック・サービス (MyMath.py、my_math.rb) を使用していることがわかり、こうした単純なサービスの割には大がかりなことをしています。

リスト 5. Ruby クライアントをテストする
$ ruby client.rb 
6
9
$

このように、Thrift を使用することにより、サービスを公開するクライアント・アプリケーションやサーバー・アプリケーションを容易に作成することができます。また Thrift を使用するとインターフェース定義フォーマットを統一できるため、このようなアプリケーションを複数のプログラミング言語で作成する場合であっても作業が容易になります。


Thrift の通信をセキュアにする

本番アプリケーションや Web アクセスがあるアプリケーションの場合は、セキュリティーが重要です。Thrift は、トランスポート・レイヤーでの TSSLTransportFactory クラスを使用したセキュアな通信をサポートすることができます。このトランスポートの実装には、SSL (Secure Socket Layer) 機能を持つ TSocketTServerSocket の実装がラップされています。RSA 鍵ペアとの組み合わせにより、Thrift クライアントと Thrift サーバーとの間で確実かつセキュアに通信を行うことができます。


さらに詳しく調べてください

Thrift は、複数のアプリケーション環境およびそれらの通信を橋渡しする優れた手段ですが、気軽に使用できるものではありません。ドキュメントは少ない上に古く、またプログラミング言語ごとにサポートのレベルが異なるため、苛立たしい思いをさせられることがあります。Thrift のドキュメントには、サポートされている多くのプログラミング言語で例が示されておらず、生成されたコードを念入りに調べない限り動作を理解することができません。

Thrift にはこのような未熟な部分があるものの、このユーティリティーによって、通信、シリアライズ、サービス作成のための詳細なコードの大部分を自動生成することができます。Thrift が裏で行うこうした作業のおかげで、アプリケーションに集中することができるとともに、複数のプログラミング言語を使用する分散アプリケーションを短時間で作成することができます。

参考文献

学ぶために

  • Apache Thrift の Web サイトは、Thrift に関する主要な情報源です。Thrift の最新リリースを入手して、そのドキュメントとチュートリアルから Thrift を使用する方法について学んでください。
  • Thrift のインストールは、比較的容易に行える手順になっています。問題が生じた場合や、Thrift を使用するにあたっての要件がよくわからない場合は、こちらのビルド用件のリストを調べてください。
  • Facebook の Mark Slee 氏、Aditya Agarwal 氏、Marc Kwiatkowski 氏による「Thrift: Scalable Cross-Language Services Implementation」は、Thrift の概要、Thrift を作成するに至った動機、興味深い設計上のトレードオフと実装の詳細の一例について紹介する素晴らしい論文です。
  • Meet the Extensible Messaging and Presence Protocol (XMPP)」(M. Tim Jones 著、developerWorks、2009年9月) では、XMPP の概要、インスタント・メッセージング・プロトコルとしての XMPP のアプリケーションについて紹介しています。XMPP は、シリアライズ、そしてクライアントとサーバーとの間の通信を管理するために、XML を使用しています。XMPP は、Thrift と同じ要件に対応するわけではありませんが、複数のプログラミング言語によるアプリケーション間の通信をサポートする、Thrift とは別の例です。
  • クラウド開発者向けリソースを提供している developerWorks のサイトで、クラウドにデプロイするためのプロジェクトを構築している、アプリケーションおよびサービスの開発者の知識と経験を見つけて共有してください。
  • Twitter で developerWorks をフォローしてください。また、この記事の著者である M. Tim Jones を Twitter でフォローすることもできます。
  • 初心者向けの製品のインストールおよびセットアップから熟練開発者向けの高度な機能に至るまで、さまざまに揃った developerWorks デモを見てください。

議論するために

  • developerWorks コミュニティーに参加してください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者によるブログ、フォーラム、グループ、Wiki を調べることができます。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Cloud computing
ArticleID=956650
ArticleTitle=スケーラブルなクラウド・ソフトウェアの開発を Apache Thrift で単純化する
publish-date=12122013