安装rabbitmq-c
wget https://github.com/alanxz/rabbitmq-c/archive/v0.9.0.tar.gz
tar xf v0.9.0.tar.gz
cd rabbitmq-c-0.9.0
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr/local/rabbitmq-c ..
cd build/
cmake --build . --target install
cd /usr/local/rabbitmq-c/
ln -s lib64 lib
/usr/local/rabbitmq-c是等下安装PHP扩展amqp需要用的路径
pecl安装php扩展amqp
sudo pecl install amqp
中途会出现:Set the path to librabbitmq install prefix [autodetect] :
遇见提示需要需要librabbitmq路径是否输入:
上面安装的/usr/local/rabbitmq-c
安装完毕后 php -m |grep amqp 查看是否安装成功
二、php使用demo
1.消费者
第一次执行,应该先运行消费者,消费者才可以创建Exchange和queue
<?php
/**
* Created by PhpStorm.
* User: jmsite.cn
* Date: 2019/1/15
* Time: 13:16
*/
//声明连接参数
$config = array(
'host' => '192.168.6.210',
'vhost' => '/',
'port' => 5672,
// 'login' => 'test',
// 'password' => 'test'
);
//连接broker
$cnn = new AMQPConnection($config);
if (!$cnn->connect()) {
echo "Cannot connect to the broker";
exit();
}
//在连接内创建一个通道
$ch = new AMQPChannel($cnn);
//创建一个交换机
$ex = new AMQPExchange($ch);
//声明路由键
$routingKey = 'key_1';
//声明交换机名称
$exchangeName = 'exchange_1';
//设置交换机名称
$ex->setName($exchangeName);
//设置交换机类型
//AMQP_EX_TYPE_DIRECT:直连交换机
//AMQP_EX_TYPE_FANOUT:扇形交换机
//AMQP_EX_TYPE_HEADERS:头交换机
//AMQP_EX_TYPE_TOPIC:主题交换机
$ex->setType(AMQP_EX_TYPE_DIRECT);
//设置交换机持久
$ex->setFlags(AMQP_DURABLE);
//声明交换机
$ex->declareExchange();
//创建一个消息队列
$q = new AMQPQueue($ch);
//设置队列名称
$q->setName('queue_1');
//设置队列持久
$q->setFlags(AMQP_DURABLE);
//声明消息队列
$q->declareQueue();
//交换机和队列通过$routingKey进行绑定
$q->bind($ex->getName(), $routingKey);
//接收消息并进行处理的回调方法
function receive($envelope, $queue) {
//随机失败
$t= rand(1, 2);//在1~2之间取一个数
//休眠两秒,
// sleep(3);
//echo消息内容
echo $envelope->getBody()."\n";
//显式确认,队列收到消费者显式确认后,会删除该消息
if($t==1){
var_dump('ack');
$queue->ack($envelope->getDeliveryTag());
}else{
var_dump('nack');
//AMQP_REQUEUE 可以重新投回队列
$queue->nack($envelope->getDeliveryTag(),AMQP_REQUEUE);
// $queue->reject($envelope->getDeliveryTag(),AMQP_REQUEUE);
}
}
//设置消息队列消费者回调方法,并进行阻塞
$q->consume("receive");
//$q->consume("receive", AMQP_AUTOACK);//隐式确认,不推荐
2.生产者
<?php
/**
* Created by PhpStorm.
* User: jmsite.cn
* Date: 2019/1/15
* Time: 13:15
*/
$config = array(
'host' => '192.168.6.210',
'vhost' => '/',
'port' => 5672,
// 'login' => 'test',
// 'password' => 'test'
);
$cnn = new AMQPConnection($config);
if (!$cnn->connect()) {
echo "Cannot connect to the broker";
exit();
}
$ch = new AMQPChannel($cnn);
$ex = new AMQPExchange($ch);
//消息的路由键,一定要和消费者端一致
$routingKey = 'key_1';
//交换机名称,一定要和消费者端一致,
$exchangeName = 'exchange_1';
$ex->setName($exchangeName);
$ex->setType(AMQP_EX_TYPE_DIRECT);
$ex->setFlags(AMQP_DURABLE);
$ex->declareExchange();
//创建10个消息
for ($i=1;$i<=10;$i++){
// sleep(1);
//消息内容
$msg = array(
'data' => 'message_'.$i,
'hello' => 'world',
);
//发送消息到交换机,并返回发送结果
//delivery_mode:2声明消息持久,持久的队列+持久的消息在RabbitMQ重启后才不会丢失
echo "Send Message:".$ex->publish(json_encode($msg), $routingKey, AMQP_NOPARAM, array('delivery_mode' => 2))."\n";
//代码执行完毕后进程会自动退出
}
执行过程可以看见如果重新投回队列的优先级是最高的
默认如果有3次没有回应(既没有ack也没有nack),那么消费者不再能获取消息
3.ACK消息确认机制
首先RabbitMQ支持消息确认机制来本证消息被consumer正常处理,当然也可以通过no-ack不使用确认机制。RabbitMQ默认是使用ACK确认机制的。当Consumer接收到RabbitMQ发布的消息时需要在适当的时机发送一个ACK确认的包来告知RabbitMQ,自己接收到了消息并成功处理。所以前面讲到适当的时机建议是在处理完消息任务后发送。正如我们之前的代码。
$msg = $envelope->getBody();
sleep(1); //sleep1秒模拟任务处理
echo $msg."\n"; //处理消息
$q->ack($envelope->getDeliveryTag()); //手动发送ACK应答
那如果不发送会怎样呢?
在RabbitMQ中有一个prefetch_count的概念,这个参数的意思是允许Consumer最多同时处理几个任务。我的版本的RabbitMQ默认这个参数是3,也就是说如果某一个Consumer在收到消息后没有发送ACK确认包,RabbitMQ就会任务Consumer还在处理任务,当有3个消息都没有发送ACK确认包时,RabbitMQ就不会再发送消息给该Consumer。
$msg = $envelope->getBody();
sleep(1); //sleep1秒模拟任务处理
echo $msg."\n"; //处理消息
//$q->ack($envelope->getDeliveryTag()); //手动发送ACK应答
3.对于死信队列的里面什么情况下才会丢给死信交换机
1,消息被拒绝(Basic.Reject/Basic.Nack) ,井且设置requeue 参数为false
2,消息过期
3,队列达到最大长度
4.当消息在一个队列中变成了死信消息后,可以被发送到另一个交换机,这个交换机就是DLX,绑定DLX的队列成为死信队列。当这个队列中存在死信时, RabbitMQ 就会立即自动地将这个消息重新发布到设置的DLX 上去,进而被路由到绑定该DLX的死信队列上。可以监听这个队列中的消息、以进行相应的处理,这个特性与将消息的TTL 设置为0 配合使用可以弥补imrnediate 参数的功能
这里需要注意的是,你在监听正常消费的设置死信的队列的时候,即使设置的时间到了也是不会丢给死信队列的,如果你不开启正常消费队列的监听,这个设置了死信的队列就成了延迟队列的效果,再次强调 理解概念
也可以用rabbitmq-delayed-message-exchange插件实现延迟功能
4.生成者确认模式
- 在channel上开启确认模式:$channel->confirm_select();
- 在channel上添加监听:$channel->wait_for_pending_acks();监听成功和失败的返回结果,根据具体的结果对
消息进行重新发送、或记录日志等后续处理。
上述使用php-amqp
//创建10个消息
for ($i=1;$i<=10;$i++){
// sleep(1);
//消息内容
$msg = array(
'data' => 'message_'.$i,
'hello' => 'world',
);
//发送消息到交换机,并返回发送结果
//delivery_mode:2声明消息持久,持久的队列+持久的消息在RabbitMQ重启后才不会丢失
echo "Send Message:".$ex->publish(json_encode($msg), $routingKey, AMQP_NOPARAM, array('delivery_mode' => 2))."\n";
}
$ack_call = function ($delivery_tag,$multiple){
echo 'Message acked' . PHP_EOL;
return true;
};
$nack_call = function ($delivery_tag,$multiple,$requeue){
echo 'Message nacked' . PHP_EOL;
};
$ch->setConfirmCallback($ack_call,$nack_call);//设置ack和nack的回调逻辑
$ch->waitForConfirm(3); //在setConfirmCallback后使用
但是发现处理完ack或者nack逻辑后,waitForConfirm依旧一直阻塞,如果设置waitForConfirm(3) 表示3秒后会报错超时,只能设置为0作为进程一直阻塞调用,查询很多资料发现
PECL AMQP库和纯php-amqplib库都没有实现Confirms扩展
因此采用另一个方式,composer的扩展
-vvv能查看具体进度
composer require -vvv php-amqplib/php-amqplib
$routeKey = 'route';
$exchangeName = 'exchange_1';
$queueName = 'queue';
//连接mq server
$connection = new AMQPStreamConnection("192.168.6.210", 5672,'guest','guest','/'); //连接server
$channel = $connection->channel(); //创建通道
//推送成功
$channel->set_ack_handler(
function (AMQPMessage $message) {
echo "发送成功: " . $message->body . PHP_EOL;
}
);
//推送失败
$channel->set_nack_handler(
function (AMQPMessage $message) {
echo "发送失败: " . $message->body . PHP_EOL;
}
);
/*
* bring the channel into publish confirm mode.
* if you would call $ch->tx_select() before or after you brought the channel into this mode
* the next call to $ch->wait() would result in an exception as the publish confirm mode and transactions
* are mutually exclusive
*/
/*
* 进入发布确认模式。
* 如果在将通道引入此模式之前或之后调用$ch->tx_select()
* 下一个调用$ch->wait()将导致发布确认模式和事务异常
* 是互斥的
*/
$channel->confirm_select(); // 发布确认模式
// 通道
$channel->exchange_declare($exchangeName, 'direct', false, false, false);
// 队列
$channel->queue_declare($queueName, false, false, false, false);
// 使用routeKey绑定交换机和队列
$channel->queue_bind($queueName, $exchangeName, $routeKey);
//$channel->wait_for_pending_acks();
for ($i = 0; $i < 10; $i++) {
$msg = new AMQPMessage('Hello World!'.$i);
$channel->basic_publish($msg, $exchangeName, $routeKey);
sleep(1);
}
/*
* you do not have to wait for pending acks after each message sent. in fact it will be much more efficient
* to wait for as many messages to be acked as possible.
*/
/*
*您不必在每条消息发送后等待挂起的acks。事实上,这样会更有效率
*等待尽可能多的邮件被屏蔽。
*/
$channel->wait_for_pending_acks(5);
// 监听成功或失败返回结束 成功/失败 => set_ack_handler/set_nack_handler
$channel->close();
$connection->close();
这个方式wait_for_pending_acks测试后发现不再阻塞下面的代码执行,能实现异步获取生成者的ack确认消息
链接地址:https://www.cnblogs.com/-mrl/p/11114116.html
https://blog.51cto.com/chinalx1/2150793
https://blog.csdn.net/qq_42724459/article/details/109489966