gpt4 book ai didi

ruby-on-rails - Redis + ActionController::Live 线程不会死

转载 作者:IT王子 更新时间:2023-10-29 05:54:26 26 4
gpt4 key购买 nike

背景:我们已经在我们现有的一个 Rails 应用程序中构建了一个聊天功能。我们正在使用新的 ActionController::Live模块并运行 Puma(在生产中使用 Nginx),并通过 Redis 订阅消息。我们正在使用 EventSource客户端异步建立连接。

问题摘要:连接终止时,线程永远不会消亡。

例如,如果用户离开、关闭浏览器,甚至转到应用程序中的不同页面,则会产生一个新线程(如预期),但旧线程继续存在。

我目前看到的问题是,当任何这些情况发生时,服务器无法知道浏览器端的连接是否终止,直到有人尝试写入这个损坏的流,这在浏览器之后永远不会发生已离开原始页面。

这个问题似乎被记录在案 on github ,并且在 StackOverflow here (pretty well exact same question) 上提出了类似的问题和 here (regarding getting number of active threads) .

基于这些帖子,我能够提出的唯一解决方案是实现一种线程/连接扑克。尝试写入断开的连接会生成 IOError我可以捕获并正确关闭连接,从而使线程死亡。这是该解决方案的 Controller 代码:

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

(注意: events 方法似乎在 subscribe 方法调用中被阻塞。其他一切(流媒体)工作正常,所以我认为这是正常的。)

(其他注意事项:flusher 线程概念作为单个长时间运行的后台进程更有意义,有点像垃圾线程收集器。我上面实现的问题是为每个连接生成一个新线程,这是毫无意义的。任何人尝试实现这个概念应该更像是一个单一的过程,而不是像我概述的那样。当我成功地将它重新实现为一个单一的后台过程时,我会更新这篇文章。)

这个解决方案的缺点是我们只是延迟或减轻了问题,并没有完全解决它。我们每个用户仍然有 2 个线程,除了 ajax 等其他请求,从扩展的角度来看,这似乎很糟糕;对于具有许多可能的并发连接的更大系统来说,这似乎是完全无法实现和不切实际的。

我觉得我错过了一些重要的东西;我发现很难相信 Rails 有一个特性,如果没有像我那样实现自定义连接检查器,它就会明显损坏。

问题:我们如何允许连接/线程在不实现诸如“连接扑克”或垃圾线程收集器之类的老生常谈的情况下死亡?

和往常一样,如果我遗漏了什么,请告诉我。

更新
只是添加一些额外的信息:Huetsch 在 github 上发布了 this comment指出 SSE 是基于 TCP 的,它通常在连接关闭时发送一个 FIN 数据包,让另一端(在这种情况下为服务器)知道关闭连接是安全的。 Huetsch 指出浏览器没有发送该数据包(可能是 EventSource 库中的错误?),或者 Rails 没有捕获它或对其进行任何处理(如果是这种情况,肯定是 Rails 中的错误)。搜索还在继续……

另一个更新
使用 Wireshark,我确实可以看到正在发送的 FIN 数据包。诚然,我对协议(protocol)级别的东西不是很了解或没有经验,但是据我所知,当我使用浏览器的 EventSource 建立 SSE 连接时,我肯定会检测到从浏览器发送的 FIN 数据包,如果我没有发送数据包删除该连接(意味着没有 SSE)。虽然我对 TCP 知识不是很了解,但这似乎向我表明连接确实被客户端正确终止了;也许这表明 Puma 或 Rails 中存在错误。

又一个更新
@JamesBoutcher/boutcheratwest(github) 给我指了一个 discussion on the redis website regarding这个问题,特别是关于 .(p)subscribe 的事实方法永远不会关闭。该站点上的海报指出了我们在此处发现的同一件事,当客户端连接关闭时,Rails 环境永远不会收到通知,因此无法执行 .(p)unsubscribe方法。他询问 .(p)subscribe 的超时方法,我认为它也可以工作,但我不确定哪种方法(我上面描述的连接扑克,或他的超时建议)会是更好的解决方案。理想情况下,对于连接扑克解决方案,我想找到一种方法来确定连接是否在另一端关闭而不写入流。就像现在一样,正如您所看到的,我必须实现客户端代码来单独处理我的“戳”消息,我认为这很突兀和愚蠢。

最佳答案

我刚刚做的一个解决方案(从@teeg 借了很多),它似乎工作正常(还没有失败测试,​​tho)

配置/初始化程序/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

然后在我的 Controller 中:
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

关于ruby-on-rails - Redis + ActionController::Live 线程不会死,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18970458/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com