tag:crieit.net,2005:https://crieit.net/tags/elb/feed 「elb」の記事 - Crieit Crieitでタグ「elb」に投稿された最近の記事 2019-06-07T01:08:27+09:00 https://crieit.net/tags/elb/feed tag:crieit.net,2005:PublicArticle/15073 2019-06-07T01:07:02+09:00 2019-06-07T01:08:27+09:00 https://crieit.net/posts/AWS-ELB-ALB-Socket-IO-websocket-Stickiness-Sticky-session AWSのELB(ALB)におけるSocket.IOのwebsocket接続と負荷分散 - Stickiness(Sticky session)の活用 <p>AWSではELB(Elastic Load Balancing)を活用し、アクセスの振り分けや負荷分散を行なうのがメジャーです。<br /> ロードバランサーを使った構成は、通常のWebシステムではごくごく一般的です。</p> <p>ただしSocket.IOを使ったウェブソケット(websocket)接続では、問題となる場合があります。<br /> これには理由がありまして、私も調査に苦労しました。</p> <p>まずは前提となる知識を共有しつつ、備忘録を兼ねて解説します。</p> <h2 id="Socket.ioの仕組みをざっくりと理解する"><a href="#Socket.io%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF%E3%82%92%E3%81%96%E3%81%A3%E3%81%8F%E3%82%8A%E3%81%A8%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B">Socket.ioの仕組みをざっくりと理解する</a></h2> <p>websocketのライブラリでSocket.ioが多用されているのは、いくつか理由があります。</p> <p>その大きな理由のひとつに<strong>「HTTPの仕組みを使っているため、企業のファイアウォールやセキュリティーソフトのブロックに対して耐性がある」</strong>という点が挙げられます。</p> <p>[https://socket.io/:embed:cite]</p> <h3 id="ロングポーリング(polling)"><a href="#%E3%83%AD%E3%83%B3%E3%82%B0%E3%83%9D%E3%83%BC%E3%83%AA%E3%83%B3%E3%82%B0%28polling%29">ロングポーリング(polling)</a></h3> <p>Socket.ioでは、まずロングポーリングを使ってサーバーと接続します。<br /> ロングポーリングは、初期のLINEアプリでも採用されていた実績のある方式です。</p> <p>ひとことで言ってしまえば、<strong>「通常のHTTPリクエストに対するレスポンスを引き伸ばすことで、長時間の接続を維持する」</strong>のがロングポーリングです。<br /> サーバーはHTTPリクエストのレスポンスをすぐに終わらせず留保するため、クライアントとのコネクションを維持します。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/masarufuruya/items/2bd5dfe03096057af63f" target="_blank" rel="noopener">Socket.ioとは何か?リアルタイムWebアプリケーションを実現する技術をまとめてみた #JavaScript - Qiita</a></p> <p>ロングポーリングは、一般的なHTTPリクエストの延長線上にある技術です。<br /> そのためセキュリティー制約の強い企業のファイアウォールや、セキュリティーソフトのブロックに対して強みがあります。</p> <h3 id="ロングポーリングからウェブソケット(websocket)へ"><a href="#%E3%83%AD%E3%83%B3%E3%82%B0%E3%83%9D%E3%83%BC%E3%83%AA%E3%83%B3%E3%82%B0%E3%81%8B%E3%82%89%E3%82%A6%E3%82%A7%E3%83%96%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88%28websocket%29%E3%81%B8">ロングポーリングからウェブソケット(websocket)へ</a></h3> <p>次にSocket.ioが実施するのは、プロトコルの昇格です。</p> <p>ロングポーリングは素晴らしい仕組みですが、基本的にHTTPの接続を維持できる時間には限界があります。<br /> もちろん接続が切れてしまったら再接続すれば良いのですが、これでは一定間隔で切れ目が生じてしまいます。</p> <p>そこで登場するのがwebsocketです。<br /> Socket.ioは<strong>「ロングポーリングからwebsocketへプロトコルを昇格(Upgrade)」</strong>しようと試みます。</p> <blockquote> <p>Once all the buffers of the existing transport (XHR polling) are flushed, an upgrade gets tested on the side by sending a probe.</p> <p><em>引用:<a target="_blank" rel="nofollow noopener" href="https://socket.io/docs/internals/#Upgrade">https://socket.io/docs/internals/#Upgrade</a></em></p> </blockquote> <h3 id="Connection: Upgrade"><a href="#Connection%3A+Upgrade">Connection: Upgrade</a></h3> <p>HTTP/1.1にはConnectionというヘッダーがあり、<strong>コネクションのプロトコルをアップグレード</strong>することができます。</p> <blockquote> <p>Upgrade: websocket<br /> Connection: Upgrade<br /> <em>引用:<a target="_blank" rel="nofollow noopener" href="http://jxck.hatenablog.com/entry/20120725/1343174392">http://jxck.hatenablog.com/entry/20120725/1343174392</a></em></p> </blockquote> <p>もしコネクションのアップグレードに成功したならば、Socket.ioはウェブソケットでの通信を開始します。<br /> 一方でwebsocket接続に失敗したとしても、ロングポーリングでの接続を継続することができます。</p> <p>このプロトコル選択の柔軟性が、Socket.ioのすごいところです。<br /> websocketが使える環境下ではwebsocketを利用し、そうでない環境下でもロングポーリングで接続を維持できます。</p> <h2 id="ロードバランスされるとセッション(sid)が引き継げない問題"><a href="#%E3%83%AD%E3%83%BC%E3%83%89%E3%83%90%E3%83%A9%E3%83%B3%E3%82%B9%E3%81%95%E3%82%8C%E3%82%8B%E3%81%A8%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%28sid%29%E3%81%8C%E5%BC%95%E3%81%8D%E7%B6%99%E3%81%92%E3%81%AA%E3%81%84%E5%95%8F%E9%A1%8C">ロードバランスされるとセッション(sid)が引き継げない問題</a></h2> <p>socket.ioがウェブソケット接続を開始する場合、以下のようなリクエストを送信します。<br /> ここで注意してほしいのが「sid」という名のパラメーターです。</p> <pre><code class="txt">GET wss://myhost.com/socket.io/?EIO=3&transport=websocket&sid=36Yib8-rSutGQYLfAAAD with: "EIO=3" # again, the current version of the Engine.IO protocol "transport=websocket" # the new transport being probed "sid=36Yib8-rSutGQYLfAAAD" # the unique session id </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://socket.io/docs/internals/#Upgrade">https://socket.io/docs/internals/#Upgrade</a></p> <p>「sid」とはセッションIDのことですが、ここに大きな問題があります。<br /> <strong>最初にアクセスしたサーバーと違うサーバーに振り分けられてしまった場合、発行されたセッションが見つからなくなってしまう</strong>のです。</p> <p>その際は「Session ID unknown」のようなエラーが発生します。</p> <p>つまり最初にリクエストを送信してからウェブソケットの接続を確立するまで、常に同じサーバーにロードバランスされないと困ってしまうのです。</p> <h3 id="Redisによるセッションの共有"><a href="#Redis%E3%81%AB%E3%82%88%E3%82%8B%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E5%85%B1%E6%9C%89">Redisによるセッションの共有</a></h3> <p>Socket.ioのセッション情報を引き継げない問題でよく選択されてきた対応策は、インメモリデータベースのRedisによるセッション情報の共有です。<br /> AWSで言うならば、Redis用のElastiCacheを使ってセッション情報を保存します。</p> <blockquote> <p>複数立っているsocket.ioサーバのセッション情報をRedisを介して共有することができるようなので、そうすれば複数のリクエストが異なるサーバに繋がっても問題なくなります。<br /> <em>引用:<a target="_blank" rel="nofollow noopener" href="https://qiita.com/shunjikonishi/items/526e6181efecf83a5f5b">https://qiita.com/shunjikonishi/items/526e6181efecf83a5f5b</a></em></p> </blockquote> <p>「socket.io-redis」というRedis向けのライブラリがあり、これを使います。<br /> 2年ほど前の話ではありますが、実際に試してみたところ確かに問題ありませんでした。</p> <ul> <li>socketio/socket.io-redis</li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/socketio/socket.io-redis">https://github.com/socketio/socket.io-redis</a></li> </ul> <p>ElastiCacheでなく自前でRedisを用意しても構わないですが、兎にも角にもRedisが必要です。</p> <h2 id="Application Load Balancer"><a href="#Application+Load+Balancer">Application Load Balancer</a></h2> <p>さてAWSには、Application Load BalancerというL7のロードバランサーがあります。<br /> 単なるELBとの違いがややこしいですが、OSI参照モデルで言えば<strong>最上位の第7層(アプリケーション層)でリクエストを振り分ける</strong>ことが可能です。</p> <p><a href="https://crieit.now.sh/upload_images/88927757e552ab546697edb0b21408565cf93a76822df.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/88927757e552ab546697edb0b21408565cf93a76822df.png?mw=700" alt="20190606030658.png" /></a></p> <h3 id="Stickiness(Sticky session)"><a href="#Stickiness%28Sticky+session%29">Stickiness(Sticky session)</a></h3> <p>ここで登場するのが、Stickiness(Sticky session)というALBの仕組みです。<br /> スティッキーセッションを使うと、ロードバランサーのアクセスの振り分けを固定することができます。</p> <p>具体的な手順として、<strong>ELB(ALB)経由でサーバーにアクセスすると、HTTPレスポンスに「AWSELB」という名前のCookieが追加</strong>されます。<br /> このクッキーはALBが自動に付与する設定もできますし、手動でサーバーから追加する選択肢も可能です。</p> <blockquote> <p>ELBのスティッキーセッションはELBがサーバにリクエスト振り分ける際、特定のCookieを確認することで、特定のクライアントからのリクエストを特定のサーバに紐付けることが出来る機能です。<br /> ELBのスティッキーセッションの設定は以下3つのパターンが選択出来ますので、それぞれどのような動作となるか確認してみます。</p> <ul> <li><p>維持無し</p></li> <li><p>ELBによって生成されたCookieの維持</p></li> <li><p>アプリケーションによって生成されたCookieの維持</p></li> </ul> <p><em>引用: <a target="_blank" rel="nofollow noopener" href="http://blog.serverworks.co.jp/tech/2017/01/05/elb-sticky/">http://blog.serverworks.co.jp/tech/2017/01/05/elb-sticky/</a></em></p> </blockquote> <p>HTTPレベルの仕組みであるCookieを読み取ってアクセスを振り分けるため、高機能なアプリケーション層レベルのL7ロードバランサーであると言えます。</p> <p>なお私は、実際のスティッキーセッションを使ったsocket.ioのアクセスの振り分けは、uorat様のブログ記事を参考に行ないました。<br /> (注釈:ものすごく参考になりました。ありがとうございます)</p> <p><a target="_blank" rel="nofollow noopener" href="http://uorat.hatenablog.com/entry/2016/09/26/070000" target="_blank" rel="noopener">WebSocket対応した噂のALB (Application Load Balancer) を試してみた - tail my trail</a></p> <h3 id="ELB(ALB)のwebsocket対応"><a href="#ELB%28ALB%29%E3%81%AEwebsocket%E5%AF%BE%E5%BF%9C">ELB(ALB)のwebsocket対応</a></h3> <p>従来のELBには、websocket接続できない欠点がありました。<br /> そのためsocket.ioでwebsocket接続してるのかと思いきや、実際はロングポーリングでしたという事態が発生します。</p> <p>これを解消したのが、2016年8月の新機能です。なんと<strong>websocket対応が追加</strong>されました。<br /> これによりELBの負荷分散の恩恵を受けつつ、よりリアルタイム性の高い接続を維持することができるようになりました。</p> <blockquote> <p>WebSocketサポート:WebSocketのリクエストを受けられるようになりました。<br /> <em>引用: <a target="_blank" rel="nofollow noopener" href="https://dev.classmethod.jp/cloud/aws/alb-application-load-balancer/" target="_blank" rel="noopener">【新機能】新しいロードバランサー Application Load Balancer(ALB)が発表されました | DevelopersIO</a></em></p> </blockquote> <h2 id="さいごに"><a href="#%E3%81%95%E3%81%84%E3%81%94%E3%81%AB">さいごに</a></h2> <p><strong>Stickiness(Sticky session)の活用とELB(ALB)のwebsocket対応</strong>により、Socket.ioでも通常のWebシステムと同じようにロードバランスすることができます。</p> <p>Cookieを使ってクライアント毎にロードバランサーの振り分け先を固定すれば良いなんて、当初は思いもしませんでした。<br /> とても便利な機能なので、今後も活用していきたいです!</p> このすみ