①. 跨系统的异步通信: 所有需要异步交互的地方都可以使用消息队列.
②. 多个应用之间的解耦: 由于消息队列是平台无关和语言无关的,而且语义上也不再是函数调用,因此适合作为多个应用之间的松耦合的接口.
③. 应用内的同步变异步: 比如订单处理,就可以由前端应用将订单信息放到队列,后端应用从队列里依次获得消息处理,高峰时的大量订单可以积压在队列里慢慢处理掉.
④. 消息驱动的架构(EDA): 系统分解为消息队列,消息制造者和消息消费者,一个处理流程可以根据需要拆成多个阶段,阶段之间用队列连接起来,前一个阶段处理的结果放入队列,后一个阶段从队列中获取消息继续处理.
⑤. 跨局域网甚至跨城市的通讯: 比如北京机房与广州机房的应用程序的通信.
生产者: 消息的创建者,负责创建和推送数据到消息服务器;
消费者: 消息的接收方,用于处理数据和确认消息;
代理: 就是 RabbitMQ 本身,用于扮演“快递”的角色,本身不生产消息,只是扮演“快递”的角色.
ConnectionFactory(连接管理器): 应用程序与Rabbit之间建立连接的管理器,程序代码中使用.
Channel(信道): 消息推送使用的通道.
Exchange(交换器): 用于接受、分配消息.
Queue(队列): 用于存储生产者的消息.
RoutingKey(路由键): 用于把生产者的消息分配到交换器上.
BindingKey(绑定键): 用于把交换器的消息绑定到队列上.
vhost 可以理解为虚拟 broker,即 mini-RabbitMQ server.其内部均含有独立的 queue、exchange 和 binding 等.但最最重要的是,其拥有独立的权限系统,可以做到 vhost 范围的用户控制.当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的 vhost 中).
首先客户端必须连接到 RabbitMQ 服务器才能发布和消费消息,客户端和 rabbit server 之间会创建一个 tcp 连接,一旦 tcp 打开并通过了认证(认证就是你发送给 rabbit 服务器的用户名和密码),你的客户端和 RabbitMQ 就创建了一条 amqp 信道(channel),信道是创建在“真实” tcp 上的虚拟连接,amqp 命令都是通过信道发送出去的,每个信道都会有一个唯一的 id,不论是发布消息,订阅队列都是通过这个信道完成的.
消息事务;
消息确认机制.
消息持久化;
ACK确认机制;
设置集群镜像模式;
消息补偿机制.
声明队列必须设置持久化 durable 设置为 true.
消息推送投递模式必须设置持久化,deliveryMode 设置为 2(持久);
消息已经到达持久化交换器;
消息已经到达持久化队列.
以上四个条件都满足才能保证消息持久化成功。
持久化的缺地就是降低了服务器的吞吐量,因为使用的是磁盘而非内存存储,从而降低了吞吐量,可尽量使用 ssd 硬盘来缓解吞吐量的问题.
fanout: 所有bind到此exchange的queue都可以接收消息(纯广播,绑定到RabbitMQ的接受者都能收到消息);
direct: 通过routingKey和exchange决定的那个唯一的queue可以接收消息;
topic: 所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息;
headers:
通过消息过期后进入死信交换器,再由交换器转发到延迟消费队列,实现延迟功能;
使用 RabbitMQ-delayed-message-exchange
插件实现延迟功能.
高可用: 某个服务器出现问题,整个 RabbitMQ 还可以继续使用;
高容量: 集群可以承载更多的消息量.
磁盘节点: 消息会存储到磁盘.
内存节点: 消息都存储在内存中,重启服务器消息丢失,性能高于磁盘类型.
各节点之间使用“--link”连接,此属性不能忽略;
各节点使用的 erlang cookie 值必须相同,此值相当于“秘钥”的功能,用于各节点的认证.
整个集群中必须包含一个磁盘节点.
不是,原因有以下两个:
存储空间的考虑: 如果每个节点都拥有所有队列的完全拷贝,这样新增节点不但没有新增存储空间,反而增加了更多的冗余数据;
性能的考虑: 如果每条消息都需要完整拷贝到每一个集群节点,那新增节点并没有提升处理消息的能力,最多是保持和单节点相同的性能甚至是更糟.
如果唯一磁盘的磁盘节点崩溃了,则不能进行以下操作:
不能创建队列;
不能创建交换器;
不能创建绑定;
不能添加用户;
不能更改权限;
不能添加和删除集群节点;
唯一磁盘节点崩溃了,集群是可以保持运行的,但你不能更改任何东西.
RabbitMQ 对集群的停止的顺序是有要求的,应该先关闭内存节点,最后再关闭磁盘节点.如果顺序恰好相反的话,可能会造成消息的丢失.
可以认为是无限制,因为限制取决于机器的内存,但是消息过多会导致处理效率的下降.
是的.客户端感觉不到有何不同.
都会收到 Channel.Close 信令告之不存在(内含原因 404 NOT_FOUND).
255 字节.
根据 AMQP 协议规定,消息体的大小由 64-bit 的值来指定,所以你就可以知道到底能发多大的数据了.
当消息被 RabbitMQ server 投递到 consumer 后,但 consumer 却通过 Basic.Reject 进行了拒绝时(同时设置 requeue=false),那么该消息会被放入“dead letter”queue 中.该 queue 可用于排查 message 被 reject 或 undeliver 的原因.
其实就是问问你消息队列都有哪些使用场景,然后你项目里具体是什么场景,说说你在这个场景里用消息队列是什么?
面试官问你这个问题,期望的一个回答是说,你们公司有个什么业务场景,这个业务场景有个什么技术挑战,如果不用 MQ 可能会很麻烦,但是你现在用了 MQ 之后带给了你很多的好处.
说一下消息队列常见的使用场景,比较核心的有 3 个: 解耦、异步、削峰.
常见的mq队列有ActiveMQ、RabbitMQ、RocketMQ、Kafka,区别可见图:

