鱒身(Masu_mi)のブログ

知った事をメモする場所。

今更 Thrift を触ってみました

GoでThrift を試した。Thrift 自体が初めてだったがチュートリアル・サンプルが準備されていて簡単に利用できた。

はじめに感想を述べる。その後に読んだ資料・チュートリアルに関するメモなどを載せる。 最後にチュートリアルとThrift Architecture の対応を確認する。

tl;dr

Thrift はRCPを提供するミドルウェア だ。 (複数言語間を繋げる目的があり複雑なデータ構造を渡す事は考えられていない。また参照渡しなどRPCの難しい問題は避けている感じがある。) Thfift で責任を持つのはISO参照モデルで捉えるとプレゼンテーション層・セッション層になる。 この2層はThrift Architecture ではTProtocol, TTransport に当てはまりコンポーネントを差し替えられる様になっている。

Thrift Architecture

気になった事

  • TTransport でフレーミング実装がありTCPハンドシェイクを減らせる
  • 公式Goライブラリでノンブロッキングサーバーがサポートされてない
    • 1コネクション内で処理を多重化するのは難しい
    • goroutine で対応できる可能性はある
  • 負荷分散してくれるクライアント実装を探す必要がある
    • VIPによる負荷分散はワークロードで分散できず、フレーミング利用時に注意が必要
  • 言語間の差異を吸収し辛い機能はサポートされていない
    • 構造体継承, ポリモーフィズム, オーバーロード, heterogeneous containers, Null return
    • 参照渡し, unsigned int

チュートリアルを試す

インストール

チュートリアルみたいにwget などソースからやるのが面倒なので linuxbrew のお世話になる。

$ brew install thrift
$ go get git.apache.org/thrift.git/lib/go/thrift

コードジェネレート

GOPATH 配下では相対パスを利用できない事と、tutorial, shared 間で相対パスを一律で与える事が難しいため以下の様に生成するコード配置を意識して pacakge_prefix を与える必要がある。

$ thrift -o ./pkg/ --gen go:package_prefix=github.com/masu-mi/test/pkg/gen-go/ shared.thrift
$ thrift -o ./pkg/ --gen go:package_prefix=github.com/masu-mi/test/pkg/gen-go/ tutorial.thrift

チュートリアルのコードを取ってくる

go サンプル から取ってこれる。

実際に走らせる

$ go build .
$ nohup ./test -server &
$ ./test

チュートリアルコードの説明

Thrift network stack がプロトコルスタックがわかる。 4レイヤーで構成される。

Server 下位コンポーネントの管理を行う
Processor 通信のInput/Outputのやりとりを担当する
Protocol データのシリアライズなどを行う
Transport 下位レイヤ通信(TCPなど) を隠蔽する

このうちProcessor 部分がIDLにより生成され、実処理を行うハンドラをプログラマーが実装する。 他のレイヤーはThfirt ライブラリに含まれているのを利用する。コンポーネントは差し替えられるようになっている。 同様の図はチュートリアルにもあったが少しイメージが違うので気にかかる。

Thrift Architecture

チュートリアルとの対応

server定義箇所

server.go にある。抜粋すると以下な感じのコードになっている。

func runServer(transportFactory thrift.TTransportFactory, protocolFactory thrift.TProtocolFactory, addr string, secure bool) error {
  transport, _ = thrift.NewTServerSocket(addr)
  handler := NewCalculatorHandler()
  processor := tutorial.NewCalculatorProcessor(handler)

  server := thrift.NewTSimpleServer4(processor, transport, transportFactory, protocolFactory)
  server.Serve()
}

アドレスを元にソケットを作成している(NewTServerSocket)。 次にコードジェネレートしたNewCalculatorProcessor() に自作したハンドラを渡してプロセッサを完成させる。 最後にNewTSimpleServer4() に上記2つとtransportFactory, protocolFactory を渡しサーバーを完成させServe メソッドでホスティングを完了している。 transportFactory, protocolFactory はアーキテクチャで出てきた2レイヤに対応している。この部分は外部から与えているように差し替え可能。 チュートリアルのmain.go からどう使うかは判断できる。

Protocol, Transport の設定方法

チュートリアルではオプションによりProtocol が変えられるように書かれている同じくTransport も変えられる。 当然だけどClient, Serverの両方で同じProtocol, Transport を利用しないと上手く機能しない。

Transportにはフレーミングをサポートしたもの(TFramedTransport)が存在し別のTTransportFactory を受け取り機能するようになっている。 フレーミングでコネクションを使い回す実装はハンドシェイクを避けられるため効率良さそうに思える。

しかし負荷分散は考えにくくなる。L3DSRなどVIP構成を行うとコネクション単位で分散できるが実際のワークロードで分散が簡単には出来ない。 そのため複数サーバーに分散するクライアント実装があるかは調べたい。

またフレーミングをTransportで採用してもTNonblockingServer が公式(git.apache.org/thrift.git/lib/go/thrift)には見当たらない。 そのためgolanlg でサーバー実装するとフレーミングを活かせなさそう。 ただメンテされていない google code内のthrift-go には TNonblockingServer が存在する。 読んでみると以下の様にgoroutine を使うか否かの違いだけなので検証して問題がなければ公式にパッチを送るのを検討しても良さそう。