Pau*_*ter 34 multithreading ruby-on-rails publish-subscribe redis ruby-on-rails-4
背景:我们在一个现有的Rails应用程序中构建了一个聊天功能.我们正在使用新ActionController::Live模块并运行Puma(在生产中使用Nginx),并通过Redis订阅消息.我们正在使用EventSource客户端异步建立连接.
问题摘要:当连接终止时,线程永远不会死亡.
例如,如果用户离开,关闭浏览器,甚至转到应用程序内的其他页面,则会生成一个新线程(如预期的那样),但旧的线程继续存在.
我现在看到的问题是,当出现任何这种情况时,服务器无法知道浏览器端的连接是否被终止,直到某些东西试图写入这个损坏的流,这在浏览器中永远不会发生已离开原始页面.
这个问题似乎记录在github上,类似的问题在这里问StackOverflow (非常完全相同的问题)和这里(关于获取活动线程的数量).
基于这些帖子,我能够提出的唯一解决方案是实现一种线程/连接扑克.尝试写入断开的连接会生成一个IOError我可以捕获并正确关闭连接,允许线程死亡.这是该解决方案的控制器代码:
def events
response.headers["Content-Type"] = "text/event-stream"
stream_error = false; # used by flusher thread to determine when to stop
redis = Redis.new
# Subscribe to our events
redis.subscribe("message.create", "message.user_list_update") do |on|
on.message do |event, data| # when message is received, write to stream
response.stream.write("messageType: '#{event}', data: #{data}\n\n")
end
# This is the monitor / connection poker thread
# Periodically poke the connection by attempting to write to the stream
flusher_thread = Thread.new do
while !stream_error
$redis.publish "message.create", "flusher_test"
sleep 2.seconds
end
end
end
rescue IOError
logger.info "Stream closed"
stream_error = true;
ensure
logger.info "Events action is quitting redis and closing stream!"
redis.quit
response.stream.close
end
Run Code Online (Sandbox Code Playgroud)
(注意:该events方法似乎在subscribe方法调用时被阻止.其他所有内容(流式传输)都正常工作,所以我认为这是正常的.)
(其他注意:flusher线程概念作为一个长时间运行的后台进程更有意义,有点像垃圾线程收集器.上面我的实现的问题是为每个连接生成一个新线程,这是没有意义的.尝试实现这个概念应该更像是一个单一的过程,而不是我概述的那样.当我成功地将其重新实现为单个后台进程时,我将更新这篇文章.)
这个解决方案的缺点是我们只是延迟或减少了问题,而没有完全解决它.除了其他请求(如ajax)之外,我们仍然有每个用户2个线程,从扩展的角度来看这似乎很糟糕; 对于具有许多可能的并发连接的大型系统来说,这似乎是完全无法实现的,也是不切实际的.
我觉得我错过了一些至关重要的东西; 我觉得有点难以相信Rails有一个明显被破坏的功能而没有像我那样实现自定义连接检查器.
问题:我们如何在不实现诸如"连接扑克"或垃圾线程收集器之类的东西的情况下允许连接/线程死亡?
如果我遗漏了任何东西,请随时告诉我.
更新
只是为了添加一些额外的信息:Huetsch在github发布了这条评论指出SSE基于TCP,它通常在连接关闭时发送FIN数据包,让另一端(本例中的服务器)知道它可以安全地关闭连接.Huetsch指出浏览器没有发送该数据包(可能是EventSource库中的错误?),或者Rails没有捕获它或用它做任何事情(如果是这种情况,肯定是Rails中的一个错误).搜索继续......
使用Wireshark的另一个更新,我确实可以看到FIN数据包被发送.不可否认,我对协议级别的东西知之甚少或不熟悉,但据我所知,当我使用浏览器中的EventSource建立SSE连接时,我肯定会检测到从浏览器发送的FIN数据包,如果我发送了NO数据包删除该连接(意味着没有SSE).虽然我对TCP的知识并不十分了解,但这似乎向我表明,客户端确实正确地终止了连接; 也许这表明Puma或Rails中存在错误.
另一个更新
@JamesBoutcher/boutcheratwest(github)向我指出了关于redis网站关于这个问题的讨论,特别是关于该.(p)subscribe方法永远不会关闭的事实.该站点上的海报指出了我们在此处发现的相同内容,即当客户端连接关闭时,Rails环境永远不会得到通知,因此无法执行该.(p)unsubscribe方法.他询问有关的超时.(p)subscribe方法,我认为也可以,但我不确定哪种方法(我上面描述的连接扑克,或他的超时建议)将是一个更好的解决方案.理想情况下,对于连接扑克解决方案,我想找到一种方法来确定连接是否在另一端关闭而不写入流.就像现在一样,正如你所看到的,我必须实现客户端代码来单独处理我的"戳"消息,我认为这是一种突兀和愚蠢的行为.
Jam*_*her 15
我刚刚做的一个解决方案(从@teeg借了很多)似乎工作正常(没有失败测试它,所以)
配置/初始化/ redis.rb
$redis = Redis.new(:host => "xxxx.com", :port => 6379)
heartbeat_thread = Thread.new do
while true
$redis.publish("heartbeat","thump")
sleep 30.seconds
end
end
at_exit do
# not sure this is needed, but just in case
heartbeat_thread.kill
$redis.quit
end
Run Code Online (Sandbox Code Playgroud)
然后在我的控制器中:
def events
response.headers["Content-Type"] = "text/event-stream"
redis = Redis.new(:host => "xxxxxxx.com", :port => 6379)
logger.info "New stream starting, connecting to redis"
redis.subscribe(['parse.new','heartbeat']) do |on|
on.message do |event, data|
if event == 'parse.new'
response.stream.write("event: parse\ndata: #{data}\n\n")
elsif event == 'heartbeat'
response.stream.write("event: heartbeat\ndata: heartbeat\n\n")
end
end
end
rescue IOError
logger.info "Stream closed"
ensure
logger.info "Stopping stream thread"
redis.quit
response.stream.close
end
Run Code Online (Sandbox Code Playgroud)
我目前正在制作一个围绕 ActionController:Live、EventSource 和 Puma 的应用程序,对于那些遇到关闭流等问题的应用程序,IOError在 Rails 4.2 中您需要拯救 ,而不是拯救ClientDisconnected。例子:
def stream
#Begin is not required
twitter_client = Twitter::Streaming::Client.new(config_params) do |obj|
# Do something
end
rescue ClientDisconnected
# Do something when disconnected
ensure
# Do something else to ensure the stream is closed
end
Run Code Online (Sandbox Code Playgroud)
我从这个论坛帖子中找到了这个方便的提示(一直在底部):http://railscasts.com/episodes/401-actioncontroller-live ?view=comments
| 归档时间: |
|
| 查看次数: |
6110 次 |
| 最近记录: |