我应该如何使用EventMachine处理这个用例?

时间:2012-09-06 19:29:48

标签: ruby eventmachine

我有一个应用程序可以响应客户端发送的消息。一条消息是reload_credentials,应用程序会在新客户端注册时收到该消息。然后,此消息将连接到PostgreSQL数据库,对所有凭据执行查询,然后将它们存储在常规Ruby散列(client_id => client_token)中。

应用程序可能会收到的其他一些消息是startstoppause,用于跟踪某些会话时间。我的观点是,我设想应用程序以下列方式运行:

  • 客户端发送消息
  • 消息排队
  • 正在处理
  • 队列

但是,例如,我不想阻止反应堆。此外,让我们想象一下,我的队列中有一条reload_credentials消息。在从数据库重新加载凭据之前,我不希望处理队列中的任何其他消息。此外,当我处理某个消息(比如等待凭据查询完成)时,我想允许其他消息入队。

你能指导我解决这个问题吗?我想我可能不得不使用em-synchrony,但我不确定。

2 个答案:

答案 0 :(得分:7)

使用其中一个Postgresql EM驱动程序或EM.defer,这样就不会阻塞反应堆。

当您收到'reload_credentials'消息时,只需翻转一个标志,该标志会导致所有后续消息入队。 'reload_credentials'完成后,处理队列中的所有消息。在队列为空之后,翻转导致消息在接收时被处理的标志。

Postgresql的EM驱动程序列于此处:https://github.com/eventmachine/eventmachine/wiki/Protocol-Implementations

module Server
  def post_init
    @queue               = []
    @loading_credentials = false
  end

  def recieve_message(type, data)
    return @queue << [type, data] if @loading_credentials || !@queue.empty?
    return process_msg(type, data) unless :reload_credentials == type
    @loading_credentials = true
    reload_credentials do
      @loading_credentials = false
      process_queue
    end
  end

  def reload_credentials(&when_done)
    EM.defer( proc { query_and_load_credentials }, when_done )
  end


  def process_queue
    while (type, data = @queue.shift)
      process_msg(type, data)
    end
  end

  # lots of other methods
end

EM.start_server(HOST, PORT, Server)

如果您希望所有连接在任何连接收到“reload_connections”消息时对消息进行排队,则必须通过本征类进行协调。

答案 1 :(得分:4)

以下是我的假设,类似于您当前的实现:

    class Worker
      def initialize queue
        @queue = queue
        dequeue
      end

      def dequeue
        @queue.pop do |item|
          begin
            work_on item
          ensure
            dequeue
          end
        end
      end

      def work_on item
        case item.type
        when :reload_credentials
          # magic happens here
        else
          # more magic happens here
        end
      end
    end


    q = EM::Queue.new

    workers = Array.new(10) { Worker.new q }

如果我理解正确的话,上面的问题是,您不希望工作人员处理作业(生产者时间轴中较早到达的作业),而不是任何reload_credentials作业。以下内容应该为此提供服务(最后还要提醒注意)。

    class Worker
      def initialize queue
        @queue = queue
        dequeue
      end

      def dequeue
        @queue.pop do |item|
          begin
            work_on item
          ensure
            dequeue
          end
        end
      end

      def work_on item
        case item.type
        when :reload_credentials
          # magic happens here
        else
          # more magic happens here
        end
      end
    end

    class LockingDispatcher
      def initialize channel, queue
        @channel = channel
        @queue = queue

        @backlog = []
        @channel.subscribe method(:dispatch_with_locking)

        @locked = false
      end

      def dispatch_with_locking item
        if locked?
          @backlog << item
        else
          # You probably want to move the specialization here out into a method or
          # block that's passed into the constructor, to make the lockingdispatcher
          # more of a generic processor
          case item.type
          when :reload_credentials
            lock
            deferrable = CredentialReloader.new(item).start
            deferrable.callback { unlock }
            deferrable.errback  { unlock }
          else
            dispatch_without_locking item
          end
        end
      end

      def dispatch_without_locking item
        @queue << item
      end

      def locked?
        @locked
      end

      def lock
        @locked = true
      end

      def unlock
        @locked = false
        bl = @backlog.dup
        @backlog.clear
        bl.each { |item| dispatch_with_locking item }
      end

    end

    channel = EM::Channel.new
    queue = EM::Queue.new

    dispatcher = LockingDispatcher.new channel, queue

    workers = Array.new(10) { Worker.new queue }

因此,第一个系统的输入会出现在q上,但在这个新系统中,它会进入channelqueue仍用于工作人员之间的工作分配,但在刷新凭据操作正在进行时未填充queue。遗憾的是,由于我没有花费更多时间,因此我没有概括LockingDispatcher,因此它没有与调度CredentialsReloader的项类型和代码相结合。我会留给你的。

您应该在此注意,虽然这可以解释我对您原始请求的理解,但通常最好放宽这种要求。如果不改变该要求,有几个突出的问题基本上无法根除:

  • 在启动凭据作业之前,系统不会等待执行作业完成
  • 系统将非常严重地处理凭证作业的突发 - 其他可能可处理的项目将不会。
  • 如果凭据代码中存在错误,则积压可能会填满内存并导致失败。如果代码是可中止的,那么简单的超时可能足以避免灾难性的影响,并且后续消息可以充分处理以避免进一步的死锁。

实际上听起来你在系统中有一些用户ID的概念。如果您仔细考虑您的要求,则可能只需要对与凭据处于刷新状态的用户标识相关的项目进行积压。这是一个不同的问题,涉及不同类型的调度。尝试为这些用户提供锁定积压的哈希值,并在凭证完成时回调以将这些积压消耗到工作人员或类似的安排中。

祝你好运!