使用amqp

时间:2015-12-07 05:47:15

标签: ruby-on-rails rabbitmq amqp

我正在使用rabbitmqamqp protocol在我的应用程序中聊天。我希望成功收到发件人的邮件acknowledgement

创建了一个频道

channel  = AMQP.channel

在rabbitmq上创建了一个队列

channel.queue(receiver_id, :auto_delete => false, durable: true)

频道播出

sender_exchange = channel.fanout(sender_id+"exchange") 

频道发布

channel.publish(message)

现在我希望获得acknowledged使用ruby on rails收到的邮件,让我知道我使用哪种方法获取acknowledgement

2 个答案:

答案 0 :(得分:3)

答案实际上取决于“收到的消息确认”应该在您的申请中意味着什么。这有两种可能性:

  1. 经纪人已收到该消息
  2. 该消息已由经纪人的一个或多个消费者收到
  3. 在您的聊天功能中,此确认是否意味着选项1(从客户端向服务器发送的消息)或选项2(从客户端向一个或多个其他客户端发送的消息)?

    Confirms (Publisher Acknowledgements)是RabbitMQ的一项功能,它扩展了AMQP,以向发布者确认代理已收到消息。这可能是选项1的最佳解决方案。它可能不是您想要的选项2,因为即使消息未路由到任何消费者,它也会确认。请参阅“何时确认消息?”上面链接的文档中的部分。

    选项2有点像电子邮件客户端中的“已读回执”。它将要求接收方在收到并显示消息后发送收据消息。

    要在rails中实现确认,假设您使用Bunny作为AMQP客户端,请遵循发布者确认示例here。它可能看起来像这样:

    # wherever I create my channel
    channel.confirm_select
    
    # publish my messages
    
    channel.wait_for_confirms
    

答案 1 :(得分:0)

如果您想知道消费者收到消息的时间(在知道经纪人收到消息后,它听起来并不像您;如果是, @Ryan Hoegg的回答将为您提供所需的信息,您需要做RPC pattern的高度异步变体。

RPC通常是同步的:你发送一个命令(在这种情况下通过RabbitMQ队列)并在它完成运行后立即得到响应。如果是"读取收据",该命令可能会在将来很长时间内执行。

如果你坚持使用RabbitMQ,下面就是你如何在概念上实现读取收据。它需要相当多的代码,因为RabbitMQ不会自动支持这个/没有原语#很长一段时间后,看看是否已收到某些内容"。读取收据可以通过每个聊天(对等对)的一个队列(和常量空间;没有备份读取通知消息填充RabbitMQ)来实现。

一般模式

说Alice想要发送给Bob的消息的收据。

  1. 为每封邮件提供monotonically increasing数字标识符。第一条消息是#1,第二条消息是#2,依此类推。 RabbitMQ中的message_id字段非常适合此目的。爱丽丝应该手动"标记"每条消息都有不断增加的ID。
  2. 示例(Alice):

    message_number = message_number + 1
    sender_exchange.publish(message_body, :message_id => message_number)
    
    1. Alice的客户跟踪"让Bob看了我的留言?"通过存储两个数字:Alice发送的最高编号的消息,以及Bob读取的最新消息。因此,如果爱丽丝的阅读状态是"是3,这意味着Alice已经确认Bob已经阅读了消息1,2和3。
    2. 当Alice的客户端启动时,它应该声明一个为该对话唯一命名的队列alice_bob_read_receipts(或与您的receiver_id变量对应的内容;只要它是唯一的)。宣言应该是幂等/无条件的。该队列应声明为x-max-length为1且x-overflowdrop-head(如果在较旧版本的RabbitMQ上,可能不支持x-overflow,但默认行为将是你想要的。)
    3. 示例(Alice):

      read_receipt_queue = channel.queue(
        "#{receiver_id}_#{sender_id}_read_receipts",
        :arguments => {
          "x-max-length".to_sym => 1,
          "x-overflow".to_sym => "drop-head"
      })
      
      1. 当Bob读取消息时(例如,通过滚动消息或打开应用程序),Bob的客户端应该使用读取消息的ID向alice_bob_read_receipts队列发布消息。 correlation_id RabbitMQ元数据字段用于此类用途,因此可用于存储Bob读取的消息ID。
      2. 示例(Bob):

        read_receipt_queue = channel.queue(
          "#{receiver_id}_#{sender_id}_read_receipts",
          :arguments => {
            "x-max-length".to_sym => 1,
            "x-overflow".to_sym => "drop-head"
        })
        exchange = read_receipt_queue.default_exchange
        
        # Assume we've got a list of messages Bob has "read" somewhere
        # and each item in that list is the exact same triple as received
        # by the Queue#consume method's block:
        # http://rubybunny.info/articles/queues.html#handling_messages_with_a_block
        viewed_messages.each do |delivery_info, properties, payload|
          exchange.publish("", :correlation_id => properties.message_id)
        end
        
        1. Alice的客户端应该订阅读取接收队列(因为这可以在轮询的基础上完成,如果需要,可以跳过消费者逻辑,只需使用Queue#pop,但是&# 39;效率较低,可以阻止你的程序)。如果消费者获得任何数据,Alice应该设置她" Bob已经阅读了多达这个号码的消息"与弹出的消息correlation_id中收到的值相反,然后更新UI /读取接收状态。只有在收到的值大于Alice之前知道的读取接收状态时才应执行该更新(因为消息可能丢失或重新传送)。
        2. 示例:

          read_receipt_queue.subscribe(...) do |delivery_info, properties, payload|
            if properties.correlation_id > known_messages_bob_read
               known_messages_bob_read = properties.correlation_id
            end
          end
          

          可能的增强功能

          有几种方法可以提高"读取收据的可靠性和/或行为。图案:

          • 我给出的所有例子都假设对等方可以将最后发送的消息和最后收到的收据号存储在可靠的数据库中。如果不可能,则必须放弃单调计数器,并在每次对等体连接到会话时从一定数量开始。这很好用;你的同伴只需要知道他们可能会看到比他们之前所知的更低的消息号码;您可以使用辅助客户端会话ID检测无序/重试消息传递与意外较低号码之间的差异,以及重新连接的客户端以较低的号码重新开始。
          • 如果您的消息历史记录是暂时的(每次聊天后消失),或者您希望确定重新连接的客户端既不重复也不会丢失消息或阅读收据,那么您需要在会话首次启动时,在对等体之间进行某种握手协商。这样,两个对等端都可以获得保证唯一的共享会话ID。由于RabbitMQ处于这一切的中间位置,因此您无法使用现有的库(例如SSL),因此您必须自己滚动它。如果您这样做,请不要将其视为安全功能(除此之外还要使用真正的安全性),只是一种状态同步功能。
          • 如果您的消息历史记录不是瞬态(所有消息都在某个可靠的数据库中),那么根本不使用RabbitMQ来同步读取收据可能会有所回报。如果您可以存储"是否读取"每个消息的位,或单个单元"最大读取消息ID"在数据库中,您的客户端可以轮询该数据库以更新其已读回执。如果轮询太慢或造成性能损失,您可以使用我的示例中提到的读取接收队列,但在客户端重新连接时会回退到数据库。或者,只需发送一个"轮询数据库以获取读取通知,就可以使用读取接收队列来减少轮询。有点而不是反击。