LoginSignup
122

More than 5 years have passed since last update.

WebSocket と ActionCable

Last updated at Posted at 2016-08-18
1 / 35

  • Rails5 Meetup 発表資料

はじめに

  • 学生の頃に Socket.IO でゲームを作ってた
  • Rails は業務でコントローラに API 生やす程度
  • rspec が全然わからん

無茶振り

yuku 「mizchi なら ActionCableでなんか作れるでしょ」


なんか作った


今日の発表内容

  • WebSocket の現状
  • ActionCable

既存機能のRails5の拡張については @takashi に任せる


1. WebSocket


WebSocketとは

  • Webブラウザで扱えるTCP Socket抽象
  • HTTP1.1と比べて並列/高頻度イベントの効率が良い
  • プッシュ配信

今までWebSocket が使えなかった背景



昔話

  • 未対応ブラウザが多すぎて、フォールバック必要
    • まともな Fallback は、ほぼ Socket.IO の特権
  • ロードバランサが辛い
    • 二度目以降のリクエストを、必ず最初に接続した場所に戻す必要

フォールバック先

  • Commet
  • XHL Long Polling

いずれも Performance に難


  • フォールバック不要(IE>=11)
  • AWS ALB
  • Nginx のWebSocket サポート
  • Heroku の WebSocket サポート(※近くの Region がない)

競合するスペック

  • WebRTC の P2P
  • HTTP/2のリクエスト多重化
  • ServiceWorker はバックグラウンドでもプッシュ通知を扱う

=> 用途を選びましょう


どこで使う?

  • チャットサーバー
  • MMORPGのようなゲーム
  • SNS でのリアルタイムイベント通知
  • 高頻度な時系列データのビジュアライズ

2 ActionCable


ActionCable

  • 中身は FayeWebsocket の Rails用ラッパー といった出で立ち


ActionCable のメリット

  • シームレスなRailsインテグレーション
  • Rails::Engine をマウントする実装なので、WebSocketサーバーを切り離せる
  • 比較的枯れたRails運用ノウハウがそのまま使える

ActionCable を使うには

  • Unicorn と EventMachine が相性悪いため、Puma必須
  • Rails 側で Channel を作成
  • JS 側 で 任意の Channel を 購読する

Channel

  • 購読される単位
    • チャットアプリならば1つのチャットルームに相当
  • bloadcast でChannel 購読者全員にプッシュ
  • bloadcast_to 特定のユーザーにプッシュ

実例: JS から ActionCable への接続

application.js
// sprockets
//= aciton_cable
var cable = ActionCable.createConsumer("/cable");

or

main.js
// babel/browserify env
// npm install actioncable
import ActionCable from "actioncable";
const cable = ActionCable.createConsumer(/"cable");

引数で任意のエンドポイントを取れる


実例: チャンネル名を指定して接続

app/channels/chat_room.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_#{params[:room_id]}"
  end
end
application.js
cable.subscriptions.create({
   channel: "ChatChannel",
   room_id: "my-chat-room"
}, {
  connected: function() {
    console.log("connected");
  }
  // ...
});

実例: クライアントからイベントを投げる

main.js
const subscription = cable.subscriptions.create("ChatChannel", {/* callback */});

subscription.perform("foo", {data: 1})
app/channels/chat_room.rb
class ChatChannel < ApplicationCable::Channel
  //...
  def foo(data)
    puts data
  end
end

perform(action, data) を実行すると ChatChannel[:action] が呼ばれる。


bloadcast と received

app/channels/chat_room.rb
class ChatChannel < ApplicationCable::Channel
  //...
  def foo(data)
    ActionCable.server.broadcast "ChatChannel", data
  end
end

ChatChannel の購読者全員に data をプッシュする

cable.subscriptions.create("ChatChannel", {
  // ...
  received(data) {
    console.log(data) // => {data: 1, action: "foo"}
  }
});

bloadcast_to でユーザー個別に送ることもできる


デモ


本当は作りたかったもの

  • 編集が1s止まったら入力を送信(ここまで実装済み)
  • 3 way merge
    • 他人のチャンク編集後、他人のチャンク編集前、自分の状態
  • 更新後のカーソル位置補正

ActionCable の学習資料


閑話休題: Elixir/Phoenix

  • Phoenix.Channel は ActionCable 丸パクリ(作者はRailsコミッタの Chris McCord)
  • プロセスモデル/応答性の観点からはPhoenixの方が WebSocket に向いてそう

翻訳: 似て非なる Phoenix と Rails(原題『Phoenix is not Rails』) - Qiita


設計を考える


WebSocket アプリケーションの設計

  • 高頻度イベントにまつわる状態は Redis が向いてる
  • DBに 永続化するのは何らかのチェックポイントを通った時
  • 状態の差分を更新し続けるのは難易度が高いので、定期的に state を全部渡してsyncする、などの保証は欲しい

チャットの場合

  • 最初のユーザーが部屋にログイン
    • => 部屋名をキーにRedisのリスト要素を作成
  • 誰かが発言イベントを投げる
    • => リスト要素を更新
    • => 発言を bloadcast
  • 1分に1回
    • => 全員に現在のリストを配信して補正
  • 5分に1回
    • => リスト要素の中身を DB に保存
  • 最後のユーザーがログアウト
    • => リスト要素の中身を DB に保存
    • => Redisの要素を削除

設計意図

  • できるだけ DB を触らず Redis のインメモリデータだけ使う

    • Redisは肥大化すると運用が辛いので、使い終わったらすぐ消す
    • Redisの スナップショット を DB に保存する
  • ここまで来ると、あとは オンラインゲームを支える技術 などが参考になる


趣味でオンラインゲーム作ってた時

  • ダンジョン階層ごとにチャンネル分割
  • 12FPSでゲームステートを配信
  • クライアントは60FPSなので、時系列データから他のEntityの座標予測
  • モンスターを倒したら保存

最後に

  • まだ負荷計測に至ってないので、どこがボトルネックになるかわかってない
  • 高頻度イベントはWebのお約束が通じなかったりするんで、頑張りましょう

終わり

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
122