RabbitMQ windows安装
安装Erlang
RabbitMQ服务端代码是使用并发式语言Erlang编写的,安装Rabbit MQ的前提是安装Erlang。
下载地址:Erlang Programming Languagehttp://www.erlang.org/downloads
配置一下系统的环境变量
ERLANG_HOME=D:\erlan
%ERLANG_HOME%\bin加入到path中。
cmd,再输入erl回车
安装RabbitMQ
下载地址:Downloading and Installing RabbitMQ — RabbitMQ
rabbitmq_management图形界面安装
Cmd 进入 RabbitMQ的sbin目录
输入rabbitmq-plugins enable rabbitmq_management命令进行安装
打开sbin目录,双击rabbitmq-server.bat启动
访问http://localhost:15672
默认用户名和密码都是guest
角色
1、超级管理员(administrator)
可登录管理控制台,可查看所有的信息,并且可以对用户,策略(policy)进行操作。
2、 监控者(monitoring)
可登录管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等)
3、 策略制定者(policymaker)
可登录管理控制台, 同时可以对policy进行管理。但无法查看节点的相关信息(上图红框标识的部分)。
4、 普通管理者(management)
仅可登录管理控制台,无法看到节点信息,也无法对策略进行管理。
5、 其他
无法登录管理控制台,通常就是普通的生产者和消费者。
管理界面标签页介绍
overview:概览
connections:无论生产者还是消费者,都需要与RabbitMQ建立连接后才可以完成消息的生产和消费,在这里可以查看连接情况
channels:通道,建立连接后,会形成通道,消息的投递获取依赖通道。
Exchanges:交换机,用来实现消息的路由
Queues:队列,即消息队列,消息存放在队列中,等待消费,消费后被移除队列。
端口:
5672:rabbitMq的编程语言客户端连接端口
15672:rabbitMq管理界面端口
25672:rabbitMq集群的端口
创建Virtual Hosts
类似于mysql中的database。他们都是以“/”开头
添加队列
queue :队列名称
durable : 是否持久化。true持久化,队列会保存磁盘。服务器重启时可以保证不丢失相关信息。
exclusive :设置是否排他。true排他的。如果一个队列声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。
排它是基于连接可见的,同一个连接不同信道是可以访问同一连接创建的排它队列,“首次”是指如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列,即使这个队列是持久化的,一旦连接关闭或者客户端退出,该排它队列会被自动删除,这种队列适用于一个客户端同时发送与接口消息的场景。
autoDelete :设置是否自动删除。true是自动删除。自动删除的前提是:致少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开 时,才会自动删除
生产者创建这个队列,或者没有消费者客户端与这个队列连接时,都不会自动删除这个队列
Max length 设置队列中可以存储处于ready状态消息的数量
Max length bytes 队列中可以存储处于ready状态消息占用内存的大小(只计算消息体的字节数,不计算消息头、消息属性占用的字节数)
Overflow behaviour Max length队列的处于ready状态存储消息的个数或消息占用的容量超过设定值后的处理策略
drop-head(删除queue头部的消息)
reject-publish(最近发来的消息将被丢弃)
reject-publish-dlx(拒绝发送消息到死信交换器DLX)
利用DLX,当消息在一个队列中变成死信后,它能被重新publish到另一个Exchange,这个Exchange就是DLX。
应用场景
用户注册后,需要发注册邮件,注册短信
流量消峰
用户的请求,服务器收到之后,首先写入消息队列,加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面
日志收集
几个概念说明:
Broker:它提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输,
Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
Queue:消息的载体,每个消息都会被投到一个或多个队列。
Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来.
Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
vhost:虚拟主机,一个broker里可以有多个vhost,用作不同用户的权限分离。
Producer:消息生产者,就是投递消息的程序.
Consumer:消息消费者,就是接受消息的程序.
Channel:消息通道,在客户端的每个连接里,可建立多个channel.
任务分发机制
Round-robin dispathching循环分发
RabbbitMQ的分发机制非常适合扩展,而且它是专门为并发程序设计的,如果现在load加重,那么只需要创建更多的Consumer来进行任务处理。
Message acknowledgment消息确认
为了保证数据不被丢失,RabbitMQ支持消息确认机制,为了保证数据能被正确处理而不仅仅是被Consumer收到,那么我们不能采用no-ack,而应该是在处理完数据之后发送ack. 在处理完数据之后发送ack,就是告诉RabbitMQ数据已经被接收,处理完成,RabbitMQ可以安全的删除它了. 如果Consumer退出了但是没有发送ack,那么RabbitMQ就会把这个Message发送到下一个Consumer,这样就保证在Consumer异常退出情况下数据也不会丢失. RabbitMQ它没有用到超时机制.RabbitMQ仅仅通过Consumer的连接中断来确认该Message并没有正确处理,也就是说RabbitMQ给了Consumer足够长的时间做数据处理。如果忘记ack,那么当Consumer退出时,Mesage会重新分发,然后RabbitMQ会占用越来越多的内存.
Message durability消息持久化
要持久化队列queue的持久化需要在声明时指定durable=True; 这里要注意,队列的名字一定要是Broker中不存在的,不然不能改变此队列的任何属性. 队列和交换机有一个创建时候指定的标志durable,durable的唯一含义就是具有这个标志的队列和交换机会在重启之后重新建立,它不表示说在队列中的消息会在重启后恢复
消息持久化包括3部分
10.1 exchange持久化,在声明时指定durable => true
10.2 queue持久化,在声明时指定durable => true
10.3 消息持久化,在投递时指定delivery_mode => 2(1是非持久化)
如果exchange和queue都是持久化的,那么它们之间的binding也是持久化的,如果exchange和queue两者之间有一个持久化,一个非持久化,则不允许建立绑定.
注意:一旦创建了队列和交换机,就不能修改其标志了,例如,创建了一个non-durable的队列,然后想把它改变成durable的,唯一的办法就是删除这个队列然后重现创建。
Fair dispath 公平分发
默认状态下,RabbitMQ将第n个Message分发给第n个Consumer。n是取余后的,它不管Consumer是否还有unacked Message,只是按照这个默认的机制进行分发. 那么如果有个Consumer工作比较重,那么就会导致有的Consumer基本没事可做,有的Consumer却毫无休息的机会,那么,Rabbit是如何处理这种问题呢?
通过basic.qos方法设置prefetch_count=1,这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理一个Message,换句话说,在接收到该Consumer的ack前,它不会将新的Message分发给它
注意,这种方法可能会导致queue满。当然,这种情况下你可能需要添加更多的Consumer,或者创建更多的virtualHost来细化你的设计。
分发到多个Consumer
交换机路由的几种类型:
12.1 Direct Exchange: 直接匹配,通过Exchange名称+RountingKey来发送与接收消息.
12.2 Fanout Exchange: 广播订阅,向所有的消费者发布消息,但是只有消费者将队列绑定到该路由器才能收到消息,忽略Routing Key.
12.3 Topic Exchange: 主题匹配订阅,这里的主题指的是RoutingKey,RoutingKey可以采用通配符,如:*或#,RoutingKey命名采用.来分隔多个词,只有消息这将队列绑定到该路由器且指定RoutingKey符合匹配规则时才能收到消息;
12.4 Headers Exchange: 消息头订阅,消息发布前,为消息定义一个或多个键值对的消息头,然后消费者接收消息同时需要定义类似的键值对请求头:(如:x-mactch=all或者x_match=any),只有请求头与消息头匹配,才能接收消息,忽略RoutingKey.
12.5 默认的exchange: 如果用空字符串去声明一个exchange,那么系统就会使用”amq.direct”这个exchange,我们创建一个queue时,默认的都会有一个和新建queue同名的routingKey绑定到这个默认的exchange上去
消息序列化
RabbitMQ使用ProtoBuf序列化消息,它可作为RabbitMQ的Message的数据格式进行传输,由于是结构化的数据,这样就极大的方便了Consumer的数据高效处理,当然也可以使用XML,与XML相比,ProtoBuf有以下优势:
1.简单
2.size小了3-10倍
3.速度快了20-100倍
4.易于编程
6.减少了语义的歧义
<?php
//消费方
//端口的问题,15672是对外,对内的参数连接还是要用5672
$conn_args = array('host' => '127.0.0.1', 'port' => 5672, 'login' => '432', 'password' => '442234', 'vhost' => '/test1');
$conn = new AMQPConnection($conn_args);
try {
if ($conn->connect()) {
echo "Established a connection to the broker n";
} else {
echo "Cannot connect to the broker n ";
}
} catch (Exception $e) {
echo $e->getMessage();
exit;
}
$q_name = 'duilie13';//队列名称
$channel = new AMQPChannel($conn);
$q = new AMQPQueue($channel);
$q->setName($q_name);
$q->setFlags(AMQP_DURABLE);
$q->bind('amq.direct', 'routing.test1');
$i = 1;
while ($i) {
//echo "queue status: ".$a;
//echo "==========n";
//sleep(1);
//自动ACK
$messages = $q->get(AMQP_AUTOACK);//AMQP_AUTOACK自动ack提交删除数据
if ($messages) var_dump($messages->getBody() . '-' . time());
//手动ACK
//无数量限制
//连接断开后自动恢复回去,其他连接还可以读取到数据
//未断开连接,不能读取
//$messages = $q->get(AMQP_NOPARAM);//consume()方法的非阻塞替代方法
//if($messages) var_dump($messages->getBody() .'-'.time());
//NACK (否定响应)
//如果既不想对消息执行确定响应, 也不需要消息继续出现在队列中, 可以使用 Queue 的 nack 方法
//$q->nack($messages->getDeliveryTag());
//nack 方法除了可以从队列中过滤掉不需要的方法, 也可以将暂时不需要的方法重新放回队列
//如果此时队列只有一个消费者, 将会造成死循环
//放到前面
//$q->nack($messages->getDeliveryTag(), AMQP_REQUEUE);
//确定响应
//$q->ack($messages->getDeliveryTag());
// $i++;
// if($i>20) $i = 0;
//sleep(1);
//$i = 0;
//echo "n";
}
$conn->disconnect();
?>
<?php
//生产方:
//端口的问题,15672是对外,对内的参数连接还是要用5672
$conn_args = array('host' => '127.0.0.1', 'port' => 5672, 'login' => 'tyr', 'password' => '4543543', 'vhost' => '/test1');
try {
$conn = new AMQPConnection($conn_args);
if ($conn->connect()) {
echo "Established a connection to the broker n";
} else {
echo "Cannot connect to the broker n ";
}
} catch (Exception $e) {
echo $e->getMessage();
exit;
}
$e_name = 'amq.direct';//交换机名
$q_name = 'duilie13';//队列名称
$r_key = 'routing.test1';
//你的消息
$message = json_encode(array('Hello World!'));
//创建channel
$channel = new AMQPChannel($conn);
//创建exchange
$ex = new AMQPExchange($channel);
$ex->setName($e_name);//创建交换机名
$ex->setType(AMQP_EX_TYPE_DIRECT);
$ex->setFlags(AMQP_DURABLE);
//echo "exchange status:".$ex->declare();
echo "n";
//创建队列
$q = new AMQPQueue($channel);
//设置队列名字 如果不存在则添加
$q->setName($q_name);
$q->setFlags(AMQP_DURABLE);//$q->setFlags(AMQP_DURABLE | AMQP_AUTODELETE);
//声明declareQueue 已存在的队列返回消息数量,不存在新建
//Declare a new queue on the broker.
//我们需要一个全新的、空的队列。我们可以手动创建一个随机的队列名,或者让服务器为我们选择一个随机的队列名(推荐)。我们只要在调用$queue->declare();
//echo "queue status: ".$q->declareQueue();
//$queue->setFlags(AMQP_EXCLUSIVE);//declareQueue队列断连自动删除
echo "n";
echo 'queue bind: ' . $q->bind($e_name, $r_key);//将你的队列绑定到routingKey
echo "n";
//$channel->startTransaction();
//$channel->rollbackTransaction();
//$channel->commitTransaction();
$i = 0;
$n = 1000;
for ($i; $i < $n; $i++) {
//默认超过长度丢弃之前的数据
//queue,exchange, message都要设置持久化 delivery_mode=2消息持久化
$re = $ex->publish($message . $i, $r_key, AMQP_NOPARAM, ['delivery_mode' => 2]);
if (!$re) {
echo 'err';
break;
}
}
$conn->disconnect();
?>
<?php
abstract class RabbitMqParernt
{
//rabbitMQ配置信息(默认配置)
public $config = array(
'host' => '127.0.0.1', //host
'port' => 5672, //端口 //端口的问题,15672是对外,对内的参数连接还是要用5672
'login' => 'mafei', //账号
'password' => '901227', //密码
'vhost' => '/test1' //虚拟主机
);
public $exchangeName = ''; //交换机
public $queueName = ''; //队列名
public $routeKey = ''; //路由键
public $exchangeType = ''; //交换机类型
public $channel; //信道,通道
public $connection; //连接
public $exchange; //交换机
public $queue; //队列
//初始化RabbitMQ($config数组是用来修改rabbitMQ的配置信息的)
public function __construct($exchangeName, $queueName, $routeKey, $exchangeType = '', $config = [])
{
$this->exchangeName = $exchangeName;
$this->queueName = $queueName;
$this->routeKey = $routeKey;
$this->exchangeType = $exchangeType;
if (!empty($config)) {
$this->setConfig($config);
}
$this->createConnet();
}
//对RabbitMQ的配置重新进行配置
public function setConfig($config)
{
if (!is_array($config)) {
throw new Exception('config不是一个数组');
}
foreach ($config as $key => $value) {
$this->config[$key] = $value;
}
}
//创建连接与信道
public function createConnet()
{
try {
//创建连接
$this->connection = new AMQPConnection($this->config);
if (!$this->connection->connect()) {
throw new Exception('RabbitMQ创建连接失败');
}
//创建信道 通道
$this->channel = new AMQPChannel($this->connection);
//非自动ACK是能获取数据条数,自动ACK忽略此配置
$this->channel->setPrefetchCount(1);
//prefetch_count=1 公平分发,收不到ack获取不到新数据
//AMQPChannel::qos ( int $size , int $count )
//创建交换机
$this->createExchange();
//生产时不需要队列,故队列名为空,只有消费时需要队列名
if (!empty($this->queueName)) {
$this->createQueue();
}
} catch (Exception $e) {
echo 'q ' . $e->getMessage();
exit;
}
}
//创建交换机
public function createExchange()
{
$this->exchange = new AMQPExchange($this->channel);
$this->exchange->setName($this->exchangeName);
$this->exchange->setType(AMQP_EX_TYPE_DIRECT);//A direct exchange type.
//Direct Exchange:直接匹配,通过Exchange名称+RountingKey来发送与接收消息.
//Fanout Exchange:广播订阅,向所有的消费者发布消息,但是只有消费者将队列绑定到该路由器才能收到消息,忽略Routing Key.
//Topic Exchange:主题匹配订阅,这里的主题指的是RoutingKey,RoutingKey可以采用通配符,如:*或#,RoutingKey命名采用.来分隔多个词,只有消息这将队列绑定到该路由器且指定RoutingKey符合匹配规则时才能收到消息;
//Headers Exchange:消息头订阅,消息发布前,为消息定义一个或多个键值对的消息头,然后消费者接收消息同时需要定义类似的键值对请求头:(如:x-mactch=all或者x_match=any),只有请求头与消息头匹配,才能接收消息,忽略RoutingKey.
//默认的exchange:如果用空字符串去声明一个exchange,那么系统就会使用”amq.direct”这个exchange,我们创建一个queue时,默认的都会有一个和新建queue同名的routingKey绑定到这个默认的exchange上去
$this->exchange->setFlags(AMQP_DURABLE);//durable:true 持久化
}
//创建队列,绑定交换机
public function createQueue()
{
$this->queue = new AMQPQueue($this->channel);
$this->queue->setName($this->queueName);
$this->queue->setFlags(AMQP_DURABLE);
$this->queue->bind($this->exchangeName, $this->routeKey);
}
public function dealMq($flag)
{
if ($flag) {
//$messages = $this->queue->get();//consume方法的非阻塞替代方法
$this->queue->consume(function ($envelope) {
$this->getMsg($envelope, $this->queue);
}, AMQP_AUTOACK);//自动ACK应答
} else {
$this->queue->consume(function ($envelope) {
$this->processMessage($envelope, $this->queue);
});
}
}
public function getMsg($envelope, $queue)
{
$msg = $envelope->getBody();
$this->doProcess($msg);
}
public function processMessage($envelope, $queue)
{
$msg = $envelope->getBody();
$this->doProcess($msg);
//$queue->ack($envelope->getDeliveryTag()); //手动发送ACK应答
}
//处理消息的真正函数,在消费者里使用
abstract public function doProcess($msg);
//发送消息
public function sendMessage($message)
{
//queue,exchange, message都要设置持久化
$this->exchange->publish($message, $this->routeKey, AMQP_NOPARAM, ['delivery_mode' => 2]);
}
//关闭连接
public function closeConnect()
{
$this->channel->close();
$this->connection->disconnect();
}
}
?>
<?php
include_once('RabbitMqParernt.php');
/**
* 消费
* Class Consum
*/
class Consum extends RabbitMqParernt
{
public function __construct()
{
parent::__construct('amq.direct', 'duilie2', 'routing.test1');
}
public function doProcess($msg)
{
echo '消费成功 '. $msg . "\r\n";
}
}
$consum = new Consum();
//一直等待
$consum->dealMq(false);
//$consum->dealMq(true);
?>
<?php
include_once('RabbitMqParernt.php');
/**
* 发布消息
* Class Publish
*/
class Publish extends RabbitMqParernt
{
public function __construct()
{
//发布不用队列名
parent::__construct('amq.direct', '', 'routing.test1');
}
public function doProcess($msg)
{
echo '发布成功 '. $msg . '\r\n';
}
}
try{
$publish = new Publish();
$n = 10;
for($i = 0; $i < $n; $i++)
{
$publish->sendMessage('Hello,World!'.$i.'_'.time());
sleep(1);
//echo 1;
}
$publish->closeConnect();
}catch (Exception $e){
echo 'e '.$e->getMessage();exit;
}
?>