php下rabbitmq防止消息丢失

消息丢失的情况在rabbitmq的消息的任何一个节点都会出现丢失。

生产者没有发送成功

因为网络等原因生产者在向MQ发送消息过程中MQ没有成功接收,但生产者却以为MQ成功接收了消息,不会再次重复发送该消息,从而导致消息的丢失。

解决方案:

  1.事务机制

        rabbitmq提供了事务功能,生产者发送数据之前开启rabbitmq的事务,然后发送消息,如果消息没有成功被rabbitmq接收,那生产者会收到异常报错,此时就可以回滚事务,然后尝试发送消息,如果rabbitmq接收了消息,那就可以提交事务。代码如下:

<?php
require_once __DIR__ . '/vendor/autoload.php';

use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

// RabbitMQ服务器连接信息
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');

// 创建一个通道
$channel = $connection->channel();

// 定义队列名称
$queueName = 'transaction_queue';

// 开启事务
$channel->tx_select();

try {
    // 生产者发布消息
    for ($i = 0; $i < 5; $i++) {
        $messageBody = "Message $i";
        $message = new AMQPMessage($messageBody);

        // 发布消息到队列
        $channel->basic_publish($message, '', $queueName);

        echo " [x] Sent '$messageBody'\n";
    }

    // 提交事务
    $channel->tx_commit();

} catch (\Exception $e) {
    // 发生异常时回滚事务
    $channel->tx_rollback();

    echo "Error: " . $e->getMessage() . "\n";
} finally {
    // 关闭通道和连接
    $channel->close();
    $connection->close();
}
?>

        在生产者示例中,我们使用 $channel->tx_select() 开启事务,然后在发布消息后使用 $channel->tx_commit() 提交事务。如果在事务提交期间发生异常,可以使用 $channel->tx_rollback() 进行回滚。

2.confirm机制(最常用的就是发布确认机制)

发布者确认是 在通道级别使用以下方法启用:confirm_select,将信道设置成confirm模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。

如果消息成功写入 RabbitMQ 中,RabbitMQ 会给生产者回传一个 ack 消息,告诉你说这个消息 ok 了。如果 RabbitMQ 没能处理这个消息,会回调你的一个 nack 接口,告诉你这个消息接收失败,生产者可以重新发送。而且你可以结合这个机制自己在内存里维护每个消息 id 的状态,如果超过一定时间还没接收到这个消息的回调,那么可以重发。

发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息

        PHP 生产者和消费者的示例,演示如何使用 RabbitMQ 的确认模式,批量发布消息,异步处理发布者确认、丢失消息回调,以及手动 ACK 模式。在这个示例中,消息和队列都被持久化。

生产者 Demo

<?php
require_once __DIR__ . '/vendor/autoload.php';

use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

// RabbitMQ 服务器连接信息
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');

// 创建一个通道
$channel = $connection->channel();

// 定义队列名称
$queueName = 'confirm_mode_queue';

// 声明队列为持久化
$channel->queue_declare($queueName, false, true, false, false);

// 设置消息为持久化
$deliveryMode = AMQPMessage::DELIVERY_MODE_PERSISTENT;

try {
    // 开启确认模式(Publisher Confirms)
    $channel->confirm_select();

    // 生产者批量发布消息
    for ($i = 0; $i < 5; $i++) {
        $messageBody = "Message $i";
        $message = new AMQPMessage($messageBody, ['delivery_mode' => $deliveryMode]);

        // 发布消息到队列
        $channel->batch_basic_publish($message, '', $queueName);

        echo " [x] Sent '$messageBody'\n";
    }

    // 设置异步发布者确认的回调函数
    $channel->set_ack_handler(function (AMQPMessage $message) {
        echo " [x] Message confirmed: ", $message->getBody(), "\n";
    });

    // 设置异步丢失消息的回调函数
    $channel->set_nack_handler(function (AMQPMessage $message) {
        echo " [x] Message lost: ", $message->getBody(), "\n";
    });

    // 提交批量发布
    $channel->publish_batch();

    // 等待确认,确保消息被正确发送
    if ($channel->wait_for_pending_acks()) {
        echo " [x] All messages sent successfully.\n";
    } else {
        throw new Exception("Failed to confirm messages to RabbitMQ.");
    }

} catch (\Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
} finally {
    // 关闭通道和连接
    $channel->close();
    $connection->close();
}
?>

