高性能サーバーの実装を一般化するアーキテクチャ

ある日のTwitter。途中でネットワーク分散に話がそれるけども、基本的に1つのプログラムの中の話。


高速サーバーの実装を一般化するアーキテクチャ:入力+ディスパッチ・ロジック・出力 の各ステートにそれぞれスレッドプールを割り当てる。各ステートは非同期通信でメッセージをやりとりする。
ステート間の通信は要するに非同期RPCなので、容易にネットワーク上に拡張できると気付いた。ステート間のつながりが非常に疎。
各ステート間のつながりを抽象化した状態でプログラミングしておくことで、単一のプログラムを複数ホストで分散処理する分散プログラムに容易に拡張することができる。ちょっとErlangに近いけど、スレッド間通信の粒度を一定の大きさに保っている点が現状に即している。
リバースプロキシと実サーバーの間の通信がHTTPである必要はあるのだろうか。HTTPは同期通信なので並列性が悪く、性能が出ない。どうせWebアプリケーションはLLで書くのだから、RackとかWSGIとかに対応したインターフェイスを書けばいい。非同期RPCでも良いはず。
リバースプロキシが実サーバーとHTTPで通信するのは、実サーバーで運用してきたApacheモジュールの資産を活かすためか。しかしlighttpdで運用するサービスもあるし、移行も不可能ではないように思える。リバースプロキシにリッチなプラグイン機構を用意する必要はある。
リバースプロキシ・ アプリケーションサーバーは、入力+ディスパッチ・ロジック・出力のアーキテクチャとほぼ同じ。入力と出力が両方リバースプロキシを通るところが気になるが、同期通信であるHTTPと非同期な内部通信を同期させるホストがどこかに必要になってしまう。
リバースプロキシの上にL3ロードバランサを置けば、リバースプロキシはスケールアウトする。足りなければ足す。
入力+ディスパッチ・ロジック・出力 アーキテクチャの良いところは、ロジックステートをシングルスレッドで書けるところにある。完全に性能重視ではなくて、実装コスト面で妥協している。ボトルネックはロジックステートの実装に来る。
もう一つ良いところは、ディスパッチの一部とロジックステート以外はすべて抽象化できる点。システムのうち性能に関わる多くの部分を一般化できる。一般化した部分をライブラリにまとめることで、高性能サーバーを量産できる。


Twitter検索で「RPC」を検索してみたら、Protocol Buffers とか JSON-RPC とか Aerial とか libpolatsk とか Thrift とか XML-RPC とか。
2007-12-26 「@kzk_mover 1 rpc framework / 1 company という話を先日もしたけれど、本当に効率も良くて使いやすい rpc 機構って無いものなんだろうか。 Thrift とかどうなんだろう?要調査。」周辺の発言が興味深い。
RPC、というかプロセス間通信って、RPCアーキテクチャとロジックの設計が密結合になってしまい、疎結合性が要求されるライブラリとしては表に出しにくかったんだと思う。
そこで疎結合にするために提案されているのが、コードジェネレータでがんばる方法と、JSON-RPCとかXML-RPCとかの動的性でなんとかする方法がある気がする。前者は面倒。後者は遅い。
あと、RPCの実装は基本的にIOレイヤと密接に関係している。スレッドモデルにするかイベント駆動モデルにするか。相手が落ちているっぽかったらどうするか。再送するかダブりを避けるか。IOレイヤはプログラムの駆動方法や、耐障害性を大きく決定づけてしまう。
とはいえ、やはり「使えるRPCフレームワークの決定版」は欲しい。現状では高速IOとメッセージングの実装にコストがかかり過ぎている。RPCフレームワークがあれば、ネットワークを構築する層と、ロジック層だけ、つまり本質的な部分だけに実装を集中できる。(でもまぁたぶん幻想)
MessagePackを実装したときに思ったのが、また独自プロトコルを乱立させてしまうぞーということ。後で気付いたことだけど、バイナリ列の仕様はMessagePackもProtocol Buffersもそんなに変わらない。…いや違うけど。しかし目指す方向が違った。
いや、たぶん目指す方向はあまり違っていない。要するにはイケてるRPCフレームワークが欲しいだけ。逆にたぶんきっかけが違った。MessagePackは、Rubyで書いたプロトタイプをC++で清書するときに同じプロトコルを使えるようにしたかった。