Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/ruby/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ruby WebSocket和Redis导致与pubsub和/或brpop的挂起连接_Ruby_Websocket_Redis_Publish Subscribe_Eventmachine - Fatal编程技术网

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

Ruby WebSocket和Redis导致与pubsub和/或brpop的挂起连接,ruby,websocket,redis,publish-subscribe,eventmachine,Ruby,Websocket,Redis,Publish Subscribe,Eventmachine,我正在WebSocket(WS)中发布一个Redis订阅。当我收到WS-open时,我对请求执行线程,然后实例化Redis客户端。在open中,我执行Redis线程并发布订阅 这一切都很好,直到我收到一个意外的WS-close。此时,运行Redis订阅的线程消失了。如果我退订,我会被绞死。如果我不退订,我留下了一个虚拟订阅,这会给我下一轮带来麻烦 一旦发布订阅的线程终止,是否有办法删除订阅?我注意到Redis实例对于终止的线程有一个mon变量。示例Ruby代码是: class Backend

我正在WebSocket(WS)中发布一个Redis订阅。当我收到WS-open时,我对请求执行线程,然后实例化Redis客户端。在open中,我执行Redis线程并发布订阅

这一切都很好,直到我收到一个意外的WS-close。此时,运行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

一旦我真正理解了问题,解决方法就相当简单了。Redis线程实际上仍然存在。但是,Redis仍然挂起,因为它正在等待线程获得控制权。为此,WS.close代码需要通过在WS.close中使用EM.next_勾选来放弃控制,如下所示:

ws.on :close do |event|
  EM.next_tick do
    # Hang occurs here
    unsubscribed = credis.unsubscribe(channel)
  end
end

一旦我真正理解了问题,解决方法就相当简单了。Redis线程实际上仍然存在。但是,Redis仍然挂起,因为它正在等待线程获得控制权。为此,WS.close代码需要通过在WS.close中使用EM.next_勾选来放弃控制,如下所示:

ws.on :close do |event|
  EM.next_tick do
    # Hang occurs here
    unsubscribed = credis.unsubscribe(channel)
  end
end

这是一个较长的评论,旨在提供一个解决方案,而不是一个解决方案

如果是我的申请,我会重新考虑设计

为每个websocket客户端打开一个新的Redis连接和一个新线程是一项相当大的资源投入

只是澄清一下,每个到Redis的连接都需要一个TCP/IP套接字(这是一个有限的资源)和内存。每个线程的预留堆栈内存应该大约为2MB。。。因此,1K Redis连接和线程可能会产生大约2GB的内存成本

此外,Redis服务器本身通常可以接受的连接数量有限(尽管这通常是一个价格问题,而不是一个硬限制,因为它们是为扩展而设计的)

重新调整设计应该非常简单,这样一个线程和连接就可以为所有websocket客户端提供服务,这还可以更轻松地进行
订阅
/
取消订阅
管理


这可以使用内部每进程广播系统(如由实现)或使用Redis
subscribe
/
punsubscribe
命令来执行。

这是一个较长的注释,提供了一种解决方法,而不是解决方案

如果是我的申请,我会重新考虑设计

为每个websocket客户端打开一个新的Redis连接和一个新线程是一项相当大的资源投入

只是澄清一下,每个到Redis的连接都需要一个TCP/IP套接字(这是一个有限的资源)和内存。每个线程的预留堆栈内存应该大约为2MB。。。因此,1K Redis连接和线程可能会产生大约2GB的内存成本

此外,Redis服务器本身通常可以接受的连接数量有限(尽管这通常是一个价格问题,而不是一个硬限制,因为它们是为扩展而设计的)

重新调整设计应该非常简单,这样一个线程和连接就可以为所有websocket客户端提供服务,这还可以更轻松地进行
订阅
/
取消订阅
管理


这可以使用内部每进程广播系统(如由实现)或使用Redis
subscribe
/
punsubscribe
命令来执行。

您可以尝试的另一种方法是让Redis定期向主题发布一些无害的消息,强制写入所有订阅。任何“幻影”订阅都应该被清除/关闭,因为Redis检测到向死掉/关闭的对等方写入的错误。@Castaglia有趣。在我的情况下,知道之前的订阅无法清除,但不知道如何清除,恢复后,我发布了另一个订阅并继续处理。这会将消息发布到同一频道,虚拟订阅和新直播频道都使用相同的名称。你希望这也能清除幻影吗?我这样问是因为,随着时间的推移,我在那个场景中遇到了资源限制。啊,我想我明白了。订阅绑定到TCP连接,并且TCP连接仍然存在;客户端上的线程从应用程序其余部分的POV中退出,从而“丢失”订阅。对我想知道从同一TCP连接(清除任何虚订阅/丢失/延迟订阅)取消订阅(指定所有频道,或不取消订阅所有频道)然后显式重新订阅是否可行。@Castaglia,您是否同意我添加的更新和代码将可靠地终止连接?到目前为止,似乎对我有效,但是,好吧,还需要进一步的测试和验证。谢谢。因为没有其他方法可以获得连接ID,我想不出更优雅的方法了。如果您想使用客户机的本地地址/端口,而不是连接ID来执行
client KILL
命令,可能会这样做?另一种方法是让Redis定期向主题发布一些无害的消息,强制写入所有订阅。任何“幻影”订阅都应该被清除/关闭,因为Redis检测到向死掉/关闭的对等方写入的错误。@Castaglia有趣。在我的情况下,知道之前的订阅无法清除,但不知道如何清除,恢复后,我发布了另一个订阅并继续处理。这会将消息发布到同一频道,包括虚拟订阅和同一na下的新直播频道