RocketMQ相关面试题(上)


前言

RocketMQ相关面试题,以及对于一些概念的补充(纯理论版)


1.RocketMQ的架构是怎么样的?

RocketMQ主要由Producer、Broker和Consumer三部分组成。
Producer:消息生产者,负责将消息发送到Broker。
Broker:消息中转服务器,负责存储和转发消息。RocketMQ支持多个Broker构成集群,每个Broker都拥有独立的存储空间和消息队列。它接收Producer发送的消息,并将其存储在内部存储中,还负责处理Consumer的订阅请求,将消息推送给订阅了相应Topic的Consumer。
Consumer:消息消费者,负责从Broker消费消息。
NameServer:名称服务,RocketMQ的路由和寻址中心,负责维护Broker的元数据信息,包括Broker地址、Topic和Queue等信息。Producer和Consumer在启动时需要连接到NameServer获取Broker的地址信息。NameServer还负责监控Broker的状态,并提供自动发现和故障恢复的功能。
Topic:消息主题,是消息的逻辑分类单位。Producer将消息发送到特定的Topic中,Consumer从指定的Topic中消费消息。
Message Queue:消息队列,是Topic的物理实现。一个Topic可以有多个Queue,每个Queue都是独立的存储单元。Producer发送的消息会被存储到对应的Queue中,Consumer从指定的Queue中消费消息。

2.介绍有一下RocketMQ的工作流程?

首先介绍一下RocketMQ的架构(上题)
RocketMQ的工作过程大致如下:
1、启动NameServer,他会等待Broker、Producer以及Consumer的链接。
2、启动Broker,会和NameServer建立连接,定时发送心跳包。心跳包中包含当前Broker信息(ip、port等)、Topic信息以及Borker与Topic的映射关系。
3、启动Producer,启动时先随机和NameServer集群中的一台建立长连接,并从NameServer中获取当前发送的Topic所在的所有Broker的地址;然后从队列列表中轮询选择一个队列,与队列所在的Broker建立长连接,进行消息的发送。
4、Broker接收Producer发送的消息,当配置为同步复制时,master需要先将消息复制到slave节点,然后再返回“写成功状态”响应给生产者;当配置为同步刷盘时,则还需要将消息写入磁盘中,再返回“写成功状态”;要是配置的是异步刷盘和异步复制,则消息只要发送到master节点,就直接返回“写成功”状态。
5、启动Consumer,过程和Producer类似,先随机和一台NameServer建立连接,获取订阅信息,然后在和需要订阅的Broker建立连接,获取消息。

3.RocketMQ的事务消息是如何实现的?

RocketMQ的事务消息是通过TransactionListener接口来实现的。
在发送事务消息时,首先向Broker发送一条“half消息”(即半消息),半消息将被存储在Broker端的事务消息日志中,但是这个消息还不能被消费者消费。
接下来,在半消息发送成功后,应用程序通过执行本地事务来确定是否要提交该事务消息。如果本地事务执行成功,就会通知Broker提交该事务消息,使得该消息可以被消费者消费;否则,就会通知Broker回滚该事务消息,该消息将被删除,从而保证消息不会被消费者消费。
拆解下来的话,主要有以下4个步骤:

  • 发送半消息:应用程序向Broker发送一条半消息,该消息在Broker端的事务消息日志中被标记为“prepared”状态。
  • 执行本地事务:RocketMQ会通知应用程序执行本地事务。如果本地事务执行成功,应用程序通知Broker提交该事务消息。
  • 提交事务消息:RocketMQ收到提交消息以后,会将该消息的状态从“prepared”改为“committed”,并使该消息可以被消费者消费。
  • 回滚事务消息:如果本地事务执行失败,应用程序通知RocketMQ Broker回滚该事务消息,RocketMQ将该消息的状态从“prepared”改为“rollback”,并将该消息从事务消息日志中删除,从而保证该消息不会被消费者消费。

