什么是消息队列?

就是生产者生产一条消息,发送到这个rabbitmq,消费者连接rabbitmq并且进行消费,生产者和消费者并需要知道对方是如何工作的,从而实现程序之间的解耦,异步和削峰,这也就是消息队列的作用。

使用的场景也有很多,比如用户支付购买之后的发送短信,增加用户积分等等,只要能将业务逻辑抽象出来,就能很好得使用它。

下面进入正题:

先来介绍一下基本概念和参与生命周期的各个成员。

publisher:消息生产者,负责创建消息,并发送到代理服务器(rabbitmq)

message:发送的消息,由 有效负载(payload) 和 标签 (label) 组成

exchange:交换器,负责接收消息并路由给服务器的队列

queue:消息队列,就是消息最后要去的地方。然后等待消费者取走并消费

consumer:消息消费者,与生产者对应,程序的另外一方,负责消费信息,并完成相应的业务逻辑

channel:信道,在tcp之上建立的通道,负责传送消息。队列的传输都是基于信道来完成的。

broker:消息队列服务器实体

下面来解析一下这张图,这张图是网上找的,虽然不够详细,但是勉强能用。

准备前提,开启rabbitmq服务,生命队列和交换器,并将两者进行绑定。

生产逻辑:

publisher 通过 broker服务器ip 和 端口 尝试建立与 broker 的 tcp 连接,连接成功之后,会尝试验证验证用户名和密码,如果错误,则拒绝访问。

验证成功之后,在tcp上建立一个 信道(channel) ,publisher 通过信道,发送消息到 broker,进入指定的虚拟主机virtual host。然后查找到交换器绑定的队列,并将消息推送进入队列。

消费逻辑:

consumer 建立连接和验证和生产者一样。接下来通过队列名,直接找到队列进行监听并消费。

有几个比较重要的知识点:

1.  rabbitmq的交换器并不是真正意义的交换器,它本质上其实就是一张表,里面存放和交换器名称和消息队列的映射关系。所以说队列的传输都是通过信道来完成的。

2.  信道存在的意义在于 创建和销毁tcp连接非常消耗资源

交换器的类型

当生产的消息进入虚拟主机时,会去寻找一张表,就是交换器和队列映射关系的一张表。而交换器的类型也分了好多种,可以根据不同的场景自由选择。

目前总共分了4种,direct,fanout,topic,headers。其中headers因为性能问题几乎不在使用,这里就不做过多的讨论。

1.direct

direct是直接,完全匹配,单播的模式。

php简单代码实现:

//创建队列
$q = new AMQPQueue($channel);
$q->setName($q_name);
$q->setFlags(AMQP_DURABLE); //持久化
$q->declare(); //创建交换机对象
$ex = new AMQPExchange($channel);
$ex->setName($e_name);
$ex->setType(AMQP_EX_TYPE_DIRECT); //direct类型
$ex->setFlags(AMQP_DURABLE); //持久化
$ex->declare(); //绑定交换机与队列,并指定路由键
$q->bind($e_name, $k_route);
//生产消息
$ex->publish($message, $k_route)

特性: 当生产消息时,未指定交换器,则会默认使用 (AMQP default) 交换器,然后路由到 和路由键名称相同的队列中去

2.fanout

如果说 direct 是 单对单 的关系,那么 fanout就是单对多的关系,即一个交换器对应多个队列。

fanout交换器不通过路由键路由到队列,而是通过将队列绑定在交换器上,当消息进来时,直接路由到该交换器绑定的队列去。

3.topic

topic交换器通过路由键,将自动匹配允许匹配的队列,相比fanout,更加灵活,不过对架构要求更高。如下图所示

$e_name = 'logs-exchange'; //交换机名
$q_name = 'msg-inbox-errors'; //队列名 //创建队列
$q = new AMQPQueue($channel);
$q->setName($q_name);
$q->setFlags(AMQP_DURABLE); //持久化
$q->declare(); //创建交换机对象
$ex = new AMQPExchange($channel);
$ex->setName($e_name);
$ex->setType(AMQP_EX_TYPE_TOPIC); //direct类型
$ex->setFlags(AMQP_DURABLE); //持久化
$ex->declare(); $q->bind($e_name, '*.msg-inbox'); $ex->publish($message, 'wonima.msg-inbox');

高级特性:

为了确保消息可靠性,有两种处理方式.

1.rabbitmq事务

事务主要是对信道进行设置,示例代码如下

$channel->startTransaction(); //开始事务
for($i=0; $i<5; ++$i){
$message = "TEST MESSAGE! 测试消息!";
$message = $message.$i."---";
echo "Send Message:".$ex->publish($message, 'xxxx')."\n"; }
$channel->commitTransaction(); //提交事务

经测验,使用事务之后,性能会造成相当大的影响,与不实用事务相比,性能可以相差百倍以上。

2.confirm 模式

当信道设置未 confirm 模式的时候,每一条消息都会获的唯一的id。当消费者接收到消息的时候,自动发送 或 手动发送消息  进行消息确认。

