現在、業務でwebsocketを使用したチャットアプリケーションの開発に関わっている。自分が担当しているのはサーバーサイドのみで、実装はgolangを使用している。websocketのメッセージのやりとりの部分はgorilla/websocketのexamplesにあったものをパクリスペクトしている。clientに関する情報(struct)を生成。protocol)これだけでも十分な実装だが、自分が担当している業務では追加の機能要件がある。1つはredisのpubsub機能を使用した購読済みの別サーバーへのメッセージのpublish機能の実装で、これは予想の通り、kubernetesを使用してサーバーがスケールする予定であるため必要になるとは言いつつも、参加当時はkubernetesに関する知識は皆無だったので、どのようにスケールするかなんて考えていなかった。いうか、考えられなかったので、後のやらかしポイントになる合わせて、メッセージの送受には並行処理が欠かせない。1つのプロセス、もしくはスレッドで処理を占有してしまうと、チャットとは呼べない代物が出来上がる。メッセージがサーバーに受信して送信されるまで、誰もメッセージを送ることが出来ないようなものになる。接続数が少ないうちは何とかなるかもしれないが、接続数に比例してストレスを感じるようになるだろうやらかしたこと開発初期当時は環境の準備や実装の遅れなどの問題で、シングルサーバーでの動作確認しか行なっておらず、上手く動いているものだと錯覚していた。実際に、複数サーバーにしてみて動作を検証したはずなのだが、見落としていた様だでは実際に何をやらかしていたかという話に入る。サーバーでのwebsocketのメッセージの処理とgolangの並行処理をするためにgoroutinesでどのようにスレッドを用意しているかを簡単に説明していく。ちなみにチャンネルはpubsubのチャンネルを指しており、チャネルはgolangのスレッド間通信で扱うデータ構造を指しており、タイポではないsetupサーバーを起動する。この時に合わせて、Hub(examplesのhub.go)をgoroutinesを使用して立ち上げて待機させるredisのpubsub機能を利用して対象のチャンネルを購読(subscribe)して、publishされたメッセージを受信するためのスレッドを待機させるユーザーがブラウザ経由でwebsocketの新規セッションのrequestを送信するサーバー側で接続を許可して、このユーザー専用のメッセージ読み込みスレッドとメッセージ書き込みスレッドを立ち上げる新規でユーザーが接続される毎に同様に専用のスレッドを立ち上げる(認証で新規ユーザーなのかを判定している)この時点でユーザーが2人であるとfixすると存在しているスレッドの内訳は以下の様に7つになるメインスレッド(httpのrequestを受け付ける)Hub(メイン処理を実行)スレッドpubsubの受信用スレッドユーザーA専用の読み取りスレッドユーザーB専用の読み取りスレッドユーザーA専用の書き込みスレッドユーザーB専用の書き込みスレッドメッセージの受信と送信webscoketのセッションを張っているユーザーから新規のメッセージが送信されるユーザー専用の読み取りスレッドでメッセージを読み込んでredisのpubsub機能で購読済みのチャンネルにメッセージをpublishpublishされたメッセージを受信用のスレッドで受け取り、チャンネルを購読しているサーバーにメッセージを配信してチャネルを通じてHubにメッセージを渡すHubで受け取ったメッセージを元に、処理を行い対象のユーザーに配信するために各ユーザー専用の書き込みスレッドにメッセージをチャネル経由で送る専用の書き込みスレッドで受け取ったメッセージをwebsocket経由でユーザーに送信するこれが概要。あっ....これをスケールすると...コードはかなり、省略してアレンジしたものを載せておく。publishは自前実装の関数。何と同じメッセージがスケールしたサーバーの数(Pods)だけ、送信されているではないか。読み取ったメッセージをpublishして、受け取り配信するためのスレッドと各サーバー毎に立ち上がっているのだから。ついでに処理もサーバー台数分だけ走るので、databaseの値もめちゃくちゃになっている。構成に手を加えて、スケールしても問題なく、処理が1度のみ走り、メッセージが複数回、送信されないようにした。setupの部分の更新はなく、全体の流れは以下の様になった。大きな変更としてはスレッド間で扱うチャネルを1つ追加した。*Pubsub}メッセージの受信と送信webscoketのセッションを張っているユーザーから新規のメッセージが送信されるユーザー専用の読み取りスレッドでメッセージを読み込んでHubにチャネル経由でメッセージを渡すメッセージを元に処理を行い、redisのpubsub機能で購読済みのチャンネルにメッセージをpublishpublishされたメッセージを受信用のスレッドで受け取り、チャンネルを購読しているサーバーにメッセージを配信して別のチャネルを通じてHubにメッセージを渡すHubで受け取ったメッセージから送信先のユーザーのwebsocketのセッションが自身のサーバーに存在するかを確認。存在している場合に、ユーザー専用の書き込みスレッドにチャネル経由でメッセージを渡す専用の書き込みスレッドで受け取ったメッセージをwebsocket経由でユーザーに送信するこのように変更することで無事に、スケールした状態でもメッセージが1通のみ送信されるようなり、冷や汗が引いた。}}こんな感じで落ち着いた。Elixir/Erlangの勉強が意外なところで役に立った並行処理と聞いて、反応せずにはいられない。Elixir・Erlangの得意とするところだ。Qiitaのアドベントカレンダー...2019-12-04Elixrのプロセスとは一体何なのか。日)22歳の新卒エンジニアです。elixirという言語に惹かれています。人より少し自転車を漕ぐのが速めです。無駄なことを覚えるのが得意です。(2)月別アーカイブ広告はてなブログをはじめよう!takamizawa46さんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。