消费者basic_cancel

时间:2018-08-29 19:41:44

标签: php rabbitmq php-amqp

好,不必详细介绍我设置的整个系统,

我遇到的问题是,当使用者取消(AMQPChannel-> basic_cancel)侦听队列时,它留下了该工作人员未确认的另一条消息。它也不会触发正常的回调来处理此消息。

一些细节

  • 队列正在阻塞(使用等待)while(count($channel->callbacks)) { $channel->wait( ... ) ... }
  • 预取为1,您可以拥有的最小
  • 消费者可以动态收听(AMQPChannel->basic_consume
  • 消费者可以动态忘记(AMQPChannel->basic_cancel

我不会讲确切的方法来告诉给定的消费者使用或取消给定队列。但这一切都很好,一开始消耗或取消就可以了。但是,当我在仍然有消息的队列中取消时,他们只是忘记了最后一条消息,并且我相信cancel会删除该队列的回调,因此没有办法使该消息恢复正常,从而杀死消费者。

我做了一些调试(只是一个例子,不是我的实际工作)

   Debug::dump($this->getAmqpChannel()->getMethodQueue());
   $tag = $this->_tags[$queue]; //I keep track of the consumer tag on a queue by queue basis, $queue == {queuename} below
   $this->getAmqpChannel()->basic_cancel( $tag );
   Debug::dump($this->getAmqpChannel()->getMethodQueue());

此输出大致为

  array()
  RunCommand: basic_cancel //this works fine consumer forgets queue except ->
  array(1){
    [0] => array(3){
        [0] => string(5) "60,60",
        [1] => string(114) "amq.ctag-D9om-gD_cPevzeon52zpig\0\0\0\0\0\0\0\0\0G{queuename}",  //{queuename} is the name of the queue, which is based on clients information I cant share (such as their name)
       [2] => object(PhpAmqpLib\Message\AMQPMessage)#0 (9) {
            ["DELIVERY_MODE_NON_PERSISTENT":constant] => int(1),
            ["DELIVERY_MODE_PERSISTENT":constant] => int(2),
            ["body":public] => string(1358647) "{ ... "correlation_id":32,"max_correlation_id":38}"
            ["body_size":public] => int(135864),
            ["is_truncated":public] => bool(false),
            ["content_encoding":public] => null,
            ["propertyDefinitions":protected static] => array(14){ ... }
            ["delivery_info":public] => array(0){},
            ["prop_types":protected] => array(14){ ... }
      }
    }

一旦工作人员死亡(或者我杀死了它),该消息就会被放回队列中,并且我可以在get消息下的RabbitMq Management事物(插件)中将其拉出。在那里

  Properties
    correlation_id: 32:38
    delivery_mode:  2
    content_encoding:   text/plain
    content_type:   application/json

"correlation_id":32,"max_correlation_id":38对应于correlation_id: 32:38,因为我需要跟踪消息部分。所以我知道这是相同的消息。

所以为什么我取消后会收到卡在僵尸土地上的最后一条消息,并且无论如何都可以将其踢回队列,而不会杀死消费者。

这也不是一次性的,每次我取消仍然有消息的队列时都会发生。因此,它与给定的消息无关。这就好像它收到了最后一条预取的消息,然后因为它已被取消,所以没有要运行的最后一个回调,它只是陷入了困境。请记住,0预取是获取所有消息,1是您可以设置的最低值。

任何可以帮助的人都很棒。

更新

我可以通过致电

找到解决方案
 $this->getAmqpChannel()->basic_recover(true); //basic_recover($requeue)

basic_cancel之前或之后

这拒绝了该消息,我甚至可以如上所述测试$this->getAmqpChannel()->getMethodQueue()来查看我要取消的$queue是否已装满消息(尚未实现)。

我试图避免使用recover,但我认为应该没问题,因为消费者使用单个通道并且正在阻塞,并且在最坏的情况下,它只会拒绝有效消息1次,尽管这不是理想的选择可以接受。

但是,在某些情况下,我从Rabbit那里获得了另一个例外

  PRECONDITION_FAILED - unknown delivery tag {n}

如果有人对此附加错误有任何详细信息,那就太好了。另外,所有队列都需要Ack,没有一个是自动的。

UPDATE1

我在堆栈跟踪queue_unbind中注意到了,所以我这样做是在内部跟踪绑定,因此我可以确保取消绑定仅执行一次。明天进行更多测试之后,我将发布一些代码,但是实现之后的最初测试不再产生错误。

所有这些听起来都有些“奇怪”,我可以解释为什么以及我在做什么,但是那可能超出了问题的范围。我要说的是,我已经在生产中使用该系统2年了(我设计了它),我们每分钟可以进行18万次搜索(如果考虑系统的所有部分,则大约为100千次)。我们也已经完成了超过2800亿次搜索。我们还是该行业中的领先公司,要么淘汰了竞争对手,要么他们将自己的东西寄给我们,不再在内部生产。这在很大程度上是因为我们快速周转以及我们的数据质量。因此,该系统确实可以正常运行,而且效果很好。

但是在最近的审计中,我注意到每日消费者只需要处理大约1000万行(大约100分钟的工作),而夜间消费者则需要处理大约1亿行(或大约20小时的工作)。每日消费者可以做夜间工作,但只能在工作时间以外进行(因为这样可以减少白天的响应时间),因此,大约有10个小时的窗口,夜间工作只能在体积更小,功能更差的服务器上运行。这给我们提供的解决方案是,如果没有每日工作(客户提交的工作),它们可以动态地动态交换为每晚的工作(数据仓库)。这应该保持大多数响应,同时在没有作业提交时不浪费资源。我们可以根据需要在搜索上水平扩展,但是我们确实为主服务器支付了很多钱,并且浪费了大约8个小时的工作量。

我可能会写一本小书,介绍所有工作原理,但是希望可以对我所做的事情有一些基本的了解。我的合同中还包含一些“保密”和“不竞争”的内容,因此我可以真正了解具体细节。

1 个答案:

答案 0 :(得分:0)

RabbitMQ措辞中的

Consumer表示队列中的订户。 (有关渠道,使用者和连接之间差异的详细信息,请参见this answer

当您打开确认时,它们会为channel打开。在该通道上传递的所有消息都将有一个与之关联的delivery tag。处理完消息后,您需要通过同一通道告知服务器该消息已被处理。取消使用者不会影响对已传递消息的确认。实际上,接收消息,取消使用者,处理消息然后发送确认是一个完全有效的用例。

因此,您有两个选择。您可以使该消息不被确认,在这种情况下,您要做的就是关闭通道,它将在队列的开头重新排队。或者,您可以确认(nackack),在这种情况下,如果nack,消息将重新排队,如果ack,消息将被丢弃。

如果我没记错的话,未指定预取计数(通过basic.qos)将导致预取值为零,这意味着您必须先确认上一条消息,然后才能接收下一条消息。我对此可能是错的。当然,如果您使用basic.get,则可以避免此问题,并且对性能的影响很小。