系统布局
我们有三个系统:
系统1和3都使用Laravel,并使用PHPAMQPLIB与RabbitMQ进行交互。
消息的路径
系统1(API端点)将序列化作业发送到RabbitMQ服务器以供系统3处理。然后,它立即声明一个新的随机命名队列,使用相关ID将交换绑定到该队列 - 并开始侦听消息。
与此同时,系统3完成了这项工作,一旦完成,就会通过该工作向RabbitMQ回复交换中的相关ID。
问题和我尝试的内容
我经常发现这个过程失败了。作业被发送和接收,响应被发送 - 但系统1从不读取此响应,我也看不到它在RabbitMQ中发布。
我已经对此进行了一些广泛的调试,但没有找到根本原因。我目前的理论是系统3在返回响应时非常快,新的队列和交换绑定甚至还没有从系统1中声明。这意味着系统3的响应无处可去,结果消失了。该理论主要基于以下事实:如果我在系统3上设置要以较低频率处理的作业,则系统变得更可靠。作业处理越快,就越不可靠。
问题是:我该如何防止这种情况?或者还有其他我想念的东西?我当然希望这些工作能够快速有效地处理,而不会破坏请求/响应模式。
我记录了两个系统的输出 - 两者都使用相同的相关ID,系统3在发布时获得ACK - 而系统1有一个声明的队列,没有消息最终只会超时。
代码示例1:发布消息
/**
* Helper method to publish a message to RabbitMQ
*
* @param $exchange
* @param $message
* @param $correlation_id
* @return bool
*/
public static function publishAMQPRouteMessage($exchange, $message, $correlation_id)
{
try {
$connection = new AMQPStreamConnection(
env('RABBITMQ_HOST'),
env('RABBITMQ_PORT'),
env('RABBITMQ_LOGIN'),
env('RABBITMQ_PASSWORD'),
env('RABBITMQ_VHOST')
);
$channel = $connection->channel();
$channel->set_ack_handler(function (AMQPMessage $message) {
Log::info('[AMQPLib::publishAMQPRouteMessage()] - Message ACK');
});
$channel->set_nack_handler(function (AMQPMessage $message) {
Log::error('[AMQPLib::publishAMQPRouteMessage()] - Message NACK');
});
$channel->confirm_select();
$channel->exchange_declare(
$exchange,
'direct',
false,
false,
false
);
$msg = new AMQPMessage($message);
$channel->basic_publish($msg, $exchange, $correlation_id);
$channel->wait_for_pending_acks();
$channel->close();
$connection->close();
return true;
} catch (Exception $e) {
return false;
}
}
代码示例2:等待消息响应
/**
* Helper method to fetch messages from RabbitMQ.
*
* @param $exchange
* @param $correlation_id
* @return mixed
*/
public static function readAMQPRouteMessage($exchange, $correlation_id)
{
$connection = new AMQPStreamConnection(
env('RABBITMQ_HOST'),
env('RABBITMQ_PORT'),
env('RABBITMQ_LOGIN'),
env('RABBITMQ_PASSWORD'),
env('RABBITMQ_VHOST')
);
$channel = $connection->channel();
$channel->exchange_declare(
$exchange,
'direct',
false,
false,
false
);
list($queue_name, ,) = $channel->queue_declare(
'',
false,
false,
true,
false
);
$channel->queue_bind($queue_name, $exchange, $correlation_id);
$callback = function ($msg) {
return self::$rfcResponse = $msg->body;
};
$channel->basic_consume(
$queue_name,
'',
false,
true,
false,
false,
$callback
);
if (!count($channel->callbacks)) {
Log::error('[AMQPLib::readAMQPRouteMessage()] - No callbacks registered!');
}
while (self::$rfcResponse === null && count($channel->callbacks)) {
$channel->wait();
}
$channel->close();
$connection->close();
return self::$rfcResponse;
}
感谢您提出的任何建议!
答案 0 :(得分:1)
我可能会遗漏一些东西,但当我读到这篇文章时:
系统1(API端点)将序列化作业发送到RabbitMQ服务器以供系统3处理。然后,它立即声明一个新的随机命名队列,使用相关ID将交换绑定到该队列 - 并开始侦听消息。
我的第一个想法是"为什么要等到发出消息之后再声明返回队列?"
事实上,我们在这里有一系列单独的步骤:
直到第2步之后才能回复,所以我们希望尽可能晚地做到。在此之前唯一的步骤是步骤6,但在代码中将步骤5和6保持在一起可能很方便。所以我会重新安排代码:
这样,无论响应如何快速发布,它都会被步骤2中声明的队列选中,一旦绑定回调并开始等待,您就会处理它。
请注意,readAMQPRouteMessage
并不知道publishAMQPRouteMessage
没有,因此您可以在它们之间自由移动代码。当你想要从响应队列中消费时,你需要的只是它的名字,你可以将它保存到变量中并传递,或者自己生成而不是让RabbitMQ命名它。对于即时,您可以在它正在侦听的相关ID之后命名它,这样您就可以通过简单的字符串操作来解决它的问题,例如: "job_response.{$correlation_id}"