1️⃣.生产者和mq队列之前传输要使用确认机制,生产者发送消息给mq丢列后,mq要进行接口回调告诉生产者这个数据我已经收到了.如果发送失败生产者要有重试机制进行重新发送.
2️⃣.mq队列要进行数据的持久化,保证mq挂掉后数据不会丢失.或者保持mq队列的高可用性,防止mq宕机造成数据丢失.
3️⃣.消费者和mq队列之间也要加入确认机制,待消费者处理完具体的业务后,注意是处理完业务逻辑后,再回调mq,告诉mq我已经处理完了.
保证幂等性的意思就是保证数据不被消费者重复消费,不在在数据库中插入重复的数据,要解决这个问题就要在消费者端入手.
1️⃣.进行数据库操作的时候,可以先查下这个数据是否已经存在了,如果存在了进行更新操作,不存在就进行插入操作;
2️⃣.写入redis:这个比较简单,直接利用redis的set数据类型就可以保证数据不会重复了;
3️⃣.如果不是数据库业务情景的话,如直接根据得到的数据进行发送邮件或者短信.这种情况的话可以加一个redis的set数据类型进行消重处理,或者用数据库做一个发送的日志记录,用1️⃣的方式去处理.
每种mq都有实现高可用性的方式,activeMQ和rabbitMQ是通过主从集群的方式实现高可用性的,但是存在一个致命问题就是,当mq队列爆满的时候,主从架构不能横向扩容,因为每个queue保持的数据都是一样的.
1️⃣. kafka的一个topic的数据分布在分区(partition)中,而每个partition都分布在不同的broker中,这样就保证了同一个topic的数据分布在不同的机器中;
2️⃣. kafka每个partition都有一个leader和多个follower,当leader挂了,follower会自动选举出来一个leader,这样就保证了mq的健壮性;
3️⃣. 生产者和消费者的读写只能通过leader进行,不能读写follower,同时只有当leader的数据同步给follower之后才认为是写成功了,保证数据不丢失.
可以看到,相比主从架构,分布式架构具有更高的可扩展性,可用横向增加partition进行扩容.
RabbitMQ 是比较有代表性的,因为是基于主从(非分布式)做高可用性的,我们就以 RabbitMQ 为例子讲解第一种 MQ 的高可用性怎么实现.
RabbitMQ 有三种模式: 单机模式、普通集群模式、镜像集群模式.
单机模式,就是 Demo 级别的,一般就是你本地启动了玩玩儿的,没人生产环境用单机模式.
普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个.你创建的 queue,只会放在一个 RabbitMQ 实例上,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例).你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来.

