THINKING MEGANE

Go言語でTCPやソケット通信を多重化,高速化するsmux(ソケットマルチプレクサ)をつくった

サーバ間で分散処理を行う際の相互通信におけるボトルネックを解消するため,smux(Socket multiplexer)を開発している.

サーバ間の相互通信におけるボトルネックとその解決策

一対のサーバ間で多数のリクエストとレスポンスが送受信され,信頼性の高い通信としてTCPを利用する場合,コネクション確立のオーバーヘッドを排除するために接続の再利用が行われる.しかしながら,クライアントは送信に対する受信を待つ必要があるため,レスポンスまでに幾許かの処理時間を要する状況では送信のキューがたまってしまう.そこで複数の接続を利用することでこれを解消する方法が取られるが,追加の接続はリソース使用に関するオーバーヘッドを発生させてしまう.なにより各接続におけるレスポンス待ち時間は依然として解決しておらず,接続の利用面から見て非効率である.そこで,単一の接続において,仮想的に並行送受信を行う方法が提案されている.これは,HTTP/2におけるバイナリフレーミングレイヤの役割であり,コネクション内にストリームと呼ばれる仮想的なチャンネルを設けて双方向の通信を並行して行う.下位レイヤの制約であるリクエスト送信後のブロッキングは,リクエストやレスポンスをフレームと呼ばれる単位に分割することで影響を抑えている.

このように多数のリクエストとレスポンスが送受信され,レスポンスまでに幾許かの処理時間を要する状況においては,HTTP/2は非常に魅力的であり,はじめに採用を検討すべき価値がある.一方で,HTTP/2を利用する場合,HTTPプロトコルに依存した一定のオーバーヘッドは発生する.もちろんHTTP/2にはバイナリ化やヘッダ圧縮などの多数の対策が組み込まれているため,自身の適用課題において十分な性能が見込めるならば問題ないが,より最適化したパフォーマンスが必要な状況では,これらのオーバーヘッドの除去も検討の余地がある.

smux(Socket multiplexer)

smux(Socket multiplexer)はこのような状況において,非常にシンプルなバイナリフレーミングレイヤの振る舞いをアプリケーションに提供する.すなわち,単一コネクションの仮想多重化である.アプリケーションは独自の(そしておそらく簡潔な)プロトコルに則ってストリーム経由でデータを送受信する.複数コネクションのハンドリングが不要になり,複数のリクエストを並行して発行することができるため,アプリケーション実装は簡潔に,通信は高速になることが見込める.

smuxはGo言語で実装されており,アプリケーションがGo言語であれば,以下のように利用することができる.

// smux server
server := smux.Server{
	Network: "tcp", // or "unix"
	Address: "localhost:3000", // or "sockfile"
        Handler: smux.HandlerFunc(func(w io.Writer, r io.Reader) {
                io.Copy(ioutil.Discard, r)
		fmt.Fprint(w, "Hello, smux client!")
        }),
}

server.ListenAndServe()
// smux client
client := smux.Client{
	Network: "tcp", // or "unix"
	Address: "localhost:3000", // or "sockfile"
}

body, _ := client.Post([]byte("Hello, smux server!"))
fmt.Printf("%s\n", body) // "Hello, smux client!"

内部的には,streamはReadやWriteのメソッドを持っているため,より最適化したい場合はこれらを自身で利用することもできる. また,あくまでソケットインターフェースを経由したやり取りであり,smuxのプロトコルに従えば他の言語での実装も可能である.

パフォーマンス

以下は,smuxとHTTP/1.1,HTTP/2を対象としたベンチマークである.それぞれ多重度として,コネクション数やストリーム数を増加させながら,一定数のリクエストを捌くまでの時間を計測した.リクエストとレスポンスの内容,サーバサイドでの処理は各手法で統一している.また,サーバーサイドでの処理の代替に数十msのsleepを入れている.

benchmark

今回の条件では多重度を上げた場合に,手法によっては性能が頭打ちになる中で,smuxが特に高い多重度において性能を十分に発揮できた.今回のベンチマークはサーバーとクライアントが同一の環境であり,各手法に適したチューニングや適用時の条件によっても変動はあると考えられるため,利用においては条件に合わせたパフォーマンス計測を行うことが望ましい.ベンチマークのコードはここにおいている.

まとめ

サーバ間通信のボトルネックを解決するため,HTTP/2のバイナリフレーミングレイヤを参考に,プロトコルのボトルネックを排除するシンプルなコネクションの仮想多重化を行うsmuxを開発した.手元でのベンチマークでは,できるだけ多くのリクエストを並列に処理したいような状況で効果がありそうであることが確認できた.まだまだ安定性の面などで不安もあるが,引き続き本番運用に向けて開発を継続していきたい.


2018/05/04追記

コメントにて先行実装として,hashicorp/yamuxxtaci/smuxを教えていただいたので,ベンチマークを再実行した.xtaci/smuxはSimple Stream Multiplexingらしいので,ここではssmuxと呼称する.

benchmark_2

yamuxに対しては同等以上と言えるが,ssmuxが優秀であるという結果になった.両者とも,ストリームにおけるリクエストに対してレスポンスが発生する場合では,リクエスト側の終了を検知するためにクローズ処理が利用できないため,別途自前でデータサイズを送信する必要があるなど使い勝手に関しては多少の難もありつつも,実装や性能については参考になる部分が多い.まだ改善の余地が残っていること,着眼点自体は良さそうであるという点は喜びたい.並列時の性能に効果があると考えられるフロー制御周りを中心に実装参考しながらパフォーマンスなど改めて見直していく.

このエントリーをはてなブックマークに追加