MQ面试题
RabbitMQ
MQ 有哪些常见问题?如何解决这些问题?
消息顺序消费。保证生产者 - MQServer - 消费者是一对一对一的关系可以保证消息顺序消费。
消息重复。消费端处理消息的业务逻辑保持幂等性。只要保持幂等性,不管来多少条重复消息,最后处理的结果都 一样。保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现。利用一张日志表来记 录已经处理成功的消息的 ID,如果新到的消息 ID 已经在日志表中,那么就不再处理这条消息。
RabbitMQ基本概念
- Broker: 简单来说就是消息队列服务器实体
- Exchange: 消息交换机,它指定消息按什么规则,路由到哪个队列
- Queue: 消息队列载体,每个消息都会被投入到一个或多个队列
- Binding: 绑定,它的作用就是把exchange和queue按照路由规则绑定起来
- Routing Key: 路由关键字,exchange根据这个关键字进行消息投递
- VHost: vhost 可以理解为虚拟 broker ,即 mini-RabbitMQ server。其内部均含有独立的 queue、exchange 和 binding 等,但最最重要的是,其拥有独立的权限系统,可以做到 vhost 范 围的用户控制。当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段(一个典 型的例子就是不同的应用可以跑在不同的 vhost 中)。
- Producer: 消息生产者,就是投递消息的程序
- Consumer: 消息消费者,就是接受消息的程序
- Channel: 消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话 任务 由Exchange、Queue、RoutingKey三个才能决定一个从Exchange到Queue的唯一的线路。
如何保证RabbitMQ消息的顺序性?
拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同 的 worker 来处理。
消息基于什么传输?
由于 TCP 连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ 使用信道的方式来传输数据。信道是建立在真实的TCP连接内的虚拟连接,且每条TCP连接上的信道数量没有限制。
如何保证消息消费时的幂等性?
在消费端保证消息的唯一性,就算是多次传输,不要让消息的多次消费带来影响;保证消息等幂性,在写入消息队列的数据做唯一标示,消费消息时,根据唯一标识判断是否消费过
如何保证消息的可靠性
生产者丢失消息:从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息;transaction机制就是说:发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务 (channel.txCommit())。然而,这种方式有个缺点:吞吐量下降,一般confirm模式用的居多,一旦channel进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,rabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果rabbitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。
消息队列丢数据:消息持久化。 处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。 这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。 这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。 那么如何持久化呢? 这里顺便说一下吧,其实也很容易,就下面两步:
- 将queue的持久化标识durable设置为true,则代表是一个持久的队列
- 发送消息的时候将
deliveryMode=2
这样设置以后,即使rabbitMQ挂了,重启后也能恢复数据
消费者丢失消息:消费者丢数据一般是因为采用了自动确认消息模式,改为手动确认消息即可! 消费者在收到消息之后,处理消息之前,会自动回复RabbitMQ已收到消息; 如果这时处理消息失败,就会丢失该消息; 解决方案:处理消息成功后,手动回复确认消息。
相关信息
自动确认模式下,消息发送后立即认为已成功发送。这种模式以更高的吞吐量(只要消费者能够跟上)为代价,以降低交付和消费者处理的安全性。这种模式通常被称为“即发即忘”。与手动确认模型不同,如果消费者的 TCP 连接或通道在成功传递之前关闭,则服务器发送的消息将丢失。因此,自动消息确认应该被认为是不安全的。
RabbitMQ如何保证高可用
RabbitMQ 是比较有代表性的,因为是基于主从(非分布式)做高可用性的,我们就以 RabbitMQ 为例 子讲解第一种 MQ 的高可用性怎么实现。RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群 模式。
单机模式,生产环境不使用单机模式。
普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你创建的 queue,只会放在一个 RabbitMQ 实例上,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接 到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。这方案主要是提高吞吐量 的,就是说让集群中多个节点来服务某个 queue 的读写操作。 镜像集群模式:这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像 集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会存在于多个实例上,就是说,每 个 RabbitMQ 节点都有这个 queue 的一个完整镜像,包含 queue 的全部数据的意思。然后每次你写消 息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上。RabbitMQ 有很好的管理控制台, 就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节 点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据 同步到其他的节点上去了。这样的话,好处在于,你任何一个机器宕机了,没事儿,其它机器(节点) 还包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据。坏处在于,第 一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重! RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的 完整数据。
相关信息
重要提示:RabbitMQ 的未来版本将删除经典队列的镜像。考虑使用仲裁队列或非复制经典队列。
集群模式下,镜像数量怎么设置是最佳的?
镜像到所有节点是最保守的选择。它会给所有集群节点带来额外的压力,包括网络 I/O、磁盘 I/O 和磁盘空间使用。在大多数情况下,没有必要在每个节点上都有副本。对于 3 个及更多节点的集群,建议复制到法定数量(大多数)节点,例如3 节点集群中的 2 个节点或 5 节点集群中的 3 个节点。
如何解决消息队列的延时以及过期失效问题?消息队列满了以后 该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
消息积压解决方案
此时就需要最紧急的问题就是,临时紧急扩容大概思路:
- 先修复consumer的问题,确保其恢复消费速度,然后将现有cnosumer都停掉
- 新建一个topic,partition是原来的10倍,临时建立好原先10倍或者20倍的queue数量
- 然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue
- 接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据
- 这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据
- 等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息
消息过期
rabbitmq设置了TTL过期时间,如果消息在queue中积压超过一定的时间就会被rabbitmq给清理掉,现在的问题就不是消息挤压,而是直接变成大量消息丢失,这个时候,最终的解决方案,一般都是批量重新导入MQ。手动补偿
消息如何刷到磁盘?
- 写入文件前会有一个Buffer,大小为1M,数据在写入文件时,首先会写入到这个Buffer,如果Buffer已满,则会将Buffer写入到文件(未必刷到磁盘)。
- 有个固定的刷盘时间:25ms,也就是不管Buffer满不满,每个25ms,Buffer里的数据及未刷新到磁盘的文件内容必定会刷到磁盘。
- 每次消息写入后,如果没有后续写入请求,则会直接将已写入的消息刷到磁盘:使用Erlang的receive x after 0实现,只要进程的信箱里没有消息,则产生一个timeout消息,而timeout会触发刷盘操作。
rabbitmq消费消息是推还是拉模式
两种都支持,默认使用推模式。在实际生产环境中,由于 RabbitMQ 对拉模式的支持并不如推模式那么直接和高效,大多数场景下推荐使用推模式。如果确实有特殊的需求需要控制消息拉取的时机,可以结合具体的业务场景以及定时任务等手段间接实现拉模式的行为。