目录
优点
缺点
1、限流
2、return机制
3、死信队列
高可用性: RabbitMQ支持集群和镜像队列等多种方式实现高可用性,保证系统稳定运行。
可靠性强: RabbitMQ使用AMQP协议作为消息传递的标准,能够确保消息传递的可靠性和有序性。
灵活性强: RabbitMQ支持多种消息模式,包括点对点、发布订阅、路由、RPC等,可以根据业务需求选择合适的模式。
性能优异: RabbitMQ采用基于Erlang语言开发,具有高并发、低延迟等特点,支持快速处理大量数据和消息。
易于部署和管理: RabbitMQ提供了丰富的管理工具和API接口,可以方便地进行配置、监控和管理。同时也提供了可视化界面使得操作更加简单易懂。
开源免费:RabbitMQ是一款开源软件,并且完全免费使用。可以在任何环境下轻松部署和使用。
学习成本高: RabbitMQ涉及到很多概念和技术,对于初学者而言,学习起来可能会比较困难。
配置复杂: RabbitMQ的配置比较复杂,需要根据实际需求进行相应的设置。如果配置不当,可能会影响系统的性能和稳定性。
资源占用高: RabbitMQ使用Erlang语言开发,需要占用大量内存和CPU资源,如果部署在低配服务器上可能会导致系统出现延迟等问题。
单点故障: 尽管RabbitMQ支持集群模式,但是在某些情况下仍然存在单点故障的问题。
消息堆积: 如果消费者处理消息速度过慢或者出现异常情况时,RabbitMQ可能会导致消息堆积、内存溢出等问题。
所谓限流,就是限制该消费者一次最多消费多少条消息
// 每次只消费1000条消息,RabbitMQ放过来的这1000条消息没有处理完,就不会再放进来新的消息
$channel->basic_qos(null, 1000, null);
该设置在平时用不上,但是在消息积压比较严重时会用到,如果在消息积压严重时不设置该值,则很可能会导致这个消费者挂掉。
return Listener 用于处理一些不可路由的消息!
生产者通过指定一个 exchange 和 routingkey 把消息送达到某个队列中去,然后消费者监听队列,进行消费处理。但是在某些情况下,如果我们在发送消息时,当前的 exchange 不存在或者指定的 routingkey 路由不到,这个时候如果要监听这种不可达的消息,就要使用 return Listener。
# 虽然定义了交换机,但是指定了一个没有绑定队列的路由键
channel();
// 定义一个交换机
$channel->exchange_declare('test_return_exchange', AMQPExchangeType::DIRECT);
$message = new AMQPMessage("Hello World!");
// 这里我们指定了一个没有绑定队列的路由键,会导致rabbitmq找到不队列
$channel->basic_publish($message, 'test_return_exchange', 'xxxxxx');
while (true) {
$channel->wait();
}
$channel->close();
$connection->close();
} catch (Exception $e) {
print_r($e->getMessage());
}
该代码虽然执行成功了,并且创建了我们声明的交换机,但是因为我们指定的路由键找不到与之绑定的队列,所以消息并不会推送进rabbitmq,但是因为rabbitmq并没有报错,所以我们会误以为推送成功了。
即使我们使用confirm机制来监听推送状态也没用,因为confirm监听不到因为没有找到交换机或者路由不到队列的情况,这里我们只能使用rabbitmq的另一个高级特性return机制,下面代码就引入了return机制:
channel();
// 定义一个交换机
$channel->exchange_declare('test_return_exchange', AMQPExchangeType::DIRECT);
$message = new AMQPMessage("Hello World!");
// 指定第三个参数mandatory为true,表示当rabbitmq无法找到路由或队列时,将消息返回给生产者
// 这里我们指定了一个没有绑定队列的路由键,会导致rabbitmq找到不队列,进而触发return
$channel->basic_publish($message, 'test_return_exchange', 'xxxxxx', true);
// 监听消息未找到交换机或者未找到路由键对应的路由
$channel->set_return_listener(function ($replyCode, $replyText, $exchange, $routingKey, $message) {
$msg = 'oh hoo!发生错误了'.PHP_EOL;
$msg .= '错误码:'.$replyCode.PHP_EOL;
$msg .= '错误信息:'.$replyText.PHP_EOL;
$msg .= '指定的交换机:'.$exchange.PHP_EOL;
$msg .= '指定的路由键:'.$routingKey.PHP_EOL;
$msg .= '投递的消息:'.$message->body.PHP_EOL;
print_r($msg);
});
while (true) {
$channel->wait();
}
$channel->close();
$connection->close();
} catch (Exception $e) {
print_r($e->getMessage());
}
这时再运行程序,就会进行报错,我们这里是输出了错误,实际生产中应该是将错误记录到指定的日志数据表中
# 程序返回
root@204d5054860c:/data/test-yii/web# php testReturn.php
oh hoo!发生错误了
错误码:312
错误信息:NO_ROUTE
指定的交换机:test_return_exchange
指定的路由键:xxxxxx
投递的消息:Hello World!
死信队列其实只是绑定在死信交换机上的普通队列,而死信交换机也只是一个普通的交换机,不过是用来专门处理死信的交换机。
死信消息是RabbitMQ为我们做的一层保证,其实我们也可以不使用死信队列,而是在消息消费异常时,将消息主动投递到另一个交换机中或记录到日志表中等待后续专门处理。
一个消息在下列场景下会被rabbitmq判定为死信,然后投入到死信队列中。
消息被拒绝
消息过期
队列超载
下面模拟一条消息显示被投入普通队列,这条消息被设置过期时间是10秒,在这10秒内没有消费者来处理,因此这条消息就过期了,变成了死信,这时,RabbitMQ会将它放到死信队列里,也就是我们在代码中声明的死信队列。
注意:rabbitmq中不是只可以有一个死信队列,而是可以有多个死信队列,如果我们没有指定死信队列,过期的消息将被rabbitmq遗弃。
channel();
// 声明交换器,作为死信交换器
$channel->exchange_declare($deadLetterExchangeNameName, AMQPExchangeType::DIRECT, false, true);
// 声明交换器,作为普通交换器
$channel->exchange_declare($normalExchangeName, AMQPExchangeType::DIRECT, false, true);
$args = new AMQPTable();
// 设置消息过期时间为25s,25秒后消息会进入死信队列
$args->set('x-message-ttl', 25000);
// 设置死信交换器
$args->set('x-dead-letter-exchange', $deadLetterExchangeNameName);
// 设置死信路由键
$args->set('x-dead-letter-routing-key', $deadLetterRoutingKey);
// 声明队列,作为普通队列,同时将死信队列相关参数传入
$channel->queue_declare($normalQueueName, false, true, false, false, false, $args);
// 声明死信队列
$channel->queue_declare($deadLetterQueueName, false, true, false, false);
// 将普通队列与普通交换机绑定
$channel->queue_bind($normalQueueName, $normalExchangeName, $normalRoutingKey);
// 将死信队列与死信交换机绑定,同时指定死信路由键
$channel->queue_bind($deadLetterQueueName, $deadLetterExchangeNameName, $deadLetterRoutingKey);
// 设置传递的消息
$message = new AMQPMessage('Hello DLX Message queue_6');
// confirm监听消息投递【与死信队列无关,仅用于排错】
$channel->confirm_select();
$channel->set_ack_handler(function ($message) {
print_r('投递成功啦,消息是'.$message->body);
}) ;
$channel->set_nack_handler(function ($message) {
print_r('投递失败啦,消息是'.$message->body);
}) ;
// return监听未找到交换机或路由问题【与死信队列无关,仅用于排错】
$channel->set_return_listener(function ($replyCode, $replyText, $exchange, $routingKey, $message) {
$msg = 'oh hoo!发生错误了'.PHP_EOL;
$msg .= '错误码:'.$replyCode.PHP_EOL;
$msg .= '错误信息:'.$replyText.PHP_EOL;
$msg .= '指定的交换机:'.$exchange.PHP_EOL;
$msg .= '指定的路由键:'.$routingKey.PHP_EOL;
$msg .= '投递的消息:'.$message->body.PHP_EOL;
print_r($msg);
});
// 发送消息到RabbitMQ
$channel->basic_publish($message, $normalExchangeName, $normalRoutingKey, true);
while (true) {
$channel->wait();
}
// 关闭通道和连接
$channel->close();
$connection->close();
} catch (Exception $e) {
print_r($e->getMessage());
}