我刚开始使用php-amqplib和RabbitMQ,并希望有一种方法来处理由于某种原因而无法处理且无法解决的消息。我认为人们处理此问题的一种方法是使用死信队列。我正在尝试进行设置,但到目前为止还没有任何运气,希望有人能提供一些建议。
我启动队列的过程有点像:
class BaseAbstract
{
/** @var AMQPStreamConnection */
protected $connection;
/** @var AMQPChannel */
protected $channel;
/** @var array */
protected $deadLetter = [
'exchange' => 'dead_letter',
'type' => 'direct',
'queue' => 'delay_queue',
'ttl' => 10000 // in milliseconds
];
protected function initConnection(array $config)
{
try {
$this->connection = AMQPStreamConnection::create_connection($config);
$this->channel = $this->connection->channel();
// Setup dead letter exchange and queue
$this->channel->exchange_declare($this->deadLetter['exchange'], $this->deadLetter['type'], false, true, false);
$this->channel->queue_declare($this->deadLetter['queue'], false, true, false, false, false, new AMQPTable([
'x-dead-letter-exchange' => $this->deadLetter['exchange'],
'x-dead-letter-routing-key' => $this->deadLetter['queue'],
'x-message-ttl' => $this->deadLetter['ttl']
]));
$this->channel->queue_bind($this->deadLetter['queue'], $this->deadLetter['exchange']);
// Set up regular exchange and queue
$this->channel->exchange_declare($this->getExchangeName(), $this->getExchangeType(), true, true, false);
$this->channel->queue_declare($this->getQueueName(), true, true, false, false, new AMQPTable([
'x-dead-letter-exchange' => $this->deadLetter['exchange'],
'x-dead-letter-routing-key' => $this->deadLetter['queue']
]));
if (method_exists($this, 'getRouteKey')) {
$this->channel->queue_bind($this->getQueueName(), $this->getExchangeName(), $this->getRouteKey());
} else {
$this->channel->queue_bind($this->getQueueName(), $this->getExchangeName());
}
} catch (\Exception $e) {
throw new \RuntimeException('Cannot connect to the RabbitMQ service: ' . $e->getMessage());
}
return $this;
}
// ...
}
我认为应该设置我的死信交换和队列,然后还要设置我的常规交换和队列(使用扩展类提供的getRouteKey,getQueueName和getExchangeName / Type方法)
当我尝试处理以下消息时:
public function process(AMQPMessage $message)
{
$msg = json_decode($message->body);
if (empty($msg->payload) || empty($msg->payload->run)) {
$message->delivery_info['channel']->basic_nack($message->delivery_info['delivery_tag'], false, true);
return;
}
// removed for post brevity, but compose $cmd variable
exec($cmd, $output, $returned);
if ($returned !== 0) {
$message->delivery_info['channel']->basic_ack($message->delivery_info['delivery_tag']);
} else {
$message->delivery_info['channel']->basic_nack($message->delivery_info['delivery_tag']);
}
}
但是我得到了错误Something went wrong: Cannot connect to the RabbitMQ service: PRECONDITION_FAILED - inequivalent arg 'x-dead-letter-exchange' for queue 'delay_queue' in vhost '/': received 'dead_letter' but current is ''
这是我应该设置无效字体的方式吗?我所见过的不同示例似乎显示出一些不同的处理方式,但似乎没有一种适用于我。因此,我在这里显然误解了一些东西,并对任何建议表示赞赏。 :)
答案 0 :(得分:1)
设置(永久)队列和交换是您要一次进行的事情,这是在部署代码时而不是每次使用它们时。将它们视为您的数据库模式-尽管协议提供的是“声明”而不是“创建”,但是通常您应该编写代码,假设以特定方式配置事物。您可以将代码的第一部分构建到安装脚本中,或者使用the web- and CLI-based management plugin通过简单的JSON格式来管理它们。
您看到的错误可能是尝试在不同时间使用不同参数声明相同队列的结果-“ declare”将不会替换或重新配置现有队列,它将会将参数视为“前提条件” “ 被检查。您需要删除并重新创建队列,或者通过管理UI对其进行管理,以更改其现有参数。
当您要动态在代理中动态创建项目时,运行时声明变得更有用。您可以给他们提供您知道该名称唯一的名称,也可以传递null
作为名称,以接收随机生成的名称(人们有时会引用创建“匿名队列”,但是RabbitMQ中的每个队列即使没有选择也有名字)。
如果我正确阅读,则您的“模式”如下所示:
# Dead Letter eXchange and Queue
Exchange: DLX
Queue: DLQ; dead letter exchange: DLX, with key "DLQ"; automatic expiry
Binding: copy messages arriving in DLX to DLQ
# Regular eXchange and Queue
Exchange: RX
Queue: RQ; dead letter exchange: DLX, with key "DLQ"
Binding: copy messages from RX to RQ, optionally filtered by routing key
当RQ中的消息“未确认”时,它将被传递到DLX,并且其路由密钥将覆盖为“ DLQ”。然后将其复制到DLQ。如果没有从DLQ接收到它,或者或在该队列中等待的时间太长,它将被路由到自身。
我将通过两种方式进行简化:
x-dead-letter-routing-key
选项。常规队列的配置不需要知道死信交换是否连接了零个,一个或几个队列,因此不需要知道另一个队列的名称。如果您想让被拒绝的消息直接进入一个队列,只需将其设为“扇出交换”(忽略路由键)或将绑定键设置为#
的“主题交换”(这是一个匹配所有路由键)。另一种选择是将x-dead-letter-routing-key
设置为常规队列的名称,即标记它来自哪个队列。但是,除非有用例,否则我将使其保持简单,并在消息中保留其原始路由密钥。