Action Cableについてしらべてみたのでそのメモ。とは言っても公式のREADME
『rails/actioncable at master · rails/rails』
を主に読みました。READMEも内容が充実していて結構勉強になりました。
Action Cableもう少し入門したいなという人がいたらぜひ読んでみてください^^
🐝 Action Cableが登場した背景
Userの入力なしに、最新の情報をWebに表示する「Realtime性」をもつリッチな
体験をユーザーに提供したいというニーズが増えてきている。こういったにニーズへの対応。
🐹 Action Cable - README
Action Cableとは、『RailsのRESTとWebSocketのシームレスな統合』である。
🍄 用語
用語Consumer, Channel, Subscribeについての説明。
- 1つのActionCableのサーバが、複数のコネクション(Consumer)をハンドリングする - 一人のユーザーが複数のChannelをSubscribeすることがある - Channelを購読している人に対して ストリーミング or ブロードキャストする
😼 (例)
接続してきたユーザーが許可するべきユーザーかを判定して、接続を確立する。
コネクションの再確立等のために、ユーザーごとに indetify
を行う。
# app/channels/application_cable/connection.rb |
ApplicationCable::Channel
を継承したクラスを作成。
# app/channels/application_cable/channel.rb |
consumer側にもコネクションのインスタンスを確立するための設定を書く。
# app/assets/javascripts/cable.coffee |
ws://cable.example.com
はAction Cable用のサーバ。CookieのネームスペースのためにRESTアプリケーション(例: http://example.com)の配下にすること。
そうすることで正しく、Cookieを送信できる。
🚜 (例1) ユーザーのONLINE/OFFLINEの表示
ユーザーがOnline/Offlineを判定するしくみ。まずはサーバサイドのChannelについて。
# app/channels/appearance_channel.rb |
#subscribed
が呼び出されるとクライアントサイドのサブスクリプションが立ち上がる。
appear/disappearのAPIには、RedisもしくはDBなどを使うことができる。
クライアントサイドのコードはこちら。
# app/assets/javascripts/cable/subscriptions/appearance.coffee |
これでユーザーがOnline/Offlineになった時に表示を切り替えるといった処理を実装できる。
🗽 (例2) 個別のユーザーへのノーティフィケーション
Channelの定義はこちら。ユーザー単位にChannel名を変更。
# app/channels/web_notifications_channel.rb |
ノーティフィケーションのタイトルと本文を受け取る部分。
# Client-side, which assumes you've already requested the right to send web notifications |
コメント配信用のJobを作成してその中でNotificationをBroadcast(配信)する。
# Somewhere in your app this is called, perhaps from a NewCommentJob |
ActionCable.server.broadcast
はRedisのpubsubキューにもとづいてメッセージを配信する- 生きているchannelに対して配信され
#received(data)
がコールされる - サーバサードにJSONがサーブされて
#received
が受け取る
その他いくつかのサンプルがある。
rails/actioncable-examples: Action Cable Examples
にいくつかのサンプルがあるので、こちらを見てみるといいかも。
😀 Configuration (設定)
Action Cableを使う上で必要となる次の3つの設定について。
- a subscription adapter - allowed request origins request origins の許可 - ActionCable用のサーバーURL
Subscription adapterの設定(Redis)
ActionCable::Server::Base
はデフォルトで Rails.root.join('config/cable.yml')
を見ている。 このYAMLファイルは次のように環境ごとの設定を書くことができる。
production: &production |
特定のドメインのみ許可
Action Cableは許可された Origin
からのリクエストしか受け付けない。受け付けるOriginは正規表現等で書くこともできる。
Rails.application.config.action_cable.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/] |
Developmentモードでは、http://localhost:3000
のみ許可。
Consumerの設定
Cableサーバを決めたら、ServerのURLをclientサイドに提供する必要がある。
シンプルにConsumerの作成時に渡す場合
独立したサーバの場合は;
App.cable = ActionCable.createConsumer("ws://example.com:28080") |
Appサーバに含める場合は;
App.cable = ActionCable.createConsumer("/cable") |
とする。
タグを介して設定ファイルから渡す場合
上記以外のパターンとして、View側に action_cable_meta_tag
を書いて設定ファイルの config.action_cable.url
の内容を渡すこともできる。
これは環境ごとにWebsocketのURLが変わる場合に有効な手法である。もし、https
を使っている場合は wss schema
を使う必要がある。
ちなみに、 ws
や wss
とはWebsocketのスキームのことです。 ws
と wss
の違いは、wss
がSSL通信を行うためのスキームという点です。
設定例としては、環境ごとに次のような設定を書く。
config.action_cable.url = "ws://example.com:28080" |
View側にJavaScriptのタグとして次のような記述を行う
<%= action_cable_meta_tag %>%=> |
コンシューマは次のように作成する。
App.cable = ActionCable.createConsumer() |
注意点
ワーカの数だけ、DBへのコネクションが発生する点である。 database.yml
にpool数があるので適切に設定すること。
🎉 ActionCalbeのサーバを動かす
Action Cable専用のサーバを動かす(Standalone)
通常のアプリケーションサーバとAction Cable用のサーバを分けて動かす場合は、Rack Applicationとして動かす。
# cable/config.ru |
そしてbinstubとして bin/cable
を作る。
|
アプリケーション内で動かす場合(In App)
PumaやThinのようなスレッドサーバを使っている場合は、アプリケーション内でAction Cableの実装を動かすこともできる。/cable
のWebSocketのリクエストを受け取る場合の設定例がこちら。
# config/routes.rb |
RedisがメッセージをSyncさせつつ、動いているよう。
😸 注意点
現時点では、cableサーバに関する部分はauto-reloadされていない。ですので、Channelや関連するモデルを変更した場合はAction Cableのサーバを再起動する必要がある。
🍮 依存関係
Action Cableはpub/subの機能を持ったプロセスとのインタフェースのアダプタを持っている。PostgreSQLやRedisなど。
🎂 デプロイ
Action CableはWebsocketとスレッドによって作られている。
サーバでは、通常のWebのプロセスとAction Cableのプロセスが存在することになる。
Action Cableは、Rack socket hijacking API を使っている。
これによって、マルチスレッドでのconnectionの管理を実現させている。これにより、サーバがマルチスレッドでなくてもAction Cableを使うことができる。
(Unicorn, Puma, Passenger
で動かすことができる)
Rails 5でWebrickからPumaに開発サーバが変わったのは、Webrickが Rack socket hijacking API
をサポートしていなかったからである。
🐞 パフォーマンス
『Action Cable - Friend or Foe?』の抜粋。
Action Cable + Puma (16スレッド)の場合;
WebSocketの同時接続数 | 平均接続時間
|:——-|:—————-
3 | 17ms
30 | 196ms
300 | 1638ms
Action Cable + Pumaをクラスタモードにして4ワーカ プロセスにした場合;
WebSocketの同時接続数 | 平均接続時間
|:——-|:—————-
3 | 9ms
30 | 89ms
300 | 855ms
Node.js + Websocketのシンプルなチャットアプリケーション
『Action Cable - Friend or Foe?』
と比較した場合;
WebSocketの同時接続数 | 平均接続時間
|:——-|:—————-
3 | 5ms
30 | 65ms
300 | 3600ms
現実的な環境とは異なるため、一概には比較しきれないが、Action Cableでも十分なパフォーマンスを出せていることが分かる。
🤔 メモ
- Polling => Good Solutionだけどスケーラビリティやモバイルでの用途に課題がある
- Server-sent Events (SSEs) => Rails 4で
ActionController::Live
として提供。永続的なコネクションを行うため、HerokuやWebrickなどで動作しなかった - Websocket => ステートを持った接続を行う。クライアントとサーバ間の接続の数だけコネクションを持っている。
- Stickyセッション => Action CableはRedisのpub/sub機能を使うことで、複数スレッドでも安定してデータを配信できる