//创建队列
$q = new AMQPQueue($channel);
$q->setName($q_name);
$q->setFlags(AMQP_DURABLE); //持久化
echo "Message Total:".$q->declare()."\n"; //第一种:自动应答
//$q->consume('processMessage', AMQP_AUTOACK); //自动ACK应答

//第二种:手动应答
$q->consume('processMessage'); /**
* 消费回调函数
* 处理消息
*/
function processMessage($envelope, $queue) {
$msg = $envelope->getBody(); sleep(2); $myfile = fopen("newfile2.txt", "a+") or die("Unable to open file!");
$txt = $msg.time()."\n";
fwrite($myfile, $txt);
fclose($myfile); echo $msg.time()."\n"; //处理消息
$q->ack($envelope->getDeliveryTag()); //手动发送ACK应答
}

应答模式最大的好处是就是异步,执行效率高。事务和应答模式相比,后者使用更加频繁,前者几乎没有见到过。

延迟队列

首先声明rabbitmq是不支持延迟队列的,但是我们可以利用死信队列来完成。

实现延迟队列也有多种方式:

第一种:设置死信队列,并将 过期时间 加到队列里面

try {
$conn = new AMQPConnection($connectConfig);
$conn->connect();
if (!$conn->isConnected()) { echo 'rabbit-mq 连接错误:', json_encode($connectConfig);
exit();
}
$channel = new AMQPChannel($conn);
if (!$channel->isConnected()) {
echo 'rabbit-mq Connection through channel failed:', json_encode($connectConfig);
exit();
}
$exchange = new AMQPExchange($channel);
$exchange->setFlags(AMQP_DURABLE);//持久化
$exchange->setName($params['exchangeName'] ?: '');
$exchange->setType(AMQP_EX_TYPE_DIRECT); //direct类型
$exchange->declareExchange(); $queue = new AMQPQueue($channel);
$queue->setName($params['queueName'] ?: '');
$queue->setFlags(AMQP_DURABLE);
$queue->setArguments(array(
'x-dead-letter-exchange' => 'last_exchange',
'x-dead-letter-routing-key' => 'last_route',
'x-message-ttl' => 10000,
));
$queue->declareQueue(); //绑定
$queue->bind($params['exchangeName'], $params['routeKey']); $exchange2 = new AMQPExchange($channel);
$exchange2->setFlags(AMQP_DURABLE);//持久化
$exchange2->setName('last_exchange');
$exchange2->setType(AMQP_EX_TYPE_DIRECT); //direct类型
$exchange2->declareExchange(); $queue2 = new AMQPQueue($channel);
$queue2->setName('last_queue');
$queue2->setFlags(AMQP_DURABLE);
$queue2->declareQueue(); $queue2->bind('last_exchange', 'last_queue'); } catch (Exception $e) { } $time = time();
//生成消息
$exchange->publish((string)$time, $params['routeKey'], AMQP_MANDATORY, [
'delivery_mode' => 2,
]);

第二种:设置死信队列,并将 过期时间 加到消息里面,这一种更加自由。

$msg = [
'x-message-ttl' => 5,
'ttl' => 5,
'body' => time()
];
$msg = json_encode($msg);
$exchange->publish($msg, '', AMQP_MANDATORY, ['delivery_mode' => 2]);

第三种:使用延迟插件

集群

先来谈谈rabbitmq的集群是如何运行的

当你开启来两个rabbitmq(节点)服务,并将其组成为一个集群。每个节点并不会将所有的队列进行拷贝,元数据依旧保存在单个节点当中,其他节点则是通过指针。

举个例子:节点a和节点b组成了一个集群,节点a保存着一堆元数据 c 和 元数据d的指针,用来指向节点b,节点b保存一堆元数据d 和 元数据 c的指针,用来指向节点a。

这样做有两个原因

1 存储空间 :如果一个节点存储了1gb的数据,再添加节点,只会带来一摸一样的1gb的数据,非常浪费磁盘空间

2 性能:对于持久化消息来说,每一条消息都会触发磁盘io,每次新增节点,网路和磁盘负载都会增加,相对于单机来说,性能不但不会提升,反而可能下降。

但是由于交换器只是一张查询表,并非实际的路由器,因此将交换器在整个集群进行复制也不会损耗太多的性能,所以交换器在每个节点都会保存一份,以便于查询。

