WebSocket和Redis导致挂起pubsub和/或brpop的连接

时间:2017-04-02 21:58:23

标签: ruby websocket redis publish-subscribe eventmachine

我在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

2 个答案:

答案 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命令执行此操作。