よくわかる Apache Thrift J to R
ぶらいじぇん
はじめに
この記事は、サーバサイドエンジニアの平凡な日常で得た Apache Thrift の基本的な使い方を淡々と説明するものです。
過度な期待はしないでください。
それと、Swift1)アップルのiOSおよびOS Xのためのプログラミング言語と勘違いしてこのページを開いてしまった方にはすみません。
みさなまどうも。サーバサイドのエンジニアぶらいじぇんです。
エンジニアの皆様におかれましては、普段いかがおプログラミングでしょうか?
さて、諸姉兄におきましてはサービスリリース浅く監視・運用厳しき折、既存のサービスとの連携とは名ばかりで無茶な実装せざるを得ないなんてこともままあるのではないでしょうか?
私に至っては、『このサービスの連携がこんなに難しいはずがない』とか『僕には解決策が少ない』とか感じてしまう今日此頃です。2)超訳すると、RoRのサービスでJavaのAPIを叩きたかったときにThriftを調べながら使ってみた。
この記事はサンプルコード(クライアントサイドRuby、サーバーサイドJava)を実装し実行することで、Thriftの基本的な使い方を説明する内容となっています。
Thrift の概要
社内の勉強会で使用した資料3)加筆修正しています。がありますので、こちらを御覧ください。
実装サンプル(コード生成)
Thriftをインストールする
まずはThriftをインストールします。
テンプレートファイルから各言語のソースコードをジェネレートするためのもので、開発環境にさえインストールしておけばオッケーです。
brew install thrift
サンプルを作成する
あっさりと開発環境の構築をおえたところで、早速サンプルコードを書いていきます。
このサンプルでは、Thriftサーバの情報(時刻、JVMメモリ)を返すAPIを作成します。4)本番運用の際にもこういうAPIを作っておくと、接続確認などに使えるのでお勧めです。(╹◡╹)b
テンプレートファイルを作成する
environment.thrift
ファイルを作成します。
namespace java com.example.gen
namespace rb Com.Example.Gen
enum ServerInfoKey {
TIME = 1;
MEM = 2;
}
struct ReqServerInfo {
1:ServerInfoKey key;
}
struct ResServerInfo {
1:string value;
}
exception ServerInfoException {
1:list<string> traces;
}
service Environment {
ResServerInfo getServerInfo(1:ReqServerInfo req) throws (1:ServerInfoException e);
}
テンプレートファイルからジェネレートする
thrift --gen java --gen rb environment.thrift
生成されるファイル
gen-java/com/example
ディレクトリ以下に生成されます。
Environment.java
ReqServerInfo.java
ResServerInfo.java
ServerInfoException.java
ServerInfoKey.java
gem-rb/environment
ディレクトリ以下に生成されます。
environment.rb
environment_constants.rb
environment_types.rb
備考
ここでいったん Thriftテンプレートファイル での定義を補足しておきます。
namespace
- 出力されるファイルのパッケージ名を指定する (言語ごとに指定可能)exception
-service
で投げる例外struct
-service
での引数、戻り値になる構造体- 引数、戻り値を構造体(ReqServerInfo、ResServerInfo)にしていることで、null(valueプロパティ)の値を返すことができる
- 引数、戻り値そのもの(ReqServerInfo、ResServerInfo)は null にできない
enum
- enum型を定義できる- 定義済み型 - bool, byte, i16, i32, i64, double, string, binary, map, set, list
実装サンプル(サーバーサイド)
Java Servletを実装する
package com.example.servlet;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.servlet.annotation.WebServlet;
import com.example.gen.Environment.Processor;
import com.example.gen.Environment.Iface;
import com.example.gen.ReqServerInfo;
import com.example.gen.ResServerInfo;
import com.example.gen.ServerInfoException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServlet;
@WebServlet(name = "EnvironmentServlet", urlPatterns = { "/environment" })
public class EnvironmentServlet extends TServlet {
public EnvironmentServlet() {
super(new Processor<>(new Iface() {
@Override
public ResServerInfo getServerInfo(ReqServerInfo req) throws ServerInfoException {
String value;
try {
switch (req.getKey()) {
case MEM:
value = String.valueOf(Runtime.getRuntime().totalMemory());
break;
case TIME:
value = String.valueOf(new Date());
break;
default:
throw new IllegalArgumentException("Not Found: key = " + req.getKey());
}
} catch (Exception e) {
StackTraceElement[] sts = e.getStackTrace();
List<String> traces = new ArrayList<>(sts.length);
for (StackTraceElement st : sts) {
traces.add(st.toString());
}
throw new ServerInfoException(traces);
}
return new ResServerInfo(value);
}
}), new TBinaryProtocol.Factory());
}
}
pom.xml(Maven)
に追記します。
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.9.2</version>
</dependency>
実装サンプル(クライアントサイド)
Gemfile
ファイルを以下の内容で作成(or 追記)します。
source 'https://rubygems.org'
gem 'thrift', '0.9.1'
gem 'rack', '~> 1.5.2'
gem 'thin', '~> 1.6.2'
bundle install
します。
bundle config build.thrift --with-cppflags='-D_FORTIFY_SOURCE=0'
bundle --path vendor/bundle
Rubyクライアントを実装する
environment_client.rb
ファイルを以下の内容で作成します。
#!/usr/bin/env ruby
$:.push('./gen-rb')
require 'thrift'
require 'environment'
GEN = Com::Example::Gen
url = "http://localhost:8080/environment"
transport = Thrift::HTTPClientTransport.new(url)
protocol = Thrift::BinaryProtocol.new(transport)
client = GEN::Environment::Client.new(protocol)
begin
p "=== BEGIN"
transport.open
req = GEN::ReqServerInfo.new
req.key = GEN::ServerInfoKey::TIME
res = client.getServerInfo(req)
p "res = #{res}, res.value = #{res.value}"
rescue => e
p "Occurred Exception. #{e}"
else
p "Success."
ensure
transport.close
p "=== END"
end
実行する
先に実装しておいた サーバーサイドの実装(Servlet) を Tomcat や Jetty などで実行しておき、以下のコマンドを実行します。
bundle exec ruby environment_client.rb
出力結果
=== BEGIN
res = #<Com::Example::Gen::ResServerInfo:0x007fa6ac2d35c0>, res.value = Thu Jan 04 23:59:55 JST 2015
Success.
=== END
簡単でしたね(╹◡╹)
さいごに
上記サンプルではサーバーサイドを Java の Servlet として実装しているので、AWS の Opsworks(Java App)や ElasticBeanstalk 上でわりと簡単に稼働させることができます。
インターナルなAPIをプログラム言語を越えてプロキシしたいとき、解決策の一つとしていかがでしょうか?
以上になります。m(_ _)m
それでは
来週もサービス、開発
参考サイト
- Apache Thrift - Home - 詳細な情報は公式サイトのドキュメントを参照するのがお勧めです。
- Github apache/thrift - 各言語でどのようなプロトコル5)この記事では触れませんでしたが、Thriftのサーバーとクライアント間でデータを送受信する際のプロトコルのことです。があるかを調べるためにソースコードをあたりました。
- Facebookが開発したRPCフレームワーク「Thrift」入門 - サーバーをServletではなくJavaプロセスとして実行しおり、こちらのサイトも参考になります。