消息队列-一篇读懂rabbitmq(生命周期,confirm模式,延迟队列,集群)的更多相关文章

  1. 一片非常有趣的文章 三分钟读懂TT猫分布式、微服务和集群之路

    原文http://www.cnblogs.com/smallSevens/p/7501932.html#3782600 三分钟读懂TT猫分布式.微服务和集群之路   针对新手入门的普及,有过大型网站技 ...

  2. 三分钟读懂TT猫分布式、微服务和集群之路

    针对入门新手的普及,有过大型网站技术架构牛人路过,别耽误浪费了时间,阅读之前,请确保有一定的网络基础,熟练使用Linux,浏览大概需要3-5分钟的时间,结尾有彩蛋. 目录 分布式 微服务 负载均衡集群 ...

  3. 三分钟读懂TT猫分布式、微服务和集群之路 (转)

    http://www.cnblogs.com/smallSevens/p/7501932.html 针对新手入门的普及,有过大型网站技术架构牛人路过,别耽误浪费了时间,阅读之前,请确保有一定的网络基础 ...

  4. 一篇读懂HTTPS:加密原理、安全逻辑、数字证书等

    1.引言 HTTPS(全称: Hypertext Transfer Protocol Secure,超文本传输安全协议),是以安全为目标的HTTP通道,简单讲是HTTP的安全版.本文,就来深入介绍下其 ...

  5. RabbitMQ 入门教程(PHP版) 延迟队列,延迟任务

    延迟任务应用场景 场景一:物联网系统经常会遇到向终端下发命令,如果命令一段时间没有应答,就需要设置成超时. 场景二:订单下单之后30分钟后,如果用户没有付钱,则系统自动取消订单. 场景三:过1分钟给新 ...

  6. 【RabbitMQ 实战指南】一 延迟队列

    1.什么是延迟队列 延迟队列中存储延迟消息,延迟消息是指当消息被发送到队列中不会立即消费,而是等待一段时间后再消费该消息. 延迟队列很多应用场景,一个典型的应用场景是订单未支付超时取消,用户下单之后3 ...

  7. iOS系列 基础篇 03 探究应用生命周期

    iOS系列 基础篇 03 探究应用生命周期 目录: 1. 非运行状态 - 应用启动场景 2. 点击Home键 - 应用退出场景 3. 挂起重新运行场景 4. 内存清除 - 应用终止场景 5. 结尾 本 ...

  8. iOS系列 基础篇 04 探究视图生命周期

    iOS系列 基础篇 04 探究视图生命周期 视图是应用的一个重要的组成部份,功能的实现与其息息相关,而视图控制器控制着视图,其重要性在整个应用中不言而喻. 以视图的四种状态为基础,我们来系统了解一下视 ...

  9. 【转】Android总结篇系列:Activity生命周期

    [转]Android总结篇系列:Activity生命周期 Android官方文档和其他不少资料都对Activity生命周期进行了详细介绍,在结合资料和项目开发过程中遇到的问题,本文将对Activity ...

随机推荐

  1. Spring boot添加配置类@Configuration并初始化@Bean,@Resource和@Autowired都为null

    大写加黑,找了好久@Resource和@Autowired都依赖不到创建的bean的原因:@Bean的方法名即是创建的Bean名称 import org.activiti.engine.Process ...

  2. @bzoj - 2658@ [Zjoi2012]小蓝的好友(mrx)

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 终于到达了这次选拔赛的最后一题,想必你已经厌倦了小蓝和小白的故事 ...

  3. spark源码分析以及优化

    第一章.spark源码分析之RDD四种依赖关系 一.RDD四种依赖关系 RDD四种依赖关系,分别是 ShuffleDependency.PrunDependency.RangeDependency和O ...

  4. 2、Redis如何配置成一个windows服务并且设置一键安装卸载与启停

    每天启动redis虽然只是一个命令行的事情,但是还是比较烦,所以…… 参考文档:Windows Service Documentation.docx 默认前提:Redis已安装并配置完成(不知道如何配 ...

  5. 【Key】 Windows 95 到 Windows10所有KEY 包括OEM系统

    部分可能不准确,请见谅,数据源自Baidu,由noogai00整理,数据为Microsoft.所有 Windows 95 02398-OEM-0030022-5886409297-OEM-002128 ...

  6. Unity中数据的存储与交互的初步分析(PlayerPrefs,Dictionary,JsonUnility)

    1.PlayerPrefs   PlayerPrefs.SetString(key,Value);  PlayerPrefs.GetString(key,Value);字符串类型 PlayerPref ...

  7. 在Docker中运行PostgreSQL + pgAdmin 4

    拉取postgresql镜像:docker pull postgres 运行postgresql:docker run -d -p 5432:5432 --name postgresql -v pgd ...

  8. 谁再悄咪咪的吃掉异常,我上去就是一 JIO

    又到周末了,周更选手申请出站~ 这次分享一下上个月碰到的离奇的问题.一个简单的问题,硬是因为异常被悄咪咪吃掉,过关难度直线提升,导致小黑哥排查一个晚上. 这个美好的晚上,本想着开两把 LOL 无限火力 ...

  9. Elasticsearch、Solr、Lucene、Hermes区别

    Elasticsearch简介 Elasticsearch是一个实时分布式搜索和分析引擎.它让你以前所未有的速度处理大数据成为可能.它用于全文搜索.结构化搜索.分析以及将这三者混合使用:维基百科使用E ...

  10. 下订单更新订单表然后减少库存表中的数据,出现库存超卖,使用数据库和redis坚决库存超卖的问题

    上面的代码更新库存的数据,存在多线程的问题,第一种方法使用synchronized关键字修饰的语句块代码,但是性能较低,并且还是存在问题的 在分布式的场景下,当前库存系统部署在多个tomcat上,即使 ...