HTTP関連の研究をしているので、そろそろ古い技術を詰めるばかりではなく(これはこれでとても大事な事なのですが)、新しい技術についても調べておきたいところです。
ということで、僕のSPDYに関する現状の理解を、mod_spdyに関する情報を元にまとめておきたいと思います。
SPDY概要
SPDYの概要を表す図としては、下記が良く用いられます。
TLS上にのせたSPDYストリーム上でHTTPやWebSocketを扱うプロトコルで、特徴としては、以下の4つがあげられます。
- ストリームの並列化
- フレームレイヤーやヘッダーの圧縮
- リクエストの優先処理
- サーバからのリソースプッシュ
HTTP/2.0についても、SPDYを元に仕様が検討されています。では具体的に、mod_spdyの実装を元にSPDYに対する僕の理解をまとめていきます。
mod_spdy概要
mod_spdyは、読んで字のごとく、Apache httpd上でSPDYを動かすために作られたモジュールです。2012年の4月ごろにリリースされ、現在はSPDY/2及び/3に対応しています。スレッド回りで色々と工夫をしていて、現状のhttpd2.2系でもほとんどその他のモジュールや設定を弄る必要なく組み込めるようになっています。実際に組み込んだ事がある人は良くわかるでしょう。
では、そのmod_spdyの工夫からSPDYの特徴をまとめていきます。
mod_spdyの特徴
SPDYの特徴を踏まえつつ、mod_spdyには以下の特徴があります。
- Next Protocol Negotiation
- フレームレイヤやヘッダの圧縮
- ストリームの並列化
- サーバからのリソースプッシュ
では実際にそれぞれの特徴を見ていきます。
Next Protocol Negotiation
Next Protocol Negotiation(NPN)は簡単に言うと、クライアントからサーバにSSLハンドシェイクに行った際に、サーバがサポートしているプロトコル情報(例えばSPDY/2だとかSPDY/3だとかmatsumoto_r-new-protocolに対応しているとか)をクライアントに提供し、その中から使用したいプロトコルをクライアントが選択してネゴシエーションする仕組みです。
mod_spdyではそのネゴシエーションにmod_sslにパッチを当てたものを使用しており、mod_sslからNPN advertise hookというhook処理により、Apache httpdが対応しているプロトコルを組み込まれているモジュールに聞きに行きます。
例えば、mod_spdyが組み込まれていた場合、mod_spdyはSPDY/2とSPDY/3に対応しているので、その情報をmod_sslに返し、それをクライアントに伝えます。そして、クライアントは、SPDY/3を選択するとサーバ側に返事をし、それをNPN done hookというhook処理によって、mod_sslからmod_spdyに教えます。これによって、SPDYプロトコルが選択されます。
また、NPN advertise hookを受けるモジュールは、mod_spdyだけでなく別のプロトコルモジュールも受け取ることができるので、独自のSPDYのようなモジュールを書いて登録することもできます。
これらの処理は、mod_sslにcommitされているので、コードを確認してみるとよいでしょう。
フレームレイヤやヘッダの圧縮
mod_spdyではSPDYフレームをApache httpd内部でSPDY以外の場合と同じようにHTTPデータとして扱うために、コネクションレベルでフィルターをかましています。それによって、リクエストフェイズで処理されるような既存のモジュールを弄る事なく、SPDYでしゃべることができますし、ヘッダの圧縮状態をそのまま既存のモジュールに渡す事ができます。
ストリームの並列化
この特徴が、本エントリを書こうと思ったきっかけの実装です。とても面白くて参考になる事をやっていました。
まず、普通のApache httpdでは、サーバソケットに対し、複数のクライアントがコネクションのソケットをそれぞれ持っており、そこでおこされたスレッドやプロセスにおいて、
- クライアントソケットからコネクションオブジェクト生成
- ネットワークフィルター
- コネクションフィルター
- コネクションハンドラー
- リクエストフィルター
- リクエストハンドラー
- 再度逆向きにフィルターを経てコネクションオブジェクト経由でレスポンスをクライアントに返す
というフェイズを経て、レスポンスが生成されます。図だと以下のようになります。
このように1リクエスト1スレッドの処理をとっている理由としては、Apache httpd2.2系ではコアとなるデータがスレッドセーフではない事が挙げられます。そのため、1つのリクエストの処理が終わるまで、スレッドやプロセスのコントロールは占有されてしまします。2.4の場合はスレッドセーフのはずなので、また少し理解が変わると思いますが、ここではそれは省略します。
そこで、mod_spdyではストリームの並列化を実現するために、面白い工夫をしています。これは、2.2系でスレッドセーフなMPMを作るためにも参考になる技術です。mod_spdyでは、上記の通常のリクエストからレスポンス処理におけるフェイズに加えて、以下のような処理を追加しています。
- クライアントソケットからコネクションオブジェクト生成
- ネットワークフィルター
- コネクションフィルター
- コネクションハンドラー
- SPDYコネクションハンドラー呼び出し
- SPDYスレッド生成(並列)
- SPDYストリームの生成
- クライアントソケットのふりをしたソケットからSPDY用コネクションオブジェクト生成
- SPDYとHTTPの変換フィルター
- リクエストフィルター on SPDYスレッド
- リクエストハンドラー on SPDYスレッド
- 再度逆向きにフィルターを経てSPDYストリーム経由でレスポンスをクライアントに返す
という処理をしています。ここは自信がないので参考までの情報として読んでください(コードをもっとしっかり読まないといけませんが)。図では以下のようになります。
上記のように、コネクションフィルター(SSL等)を経て、コネクションハンドラーから新たにmod_spdyのプライベートスレッドプールに、SPDYスレッドを複数生成することで、スレッドセーフでない構造体を持った従来のhttpdスレッドはSPDYスレッドに対するdispatchの役目をし、dispatchした時点で処理を完了させます。これによって、本来コネクション処理後にいったんrequest処理に入ると、Apache httpdプロセス(スレッド)は一つの処理を占有していましたが、コネクション処理とリクエスト処理をそれぞれ別スレッドに分離することにより、その処理を複数のSPDYスレッド上で並列処理することが可能になります。
その上で、並列処理可能なmod_spdyのプライベートスレッドプール上のSPDYスレッドにおいて、リクエスト処理をSPDYストリームに委任し、クライアントソケットのふりをしたソケットを生成してからSPDY用コネクションオブジェクトを生成します。その生成されたスレーブコネクション上で、SPDYとHTTPの変換フィルターを介して、SPDYスレッド上でリクエストフィルター(gzipとか)とハンドラー(動的コンテンツなど)を処理します。そして、再度逆向きにフィルターを介して、レスポンスをSPDY用ストリームからマスターコネクションのキューにキューイングしていき、随時クライアントに返しているんだと解釈しています。
全体像は以下のようになります。
この実装の仕方は、Apache2.2系の並列処理なモジュールを書く際にも非常に参考になる実装方法に思えます。
サーバからのリソースプッシュ
そして、4つ目の特徴であるリソースプッシュですが、上記の実装によって生成されたSPDYスレッドは、X-Assosiated-Contentレスポンスヘッダとプッシュ対象のURLを探します。そして、そのURLに対してヘッダ情報と共に、SPDY SYN_STREAMをプッシュします。その際に、mod_spdyはHTTPリクエストデータをまとめて、httpdのかわりにSPDYストリームのために作られたSPDYスレッド上でレスポンスを生成して、SPDYフレームに変換してクライアントに送信します。
mod_spdyの何が良い所と悪い所
SPDYの良さは多くのところで語られているので、モジュールの観点から良さをいくつか挙げておきます。
- 2.2系はスレッドセーフではないのに、並列処理可能な事
- 既存のモジュールを書き換える必要がないこと
- 既存の設定をそのまま使えること
です。一方で悪いとことは、
- mod_sslにパッチをあてないといけない
- SPDYとHTTPの変換処理のパフォーマンス
- スレッドが沢山作られる
- スコアボード上でSPDYスレッドで処理されるリクエストの情報は取得できない
- リクエスト処理フェーズではSPDYスレッド上で処理が並列で動作するため、そのタイミングでフックされるモジュールはスレッドセーフでないといけない
といったところでしょうか。
最後に
今後、HTTP/2.0ではSPDYを元に仕様が策定されていくと思うのですが、Apache httpdをHTTP/2.0に対応される場合には、mod_spdyの知識が非常に重要になってくると思います。新しいプロトコルモジュールの実装の際には、NPNによるプロトコルのアドバタイズと選択処理や、スレッドセーフなモジュール実装をうまく使う事で、既存のモジュールを書き換えることなく(スレッドセーフ化は必要ですが)今後のHTTPの変化に追随した実装ができるのではないかと思います。