消费者 Demo

<?php
require_once __DIR__ . '/vendor/autoload.php';

use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

// RabbitMQ 服务器连接信息
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');

// 创建一个通道
$channel = $connection->channel();

// 定义队列名称
$queueName = 'confirm_mode_queue';

// 声明队列为持久化
$channel->queue_declare($queueName, false, true, false, false);

echo " [*] Waiting for messages. To exit press Ctrl+C\n";

// 回调函数处理消息
$callback = function ($msg) {
    echo ' [x] Received ', $msg->body, "\n";

    // 模拟处理消息的操作
    // ...

    // 模拟消息处理成功和失败的情况
    $success = mt_rand(0, 1);

    if ($success) {
        // 处理成功,手动 ACK
        $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
        echo " [x] Acknowledged\n";
    } else {
        // 处理失败,拒绝消息并重新入队
        $msg->delivery_info['channel']->basic_reject($msg->delivery_info['delivery_tag'], true);
        echo " [x] Requeued\n";
    }
};

// 设置预取计数
$channel->basic_qos(null, 1, null);

// 消费消息,手动 ACK
$channel->basic_consume($queueName, '', false, false, false, false, $callback);

// 循环等待消息
while ($channel->is_consuming()) {
    $channel->wait();
}

// 关闭通道和连接
$channel->close();
$connection->close();
?>

我们使用了 batch_basic_publish 来批量发布消息,并设置了异步发布者确认和丢失消息的回调函数。在消费者示例中,我们模拟了消息处理成功和失败的情况,并分别手动 ACK 或拒绝消息重新入队。这样可以确保消息在处理成功的情况下被确认,而在处理失败的情况下被重新入队。

RabbitMQ接收到消息之后丢失

        RabbitMQ接收到生产者发送过来的消息,是存在内存中的,此时如果消息没有被消费完,RabbitMQ宕机了,那么再次启动的时候,内存中的消息就丢失了。

解决方案

        开启RabbitMQ的持久化,

// 声明队列为持久化
$channel->queue_declare($queueName, false, true, false, false);

       队列持久化,队列持久化后,rabbitmq重启后,队列依旧存在,但不会持久化队列里面的数据,因此我们还需要把数据持久化。

$deliveryMode = 2;

$message = new AMQPMessage($messageBody, ['delivery_mode' => $deliveryMode]);

消息持久化,发送消息的时候将消息的 delivery_mode设置为 2

 注意:持久化要起作用必须同时设置这两个持久化才行。

   保证消息不丢失,除了开启队列、消息持久化的同时,也要开启 confirm(发布确认) 模式。这是因为我们在生产者中设置了队列持久化、消息持久化,但依然存在消息被传送到队列上,还没来得及存储在磁盘上,队列就宕机了,这种情况下消息也是会丢失的。所以在之前两步的基础上还是进行第三步:发布确认。三步操作加一起才能保证消息 从生产者到RabbitMQ服务器这个过程 是不丢失的。

消费者弄丢了消息

  RabbitMQ成功的把消息发送给了消费者,RabbitMQ的ack机制会自动的返回成功,表明发送消息成功,下次就不会发送这个消息。但此时,消费者还没处理完该消息,然后宕机了,那么这个消息就丢失了。

解决方案:

RabbitMQ消息应答机制分为两种:自动应答、手动应答。对于此类消息丢失,消费者必须使用手动应答,此时如果消息没有处理完成,则不对rabbitmq发送ack,此时rabbitmq则认为消息没处理完成,则把消息重新入队,如果此时有其他消费者,则将其发送其他消费者处理。

                    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dnfdsaa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值