rabbitMQ 下载官网 https://www.rabbitmq.com/
PHP链接MQ扩展地址 https://github.com/php-amqplib/php-amqplib
或者使用 composer 安装
composer require php-amqplib/php-amqplib
更详细的例子 讲解
connection=$connection;
$this->channel=$this->connection->channel();
}
//简单的 发送者
public function send(){
//要发送 我们必须为我们发送声明一个队列 然后我们可以向队列发送消息
$this->channel->queue_declare('hello', false, false, false, false);
$msg=new AMQPMessage("Hello Word");
$this->channel->basic_publish($msg, '', 'hello');
echo " [x] Sent 'Hello World!'\n";
//发送完消息后 关闭通道
$this->channel->close();
$this->connection->close();
}
//简单的 接收者
public function receive(){
//然后声明我们将要消耗的队列。请注意,这与发送的队列中的队列相匹配。
$this->channel->queue_declare('hello', false, false, false, false);
echo ' [*] Waiting for messages. To exit press CTRL+C', "\n";
//我们要创建一个回调函数来接收发送者发送的消息
$this->channel->basic_consume('hello', '', false, true, false, false, 'msg');
// 只要通道注册了回调,就进行循环
while ($this->channel ->is_consuming()) {
$this->channel->wait();
}
}
//发送工作消息队列
public function new_task($argv){
$data=$argv;
if(empty($data)){
$data="Hello Word";
}
# 使消息持久化
//为了防止系统崩溃 所有的消息消失 我们在处理队列和消息的时候 设置持久化
//要发送 我们必须为我们发送声明一个队列 然后我们可以向队列发送消息
//非持久化声明队列
//$this->channel->queue_declare('task_queue', false, false, false, false);
//为了不让队列消失 首先我们队列声明为持久化 我们可以通过设置 queue_declare方法的第三个参数 设置为 true
//持久化声明队列
$this->channel->queue_declare('task_queue', false, true, false, false);
//尽管这行代码本身不会有错 但是仍然不会正确运行 因为我们已经定义过一个这样的非持久化的队列(队列名要唯一【task_queue】 )
//这个持久化的声明(queue_declare) 发送者和消费者 声明必须一致 这个地方第三个参数为 true 消费者也需要为true
//这时候 我们就可以确保MQ在重启之后 queue_declare 队列不会消失
//另外 我们需要把我们的消息也要设置持久化 设置为 delivery_mode = 2
$msg=new AMQPMessage($data,array('delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT) );
$this->channel->basic_publish($msg, '', 'task_queue');
echo " [x] Sent '",$data,"'\n";
// 消息持久化 注意:
//将消息设置为持久化并不能保证不会完全丢失 以上代码告诉了 MQ要把消息存到硬盘,
//但从RabbitMq收到消息到保存之间还是有一个很小的间隔时间。
//因为RabbitMq并不是所有的消息都使用fsync(2)——它有可能只是保存到缓存中,并不一定会写到硬盘中。
//并不能保证真正的持久化,但已经足够应付我们的简单工作队列。如果你一定要保证持久化,
//你可以使用publisher confirms。 https://www.rabbitmq.com/confirms.html
//发送完消息后 关闭通道
$this->channel->close();
$this->connection->close();
}
//接收工作者
public function receive_work(){
//然后声明我们将要消耗的队列。请注意,这与发送的队列中的队列相匹配。
//非持久化声明队列
// $this->channel->queue_declare('task_queue', false, false, false, false);
//持久化声明队列
$this->channel->queue_declare('task_queue', false, true, false, false);
echo ' [*] Waiting for messages. To exit press CTRL+C', "\n";
//公平调度
//如果现在有两个接收工作者,处理奇数的工作者比较忙,处理偶数的工作者毕竟闲 然后MQ并不知道这些,
//他还在一如既往的发消息 这样导致处理数据两位工作者的不公平
//我们可以使用 basic_qos() 方法 并设置 prefetch_count=1
//这样是告诉RabbitMQ,再同一时刻,不要发送超过1条消息给一个工作者(worker),
//直到它已经处理了上一条消息并且作出了响应。这样,RabbitMQ就会把消息分发给下一个空闲的工作者
// 可多个工作者来执行任务 切不会重复 这个方法
#只有consumer已经处理并确认了上一条message时queue才分派新的message给它
$this->channel->basic_qos(null, 1, null);
//我们要创建一个回调函数来接收发送者发送的消息
//消息响应默认是开启的。之前的例子中我们可以使用no_ack=True标识把它关闭。是时候设置的第四个参数basic_consume为false
// (true 意味着不响应ack) ,当工作者(worker)完成了任务,就发送一个响应。
//$this->channel->basic_consume('task_queue', '', false, true, false, false, 'msgWork');
$this->channel->basic_consume('task_queue', '', false, false, false, false, 'msgWork');
//运行上面的代码,我们发现即使使用CTRL+C杀掉了一个工作者(worker)进程,消息也不会丢失。
//当工作者(worker)挂掉这后,所有没有响应的消息都会重新发送。
//如果在basic_consume 方法第四个参赛为false 的话 再回调函数里一定要basic_ack
// 只要通道注册了回调,就进行循环
while ($this->channel ->is_consuming()) {
$this->channel->wait();
}
$this->channel->close();
$this->connection->close();
}
//发布与订阅
//分发一个消息给多个消费者 这种模式叫做 “发布/订阅”
//以搭建一个简单的日志系统 来模拟这种模式
// 它包括两个程序 第一个程序负责发送日志消息 第二个程序负责获取消息并输出内容
//在我们这个日志系统中 所有正在运行的接收方程序都会接受到消息
//我们用 其中一个来把日志写入磁盘 一个来输出到屏幕上
//最终 日志消息会被广播给所有的接受者
//需要写入日志的发送者
public function log_task($argv){
$data=$argv;
if(empty($data)){
$data="Hello Word";
}
//交换机
//交换机 (Exchanges)
//我们发送消息到队列并从中取出消息。现在是时候介绍RabbitMQ中完整的消息模型了
//让我们简单的概括一下之前的内容:
//发布者(producer)是发布消息的应用程序。
//队列(queue)用于消息存储的缓冲。
//消费者(consumer)是接收消息的应用程序。
//MQ消息模型的核心理念是: 发布者不会直接发送任何消息给队列 事实上 发布者甚至不知道消息是否已经被投入到队列
// 发布者只需要把消息发送给一个交换机(Exchanges)
// 交换机非常简单 他一边从发布者那边接收消息 一边把消息推送到队列
// 交换机必须知道如何处理他接收到的消息 是应该推送到指定的队列中还是多个队列中 或者是直接忽略消息
// 这些规则是可以通过交换机类型(exchange type)来定义的
// 交换机类型:
// 直连交换机 (direct)
// 主题交换机 (topic)
// 头交换机 (headers)
// 扇交换机 (fanout)
// 在这个日志系统中 采用扇交换机类型 从字面意思 应该就能想到 扩散 的一种类型
// 现在这个场景 我们需要把消息发送给两个接受者 这种类型刚刚合适
//绑定交换机
//创建一个 扇交换机 命名为 log_queue
$this->channel->exchange_declare('log_queue', 'fanout', false, false, false);
// 查看所有交换机的列表
// 命令: rabbitmqctl list_exchanges
// 这个列表中有一些叫做amq.*的交换器。这些都是默认创建的,不过这时候你还不需要使用他们
//匿名交换机
//在调用上面写的 四个方法的时候 并没有对交换机进行配置 但仍然可以使用消息发送
// 因为我们使用了命名为空的 字符串默认了交换机 第二个参数就是交换机的名称
//$this->channel->basic_publish($msg, '', 'hello');
// 我们这里使用的默认或者匿名交换机 消息将会根据指定的routing_key 发到指定的队列
// routing key是basic_publish函数的第三个参数 第二个参数为交换机的名字
//另外 我们需要把我们的消息也要设置持久化 设置为 delivery_mode = 2
$msg=new AMQPMessage($data,array('delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT) );
//现在,我们就可以发送消息到一个我们命名的交换机:
//我们命名了 交换机 第三个参数就不需要再找寻队列了
$this->channel->basic_publish($msg, 'log_queue');
echo " [x] Sent '",$data,"'\n";
//临时队列
// 上面的四个方法中 声明了两个队列名 ( hello和task_queue)
//给队列名称很重要 我们需要把工作者指定到正确的队列
//如果你打算在发布者(producers)和消费者(consumers)之间共享同队列的话,给队列命名是十分重要的
//这个方法里的程序 看起来跟上面的四个方法没什么区别 唯一的区别就是把消息发送到了交换机上而不是匿名交换机
//
//发送完消息后 关闭通道
$this->channel->close();
$this->connection->close();
}
//需要工作的接受者
public function log_work(){
echo ' [*] Waiting for messages. To exit press CTRL+C', "\n";
// 创建一个交换机
$this->channel->exchange_declare('log_queue', 'fanout', false, false, false);
//1.当我们连接上RabbitMQ的时候,我们需要一个全新的、空的队列。
//我们可以手动创建一个随机的队列名,或者让服务器为我们选择一个随机的队列名(推荐)。
//2.当与消费者(consumer)断开连接的时候,这个队列应当被立即删除
list($queue_name, ,) = $this->channel->queue_declare("", false, false, true, false);
// 方法返回时,$queue_name变量包含一个随机生成的RabbitMQ队列名称。例如,类似amq.gen-jzty20brgko-hjmujj0wlg。
//队列交换机绑定 只需工作者队列交换机绑定 发布任务的人 不需要绑定队列 只需要把消息推送的交换机中
$this->channel->queue_bind($queue_name, 'log_queue');
//队列交换机绑定(binding)列表查询
// rabbitmqctl list_bindings
//我们要创建一个回调函数来接收发送者发送的消息
//消息响应默认是开启的。之前的例子中我们可以使用no_ack=True标识把它关闭。是时候设置的第四个参数basic_consume为false
// (true 意味着不响应ack) ,当工作者(worker)完成了任务,就发送一个响应。
$this->channel->basic_consume($queue_name, '', false, false, false, false, 'msg');
//运行上面的代码,我们发现即使使用CTRL+C杀掉了一个工作者(worker)进程,消息也不会丢失。
//当工作者(worker)挂掉这后,所有没有响应的消息都会重新发送。
//如果在basic_consume 方法第四个参赛为false 的话 再回调函数里一定要basic_ack
// 只要通道注册了回调,就进行循环
while ($this->channel ->is_consuming()) {
$this->channel->wait();
}
$this->channel->close();
$this->connection->close();
}
}
function msg($msg) {
echo "[x] Received", $msg->body, "\n";
};
function msgWork($msg){
echo " [x] Received ", $msg->body, "\n";
sleep(substr_count($msg->body, '.'));
echo " [x] Done", "\n";
// 消息响应默认是开启的。之前的例子中我们可以使用no_ack=True标识把它关闭。
//是时候设置的第四个参数basic_consume为false (true 意味着不响应ack) ,当工作者(worker)完成了任务,就发送一个响应。
$msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
//一个很容易犯的错误就是忘了basic_ack,后果很严重。
//消息在你的程序退出之后就会重新发送,
//如果它不能够释放没响应的消息,RabbitMQ就会占用越来越多的内存。
//如果在basic_consume 方法第四个参赛为false 的话 再回调函数里一定要basic_ack
};