Action Cable 即时通讯

    通讯协定可以让浏览器和服务器,进行持续性的双向连线沟通。目前支援程度还不错 Can I use?

    img2

    (图示来源 )

    但由于需要与 Server 端进行持续性的连线,对于原本采用 Process-Based 设计的 Rails 比较不适合,因此会将这部分的元件拆出去,解法有:

    • 将即时通讯的部分拆出去成为独立的 Ruby 元件,独立于 Rails 处理一般 HTTP requests 流量的 Process 之外,详见下一节。
    • 整套改成用其他程式语言,例如 Node.js 有

    发布/订阅设计模式是一种在即时通讯上很常用的架构,可以将通讯拆成发布方和订阅方,发布方异步地将讯息传送给不定数量的订阅方。Pub/Sub 部分可以从你的主应用 Process 外,独立出来成为一个单独的运作元件。

    附带有 Pub/Sub 功能的数据库:

    • PostgreSQL

    Pub/Sub 模型 + WebSocket 的 Ruby 框架

    • Rails 5 内建的 ActionCable

    因为越来越多的即时通讯需求,Rails 在 5.0 之后新增的 ActionCable 元件来支援 WebSocket,整合了后端 Rails 和前端 JavaScript 呼叫接口,可以很方便的开发即时通讯的应用。

    ActionCable 官方文件

    (图示来源 )

    安装方式

    • 升级 Rails5 并使用 ,不能用 webrick 了
    • 安装 Redis
    • 修改 config/cable.yml 把 async 都改成用 redis
    • 修改 config/application.rb 加上 config.action_cable.mount_path = '/cable'
    • 修改 app/assets/javascripts/application.js 要加载 cable.js

    即时聊天室是最常见的应用,让我们用这个例子来举例。

    WebSocket 虽然支援双向通讯,但是在实作上最重要需要即时的部分,其实只有接收资料。送资料给 Server 其实不一定需要走 WebSocket 连线,也可以用 Ajax。以下两篇教学文章,刚好分别用了不同的方式来实作:

    方法一:送出的部分沿用 ajax request,如同 Real-Time Rails: Implementing WebSockets in Rails 5 with Action Cable 这篇教学

    方法二:送出的部分也走 websocket 路线,如同 这篇教学

    img

    在 ActionCable 中可以用 partial template 在 server-side 回传给浏览器,这样就可以重用服务器的 erb template。

    注意到用 WebSocket 连线,如果浏览器有跳页的话,是会重新连线的。这就是为什么 SPA(Single-Page Application) 的形式比较适合做即时通讯,在那个应用页面中,所有的操作都是用 Ajax,这就才不会跳页。而在 Rails 中则是搭配了 Turbolinks 的 Ajax 换页效果,才不会每页都重连 Websocket (什么? 你听我在 Ajax 一章的建议已经拆除 Turbolinks 了??)

    多个聊天室、整合 Devise 认证、Broadcast 的部分改成用 ActiveJob

    参考 Create a Chat App with Rails 5, ActionCable, and Devise 这篇教学

    当 Broadcast 的呼叫是发生在一般 request/response lifecycle 时,建议改放在 ActiveJob 里,因为需要通知的用户可能很多。

    其他 ActionCable 没有帮你作的事情

    参考

    Abrupt failures can corrupt state, no ACK/NACK support, Message Ordering 等等都是没有保证的

    例如如果你重新整理一下网页,WebSocket 会重新连线,那这中间断掉时间发布的新讯息,你就不会收到。

    • 修改 config/environments/production.rb 加上 production 网址 config.action_cable.allowed_request_origins = ["https://foobar.com"]
      • Rails 5.0.1 之后变默认了
    • 记得装 Redis
    • websocket server 可以跟 rails process 放一起,也可以分开。前者比较简单,但后者可以分开 scale-up。
      • 要拆开的话,砍掉 config.action_cable.mount_path 这个设定,改成设定 config.action_cable.url 。但是如果拆开的 domain 和 port 不同的话,本来可以顺便用 cookie 做认证可能会有问题,因为 cookie 不会送来,你可以改 多一个参数是 domain: :alldomain: ["your_website.example.com", "your_ws.example.com"] 将 session cookie 扩大到跨 subdomain 都可以吃的到。
    • Passenger 和 Nginx 设定
    • CloudFlare 也有支援,但没有讲清楚到底可以接受多少连线数量
    • iOS 当然也可以接 websocket,可以 google 看看 “ios websocket”
    • Websocket Shootout: Clojure, C++, Elixir, Go, NodeJS, and Ruby 在一个 Process 下的效能 benchmark,很遗憾 Rails ActionCable 的效能敬陪末座。若依照 每mb的连线数 重新排序:
      • C++ 55
      • Node.js 43
      • Go 30
      • Clojure 18
      • Elixir: 13
      • Ruby: 3
    • 可以关注看看,用其他语言的实作来取代 Rails 的 Websocket Server