开发好几年,你真的懂MQ嘛(RabbitMQ为例)?看完别说难搞哦

你们平时也有用到一些消息中间件(MQ),可是对其理解可能仅停留在会使用 API 能实现生产消息、消费消息就完事了。面试

对 MQ 更加深刻的问题,可能不少人没怎么思考过。今天以RabbitMQ为例,和你们一块儿深刻了解MQ。数据库


概念

RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。服务器

AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。网络

RabbitMQ 最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。具体特色包括:数据结构

  • 可靠性(Reliability):RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布 确认。
  • 灵活的路由(Flexible Routing):在消息进入队列以前,经过 Exchange 来路由消息的。对 于典型的路由功能,RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,能够将多个 Exchange 绑定在一块儿,也经过插件机制实现本身的 Exchange 。
  • 消息集群(Clustering):多个 RabbitMQ 服务器能够组成一个集群,造成一个逻辑 Broker。
  • 高可用(Highly Available Queues):队列能够在集群中的机器上进行镜像,使得在部分节 点出问题的状况下队列仍然可用。
  • 多种协议(Multi-protocol):RabbitMQ 支持多种消息队列协议,好比 STOMP、MQTT等等。
  • 多语言客户端(Many Clients):RabbitMQ 几乎支持全部经常使用语言,好比 Java、.NET、 Ruby 等等。
  • 管理界面(Management UI):RabbitMQ 提供了一个易用的用户界面,使得用户能够监控 和管理消息 Broker 的许多方面。
  • 跟踪机制(Tracing):若是消息异常,RabbitMQ 提供了消息跟踪机制,使用者能够找出发生 了什么。
  • 插件机制(Plugin System):RabbitMQ 提供了许多插件,来从多方面进行扩展,也能够编 写本身的插件。


RabbitMQ 架构架构


使用场景并发

在咱们秒杀抢购商品的时候,系统会提醒咱们稍等排队中,而不是像几年前同样页面卡死或报错给用户。异步

像这种排队结算就用到了消息队列机制,放入通道里面一个一个结算处理,而不是某个时间断忽然涌入大批量的查询新增把数据库给搞宕机,因此RabbitMQ本质上起到的做用就是削峰填谷,为业务保驾护航。分布式


为何选择RabbitMQ

如今的市面上有不少MQ能够选择,好比ActiveMQ、ZeroMQ、Appche Qpid,那问题来了为何要选择RabbitMQ?微服务

除了Qpid,RabbitMQ是惟一一个实现了AMQP标准的消息服务器;

可靠性,RabbitMQ的持久化支持,保证了消息的稳定性;

高并发,RabbitMQ使用了Erlang开发语言,Erlang是为电话交换机开发的语言,天生自带高并发光环,和高可用特性;

集群部署简单,正是应为Erlang使得RabbitMQ集群部署变的超级简单;

社区活跃度高,根据网上资料来看,RabbitMQ也是首选;


工做机制

生产者、消费者和代理

在了解消息通信以前首先要了解3个概念:生产者、消费者和代理。

  • 生产者:消息的建立者,负责建立和推送数据到消息服务器;
  • 消费者:消息的接收方,用于处理数据和确认消息;
  • 代理:就是RabbitMQ自己,用于扮演“快递”的角色,自己不生产消息,只是扮演“快递”的角色。


消息发送原理

首先你必须链接到Rabbit才能发布和消费消息,那怎么链接和发送消息的呢?

你的应用程序和Rabbit Server之间会建立一个TCP链接,一旦TCP打开,并经过了认证,认证就是你试图链接Rabbit以前发送的Rabbit服务器链接信息和用户名和密码,有点像程序链接数据库,使用Java有两种链接认证的方式,后面代码会详细介绍,一旦认证经过你的应用程序和Rabbit就建立了一条AMQP信道(Channel)。

信道是建立在“真实”TCP上的虚拟链接,AMQP命令都是经过信道发送出去的,每一个信道都会有一个惟一的ID,不管是发布消息,订阅队列或者介绍消息都是经过信道完成的。


