持久化、高可用、高可靠 的企业级消息队列系统,集成强大的监控、限流、负载均衡等功能
基于Docker安装
# 默认要求至少200M空闲磁盘空间
- hostname影响存储数据路径
- 默认management账号密码guest / guest
docker run -d --hostname rabbit --name rabbit -p 8080:15672 -e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest rabbitmq:3.6.10-management-alpine
Broker
架构
- 一个
Exchange
对接多个Queue
场景 - 一个
Queue
可以承载同一类型的多个具体Job
(通过bingding_key区分),例如短信job
、邮件job
都可以投放到nofication队列
工作流
生产者 —交换机—>RoutingKey— VS —BindingKey—> 队列 ——> 消费者
1. 交换机和队列通过key匹配绑定,多个绑定可使用相同key
2. 生产者投递一个消息及key,并经由交换机路由到key匹配的队列中存储
3. 消费者监听队列获取由其分发的消息并进行处理
交换机模式
- fanout — 发布订阅模式(广播给exchange绑定的所有队列,无视key配置)
- direct — RoutingKey==BindingKey
- topic — RoutingKey match BindingKey,且Key均由点号分隔的主题词构成(*表一个词,#表多个词)
队列分发模式(QOS配置)
- 循环发放(Round-robin dispatching) - 消费者按序分发(默认分发模式)
- 公平发放(Fair dispatching) - 消费者空闲就分发,忙则检查下一个消费者
消息确认模式
- 默认模式 - 消息分发后即删除消息
- 确认模式 - 消息处理完后消费者返回Acknowledgment,接着队列删除相应消息(避免消息丢失)
几个重要指标
- Consumer utilisation:队列的消费者利用率(尽量提高该指标)
- Process memory:队列的内存消耗(ack回执、消费者缺乏时将导致消息积压,该指标升高)
基于Laravel驱动调用
安装
# composer
composer require vladimir-yuldashev/laravel-queue-rabbitmq:5.4
# 注册
VladimirYuldashev\LaravelQueueRabbitMQ\LaravelQueueRabbitMQServiceProvider::class
# 环境变量
QUEUE_DRIVER=rabbitmq
RABBITMQ_HOST=127.0.0.1
RABBITMQ_PORT=5672
RABBITMQ_VHOST=/ #虚拟机(用户和权限配置的最小单位,默认/)
RABBITMQ_LOGIN=guest
RABBITMQ_PASSWORD=guest
RABBITMQ_QUEUE=queue_name
队列配置
- config/queue.php中新增链接
- 每个connection固定host、vhost、exchange,但可以使用多个queue
- 可以通过
$job->onConnection())
切换链接来使用其他的RabbitMQ
主机和交换机
'rabbitmq' => [
'driver' => 'rabbitmq',
'host' => env('RABBITMQ_HOST', '127.0.0.1'),
'port' => env('RABBITMQ_PORT', 5672),
'vhost' => env('RABBITMQ_VHOST', '/'),
'login' => env('RABBITMQ_LOGIN', 'guest'),
'password' => env('RABBITMQ_PASSWORD', 'guest'),
'queue' => env('RABBITMQ_QUEUE'),
// name of the default queue,
'exchange_declare' => env('RABBITMQ_EXCHANGE_DECLARE', true),
// create the exchange if not exists
'queue_declare_bind' => env('RABBITMQ_QUEUE_DECLARE_BIND', true),
// create the queue if not exists and bind to the exchange
'queue_params' => [
'passive' => env('RABBITMQ_QUEUE_PASSIVE', false),
'durable' => env('RABBITMQ_QUEUE_DURABLE', true),
'exclusive' => env('RABBITMQ_QUEUE_EXCLUSIVE', false),
'auto_delete' => env('RABBITMQ_QUEUE_AUTODELETE', false),
],
'exchange_params' => [
'name' => env('RABBITMQ_EXCHANGE_NAME', null),
'type' => env('RABBITMQ_EXCHANGE_TYPE', 'direct'),
// more info at http://www.rabbitmq.com/tutorials/amqp-concepts.html
'passive' => env('RABBITMQ_EXCHANGE_PASSIVE', false),
'durable' => env('RABBITMQ_EXCHANGE_DURABLE', true),
// the exchange will survive server restarts
'auto_delete' => env('RABBITMQ_EXCHANGE_AUTODELETE', false),
],
],
基于AmqpLib包调用
安装
composer require php-amqplib/php-amqplib:2.6.*
公共文件
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
$connection = new AMQPStreamConnection(HOST, PORT, USER, PASS, VHOST);
$channel = $connection->channel();
/** 队列服务质量配置
* @param int $prefetch_size 推荐null
* @param int $prefetch_count 消费者一次预取消息数
* @param bool $a_global 推荐null
**/
$channel->basic_qos($prefetch_size, $prefetch_count, $a_global);
#配置交换机(不存在则创建)
$channel->exchange_declare(
string $exchangeName,
string $type='direct', #交换机类型
bool $passive=false,
bool $durable=true, #交换机是否持久化(如果可靠性要求不高则推荐设定为非持久化,性能差异10倍以上)
bool $auto_delete=false
);
register_shutdown_function(function() use($channel,$connection){
$channel->close();
$connection->close();
})
生产者
按需执行消息投递
include 公共文件;
$message1 = new AMQPMessage(
string $messageBody1,
[
'content_type' => 'text/plain',
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT #声明消息为持久化(推荐),
]
);
$message2 = $message1->setBody(string $messageBody2); #复用原消息体提升速度
## 单发消息到交换机 ##
$channel->basic_publish($message1, $exchange[, $routing_key]); #未指定交换机则直接路由至routing_key匹配的队列
$channel->basic_publish($message2, $exchange[, $routing_key]);
## 批发消息到交换机(批量模式提升速度) ##
$channel->batch_basic_publish($message1, $exchange[, $routing_key]);
$channel->batch_basic_publish($message2, $exchange[, $routing_key]);
$channel->publish_batch();
消费者使用
要求服务常驻,持续监听队列
include 公共文件;
#配置队列(不存在则创建)
$channel->queue_declare(
string $queueName='', #传入空串则创建自动命名队列($return[0]为队列名)
bool $passive=false,
bool $durable=false, #队列是否持久化(如果可靠性要求不高则推荐设定为非持久化,性能差异10倍以上)
bool $exclusive=false, #声明独占式队列(connection中断后队列自动删除)
bool $auto_delete=true
);
$channel->queue_bind($queueName, $exchangeName[, $binding_key]); #队列绑定交换机
function handler(AMQPMessage $message){
$message->body;
$message->delivery_info['channel']->basic_ack(string $message->delivery_info['delivery_tag'], bool $multiple = false);
}
$channel->basic_consume(
string $queueName,
string $consumerTag,
bool $no_local=false,
bool $no_ack=true, #是否启用ack确认消息(如果可靠性要求不高则推荐设定为非ack)
bool $exclusive=false,
bool $nowait=false,
callback $handler
);
### 正式结束进程:程序运转至calllbacks空 ###
while (count($channel->callbacks)) {
$channel->wait();
}
### 临时结束进程:监听Linux系统中断信号 ###
$pcntlHandler = function ($signal) {
switch ($signal) {
case \SIGTERM:
case \SIGUSR1:
case \SIGINT:
// 一些消费者退出前的收尾工作 . . .
pcntl_signal($signal, SIG_DFL);
posix_kill(posix_getpid(), $signal);
case \SIGHUP:
// 一些消费者启动前的准备工作 . . .
break;
default:
// do nothing
}
};
pcntl_signal(\SIGTERM, $pcntlHandler);
pcntl_signal(\SIGINT, $pcntlHandler);
pcntl_signal(\SIGUSR1, $pcntlHandler);
pcntl_signal(\SIGHUP, $pcntlHandler);
服务初始化
- 先启动消费者服务
- 后启动生产者服务
AMQP更对象写法
//公共部分
$connection = new AMQPConnection(['host'=>HOST, 'port'=>PORT, 'vhost'=>VHOST, 'login'=>USER, 'password'=>PWD]);
$connection->connect() OR die('can`t connect to rabbit');
register_shutdown_function(function()use($connection){
$connection->disconnect();
});
$channel = new AMQPChannel($connection);
$channel->qos($size=null, $count=1);
$exchange = new AMQPExchange($channel);
$exchange->setName($exchangeName='my_exchange');
$exchange->setType(AMQP_EX_TYPE_DIRECT);
$exchange->setFlags(AMQP_DURABLE);
$exchange->declareExchange();
//生产者
$exchange->publish($message = '', $routing_key = null, $flags = AMQP_NOPARAM);
//消费者
$queue = new AMQPQueue($channel);
$queue->setName($queueName='my_queue');
$queue->setFlags(AMQP_DURABLE);
$queue->bind($exchangeName='my_exchange', $routingKey='');
$queue->consume(function($envelope, $queue){
$routingKey = $envelope->getRoutingKey();
$msg = $envelope->getBody(;
}, AMQP_AUTOACK);
服务管理
- 启动管理插件
rabbitmq-plugins enable rabbitmq_management
(host:15672) - 罗列交换机
rabbitmqctl list_exchanges
- 罗列队列
rabbitmqctl list_queues [fields]
- 罗列绑定
rabbitmqctl list_bindings
- 增加虚拟机
rabbitmqctl add_vhost 虚拟机名
(用户和权限配置的最小单位) - 增加用户
rabbitmqctl add_user 账号 密码
- 配置权限
rabbitmqctl set_permissions -p 虚拟机名 账号 ".*" ".*" ".*"