如果一直没收到COMMIT或者ROLLBACK怎么办?

  • 在RocketMQ的事务消息中,如果半消息发送成功后,RocketMQ Broker在规定时间内没有收到COMMIT或者ROLLBACK消息,RocketMQ会向应用程序发送一条检查请求,应用程序可以通过回调方法返回是否要提交或回滚该事务消息。如果应用程序在规定时间内未能返回响应,RocketMQ会将该消息标记为“UNKNOW”状态。
  • 在标记为“UNKNOW”状态的事务消息中,如果应用程序有了明确的结果,还可以向MQ发送COMMIT或者ROLLBACK。
  • 但是MQ不会一直等下去,如果过期时间已到,RocketMQ会自动回滚该事务消息,将其从事务消息日志中删除。

第一次发送半消息失败了怎么办?

  • 在事务消息的一致性方案中,我们是先发半消息,再做业务操作的,所以,如果半消息发失败了,那么业务操作也不会进行,不会有不一致的问题,遇到这种情况重试就行了。

为什么要用事务消息?

  • 本地事务执行完成之后再发送消息可能会发消息失败。一旦发送消息失败了,如果本地事务提交了,但是消息没成功,那么监听者就收不到消息,就会产生数据不一致了。那如果用事务消息,先提交一个半消息,然后执行本地事务,再发送一个commit的半消息。如果后面这个commit半消息失败了,MQ是可以基于第一个半消息不断反查来推进状态的。这样只要本地事务提交成功,最终MQ也会成功。如果本地事务rolllback,那么MQ的消息也会rollback。保证了一致性。

事务消息详解

应用场景

一个核心业务逻辑的执行,同时需要调用多个下游业务进行处理。比如:电商交易场景中,用户支付订单这一核心操作的同时会涉及到下游物流发货、积分变更、购物车状态清空等多个子系统的变更。因此,如何保证核心业务和多个下游业务的执行结果完全一致,是分布式事务需要解决的主要问题。

其他方案的不足
  • 传统XA事务方案:性能不足
    为了保证上述四个分支(主系统订单信息更新、下游物流发货、积分变更、购物车状态清空)的执行结果一致性,典型方案是基于XA协议的分布式事务系统来实现。将四个调用分支封装成包含四个独立事务分支的大事务。基于XA分布式事务的方案可以满足业务处理结果的正确性,但最大的缺点是多分支环境下资源锁定范围大,并发度低,随着下游分支的增加,系统性能会越来越差。
  • 基于普通消息方案:一致性保障困难
    将上述基于XA事务的方案进行简化,将订单系统变更作为本地事务,剩下的系统变更作为普通消息的下游来执行,事务分支简化成普通消息+订单表事务,充分利用消息异步化的能力缩短链路,提高并发度。
    该方案中消息下游分支和订单系统变更的主分支很容易出现不一致的现象,例如:
    消息发送成功,订单没有执行成功,需要回滚整个事务。
    订单执行成功,消息没有发送成功,需要额外补偿才能发现不一致。
    消息发送超时未知,此时无法判断需要回滚订单还是提交订单变更。
  • 基于Apache RocketMQ分布式事务消息:支持最终一致性
    上述普通消息方案中,普通消息和订单事务无法保证一致的原因,本质上是由于普通消息无法像单机数据库事务一样,具备提交、回滚和统一协调的能力。
    而基于Apache RocketMQ实现的分布式事务消息功能,在普通消息基础上,支持二阶段的提交能力。将二阶段提交和本地事务绑定,实现全局提交结果的一致性。
事务消息处理流程
  1. 生产者将消息发送至Apache RocketMQ服务端。
  2. Apache RocketMQ服务端将消息持久化成功之后,向生产者返回Ack确认消息已经发送成功,此时消息被标记为"暂不能投递",这种状态下的消息即为半事务消息。
  3. 生产者开始执行本地事务逻辑。
  4. 生产者根据本地事务执行结果向服务端提交二次确认结果(Commit或是Rollback),服务端收到确认结果后处理逻辑如下:
    二次确认结果为Commit:服务端将半事务消息标记为可投递,并投递给消费者。
    二次确认结果为Rollback:服务端将回滚事务,不会将半事务消息投递给消费者。
  5. 在断网或者是生产者应用重启的特殊情况下,若服务端未收到发送者提交的二次确认结果,或服务端收到的二次确认结果为Unknown未知状态,经过固定时间后,服务端将对消息生产者即生产者集群中任一生产者实例发起消息回查。
  6. 生产者收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
  7. 生产者根据检查到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行处理。