为何不经过TCP直接发送命令?

对于操做系统来讲建立和销毁TCP会话是很是昂贵的开销,假设高峰期每秒有成千上万条链接,每一个链接都要建立一条TCP会话,这就形成了TCP链接的巨大浪费,并且操做系统每秒能建立的TCP也是有限的,所以很快就会遇到系统瓶颈。

若是咱们每一个请求都使用一条TCP链接,既知足了性能的须要,又能确保每一个链接的私密性,这就是引入信道概念的缘由。

你必须知道的Rabbit

想要真正的了解Rabbit有些名词是你必须知道的。

包括:ConnectionFactory(链接管理器)、Channel(信道)、Exchange(交换器)、Queue(队列)、RoutingKey(路由键)、BindingKey(绑定键)。

  • ConnectionFactory(链接管理器):应用程序与Rabbit之间创建链接的管理器,程序代码中使用;
  • Channel(信道):消息推送使用的通道;
  • Exchange(交换器):用于接受、分配消息;
  • Queue(队列):用于存储生产者的消息;
  • RoutingKey(路由键):用于把生成者的数据分配到交换器上;
  • BindingKey(绑定键):用于把交换器的消息绑定到队列上;


看到上面的解释,最难理解的路由键和绑定键了,那么他们具体怎么发挥做用的,请看下图:

关于更多交换器的信息,咱们在后面再讲。


消息持久化

Rabbit队列和交换器有一个不可告人的秘密,就是默认状况下重启服务器会致使消息丢失,那么怎么保证Rabbit在重启的时候不丢失呢?答案就是消息持久化。

当你把消息发送到Rabbit服务器的时候,你须要选择你是否要进行持久化,但这并不能保证Rabbit能从崩溃中恢复,想要Rabbit消息能恢复必须知足3个条件:

投递消息的时候durable设置为true,消息持久化,代码:channel.queueDeclare(x, true, false, false, null),参数2设置为true持久化;

设置投递模式deliveryMode设置为2(持久),代码:channel.basicPublish(x, x, MessageProperties.PERSISTENT_TEXT_PLAIN,x),参数3设置为存储纯文本到磁盘;

消息已经到达持久化交换器上;

消息已经到达持久化的队列;


持久化工做原理

Rabbit会将你的持久化消息写入磁盘上的持久化日志文件,等消息被消费以后,Rabbit会把这条消息标识为等待垃圾回收。


持久化的缺点

消息持久化的优势显而易见,但缺点也很明显,那就是性能,由于要写入硬盘要比写入内存性能较低不少,从而下降了服务器的吞吐量,尽管使用SSD硬盘可使事情获得缓解,但他仍然吸干了Rabbit的性能,当消息成千上万条要写入磁盘的时候,性能是很低的。

因此使用者要根据本身的状况,选择适合本身的方式。


怎么保证 MQ 消息不丢失?

使用了 MQ 以后,还要关心消息丢失的问题。这里咱们挑 RabbitMQ 来讲明一下吧。

生产者弄丢了数据

RabbitMQ 生产者将数据发送到 RabbitMQ 的时候,可能数据在网络传输中搞丢了,这个时候 RabbitMQ 收不到消息,消息就丢了。

RabbitMQ 提供了两种方式来解决这个问题:

事务方式:在生产者发送消息以前,经过`channel.txSelect`开启一个事务,接着发送消息。

若是消息没有成功被 RabbitMQ 接收到,生产者会收到异常,此时就能够进行事务回滚`channel.txRollback`,而后从新发送。假如 RabbitMQ 收到了这个消息,就能够提交事务`channel.txCommit`。

可是这样一来,生产者的吞吐量和性能都会下降不少,如今通常不这么干。


另一种方式就是经过 Confirm 机制:这个 Confirm 模式是在生产者那里设置的,就是每次写消息的时候会分配一个惟一的 ID,而后 RabbitMQ 收到以后会回传一个 ACK,告诉生产者这个消息 OK 了。

