我创建了一个简单的发布者和使用basic.consume
订阅队列的消费者。
我的消费者在作业无异常运行时确认消息。每当我遇到异常时,我都不会收到消息并提前返回。只有已确认的消息才会从队列中消失,因此工作正常 现在我希望消费者再次接收失败的消息,但重新生成这些消息的唯一方法是重新启动消费者。
我如何处理此用例?
设置代码
$channel = new AMQPChannel($connection);
$exchange = new AMQPExchange($channel);
$exchange->setName('my-exchange');
$exchange->setType('fanout');
$exchange->declare();
$queue = new AMQPQueue($channel);
$queue->setName('my-queue');
$queue->declare();
$queue->bind('my-exchange');
消费者代码
$queue->consume(array($this, 'callback'));
public function callback(AMQPEnvelope $msg)
{
try {
//Do some business logic
} catch (Exception $ex) {
//Log exception
return;
}
return $queue->ack($msg->getDeliveryTag());
}
制片人代码
$exchange->publish('message');
答案 0 :(得分:19)
如果消息未得到确认且应用程序失败,它将自动重新传送,并且信封上的redelivered
属性将设置为true
(除非您使用no-ack = true
标志消费它们。)< / p>
<强> UPD:强>
你必须在你的catch块中发送nack
带有重新传递标志的消息
try {
//Do some business logic
} catch (Exception $ex) {
//Log exception
return $queue->nack($msg->getDeliveryTag(), AMQP_REQUEUE);
}
小心无限的消息,而在RabbitMQ和AMQP协议中没有实现重新传递计数。
如果你不想搞这些消息而只是想添加一些延迟,你可能想在sleep()
方法调用之前添加一些usleep()
或nack
,但它是根本不是一个好主意。
有多种技术可以解决周期重新启动问题:
<强> 1。依靠Dead Letter Exchanges
<强> 2。使用per message or per queue TTL
示例(注意,对于队列ttl,我们只传递数字和传递消息ttl - 任何将是数字字符串的东西):
2.1每条消息ttl:
$queue = new AMQPQueue($channel);
$queue->setName('my-queue');
$queue->declareQueue();
$queue->bind('my-exchange');
$exchange->publish(
'message at ' . microtime(true),
null,
AMQP_NOPARAM,
array(
'expiration' => '1000'
)
);
<强> 2.2。每队列ttl:
$queue = new AMQPQueue($channel);
$queue->setName('my-queue');
$queue->setArgument('x-message-ttl', 1000);
$queue->declareQueue();
$queue->bind('my-exchange');
$exchange->publish('message at ' . microtime(true));
第3。保留redelivers计数或在邮件正文或标题中保留redelivers编号(即IP堆栈中的跃点限制或ttl)
代码:
$queue = new AMQPQueue($channel);
$queue->setName('my-queue');
$queue->declareQueue();
$queue->bind('my-exchange');
$exchange->publish(
'message at ' . microtime(true),
null,
AMQP_NOPARAM,
array(
'headers' => array(
'ttl' => 100
)
)
);
$queue->consume(
function (AMQPEnvelope $msg, AMQPQueue $queue) use ($exchange) {
$headers = $msg->getHeaders();
echo $msg->isRedelivery() ? 'redelivered' : 'origin', ' ';
echo $msg->getDeliveryTag(), ' ';
echo isset($headers['ttl']) ? $headers['ttl'] : 'no ttl' , ' ';
echo $msg->getBody(), PHP_EOL;
try {
//Do some business logic
throw new Exception('business logic failed');
} catch (Exception $ex) {
//Log exception
if (isset($headers['ttl'])) {
// with ttl logic
if ($headers['ttl'] > 0) {
$headers['ttl']--;
$exchange->publish($msg->getBody(), $msg->getRoutingKey(), AMQP_NOPARAM, array('headers' => $headers));
}
return $queue->ack($msg->getDeliveryTag());
} else {
// without ttl logic
return $queue->nack($msg->getDeliveryTag(), AMQP_REQUEUE); // or drop it without requeue
}
}
return $queue->ack($msg->getDeliveryTag());
}
);
可能还有其他一些方法可以更好地控制消息redelivers流。
结论:没有银弹解决方案。您必须确定最适合您需求的解决方案或找到其他解决方案,但不要忘记在此处分享;)
答案 1 :(得分:0)
如果您不想重新启动使用者,那么basic.recover
AMQP命令可能就是您想要的。根据{{3}}:
basic.recover(bit requeue)
Redeliver unacknowledged messages.
This method asks the server to redeliver all unacknowledged messages on a specified channel.
Zero or more messages may be redelivered. This method replaces the asynchronous Recover.