Apache ThriftでRPCを試す
Apache ThriftでRPCを試したメモです。
Apache ThriftでRPCを試してみました。
Apache Thrift
Apache ThriftはRPC(リモートプロシージャコール)のフレームワークです。
facebookが開発し、その後Apache Projectに移されたようです。
Apache Thrift - Home
ThriftではIDL(インタフェース記述言語)でインターフェースを定義して、
各言語ごとにソースコードを生成して実装していきます。
サポートされている言語は現時点で25以上あります。
Apache Thrift - Language and Feature Matrix
Install
下記にMacでのインストール手順が載っています。
Apache Thrift - Index of install/
Apache Thrift - OS X Install
事前にboostとlibeventをインストールした後に、各言語(今回はjavaとperl)で必要なライブラリもインストールします。
そしてThriftをインストールします。
boost
boostをインストールします。
Boost Downloads
$ curl -L -O https://dl.bintray.com/boostorg/release/1.65.1/source/boost_1_65_1.tar.gz $ tar xvzf boost_1_65_1.tar.gz $ cd boost_1_65_1 $ ./bootstrap.sh $ sudo ./b2 threading=multi address-model=64 variant=release stage install
libevent
libeventをインストールします。
libevent
$ curl -L -O https://github.com/libevent/libevent/releases/download/release-2.1.8-stable/libevent-2.1.8-stable.tar.gz $ tar xvzf libevent-2.1.8-stable.tar.gz $ cd libevent-2.1.8-stable $ ./configure --prefix=/usr/local $ make GEN test/rpcgen-attempted GEN include/event2/event-config.h /Applications/Xcode.app/Contents/Developer/usr/bin/make all-am CC buffer.lo CC bufferevent.lo CC bufferevent_filter.lo CC bufferevent_pair.lo CC bufferevent_ratelim.lo CC bufferevent_sock.lo CC event.lo CC evmap.lo CC evthread.lo CC evutil.lo CC evutil_rand.lo CC evutil_time.lo CC listener.lo CC log.lo CC select.lo CC poll.lo CC kqueue.lo CC signal.lo CC evdns.lo CC event_tagging.lo CC evrpc.lo CC http.lo CCLD libevent.la CCLD libevent_core.la CCLD libevent_extra.la CC evthread_pthread.lo CCLD libevent_pthreads.la CC libevent_openssl_la-bufferevent_openssl.lo bufferevent_openssl.c:66:10: fatal error: 'openssl/bio.h' file not found #include <openssl/bio.h> ^~~~~~~~~~~~~~~ 1 error generated. make[1]: *** [libevent_openssl_la-bufferevent_openssl.lo] Error 1 make: *** [all] Error 2
makeでエラーが出てしまいました。
どうやらopensslのバージョンが古いらしい。
opensslをアップデート。
$ openssl version OpenSSL 0.9.8zh 14 Jan 2016 $ brew install openssl $ brew link openssl --force Warning: Refusing to link: openssl Linking keg-only openssl means you may end up linking against the insecure, deprecated system OpenSSL while using the headers from Homebrew's openssl. Instead, pass the full include/library paths to your compiler e.g.: -I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib
またエラーが出た。
opensslはlink --force出来なくなったようです。
ということでpathに設定。
$ vi .zshrc # 下記を追記 export export PATH=/usr/local/Cellar/openssl/1.0.2l/bin:$PATH $ source .zshrc $ openssl version OpenSSL 1.0.2l 25 May 2017
無事opensslをバージョンアップできた。
configureにLDFLAGSとCPPFLAGSを指定して再度ビルド。
https://stackoverflow.com/questions/33165174/fatal-error-openssl-bio-h-file-not-found
$ ./configure --prefix=/usr/local LDFLAGS='-L/usr/local/Cellar/openssl/1.0.2l/lib' CPPFLAGS='-I/usr/local/Cellar/openssl/1.0.2l/include' $ make $ sudo make install
インストール出来ました。
java requirements
https://thrift.apache.org/docs/install/#language-requirements
Language requirementsを確認すると、
javaで必要なのはjava1.7以上とApache antです。
javaは1.8が入っているので、antだけインストールします。
$ curl -L -O http://ftp.yz.yamagata-u.ac.jp/pub/network/apache//ant/binaries/apache-ant-1.10.1-bin.tar.gz\n $ tar xvzf apache-ant-1.10.1-bin.tar.gz $ sudo mv apache-ant-1.10.1 /usr/local $ cd $ vi .zshrc # 下記を追記 export ANT_HOME=/usr/local/apache-ant-1.10.1 export PATH=$PATH:$ANT_HOME/bin $ sourcr .zshrc $ which ant /usr/local/apache-ant-1.10.1/bin/ant
perl requirements
https://thrift.apache.org/docs/install/#language-requirements
Language requirementsを確認すると、
perlで必要なのはBit::VectorとClass::Accessorです。
ライブラリが存在するか確認。
(何も表示されなければ入っている)
$ perl -MClass::Accessor -e '' Can't locate Class/Accessor.pm in @INC (you may need to install the Class::Accessor module) (@INC contains: /usr/local/Cellar/perl/5.26.0/lib/perl5/site_perl/5.26.0/darwin-thread-multi-2level /usr/local/Cellar/perl/5.26.0/lib/perl5/site_perl/5.26.0 /usr/local/Cellar/perl/5.26.0/lib/perl5/5.26.0/darwin-thread-multi-2level /usr/local/Cellar/perl/5.26.0/lib/perl5/5.26.0 /usr/local/lib/perl5/site_perl/5.26.0). BEGIN failed--compilation aborted. $ perl -MBit::Vector -e '' Can't locate Bit/Vector.pm in @INC (you may need to install the Bit::Vector module) (@INC contains: /usr/local/Cellar/perl/5.26.0/lib/perl5/site_perl/5.26.0/darwin-thread-multi-2level /usr/local/Cellar/perl/5.26.0/lib/perl5/site_perl/5.26.0 /usr/local/Cellar/perl/5.26.0/lib/perl5/5.26.0/darwin-thread-multi-2level /usr/local/Cellar/perl/5.26.0/lib/perl5/5.26.0 /usr/local/lib/perl5/site_perl/5.26.0). BEGIN failed--compilation aborted.
どちらも入っていないので、cpanでインストール。
$ sudo perl -MCPAN -e shell cpan[1]> install Class::Accessor cpan[2]> install Bit::Vector $ perl -MClass::Accessor -e '' $ perl -MBit::Vector -e ''
Thrift
事前準備が終わったので、やっとThriftをインストールします。
$ curl -L -O http://ftp.riken.jp/net/apache/thrift/0.10.0/thrift-0.10.0.tar.gz $ tar -xvzf thrift-0.10.0.tar.gz $ cd thrift-0.10.0/ $ ./configure --prefix=/usr/local/ --with-boost=/usr/local --with-libevent=/usr/local
するとbisonのversionのチェックでエラーになりました。
checking whether make sets $(MAKE)... (cached) yes checking for bison... yes checking for bison version >= 2.5... no configure: error: Bison version 2.5 or higher must be installed on the system!
bisonをアップデートします。
$ bison -V bison (GNU Bison) 2.3 $ brew install bison $ brew link bison --force $ bison -V bison (GNU Bison) 3.0.4
再度ビルドします。
$ ./configure --prefix=/usr/local/ --with-boost=/usr/local --with-libevent=/usr/local $ make $ sudo make install $ thrift -version Thrift version 0.10.0
インストール出来ました。
※補足
makeで下記の様なエラーが出る場合は、またopensslの問題なので、LDFLAGSとCPPFLAGSを指定するとうまくいくと思います。
libtool: compile: g++ -DHAVE_CONFIG_H -I. -I../.. -I../../lib/cpp/src/thrift -I../../lib/c_glib/src/thrift -I/usr/local/include -I/usr/local/include -I./src -D__STDC_LIMIT_MACROS -Wall -Wextra -pedantic -g -O2 -std=c++11 -MT src/thrift/transport/TSSLSocket.lo -MD -MP -MF src/thrift/transport/.deps/TSSLSocket.Tpo -c src/thrift/transport/TSSLSocket.cpp -fno-common -DPIC -o src/thrift/transport/.libs/TSSLSocket.o src/thrift/transport/TSSLSocket.cpp:41:10: fatal error: 'openssl/err.h' file not found #include <openssl/err.h> ^~~~~~~~~~~~~~~
$ make LDFLAGS='-L/usr/local/Cellar/openssl/1.0.2l/lib' CPPFLAGS='-I/usr/local/Cellar/openssl/1.0.2l/include'
Thrift
IDL(interface description language)
ThriftのIDLファイルを作成してインターフェースを定義する必要があります。
下記に仕様が載っています。
Apache Thrift - Interface Description Language (IDL)
Apache Thrift - Thrift Type system
MyService.thriftというファイルを作成して定義してみます。
MyService.thrift
// (1) namespace java com.example.thrift namespace perl exampleThrift // (3) enum Country { AMERICA=0, JAPAN=1, CANADA=2 } // (2) struct Person { 1: required string name; 2: required i32 age; 3: required Country country; 4: string hobby; } // (4) service PeopleService { list<Person> searchByName(1:string query) }
(1)
namespaceを定義します。これは言語ごとに設定する必要があります。
それぞれjavaとperlのパッケージ名になります。
(2)
structでPersonの構造体を定義します。
name, age, country, hobbyのそれぞれのフィールドを型を指定して定義。
(3)
enumで列挙型を定義します。
Countryを定義し、Personのcountryフィールドで指定しています。
(4)
serviceでインターフェースを定義します。
関数名、引数、戻り値を指定します。
string queryを引数に取り、Personのlistを返すsearchByNameを定義します。
Thriftコンパイル
Thriftコンパイルを使用して作成したIDLファイルから各言語のソースを生成します。
今回はjavaとperlで生成します。
生成方法はthriftコマンドの後に--genオプションで言語を指定し、thriftファイルを指定します。
thrift --gen <language> <Thrift filename>
javaのソースを生成。
gen-javaというディレクトリが作成され、ソースコードが生成されています。
$ thrift --gen java MyService.thrift $ ll gen-java/com/example/thrift/ total 136 drwxr-xr-x 5 xxxxxx staff 170B 12 11 20:42 . drwxr-xr-x 3 xxxxxx staff 102B 12 11 20:42 .. -rw-r--r-- 1 xxxxxx staff 938B 12 11 20:42 Country.java -rw-r--r-- 1 xxxxxx staff 37K 12 11 20:42 PeopleService.java -rw-r--r-- 1 xxxxxx staff 21K 12 11 20:42 Person.java
perlのソースを生成。
gen-perlというディレクトリが作成され、ソースコードが生成されています。
$ thrift --gen perl MyService.thrift $ ll gen-perl/exampleThrift total 32 drwxr-xr-x 5 xxxxxx staff 170B 12 12 19:45 . drwxr-xr-x 3 xxxxxx staff 102B 12 12 19:45 .. -rw-r--r-- 1 xxxxxx staff 209B 12 12 19:45 Constants.pm -rw-r--r-- 1 xxxxxx staff 7.1K 12 12 19:45 PeopleService.pm -rw-r--r-- 1 xxxxxx staff 3.1K 12 12 19:45 Types.pm
下記のようにいっぺんに生成も出来ます。
$ thrift --gen java --gen perl MyService.thrift
生成されたコードから各言語でサーバとクライアントを実装していきます。
各言語でのサンプルは下記に載っています。
https://thrift.apache.org/tutorial/#examples-clients-and-servers
Thrift Server(Java)
javaでサーバを作成してみます。
新規のMavenプロジェクトを作成し、pom.xmlにlibthriftを追加します。
pom.xml
<dependency> <groupId>org.apache.thrift</groupId> <artifactId>libthrift</artifactId> <version>0.10.0</version> </dependency>
Thriftコンパイルで生成したコードを配置します。
生成されたPeopleService.javaの中に、下記のようにIfaceインターフェースが定義されているので、実装します。
PeopleService.java
public class PeopleService { public interface Iface { public java.util.List<Person> searchByName(java.lang.String query) throws org.apache.thrift.TException; } : : }
Server.javaを作成し、実装してみます。
Server.java
public static void main(String[] args) { try { Processor<PeopleService.Iface> processor = new Processor<>(new PeopleService.Iface() { @Override public List<Person> searchByName(String query) { System.out.println("query: " + query); // dummy response.(emulate search by query from DB) Person alice = new Person("Alice Wall", 33, Country.findByValue(1), "trick"); Person bobby = new Person("Bobby Wall", 29, Country.findByValue(0), "yo-yo"); List<Person> people = Arrays.asList(alice, bobby); people.forEach(System.out::println); return people; } }); // (1) TServerTransport serverTransport = new TServerSocket(8080); // (2) TServer server = new TSimpleServer(new Args(serverTransport).processor(processor)); // (3) System.out.println("Starting the server... "); new Thread(server::serve).start(); // (4) } catch (Exception e) { e.printStackTrace(); } }
(1)
PeopleService.Processorインスタンスを生成します。
searchByNameで本来は引数のクエリを元にDBから検索することを想定しています。
ダミーのデータを返すようにします。
lambdaで書けばもっと短く書けます。
Processor<PeopleService.Iface> processor = new Processor<>(query -> { Person alice = new Person("Alice", 33, Country.findByValue(1), "trick"); Person bobby = new Person("Bobby", 29, Country.findByValue(0), "yo-yo"); return Arrays.asList(alice, bobby); });
(2)
サーバポート指定
(3)
TSimpleServerはシングルスレッドのサーバなので、
マルチスレッドにする場合は下記のようにTThreadPoolServerを使用します。
TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor(processor));
(4)
TServer.serve()でサーバ起動します。
実行してサーバ起動。
Starting the server...
Thrift Client(Perl)
perlでクライアントを作成してみます。
まず、thriftのtar.gzのインストールパッケージの中にperl用のThriftライブラリが入っているので、
適当な場所にコピーします。
$ mkdir perl_client $ cd perl_client $ cp -pR ~/xxx/thrift-0.10.0/lib/perl/lib .
クライアントはPerlClient.plというファイルで作成してみます。
PerlClient.pl
#!/usr/local/bin/perl use strict; use warnings; #(1) use lib './lib'; use lib '../gen-perl'; use Thrift; use Thrift::BinaryProtocol; use Thrift::Socket; use Thrift::BufferedTransport; use exampleThrift::Types; use exampleThrift::PeopleService; use Data::Dumper; my $socket = new Thrift::Socket('localhost',8080); #(2) my $transport = new Thrift::BufferedTransport($socket,1024,1024); my $protocol = new Thrift::BinaryProtocol($transport); my $client = new exampleThrift::PeopleServiceClient($protocol); #(3) eval{ $transport->open(); my $response = $client->searchByName("Wall"); #(4) print "response: " . Dumper($response); $transport->close(); }; if($@){ warn(Dumper($@)); }
(1)
コピーしたThriftのlibと、生成したgen-perlへライブラリのパスを通します。
(2)(3)
サーバのポートを指定し、PeopleServiceClientを作成。
(4)
searchByName()を実行。
ローカルのメソッドのようにsearchByName()を呼べます。
結果を標準出力にダンプしてみます。
実行してみます。
クライアント側の標準出力
$ perl PerlClient.pl response: $VAR1 = [ bless( { 'name' => 'Alice Wall', 'age' => 33, 'hobby' => 'trick', 'country' => 1 }, 'exampleThrift::Person' ), bless( { 'country' => 0, 'hobby' => 'yo-yo', 'name' => 'Bobby Wall', 'age' => 29 }, 'exampleThrift::Person' ) ];
サーバ側の標準出力
query: Wall Person(name:Alice Wall, age:33, country:JAPAN, hobby:trick) Person(name:Bobby Wall, age:29, country:AMERICA, hobby:yo-yo)
うまくいきました。
Thrift Server(Perl)
今度は逆でServer(Perl) と Client(Java)で試してみます。
Perlでサーバを実装してみます。
PerlServer.plという名前で作成します。
PerlServer.pl
#!/usr/local/bin/perl use strict; use warnings; # (1) use lib '../gen-perl'; use lib './lib'; use Thrift::Socket; use Thrift::Server; use Thrift::ServerSocket; use exampleThrift::Types; use exampleThrift::PeopleService; package PeopleServiceHandler; use base qw(exampleThrift::PeopleServiceIf); # (2) use Data::Dumper; sub new { my $classname = shift; my $self = {}; return bless($self,$classname); } sub searchByName{ my $self = shift; my $query = shift; print "query: $query \n"; # (3) # dummy response.(emulate search by query from DB) my $alice = new exampleThrift::Person({name => 'Alice Wall', age => 33, country => exampleThrift::Country::JAPAN, hobby => 'trick'}); my $bobby = new exampleThrift::Person({name => 'Bobby Wall', age => 29, country => exampleThrift::Country::AMERICA, hobby => 'yo-yo'}); my @people; push @people, $alice; push @people, $bobby; print Dumper(\@people); return \@people; } eval { my $handler = new PeopleServiceHandler; my $processor = new exampleThrift::PeopleServiceProcessor($handler); my $serversocket = new Thrift::ServerSocket(8080); my $forkingserver = new Thrift::SimpleServer($processor, $serversocket); # (4) print "Starting the server... \n"; $forkingserver->serve(); # (5) print "done.\n"; }; if ($@) { if ($@ =~ m/TException/ and exists $@->{message}) { my $message = $@->{message}; my $code = $@->{code}; my $out = $code . ':' . $message; die $out; } else { die $@; } }
(1)
Clientの時と同じようにlibを設定します。
(2)
Thriftコンパイルで生成されたPeopleService.pmに定義してあるexampleThrift::PeopleServiceIfを実装します。
(3)
ここも本来DBから検索するのを想定してますが、ダミーでレスポンスを返してます。
(4)
Thrift::SimpleServerはシンブルプロセスのサーバです。
マルチプロセスにするには下記の様にThrift::ForkingServerを使用します。
my $forkingserver = new Thrift::ForkingServer($processor, $serversocket);
(5)
Thrift::SimpleServer->serve()でサーバ起動。
実行して起動してみます。
$ perl PerlServer.pl Starting the server...
Thrift Client(Java)
Javaでクライアントを実装してみます。
public class Client { public static void main(String [] args) { try { TTransport transport = new TSocket("localhost", 8080); // (1) transport.open(); TProtocol protocol = new TBinaryProtocol(transport); PeopleService.Client client = new PeopleService.Client(protocol); // (2) perform(client); transport.close(); } catch (TException x) { x.printStackTrace(); } } private static void perform(PeopleService.Client client) throws TException { List<Person> people = client.searchByName("Wall"); // (3) people.forEach(System.out::println); } }
(1)
サーバ側のポートを指定
(2)
Thriftコンパイルで生成されたPeopleService.Clientを生成。
(3)
searchByNameを実行し、結果を標準出力にダンプ。
ローカルのメソッドのようにsearchByName()を呼べます。
クライアント側の標準出力
Person(name:Alice Wall, age:33, country:JAPAN, hobby:trick) Person(name:Bobby Wall, age:29, country:AMERICA, hobby:yo-yo)
サーバ側の標準出力
query: Wall $VAR1 = [ bless( { 'hobby' => 'trick', 'country' => 1, 'name' => 'Alice Wall', 'age' => 33 }, 'exampleThrift::Person' ), bless( { 'hobby' => 'yo-yo', 'country' => 0, 'name' => 'Bobby Wall', 'age' => 29 }, 'exampleThrift::Person' ) ];
うまくいきました。
終わり。
ソースは下記にあげました。