若是 RabbitMQ 没有处理到这个消息,那么就回调一个 Nack 的接口,这个时候生产者就能够重发。

事务机制和 Confirm 机制最大的不一样在于事务机制是同步的,提交一个事务以后会阻塞在那儿。

可是 Confirm 机制是异步的,发送一个消息以后就能够发送下一个消息,而后那个消息 RabbitMQ 接收了以后会异步回调你一个接口通知你这个消息接收到了。

因此通常在生产者这块避免数据丢失,都是用 Confirm 机制的。


RabbitMQ 弄丢了数据

RabbitMQ 集群也会弄丢消息,这个问题在官方文档的教程中也提到过,就是说在消息发送到 RabbitMQ 以后,默认是没有落地磁盘的,万一 RabbitMQ 宕机了,这个时候消息就丢失了。

因此为了解决这个问题,RabbitMQ 提供了一个持久化的机制,消息写入以后会持久化到磁盘。

这样哪怕是宕机了,恢复以后也会自动恢复以前存储的数据,这样的机制能够确保消息不会丢失。

设置持久化有两个步骤:

第一个是建立 Queue 的时候将其设置为持久化的,这样就能够保证 RabbitMQ 持久化 Queue 的元数据,可是不会持久化 Queue 里的数据。

第二个是发送消息的时候将消息的 deliveryMode 设置为 2,就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。

可是这样一来可能会有人说:万一消息发送到 RabbitMQ 以后,还没来得及持久化到磁盘就挂掉了,数据也丢失了,怎么办?

对于这个问题,实际上是配合上面的 Confirm 机制一块儿来保证的,就是在消息持久化到磁盘以后才会给生产者发送 ACK 消息。

万一真的遇到了那种极端的状况,生产者是能够感知到的,此时生产者能够经过重试发送消息给别的 RabbitMQ 节点。


消费端弄丢了数据

RabbitMQ 消费端弄丢了数据的状况是这样的:在消费消息的时候,刚拿到消息,结果进程挂了,这个时候 RabbitMQ 就会认为你已经消费成功了,这条数据就丢了。

对于这个问题,要先说明一下 RabbitMQ 消费消息的机制:在消费者收到消息的时候,会发送一个 ACK 给 RabbitMQ,告诉 RabbitMQ 这条消息被消费到了,这样 RabbitMQ 就会把消息删除。

可是默认状况下这个发送 ACK 的操做是自动提交的,也就是说消费者一收到这个消息就会自动返回 ACK 给 RabbitMQ,因此会出现丢消息的问题。

因此针对这个问题的解决方案就是:关闭 RabbitMQ 消费者的自动提交 ACK,在消费者处理完这条消息以后再手动提交 ACK。

这样即便遇到了上面的状况,RabbitMQ 也不会把这条消息删除,会在你程序重启以后,从新下发这条消息过来。


写在最后

看完点赞,感谢您的承认;

随手转发,分享知识,让更多人学习到;

记得点关注,天天更新的!!!

因为篇幅缘由,在这作不了所有展现,只是讲解了一些RabbitMQ的内容,我整理了多份pdf文档免费分享给那些有须要的朋友。

里面的内容包括但不限于一下内容(这是其中一份文档目录截图,附有面试题含答案以及知识点讲解):

同时整理也花费了蛮多时间,在这把这些pdf分享给你们,以为有用有须要的朋友的能够加群:1017599436便可获取免费领取方式!!!文档中有他的知识点的高频面试点答疑,很是详细!!!

同时还有一份Java中高级面试高频考点文档免费分享给你们,与上面那份文档掌握其中的大部分知识足以面对不少互联网公司包括阿里蚂蚁金服等面试了。

其中囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。

记得必定要转发!!

更多Java进阶知识笔记文档分享,这些对于面试仍是学习来讲都是一份不错的学习资料

以为有用有须要的朋友的能够加群:1017599436便可获取免费领取方式!

相关文章
相关标签/搜索