在发布

时间:2018-04-03 20:49:24

标签: rabbitmq php-amqplib

系统布局

我们有三个系统:

  1. API端点(发布者和消费者)
  2. RabbitMQ服务器
  3. 主要应用程序/处理器(发布者和消费者)
  4. 系统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;
    }
    

    感谢您提出的任何建议!

1 个答案:

答案 0 :(得分:1)

我可能会遗漏一些东西,但当我读到这篇文章时:

  

系统1(API端点)将序列化作业发送到RabbitMQ服务器以供系统3处理。然后,它立即声明一个新的随机命名队列,使用相关ID将交换绑定到该队列 - 并开始侦听消息。

我的第一个想法是"为什么要等到发出消息之后再声明返回队列?"

事实上,我们在这里有一系列单独的步骤:

  1. 生成相关ID
  2. 将包含该ID的邮件发布到交易所以便在别处进行处理
  3. 声明新队列以接收回复
  4. 使用相关ID
  5. 将队列绑定到交换机
  6. 将回调绑定到新队列
  7. 等待回复
  8. 直到第2步之后才能回复,所以我们希望尽可能晚地做到。在此之前唯一的步骤是步骤6,但在代码中将步骤5和6保持在一起可能很方便。所以我会重新安排代码:

    1. 生成相关ID
    2. 声明新队列以接收回复
    3. 使用相关ID
    4. 将队列绑定到交换机
    5. 将包含相关ID的邮件发布到交易所以便在别处进行处理
    6. 将回调绑定到新队列
    7. 等待回复
    8. 这样,无论响应如何快速发布,它都会被步骤2中声明的队列选中,一旦绑定回调并开始等待,您就会处理它。

      请注意,readAMQPRouteMessage并不知道publishAMQPRouteMessage没有,因此您可以在它们之间自由移动代码。当你想要从响应队列中消费时,你需要的只是它的名字,你可以将它保存到变量中并传递,或者自己生成而不是让RabbitMQ命名它。对于即时,您可以在它正在侦听的相关ID之后命名它,这样您就可以通过简单的字符串操作来解决它的问题,例如: "job_response.{$correlation_id}"