graphics.hatenablog.com

技術系テクニカルアーティストのあれこれ

はじめてのP2P、っぽいなにか。

NSQとHTTPをベースにしてトポロジっぽいものを組んでみた。

とはいえネットワーク周りはまったくの専門外なので、とりあえずは

  • 個数が動的に増減する複数のクライアントノード間でいいかんじに分散処理できること
  • ネットワーク上にサーバがいないときにクライアントだけが起動してても問題ないこと
  • 動作パフォーマンスよりの高さよりも実装コストの低さを優先すること

あたりを考えながらやってみた。

想定する用途は、中~大規模のゲーム制作チームで利用できるようなアセット関連のビルドキャッシュあたり。

今回のやり方がどれくらい一般的な構成なのかはよく知らない。ていうかたぶん違う。

f:id:hal1932:20170212123049j:plain
github.com

トポロジ構成

今回はハイブリッドP2Pをベースに考えてみた。

想定する用途がビルドキャッシュであるということを考えると、まず大前提として、クライアントが取得できるキャッシュデータの一貫性が確保されてないといけない。任意のデータに対するキャッシュを取得するときは、最新のキャッシュが必ず取得できなければいけない。隣の人が rev.50 のキャッシュを取得しているのに自分は rev.49 を取得するとかは駄目だし、自分はキャッシュを取得できたのに隣の人はできなかったとかも駄目。分散ハッシュテーブルとかをちゃんと実装できればいいんだろうけど、そこまでやるのはさすがにオーバースペックだろうし、そもそも実装できる気がしない。

あと、ビルド対象になるデータ間の依存関係や各種ハッシュ値なんかはビルド以外の場面でも使いたいだろうし、DBサーバとかを別途用意してそこに置いておきたい。インハウスでDBのサーバクラスタなんて運用できるわけがないし、となると結局そこがSPOFになってピュアP2Pの意味があまりない、というか、むしろ中央にいるサーバが一括して取り回すほうが何かと便利な気がする。必要に応じてデータインデックスのキャッシングとかもできそうだし。

クライアントのトポロジへの参加

いまサーバがいなくてもとりあえずクライアントだけ突っ込んどいて、サーバが目を覚ましたらなんとなくいいかんじに動いてほしい。常に動作し続けるサーバを維持管理し続けるってそれ自体が既に無視できないコストだし、それこそユーザ規模が20~30人程度ならそのときにちょうど余ってるPCとかたまたまお休みしてる人のマシンとか、そういうのを適当に突っ込んでいいかんじに動いてくれるととてもうれしいし、完全に動的にじゃなくていいから状況次第でサーバの増減とかも気軽にできるとすごくありがたい。主に機材管理的な意味で。

というわけで、クライアントからサーバへの各種リクエストは基本的にすべてメッセージング経由でやることにしてみた。これだと起きてるサーバが勝手にリクエストの処理とかしてくれるし、クライアント側も、適当にタイムアウトとか設定しといてサーバがずっと寝てたら自分も寝ちゃうみたいな拡張も比較的簡単にできそう。

クライアントの生存監視さえサーバ側でちゃんとやってれば、サーバは常に生きてるクライアントとしか通信する必要がないし、それならリクエストへの返信は直接やっても大丈夫そう。実装もそのほうが楽だし。

今回の場合も、クライアントからサーバへの「トポロジへ参加リクエスト」はメッセージングだけど、サーバからクライアントへの「参加承認通知」はHTTPでやってる。自分以外のクライアントを探すときのクエリも同じやり方。

サーバでのクライアントを管理

↑にも書いたとおり、サーバ→クライアントの通信をお手軽に済ませるためには、クライアントの生存監視だけはちゃんとやっとく必要がある。幸いなことにそれ自体はたいした手間じゃないし、まぁ別に ping っぽいやつを適当に打っとけばいい。

自分以外のクライアント情報の取得

ここはちょっと悩みどころ。ふつうのハイブリッドP2Pだったらそのへんの情報取得クエリはサーバが一括で担うべきなんだと思う。ただ今回はやってないけど、サーバ間でのクライアント情報の引き継ぎなんかを考えるとどうせどっかのDBサーバあたりでそのへん永続化しとかなきゃいけないし、だったらクライアントが直接DBにクエリ投げるとか、いや、でもそれだとハイブリッドにしてる意味がないか。トポロジの規模がそこまで大きくなければ構成が変わるたびに変更通知をブロードキャストするとかでも別にそんな悪くない気がする、無駄ではあるけど。

今回はとりあえずクライアントからサーバにクエリをメッセージで投げるようにしたけど、それだとクエリ結果を受けて他のクライアントと通信するときになってそいつと疎通がとれなくなってる可能性があるし、うーん、この時点ではサーバが起きてる前提で考えてもあまり問題にはならなそうだからサーバに直接クエリを投げるほうがよかったかもしれない。サーバ側のアドレスに変更があったときだけ、クライアント側に変更通知をブロードキャストすればいいのかな。

クライアント間での通信

ここはまぁ別になんでも。今回はとりあえず一番楽なHTTPにしてみた。本来ならUDPとか、せめてReliable UDPとかでやるべきなんだろうなぁという気はしないでもない。でもこのあたりはキャッシュの作成にかかる時間に対して通信時間が無視できるほど小さければ別になんだっていいわけだし、キャッシュ作成時間=ビルド時間のオーダーを考えると、プロトコルによる通信時間の違いなんてどうせ誤差みたいなもんだろ。というか、そもそもの話としてこのあたりは、自分の実装なんかよりも世間で十分によく叩かれたクライアントライブラリのほうがよほど信頼できる。通信中にデータ落としちゃったら何の意味もないわけだし、とりあえずそれはそれでいいや。

その他

NSQはちゃんと実績もあるしまぁいいとして、SimpleHTTPServerとかって大丈夫なのかな。たぶんこれ系ので一番大事なトポロジが大小にスケールしたときの安定性検証とか、正直全然できてないんだけど、でも自宅だとせいぜいクライアント数個が限界よね。そのうち機会があったら、もっとでかいとこでちゃんとやってみよう。