使用限制
  • 消息类型一致性:事务消息仅支持在 MessageType 为 Transaction 的主题内使用,即事务消息只能发送至类型为事务消息的主题中,发送的消息的类型必须和主题的类型一致。
  • 消费事务性:事务消息保证本地主分支事务和下游消息发送事务的一致性,但不保证消息消费结果和上游事务的一致性。因此需要下游业务分支自行保证消息正确处理,建议消费端做好消费重试,如果有短暂失败可以利用重试机制保证最终处理成功。
  • 中间状态可见性:事务消息为最终一致性,即在消息提交到下游消费端处理完成之前,下游分支和上游事务之间的状态会不一致。因此,事务消息仅适合接受异步执行的事务场景。
  • 事务超时机制:事务消息的生命周期存在超时机制,即半事务消息被生产者发送服务端后,如果在指定时间内服务端无法确认提交或者回滚状态,则消息默认会被回滚。事务超时时间,请参见参数限制。

4.RocketMQ如何保证消息的顺序性?

RocketMQ中提供了基于队列(分区)的顺序消费。即同一个队列内的消息可以做到有序,但是不同队列内的消息是无序的。

生产者按照顺序发送消息

当我们作为MQ的生产者需要发送顺序消息时,需要在send方法中,传入一个MessageQueueSelector。MessageQueueSelector中需要实现一个select方法,这个方法就是用来定义要把消息发送到哪个MessageQueue的,通常可以使用取模法进行路由,可以将需要有序的消息发送到同一个队列中。需要注意的时候,这里需要使用同步发送的方式!

消费者按照顺序拉取消息

RocketMQ的MessageListener回调函数提供了两种消费模式,有序消费模式和并发消费模式。所以,想要实现顺序消费,需要使用有序消费模式接收消息,当我们用以上方式注册一个消费之后,为了保证同一个队列中的有序消息可以被顺序消费,就要保证RocketMQ的Broker只会把消息发送到同一个消费者上,这时候就需要加锁了。在实现中,ConsumeMessageOrderlyService 初始化的时候,会启动一个定时任务,会尝试向 Broker 为当前消费者客户端申请分布式锁。如果获取成功,那么后续消息将会只发给这个Consumer。接下来在消息拉取的过程中,消费者会一次性拉取多条消息的,并且会将拉取到的消息放入 ProcessQueue,同时将消息提交到消费线程池进行执行。

消费者按照顺序消费消息

RocketMQ在消费的过程中,需要申请 MessageQueue 锁,确保在同一时间,一个队列中只有一个线程能处理队列中的消息。获取到 MessageQueue 的锁后,就可以从ProcessQueue中依次拉取一批消息处理了,但是这个过程中,为了保证消息不会出现重复消费,还需要对ProcessQueue进行加锁。然后就可以开始处理业务逻辑了。

总结

三次加锁,先锁定Broker上的MessageQueue,确保消息只会投递到唯一的消费者,对本地的MessageQueue加锁,确保只有一个线程能处理这个消息队列。对存储消息的ProcessQueue加锁,确保在重平衡的过程中不会出现消息的重复消费。

第三把锁有什么用?

这里其实主要考虑的是重平衡(rebalance)的问题。当我们的消费者集群,新增了一些消费者,发生重平衡的时候,某个队列可能会原来属于客户端A消费的,但是现在要重新分配给客户端B了。这时候客户端A就需要把自己加在Broker上的锁解掉,而在这个解锁的过程中,就需要确保消息不能在消费过程中就被移除了,因为如果客户端A可能正在处理一部分消息,但是位点信息还没有提交,如果客户端B立马去消费队列中的消息,那存在一部分数据会被重复消费。那么如何判断消息是否正在消费中呢,就需要通过这个ProcessQueue上面的锁来判断了,也就是说在解锁的线程也需要尝试对ProcessQueue进行加锁,加锁成功才能进行解锁操作。以避免过程中有消息消费。

顺序消费存在的问题

RocketMQ的顺序消费是通过在消费者上多次加锁实现的,这种方式带来的问题就是会降低吞吐量,并且如果前面的消息阻塞,会导致更多消息阻塞。所以,顺序消息需要慎用。

总结

欢迎大家讨论,如有错误还请指正!

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值