消息队列面试题
消息队列
1. 什么是消息队列
消息队列Message Queue,简称MQ。是一种应用间的通信方式,主要由三个部分组成。
- Producer:消息的产生者与调用端,主要负责消息所承载的业务信息的实例化,是一个队列的发起方负责,产生和发送消息到 Broker;
- Broker:消息处理中心,负责消息存储、确认、重试等,一般其中会包含多个 Queue;
- Consumer:消息消费者,负责从 Broker 中获取消息,并进行相应处理。
2. 消息队列有哪些使用场景
- 应用解耦: 消息队列减少了服务之间的耦合性,不同的服务可以通过消息队列进行通信,而不用关心彼此的实现细节。
- 流量削峰: 削弱瞬时的请求高峰,使系统吞吐量在高峰请求下保持可控。这种技术主要用于处理那些在特定时间段内请求量急剧增加的系统,如秒杀活动或抽奖系统。在这些场景中,系统的请求量在活动开放时会出现一个波峰,而在活动未开放时则相对平稳。为了节省机器资源,不能时时都提供最大化的资源能力来支持短时间的高峰请求,因此需要采用一些技术手段来削减瞬时的请求高峰。
- 异步处理: 消息队列本身是异步的,它允许接收者在消息发送很长时间后再取回消息。
- 消息通讯: 消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯,比如实现点对点消息队列,或者聊天室等。
- 消息广播: 如果没有消息队列,每当一个新的业务方接入,我们都要接入一次新接口。有了消息队列,我们只需要关心消息是否送达了队列,至于谁希望订阅,是下游的事情,无疑极大地减少了开发和联调的工作量。
消息队列如何解决消息丢失问题?
一个消息从生产者产生,到被消费者消费,主要经过这 3 个过程:
因此如何保证 MQ 不丢失消息,可以从这三个阶段阐述
- 生产者保证不丢消息 生产者发送消息后接收broker返回的ack,ack成功就表示消息正常送达。
- 存储端不丢失消息 存储段不丢失消息就是确保消息持久化到磁盘,也就是刷盘机制。 刷盘机制分为两种,同步刷盘和异步刷盘。 - 生产者消息发过来时,只有持久化到磁盘,RocketMQ 存储端 Broker 才返回 一个成功✁ACK 响应,这就✁同步刷盘。它保证消息不丢失,但影响了性能。- 异步刷盘话,只要消息写入 PageCache 缓存,就返回一个成功ACK 响应。 这样提高了 MQ 性能,但如果这时候机器断电了,就会丢失消息。
- 消费阶段不丢失消息 消费者执行完业务逻辑,再反馈会 Broker 说消费成功,这样才可以保证✲费 阶段不丢消息。
消息队列如何保证消息的顺序性
消息有序性,就指可以按照消息发送顺序来消费。有些业务对消息顺序有要求,比如先下单再付款,最后再完成订单,这样等。假设生产者先后产生了两条消息,分别下单消息(M1),付款消息(M2),M1 比 M2
先产生,如何保证 M1 比 M2 先被消费呢。
- Kafka:生产者发消息时,1 个 Topic只能对应 1 个 Partition, 一个 Consumer,内部单线程消费。
- RocketMq:确保生产者发送的消息去往同一个队列(Queue),确保消费者从同一个队列(Queue)拉取消息。
- RabbitMq:如果只有一个生产者向单一队列发送消息,并且这个队列只连接了一个消费者,则RabbitMQ默认情况下就能够保证消息的有序性。因为RabbitMQ会按照消息到达队列的顺序进行存储,并且当消费者开始消费时,也会按照队列中消息的FIFO(先进先出)原则来处理。
消息队列有可能重复消费,如何保证幂等
两种情况可能会导致重复消费。
- 生产端为了保证消息可靠性,它可能往 MQ 服务器重复发送消息,直到拿到成功 ACK。
- 再然后就消费端,消费端消费消息一般这个流程:拉取消息、业务逻辑处理、提交消费位移。假设业务逻辑处理完,事务提交了,但需要更新消费位移时,消费者挂了,这时候另一个消费者就会拉到重复消息了。
- kafka的个Partition Balance机制,就是把多个Partition均衡的分配给多个消费者。消费端会从分配到的Partition里面去消费消息,如果消费者在默认的5分钟内没有处理完这一批消息。就会触发Kafka的Rebalance机制,从而导致offset自动提交失败。而Rebalance之后,消费者还是会从之前没提交的offset位置开始消费,从而导致消息重复消费。 解决办法
- 提高消费端的处理性能避免出发balance,调整消息处理的超时时间
- 针对消息生成md5保存到mysql或者redis中,处理消息之前判断消息是否消费过。
实现幂等的几种方案
- 使用数据库唯一约束来实现幂等。
- 使用redis,setNx命令。
- 使用状态机实现幂等。
如何处理消息队列消息积压问题
消息积压是因为生产者生产速度大于消费者消费速度。遇到消息积压问题时,需要先排查是不是有bug产生了。如果不是bug,我们可以优化一下消费逻辑。
- 比如之前一条一条消息消费处理,可以优化为批量处理消息
- 增加水平扩容,增加topic队列数和消费组机器数量。
如果是bug导致几百万消息持续积压几小时,如何处理
- 先修复 consumer 消费者✁问题,以确保其恢复消费速度,然后将现有consumer 都停掉。
- 新建一个 topic,partition是原来的10 倍,临时建立好原先 10 倍的 queue数量。
- 然后写一个临时分发数据consumer 程序,这个程序部署上去消费积压数据,消费之后不做耗时处理,直接均匀轮询写入临时建立好10 倍数量得queue。
- 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时queue的数据。这种做法相当于临时将 queue 资源和consumer 资源扩大10 倍,以正常 10 倍速度来消费数据。
- 等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的consumer 机器来消费消息。
消息队列技术选型
三种MQ的概念详解
KafkaRocketMQRabbitMQ单机吞吐量17.3w/s11.6w/s2.6w/s(消息做持久化)开发语言Scala/JavaJavaErlang主要维护者ApacheAlibabaMozilla/Spring订阅形式基于 topic ,按照topic 进行正则匹配的发布订阅模式基于topic/messageTag , 按照消息类型、属性进行正则匹配的发布订阅模式提供了 4 种: direct, topic ,Headers 和fanout ;fanout 就是广播模式持久化支持大量堆积支持大量堆积支持少量堆积顺序消息支持支持不支持集群方式天然的Leader- Slave,无状态集群,每台服务器既是Master 也是Slave常用 多对’ Master-Slave’模式, 开源版本需手动切换Slave 变成 Master支持简单集群,'复制’模式,对高级集群模式支持不好。性能稳定性较差一般好第一列文本居中第二列文本居右第三列文本居左第三列文本居左第一列文本居中第二列文本居右第三列文本居左第三列文本居左第一列文本居中第二列文本居右第三列文本居左第三列文本居左
- RabbitMQ开源,比较稳定,活跃度也高,但是不是Java 语言开发的。
- 很多公司用RocketMQ,阿里出品。
- 如果是大数据领域的实时计算、日志采集等场景,用Kafka 是业内标准。
Kafka
优点:
- 高吞吐、低延迟:Kafka 最大的特点就是收发消息非常快,Kafka 每秒可以处理几十万条消息,它的最低延迟只有几毫秒;
- 高伸缩性:每个主题(topic)包含多个分区(partition),主题中的分区可以分布在不同的主机(broker)中;
- 高稳定性:Kafka 是分布式的,一个数据多个副本,某个节点宕机,Kafka 集群能够正常工作;
- 持久性、可靠性、可回溯:Kafka 能够允许数据的持久化存储,消息被持久化到磁盘,并支持数据备份防止数据丢失,支持消息回溯;
- 消息有序:通过控制能够保证所有消息被消费且仅被消费一次;
- 有优秀的第三方 Kafka Web 管理界面 Kafka-Manager,在日志领域比较成熟,被多家公司和多个开源项目使用。
缺点:
- Kafka 单机超过 64 个队列/分区,Load 会发生明显的飙高现象,队列越多,load 越高,发送消息响应时间变长;
- 不支持消息路由,不支持延迟发送,不支持消息重试;
- 社区更新较慢。
RocketMQ
优点:
- 高吞吐:借鉴 Kafka 的设计,单一队列百万消息的堆积能力;
- 高伸缩性:灵活的分布式横向扩展部署架构,整体架构其实和 kafka 很像;
- 高容错性:通过ACK机制,保证消息一定能正常消费;
- 持久化、可回溯:消息可以持久化到磁盘中,支持消息回溯;
- 消息有序:在一个队列中可靠的先进先出(FIFO)和严格的顺序传递;
- 支持发布/订阅和点对点消息模型,支持拉、推两种消息模式;
- 提供 docker 镜像用于隔离测试和云集群部署,提供配置、指标和监控等功能丰富的 Dashboard。
缺点:
- 不支持消息路由,支持的客户端语言不多,目前是 java 及 c++,其中 c++ 不成熟;
- 部分支持消息有序:需要将同一类的消息 hash 到同一个队列 Queue 中,才能支持消息的顺序,如果同一类消息散落到不同的 Queue中,就不能支持消息的顺序。
- 社区活跃度一般。
RabbitMQ
优点:
- 支持几乎所有最受欢迎的编程语言:Java,C,C ++,C#,Ruby,Perl,Python,PHP等等;
- 支持消息路由:RabbitMQ 可以通过不同的交换器支持不同种类的消息路由;
- 消息时序:通过延时队列,可以指定消息的延时时间,过期时间TTL等;
- 支持容错处理:通过交付重试和死信交换器(DLX)来处理消息处理故障;
- 提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker;
- 社区活跃度高。
缺点:
- Erlang 开发,很难去看懂源码,不利于做二次开发和维护,基本职能依赖于开源社区的快速维护和修复 bug;
- RabbitMQ 吞吐量会低一些,这是因为他做的实现机制比较重;
- 不支持消息有序、持久化不好、不支持消息回溯、伸缩性一般。
消息中间件如何做到高可用
Kafka
Kafka的基础集群架构,由多个 broker组成,每个 broker都是一个节点。当你创建一个 topic时,它可以划分为多个 partition,而每个 partition放一部分数据,分别存在于不同的broker 上。也就是说,一个 topic 的数据,是分散放在多个机器上的,每个机器就放一部分数据。
Kafka 0.8 之后,提供了复制品副本机制来保证高可用,即每个 partition的数据都会同步到其它机器上,形成多个副本。然后所有的副本会选举一个leader 出来,让 leader 去跟生产和消费者打交道,其他副本都是follower。写数据时,leader 负责把数据同步给所有的follower,读消息时, 直接读leader 上的数据即可。
RocketMQ
- 消息存储高可用:
- Broker允许挂起:brokerAllowSuspend参数决定了Broker是否允许挂起,在消息拉取时,该值默认为true。
- 消息消费进度缓存:如果Broker的角色为主服务器,并且hasCommitOffsetFlag为true,则使用commitOffset更新消息消费进度。
- 数据冗余存储:当数据写入Broker节点后,还会进行数据冗余操作,即将数据同步到其他Broker节点上,以确保即使某个Broker节点宕机,其他节点中还存有消息数据,从而保证Broker的高可用性。
- 集群管理高可用:
- NameServer设计:NameServer承担路由注册中心的作用,即使部分NameServer节点宕机,也不会影响RocketMQ集群的正常运行。即使所有NameServer都宕机,也不会影响已经运行的Broker、Producer和Consumer。
- NameServer的独立性:NameServer之间互相独立,彼此没有通信关系,单台NameServer挂掉不会影响其他NameServer。
- 心跳机制:Broker每隔30秒向所有NameServer发送心跳,包含自身的topic配置信息,以保证NameServer的实时更新和集群的高可用性。
RabbitMQ
- 集群(Clustering) RabbitMQ的集群提供了高可用性和负载均衡。集群中的节点共享同一个Erlang分布式数据库,队列可以在多个节点上进行镜像,以提供冗余。
- 镜像队列(Mirrored Queues) 镜像队列是RabbitMQ实现HA的核心特性。它们确保队列中的消息在多个节点上复制,这样即便一个节点失败,消息也不会丢失。这是通过设置队列的参数来启用的。
- 持久化(Persistence) 通过将消息和队列设置为持久化,可以确保即使RabbitMQ服务器重启,消息也不会丢失。
- 自动故障转移(Automatic Failover) 客户端可以配置为连接到一个节点列表,如果当前节点不可用,客户端会自动尝试连接列表中的下一个节点。
- 客户端自动恢复(Automatic Client Recovery) 某些RabbitMQ客户端支持自动恢复,它们能在连接断开后尝试重新连接,并恢复队列、交换器和绑定。
如何保证数据一致性,事务消息如何实现
一条普通的MQ 消息,从产生到被消费,大概流程如下:
- 生产者产生消息,发送带 MQ 服务器
- MQ 收到消息后,将消息持久化到存储系统。
- MQ 服务器返回 ACk 到生产者。
- MQ 服务器把消息 push 给消费者
- 消费者消费完消息,响应 ACK
- MQ 服务器收到 ACK,认为消息消费成功,即在存储中删除消息。 我们举个下订单的例子吧。订单系统创建完订单后,再发送消息给下游系统。如果订单创建成功,然后消息没有成功发送出去,下游系统就无法感知这个事情,出导致数据不一致。 如何保证数据一致性呢?可以使用事务消息。一起来看下事务消息是如何实现的吧。
- 生产者产生消息,发送一条半事务消息到 MQ 服务器
- MQ 收到消息后,将消息持久化到存储系统,这条消息✁状态✁待发送状态。
- MQ 服务器返回 ACK 确认到生产者,此时 MQ 不会触发消息推送事件
- 生产者执行本地事务
- 如果本地事务执行成功,即 commit 执行结果到 MQ 服务器;如果执行失败,发 送 rollback。
- 如果✁正常✁ commit,MQ 服务器更新消息状态为可发送;如果✁ rollback,即 删除消息。
- 如果消息状态更新为可发送,则 MQ 服务器会 push 消息给消费者。消费者消费完 就回ACK。
- 如果 MQ 服务器长时间没有收到生产者✁ commit 或者rollback,它会反查生产 者,然后根据查询到✁结果执行最终状态。
如何设计一个消息队列
- 消息队列的整体流程 producer 发送消息给 broker,broker 存储好,broker 再发送给consumer 消费,consumer 回复消费确认等
- RPC如何设计 参考dubbo的服务发现以及序列化协议
- Broker如何持久化
- 消费关系如何保存
- 消息可靠性如何保证
- 消息队列的高可用
- 消息事务特性
- MQ的伸缩性和可拓展性
版权归原作者 示圆阇梨偈 所有, 如有侵权,请联系我们删除。