什么是消息队列?
消息队列是一种在分布式系统中用于异步通信的中间件。消息队列的主要功能是解决应用解耦,异步消息,流量削峰等问题,实现高并发,高可用,可伸缩和最终一致性架构。
消息队列通常由生产者(Producer)负责产生消息,消费者(Consumer)负责消费消息,消息队列管理器(Message Queue Manager)负责存储和转发消息。
RabbitMQ
RabbitMQ 是一个由 Erlang 语言开发的基于 AMQP 的开源协议实现。
AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。
RabbitMQ 在易用性、扩展性、高可用性等方面表现不俗。具体特点包括:
- 可靠性:RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认。
- 灵活的路由:RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个 Exchange 绑定在一起,也通过插件机制实现自己的 Exchange 。
- 消息集群:多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker 。
- 高可用:队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。
- 多种协议:RabbitMQ 支持多种消息队列协议,比如 STOMP、MQTT 等等。
- 多语言客户端:RabbitMQ 几乎支持所有常用语言,比如 Java、PHP、.NET、Ruby 等等。
- 管理界面:RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker 的许多方面。
- 跟踪机制:如果消息异常,RabbitMQ 提供了消息跟踪机制,使用者可以找出发生了什么。
- 插件机制:RabbitMQ 提供了许多插件,来从多方面进行扩展,也可以编写自己的插件。
RabbitMQ的工作原理
RabbitMQ 是 AMQP 协议的一个开源实现,所以其内部实际上也是 AMQP 中的基本概念:
几个概念说明:
Producer:消息生产者,就是投递消息的程序。
Consumer:消息消费者,就是接受消息的程序。
Broker:接收和分发消息的应用,简单来说就是消息队列服务器实体。
Vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。
Exchange:消息交换机,根据分发规则,匹配查询表中的routing key,分发消息到queue中去。
常用类型:direct(point-to-point), topic(publish-subscribe), fanout (multicast)。
Queue:消息队列载体,每个消息都会被投入到一个或多个队列。
Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。
Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
Connection:网络连接,比如一个TCP连接。
Channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。
rabbitmq安装启动
rebbitmq的安装启动具体可看我这篇文章rabbitmq安装启动
php安装rabbitmq扩展
1.查看php版本
2.根据php版本下载对应的扩展
下载地址:https://pecl.php.net/package/amqp
3.将下载后的压缩包解压,并配置php的rabbitmq扩展
将php_amqp.dll放到对应的php的ext目录下
将rabbitmq.4.dll放到php的exe同级目录下
修改php.ini文件,增加extension=php_amqp.dll
重启后,查看phpinfo(),看扩展是否安装成功
至此php下rabbitmq扩展安装完成
4.下载 php-amqplib
在下面的案例中使用 php-amqplib,先用composer 下载指定的php-amqplib版本
#下载指定版本
composer require php-amqplib/php-amqplib
rabbitmq涉及队列的几种模式
simple模式
该模式下生产者生产消息推送到消息队列,消费者进行消费
simple模式生产者生产消息,demo代码如下:
<?php
require_once "../vendor/autoload.php";
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
//生产者
//Connection: publisher/consumer和broker之间的TCP连接
//Channel: 如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也较低。
//Channel是在connection内部建立的逻辑连接Channel作为轻量级的Connection极大减少了操作系统建立TCP connection的开销。
//1.建立connction
/*
主要参数说明:
$host: RabbitMQ服务器主机IP地址
$port: RabbitMQ服务器端口
$user: 连接RabbitMQ服务器的用户名
$password: 连接RabbitMQ服务器的用户密码
$vhost: 连接RabbitMQ服务器的vhost(服务器可以有多个vhost,虚拟主机,类似nginx的vhost)
*/
$connection = new AMQPStreamConnection('127.0.0.0', 5672, 'admin', '123456', "/");
//2.建立通道Channel
$channel = $connection->channel();
//3.声明队列名为:goods
$queue_name = 'goods';
/*
* 主要参数说明:
* name:队列名称
* passive 是否检测同名队列
* durable:是否开启队列持久化 非持久化队列,RabbitMQ退出或者崩溃时,该队列就不存在,持久化队列保存到磁盘,但不一定完全保证不丢失信息,因为保存总是要有时间的。
* autoDelete:队列中的数据消费完成后是否自动删除队列
* exclusive:队列是否可以被其他队列访问
* noWait:是否等待服务器返回
*/
$channel->queue_declare($queue_name, false, true, false, false);
//4.生产的数据
$data = 'this is messge';
//5.创建消息
/**
* 主要参数说明:
* data:消息
* properties Array 设置的属性,比如设置该消息持久化['delivery_mode'=>2]
*/
$msg = new AMQPMessage($data, ['delivery_mode' => AMQPMessage::DELIVERY_MODE_NON_PERSISTENT]);
//6.发布消息
/**
* basic_publish主要参数说明:
* $msg 消息内容
* $exchange 交换器
* $routing_key routing_key或者队列名称
* $mandatory 匹配不到队列时,是否立即丢弃消息
* $immediate 队列无消费者时,是否立即丢弃消息
* $ticket 这个俺也不知道 坐等大佬
*/
$channel->basic_publish($msg, $exchange = '', $queue_name);
//7.通道关闭
$channel->close();
//8.连接关闭
$connection->close();
运行生产者后,在rabbitmq的web管理界面可看到队列goods
点击进去可进入到消息详细信息
simple模式消费者接受消息,demo代码如下:
<?php
require_once "../vendor/autoload.php";
use PhpAmqpLib\Connection\AMQPStreamConnection;
//1.建立connction
$connection = new AMQPStreamConnection('127.0.0.1', 5672, 'admin', '123456', "/");
//2.建立通道Channel
$channel = $connection->channel();
//3.声明队列名为:goods
$queue_name = 'goods';
/*
* 主要参数说明:
* name:队列名称
* passive 是否检测同名队列
* durable:是否开启队列持久化 非持久化队列,RabbitMQ退出或者崩溃时,该队列就不存在,持久化队列保存到磁盘,但不一定完全保证不丢失信息,因为保存总是要有时间的。
* autoDelete:队列中的数据消费完成后是否自动删除队列
* exclusive:队列是否可以被其他队列访问
* noWait:是否等待服务器返回
*/
$channel->queue_declare($queue_name, false, true, false, false);
echo " [*] Waiting for messages. To exit press CTRL+C\n";
//开启消费
$callback = function ($msg) {
echo 'received = ', $msg->body . "\n";
};
/*
* 主要参数说明:
* $queue 队列名
* $consumer_tag
* $no_local
* $no_ack 是否不需要手动ack:true就是不需要ack|false需要手动ack
* $exclusive
* $nowait
* $callback 消息回调函数
* $ticket
*/
$channel->basic_consume($queue_name, '', false, true, false, false, $callback);
//不断的循环进行消费
while ($channel->is_open()) {
$channel->wait();
}
//关闭连接
$channel->close();
$connection->close();
执行后输出结果如下:
worker模式
此模式一个生产者对应多个消费者,但是一条消息只能有一个消费者获得消息!!!
轮询分发就是将消息队列中的消息,依次发送给所有消费者。一个消息只能被一个消费者获取。
生产者代码,demo如下:
<?php
require_once "../vendor/autoload.php";
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
//生产者
//Connection: publisher/consumer和broker之间的TCP连接
//Channel: 如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也较低。
//Channel是在connection内部建立的逻辑连接Channel作为轻量级的Connection极大减少了操作系统建立TCP connection的开销。
//1.建立connction
/*
主要参数说明:
$host: RabbitMQ服务器主机IP地址
$port: RabbitMQ服务器端口
$user: 连接RabbitMQ服务器的用户名
$password: 连接RabbitMQ服务器的用户密码
$vhost: 连接RabbitMQ服务器的vhost(服务器可以有多个vhost,虚拟主机,类似nginx的vhost)
*/
$connection = new AMQPStreamConnection('127.0.0.1', 5672, 'admin', '123456', "/");
//2.建立通道Channel
$channel = $connection->channel();
//3.声明队列名为:task_queue
$queue_name = 'task_queue';
/*
* 主要参数说明:
* name:队列名称
* passive 是否检测同名队列
* durable:是否开启队列持久化 非持久化队列,RabbitMQ退出或者崩溃时,该队列就不存在,持久化队列保存到磁盘,但不一定完全保证不丢失信息,因为保存总是要有时间的。
* autoDelete:队列中的数据消费完成后是否自动删除队列
* exclusive:队列是否可以被其他队列访问
* noWait:是否等待服务器返回
*/
$channel->queue_declare($queue_name, false, true, false, false);
//4.生产数据,创建消息,发布消息
for ($i = 10;$i < 60; $i++) {
//生产数据
$data = 'this is messgess-' . $i;
//创建消息
/**
* 主要参数说明:
* data:消息
* properties Array 设置的属性,比如设置该消息持久化['delivery_mode'=>2]
*/
$msg = new AMQPMessage($data, ['delivery_mode' => AMQPMessage::DELIVERY_MODE_NON_PERSISTENT]);
//发布消息
/**
* basic_publish主要参数说明:
* $msg 消息内容
* $exchange 交换器
* $routing_key routing_key或者队列名称
* $mandatory 匹配不到队列时,是否立即丢弃消息
* $immediate 队列无消费者时,是否立即丢弃消息
* $ticket 这个俺也不知道 坐等大佬
*/
$channel->basic_publish($msg, $exchange = '', $queue_name);
}
//5.通道关闭
$channel->close();
//6.连接关闭
$connection->close();
消费者1和消费者2代码一样,同时运行开启后会一起消费,消费者代码demo如下:
<?php
require_once "../vendor/autoload.php";
use PhpAmqpLib\Connection\AMQPStreamConnection;
//1.建立connction
$connection = new AMQPStreamConnection('127.0.0.1', 5672, 'admin', '123456', "/");
//2.建立通道Channel
$channel = $connection->channel();
//3.声明队列名为:goods
$queue_name = 'task_queue';
/*
* 主要参数说明:
* name:队列名称
* passive 是否检测同名队列
* durable:是否开启队列持久化 非持久化队列,RabbitMQ退出或者崩溃时,该队列就不存在,持久化队列保存到磁盘,但不一定完全保证不丢失信息,因为保存总是要有时间的。
* autoDelete:队列中的数据消费完成后是否自动删除队列
* exclusive:队列是否可以被其他队列访问
* noWait:是否等待服务器返回
*/
$channel->queue_declare($queue_name, false, true, false, false);
echo " [*] Waiting for messages. To exit press CTRL+C\n";
//开启消费
$callback = function ($msg) {
echo 'received = ', $msg->body . "\n";
};
/*
* 主要参数说明:
* $queue 队列名
* $consumer_tag 消费者标签,用来区分多个消费者
* $no_local AMQP的标准,但rabbitmq并没有实现
* $no_ack 是否不需要手动ack:true就是不需要ack|false需要手动ack
* $exclusive 设置是否排他
* $nowait
* $callback 消息回调函数
* $ticket
*/
$channel->basic_consume($queue_name, '', false, true, false, false, $callback);
//不断的循环进行消费
while ($channel->is_open()) {
$channel->wait();
}
//关闭连接
$channel->close();
$connection->close();
涉及rabbitmq交换器的几种模式
在RabbitMQ中,交换器主要有四种类型:direct、fanout、topic、headers
Direct 定向 消息与一个特定的路由键完全匹配
Topic 通配符 路由键和某模式进行匹配
Fanout 广播 发送到该类型交换机的消息都会被广播到与该交换机绑定的所有队列
Headers 不处理路由键,而是根据发送的消息内容中的headers属性进行匹配
fanout模式
发布/订阅模式
一个生产者将消息首先发送到交换器,交换器绑定到多个队列,然后被监听该队列的消费者所接收并消费。是公用一个交换机的消费端都能收到同样的消息,类似广播的功能。
关于exchange_declare($exchange, $type, $passive = false, $durable = false, $auto_delete = true, $internal = false, $nowait = false, $arguments = array(), $ticket = null) 。试探性申请一个交换器,若该交换器不存在,则创建;若存在,则跳过
生产者代码,demo如下:
<?php
require_once "../vendor/autoload.php";
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
$connection = new AMQPStreamConnection('127.0.0.1', 5672, 'admin', '123456', "/");
$channel = $connection->channel();
$channel->exchange_declare('logs', 'fanout', false, false, false);
$data = implode(' ', array_slice($argv, 1));
if (empty($data)) {
$data = "info: Hello World!";
}
$msg = new AMQPMessage($data);
$channel->basic_publish($msg, 'logs');
echo ' [x] Sent ', $data, "\n";
$channel->close();
$connection->close();
消费者代码如下:
<?php
require_once "../vendor/autoload.php";
use PhpAmqpLib\Connection\AMQPStreamConnection;
$connection = new AMQPStreamConnection('127.0.0.1', 5672, 'admin', '123456', "/");
$channel = $connection->channel();
$channel->exchange_declare('logs', 'fanout', false, false, false);
list($queue_name, ,) = $channel->queue_declare("", false, false, true, false);
$channel->queue_bind($queue_name, 'logs');
echo " [*] Waiting for logs. To exit press CTRL+C\n";
$callback = function ($msg) {
echo ' [x] ', $msg->getBody(), "\n";
};
$channel->basic_consume($queue_name, '', false, true, false, false, $callback);
try {
$channel->consume();
} catch (\Throwable $exception) {
echo $exception->getMessage();
}
$channel->close();
$connection->close();
direct模式
生产者将消息发送到direct交换器,在绑定队列和交换器的时候有一个路由key,生产者发送的消息会指定一个路由key,那么消息只会发送到相应key相同的队列,接着监听该队列的消费者消费消息。
是以路由规则为导向,引导消息存入符合规则的队列中。再由队列的消费者进行消费的。
生产者代码,demo如下:
<?php
require_once "../vendor/autoload.php";
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
//生产者
//Connection: publisher/consumer和broker之间的TCP连接
//Channel: 如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也较低。
//Channel是在connection内部建立的逻辑连接Channel作为轻量级的Connection极大减少了操作系统建立TCP connection的开销。
//1.建立connction
/*
主要参数说明:
$host: RabbitMQ服务器主机IP地址
$port: RabbitMQ服务器端口
$user: 连接RabbitMQ服务器的用户名
$password: 连接RabbitMQ服务器的用户密码
$vhost: 连接RabbitMQ服务器的vhost(服务器可以有多个vhost,虚拟主机,类似nginx的vhost)
*/
$connection = new AMQPStreamConnection('127.0.0.1', 5672, 'admin', '123456', "/");
//2.建立通道Channel
$channel = $connection->channel();
//3.声明交换器
$exc_name = 'direct_log';
//指定routing_key
$routing_key = 'info';
/*
* exchange_declare($exchange, $type, $passive = false, $durable = false, $auto_delete = true, $internal = false, $nowait = false, $arguments = array(), $ticket = null) 。
* 试探性申请一个交换器,若该交换器不存在,则创建;若存在,则跳过。
* 主要参数说明:
* $exchange:队列名称
* $type 交换器类型,常见类型如:fanout,dircet,topic,hraders四种
* $passive 只判断不创建,一般是判断该交换机是否存在
* $durable:是否开启持久化 设置true表示持久化,反之非持久化。持久化可以将交换器存盘,在服务器重启的适合不会丢失相关数据。
* $auto_delete:是否自动删除
* $internal:设置是否内置,设置true表示内置交换器,客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这个方式
* $nowait:如果为true表示不等待服务器回执信息,函数将返回NULL,可提高访问速度。
*/
$channel->exchange_declare($exc_name, 'direct', false, false, false);
//4.生产的数据
$data = 'this is ' . $routing_key . ' message';
//5.创建消息
/**
* 主要参数说明:
* data:消息
* properties Array 设置的属性,比如设置该消息持久化['delivery_mode'=>2]
*/
$msg = new AMQPMessage($data, ['delivery_mode' => AMQPMessage::DELIVERY_MODE_NON_PERSISTENT]);
//6.发布消息
/**
* basic_publish主要参数说明:
* $msg 消息内容
* $exchange 交换器
* $routing_key routing_key或者队列名称
* $mandatory 匹配不到队列时,是否立即丢弃消息
* $immediate 队列无消费者时,是否立即丢弃消息
* $ticket 这个俺也不知道 坐等大佬
*/
$channel->basic_publish($msg, $exc_name, $routing_key);
//7.通道关闭
$channel->close();
//8.连接关闭
$connection->close();
消费者代码如下:
<?php
require_once "../vendor/autoload.php";
use PhpAmqpLib\Connection\AMQPStreamConnection;
//1.建立connction
$connection = new AMQPStreamConnection('127.0.0.1', 5672, 'admin', '123456', "/");
//2.建立通道Channel
$channel = $connection->channel();
//3.声明交换器
$exc_name = 'direct_log';
//指定routing_key
$routing_key = 'info';
$channel->exchange_declare($exc_name, 'direct', false, false, false);
echo " [*] Waiting for messages. To exit press CTRL+C\n";
//开启消费
$callback = function ($msg) {
echo 'received = ', $msg->body . "\n";
};
//获取系统生成的消息队列名称
list($queue_name, ,) = $channel->queue_declare('', false, false, true, false);
//将队列名与交换器名进行绑定,并指定routing_key
$channel->queue_bind($queue_name,$exc_name,$routing_key);
$callback = function ($msg) {
echo 'received = ', $msg->body . "\n";
//确认消息已被消费,从生产队列中移除
$msg->ack();
};
//设置消费成功后才能继续进行下一个消费
$channel->basic_qos(null, 1, null);
//开启消费no_ack=false,设置为手动应答
$channel->basic_consume($queue_name, '', false, false, false, false, $callback);
//不断的循环进行消费
while ($channel->is_open()) {
$channel->wait();
}
//关闭连接
$channel->close();
$connection->close();
topic模式消息队列使用
通配符模式通俗的来讲就是模糊匹配。与路由模式相似,但是,主题模式是一种模糊的匹配方式。
如消费端中routing_key = ‘user.*’;
生产者demo代码:
<?php
require_once "../vendor/autoload.php";
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
//建立connction
$connection = new AMQPStreamConnection('192.168.10.105', 5672, 'root', 'root', 'order');
//Channel
$channel = $connection->channel();
//声明交换器
$exc_name = 'topic_log';
//指定routing_key
$routing_key = 'user.top';
//指定交换机类型为direct
$channel->exchange_declare($exc_name, 'topic', false, false, false);
//声明数据
$data = 'this is ' . $routing_key . ' message';
//创建消息
$msg = new AMQPMessage($data, ['delivery_mode' => AMQPMessage::DELIVERY_MODE_NON_PERSISTENT]);
//发布消息
//指定使用的routing_key
$channel->basic_publish($msg, $exc_name, $routing_key);
//关闭连接
$channel->close();
$connection->close();
消费者demo代码:
<?php
require_once "../vendor/autoload.php";
use PhpAmqpLib\Connection\AMQPStreamConnection;
//建立connction
$connection = new AMQPStreamConnection('192.168.10.105', 5672, 'root', 'root', 'order');
//Channel
$channel = $connection->channel();
//声明交换器
$exc_name = 'direct_log';
//指定routing_key
$routing_key = 'user.*';
$channel->exchange_declare($exc_name, 'topic', false, false, false);
//获取系统生成的消息队列名称
list($queue_name, ,) = $channel->queue_declare('', false, false, true, false);
//将队列名与交换器名进行绑定,并指定routing_key
$channel->queue_bind($queue_name,$exc_name,$routing_key);
$callback = function ($msg) {
echo 'received = ', $msg->body . "\n";
//确认消息已被消费,从生产队列中移除
$msg->ack();
};
//设置消费成功后才能继续进行下一个消费
$channel->basic_qos(null, 1, null);
//开启消费no_ack=false,设置为手动应答
$channel->basic_consume($queue_name, '', false, false, false, false, $callback);
//不断的循环进行消费
while ($channel->is_open()) {
$channel->wait();
}
//关闭连接
$channel->close();
$connection->close();
工作模式总结
这几种工作模式,可以归为三类:
生产者,消息队列,一个消费者;
生产者,消息队列,多个消费者;
生产者,交换机,多个消息队列,多个消费者;