使用 Rails 4 和 Redis 实现响应流
介绍
如果您对是否要更新旧应用程序以使用 Rails 4 犹豫不决,那么添加ActionController::Live可能会帮助您更轻松地做出决定。它能够保持与服务器的连接打开,然后服务器可以轻松地响应部分更新。这弥补了导致人们选择node.js而不是 Rails 进行项目的较大差距之一。
基本 Redis 连接
一年多前,Aaron Patterson 写了一篇关于Rails 中的实时流媒体的精彩文章,但界面今天基本相同。那篇文章仍然是ActionController::Live 的一个很好的起点。
我第一次接触这个主题是在学习 Code School 的Rails 4:Zombie Outlaws课程时。最后一级是关于流式传输的,最后提到了如何使用 Redis 进行流式传输。如果您在浏览器中连接到此端点,页面将永远加载,并偶尔将响应发送回浏览器。
class ActivitiesController < ApplicationController
include ActionController::Live
def index
response.headers["Content-Type"] = "text/event-stream"
redis = Redis.new
redis.psubscribe("user-#{current_user.id}:*") do |on|
on.pmessage do |subscription, event, data|
response.stream.write "data: #{data}
"
end
end
rescue IOError
# Client disconnected
ensure
redis.quit
response.stream.close
end
end
以下是发生的事情的简要回顾:
- 我们正在使用Puma,它允许与服务器进行并发连接。
- 我们创建一个与 Redis 的新连接。这很重要,因为当我们调用psubscribe时,该连接被锁定并且无法执行任何其他操作。
- 使用psubscribe通过使用表达式订阅此用户的所有消息。在应用程序的其他地方,我们正在向同一频道发布消息。
- 收到消息后,它会被传递给客户端。在本例中,我们传递的是 JSON。
- 确保redis连接退出,响应结束。
问题
如果您编写了上述代码并在浏览器中打开该操作,它实际上会正常工作 - 直到您尝试再次加载页面。此时,从服务器的角度来看将有两个连接打开,但只有一个处于活动状态。这是因为服务器不知道客户端已断开连接。
That IOError error isn't triggered when the client disconnects, as you might expect, but instead when the server attempts to write to the response.stream only to find that it is no longer active. Turns out this is a well discussed problem That leaves us with a few options on how to test if the client has disconnected:
- Have the server connection timeout every minute or so (if you're on Heroku, my guess is this will automatically happen).
- Ping the client every few seconds to see if they are still there.
A Working Solution
I ran into a StackOverflow post on this exact topic, which led to a working solution for this. This solution follows the "ping" method.
class ActivitiesController < ApplicationController
include ActionController::Live
def index
response.headers["Content-Type"] = "text/event-stream"
redis = Redis.new
ticker = Thread.new { loop { sse.write 0; sleep 5 } }
sender = Thread.new do
redis.psubscribe("user-#{current_user.id}:*") do |on|
on.pmessage do |subscription, event, data|
response.stream.write "data: #{data}
"
end
end
end
ticker.join
sender.join
rescue IOError
# Client disconnected
ensure
Thread.kill(ticker) if ticker
Thread.kill(sender) if sender
redis.quit
response.stream.close
end
end
This solution is based on the idea that the server will know the client has disconnected when it attempts to write to it only to find it's not there. In this case we open up two threads -- one that does our Redis subscription and another that handles making sure the client is still there.
Closing the Database
One downside of keeping the connection open is that if you're using ActiveRecord, that connection will not be released until the request is complete. During the Redis subscribe phase, if you don't need to keep that connection open, you can return the current connection to the connection_pool.
ActiveRecord::Base.connection_pool.release_connection
If you set this up to run in a before filter, and do any database communication before that, you shouldn't run into database connection limits.
Update
For an example of how this technique is used, read the post on Teaching iOS 7 at Code School. This post details the user experience that can be achieved using response streams. Guide was originally posted here.
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~