这种方式确实很麻烦,也不怎么好,没做到所谓的分布式,就是个普通集群.因为这导致你要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个 queue 所在实例消费数据,前者有数据拉取的开销,后者导致单实例性能瓶颈.
而且如果那个放 queue 的实例宕机了,会导致接下来其他实例就无法从那个实例拉取,如果你开启了消息持久化,让 RabbitMQ 落地存储消息的话,消息不一定会丢,得等这个实例恢复了,然后才可以继续从这个 queue 拉取数据.
所以这个事儿就比较尴尬了,这就没有什么所谓的高可用性,这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个 queue 的读写操作.
这种模式,才是所谓的 RabbitMQ 的高可用模式.跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会存在于多个实例上,就是说,每个 RabbitMQ 节点都有这个 queue 的一个完整镜像,包含 queue 的全部数据的意思.然后每次你写消息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上.
那么如何开启这个镜像集群模式呢?其实很简单,RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了.
这样的话,好处在于,你任何一个机器宕机了,都没事儿,其它机器(节点)还包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据.坏处在于:第一,这个性能开销也太大了,消息需要同步到所有机器上,导致网络带宽压力和消耗很重! 第二,这么玩儿,不是分布式的,就没有扩展性可言了,如果某个 queue 负载很重,你加机器,新增的机器也包含了这个 queue 的所有数据,并没有办法线性扩展你的 queue.你想,如果这个 queue 的数据量很大,大到这个机器上的容量无法容纳了,此时该怎么办呢?
RabbitMQ: 一个 queue,多个 consumer.比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列,有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者2先执行完操作,把 data2 存入数据库,然后是 data1/data3,这不明显乱了.

RabbitMQ: 拆分为多个 queue,每个 queue 对应一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue,但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理.

要保持多个消息之间的时间顺序,首先它们要有一个全局的时间顺序.因此,每个消息在被创建时,都将被赋予一个全局唯一的、单调递增的、连续的序列号(SerialNumber,SN),可以通过一个全局计数器来实现这一点,通过比较两个消息的SN,确定其先后顺序.
该问题其实本质针对的都是说,可能是你的消费端出了问题,不消费了;或者消费的极其极其慢.接着就坑爹了,可能你的消息队列集群的磁盘都快写满了,都没人消费,这个时候怎么办?或者是整个就积压了几个小时,你这个时候怎么办?或者是你积压的时间太长了,导致比如 rabbitmq 设置了消息过期时间后就没了怎么办?
假设你用的是 RabbitMQ,RabbtiMQ 是可以设置过期时间的,也就是 TTL.如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了.
这个情况下,就不是说要增加 consumer 消费积压的消息,因为实际上没啥积压,而是丢了大量的消息.我们可以采取一个方案,就是批量重导,就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了,这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来,也只能是这样了.
假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次.
如果是消息积压在 mq 里,那么如果你很长时间都没处理掉,此时导致 mq 都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,消费一个丢弃一个,都不要了,快速消费掉所有的消息,然后走第二个方案,到了晚上再补数据吧.
几千万条数据在 MQ 里积压了七八个小时,从下午 4 点多,积压到了晚上 11 点多.这个时候要不然就是修复 consumer 的问题,让它恢复消费速度,然后傻傻的等待几个小时消费完毕,这个肯定不能在面试的时候说吧.
一个消费者一秒是 1000 条,一秒 3 个消费者是 3000 条,一分钟就是 18 万条.所以如果你积压了几百万到上千万的数据,即使消费者恢复了,也需要大概 1 小时的时间才能恢复过来.
一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下:
1️⃣. 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 cnosumer 都停掉;
2️⃣. 再新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量;
3️⃣. 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue;
4️⃣. 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据.这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据;
5️⃣. 等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息.
其实聊到这个问题,一般面试官要考察两块:
你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理;
看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来.
说实话,问类似问题的时候,大部分人基本都会懵,因为平时从来没有思考过类似的问题,大多数人就是平时埋头用,从来不去思考背后的一些东西.类似的问题,比如,如果让你来设计一个 Spring 框架你会怎么做? 如果让你来设计一个 Dubbo 框架你会怎么做? 如果让你来设计一个 MyBatis 框架你会怎么做?
其实回答这类问题,说白了,不求你看过那技术的源码,起码你要大概知道那个技术的基本原理、核心组成部分、基本架构构成,然后参照一些开源的技术把一个系统设计出来的思路说一下就好.
比如说这个消息队列系统,我们从以下几个角度来考虑一下:
1️⃣. 首先这个 mq 得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下 kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据.如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?
2️⃣. 其次你得考虑一下这个 mq 的数据要不要持久化到磁盘吧?那肯定要了,持久化到磁盘才能保证进程挂了数据也不会丢失,那持久化到磁盘的时候怎么持久化啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是 kafka 的思路.
3️⃣. 其次你考虑一下你的 mq 的可用性,这个事儿,具体参考之前可用性那个环节讲解的 kafka 的高可用保障机制.多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务.
4️⃣. 能不能支持数据 0 丢失啊?可以的,参考我们之前说的那个 kafka 数据零丢失方案.
mq 肯定是很复杂的,面试官问你这个问题,其实是个开放题,他就是看看你有没有从架构角度整体构思和设计的思维以及能力.确实这个问题可以刷掉一大批人,因为大部分人平时不思考这些东西.