我在WebSocket(WS)中发出Redis订阅。当我收到WS打开时,我将请求线程化,然后实例化Redis客户端。在开放的内部,我为Redis编写了线程并发出订阅。
这一切都很好,直到我收到意外的WS关闭。此时,运行Redis订阅的线程消失了。如果我发出取消订阅,我会挂起。如果我没有取消订阅,我就会留下一个幻影订阅,这会让我下次麻烦。
一旦发出订阅的帖子终止,是否有某种方法可以删除订阅?我注意到Redis实例的终止线程有一个mon变量。示例Ruby代码是:
class Backend
include MInit
def initialize(app)
setup
@app = app
end
def run!(env)
if Faye::WebSocket.websocket?(env)
ws = Faye::WebSocket.new(env, [], ping: KEEPALIVE_TIME)
ws_thread = Thread.fork(env) do
credis = Redis.new(host: @redis_uri.host, port: @redis_uri.port, password: @redis_uri.password)
ws.on :open do |event|
channel = URI.parse(event.target.url).path[1..URI.parse(event.target.url).path.length]
redis_thread = Thread.fork do
credis.subscribe(channel) do |on|
on.message do |message_channel, message|
sent = ws.send(message)
end
on.unsubscribe do |message_channel|
puts "Unsubscribe on channel:#{channel};"
end
end
end
end
ws.on :message do |event|
handoff(ws: ws, event: event)
end
ws.on :close do |event|
# Hang occurs here
unsubscribed = credis.unsubscribe(channel)
end
ws.on :error do |event|
ws.close
end
# Return async Rack response
ws.rack_response
end
end
else
@app.call(env)
end
private
def handoff(ws: nil, event: nil, source: nil, message: nil)
# processing
end
end
答案 0 :(得分:1)
一旦我真正理解了这个问题,修复就会非常简单。 Redis线程实际上仍然存在。但是,Redis无论如何都在悬挂,因为它正在等待线程获得控制权。为此,WS.close代码需要通过在WS.close中使用EM.next_tick来放弃控制,如下所示:
ws.on :close do |event|
EM.next_tick do
# Hang occurs here
unsubscribed = credis.unsubscribe(channel)
end
end
答案 1 :(得分:1)
这是一个提供解决方法而不是解决方案的长篇评论。
如果是我的申请,我会重新考虑设计。
为每个websocket客户端打开一个新的Redis连接和一个新线程是一个相当大的资源承诺。
为了澄清,每次与Redis的连接都需要TCP / IP套接字(这是一种有限的资源)和内存。对于预留的堆栈内存,线程每个线程的成本应该约为2Mib ...因此1K Redis连接和线程可能会在内存中产生大约2Gib的成本。
除此之外,Redis服务器本身通常可以接受的连接数量有限(尽管这通常是价格而非硬限制的问题,因为它们的设计是为了扩展)。
重新调整设计应该非常简单,因此单个线程和连接可以为所有websocket客户端提供服务,这也可以实现更轻松的subscribe
/ unsubscribe
管理。
可以使用内部每进程广播系统(例如{{3}}实施)或使用Redis subscribe
/ punsubscribe
命令执行此操作。