RokectMQ面试题

多个MQ如何选择

RabbitMQ
erlang开发,对消息堆积的支持并不友好,当大量消息积压的时候,会导致RabbitMQ的性能急剧下降。每秒钟可以处理几万到几十万消息。
RokectMQ
java开发,面向互联网集群,功能丰富,对在线业务的响应时延做了很多优化,大多数情况下可以做到毫秒级响应,每秒钟大概能处理几十万条消息。
Kafka
Scala开发,面向日志,功能丰富,性能最高。当你的业务场景中,每秒钟消息数量没有那么多的时候,kafka的时延反而会比较高,所以,kafka不太适合在线业务场景。
ActiveMQ
java开发,简单,稳定,性能不如前面三个。不推荐。

RocketMQ组成部分有哪些

Nameserver
无状态,动态列表;这也是和zk的重要区别之一。zk是有状态的。

Producer
消息生产者,负责发消息到Broker

Consumer
消息消费者,负责用Broker上拉取消息进行消费,消费完进行ack。

RokectMQ消费模式有几种

集群消费

  • 一条消息只会被同Group中的一个consumer消息
  • 多个Group同时消费一个Topic时,每个Group都会有一个consumer消费到数据(一条消息,消息-group.consumer:一对一)

广播消费

  • 消息将对一个Consumer Group下的各个consumer实例都消费一遍。即:即使这些consumer属于同一个Consumer Group,消息也会被Consumer Group中的每一个consumer都消费一次(一条消息,消息-group.consumer:一对多)

消息重复消费如何解决

出现原因
正常情况下,在consumer真正消费完消息后应该发送ack,通知broker该消息已正常消费,从队列queue中剔除。当ack因为网络原因无法发送到broker,broker会认为这条消息没有被消费,此后会开启消息重投机制把消息再次投递到consumer。

消费模式:在clustering(集群)模式下,消息在broker中会保证相关group的consumer消费一次,但是针对不同group的consumer会推送多次

解决方案

  • 数据库表:处理消息前,使用消息主键在表中带有约束的字段中insert
  • Map:单机时可以使用map做限制,消费时查询当前消息id是不是已经存在;
  • Redis:使用分布式锁

RocketMQ如何保证消息的顺序消费

首先多个queue只能保证单个queue里的顺序,queue是一个典型的FIFO,天然顺序,多个queue同时消费是无法绝对保证消息的有序性的。
可以使用同一topic,同一个queue,发消息的时候一个线程去发送消息,消费的时候,一个线程去消费queue里的消息;

消息的顺序消费对业务系统来说非常重要,一笔订单产生了三条消息,分别是创建订单、订单付款、订单完成。消费时,必须按照顺序消费才有意义,与此同时多比订单直接又是可以进行并行消费的。

接下来,通过订单例子来展示RocketMQ如何保证消息顺序消费的:
在这里插入图片描述
我们最容易想到的应该是如图这样的,必须M1先消费后通知S2,M2才能够被消费。问题是:M1、M2分别发送到S1/S2,这样无法保证M1先到到集群MQ,也不能保证M1先被消费。

如果把多个需要顺序消费的消息都发送到同一MQ Server呢?
在这里插入图片描述
这样看起来生产者到消费者的顺序绝对能保证,先发送M1后发送M2;根据先到先消费的原则,M1会先于M2被消费,这样就能保证M1、M2的消息顺序性。问题是:途中可以看到有多个消费者,M1虽先于M2被发送,但是如果S1到消费者1的网络慢于S2到消费者2,这个时候情况就是如图下所示:
在这里插入图片描述
要解决这样的问题,可以采用生产者到MQ Server中同样的思路,让S1的消息都发送到同一个消费者
在这里插入图片描述
MQ Server到消费者都是一比一,这样就能保证消息的顺序消费,但也会有问题:MQ Server没有消费者1响应时,有两种情况:

  1. M1确实没有到达消费者1(数据可能在网络传输中丢失)
  2. 消费者发回了响应消息,但MQ Server没有收到,如果是这种情况会导致M1被重复消费

源码解析

private SendResult send() {
	// 获取topic路由信息
	TopicPublishInfo topicPublishInfo =	this.tryToFindTopicPublishInfo(msg.getTopic());
	if (topicPublishInfo != null && topicPublishInfo.ok()) {
		MessageQueue mq = null;
		// 根据我们的算法,选择一个发送队列
		// 这里的arg = orderId
		mq = selector.select(topicPublishInfo.getMessageQueueList(), msg, arg);
		if (mq != null) {
			return this.sendKernelImpl(msg, mq, communicationMode, sendCallback, timeout);
		}
	}
}

获取到路由信息后,会根据MessageQueueSelector实现的算法来选择一个队列,同一个订单号获取到的肯定是同一个队列

// RocketMQ通过MessageQueueSelector中实现的算法来确定消息发送到哪一个队列上
// RocketMQ默认提供了两种MessageQueueSelector实现:随机/Hash
// 当然你可以根据业务实现自己的MessageQueueSelector来决定消息按照何种策略发送到消息队列中
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
	@Override
	public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg)
	{
		Integer id = (Integer) arg;
		int index = id % mqs.size();
		return mqs.get(index);
	}
}, orderId);

获取到路由信息后,会根据MessageQueueSelector实现的算法来选择一个队列,同一个订单号获取到的肯定是同一个队列

// RocketMQ通过MessageQueueSelector中实现的算法来确定消息发送到哪一个队列上
// RocketMQ默认提供了两种MessageQueueSelector实现:随机/Hash
// 当然你可以根据业务实现自己的MessageQueueSelector来决定消息按照何种策略发送到消息队列中
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
	@Override
	public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg)
	{
		Integer id = (Integer) arg;
		int index = id % mqs.size();
		return mqs.get(index);
	}
}, orderId);

这里就是我们实现的select算法,最后类似于这样
在这里插入图片描述

消费顺序消费总结

通过以上分析,RocketMQ实现严格的顺序消费采用的方法是:
生产者 -> MQ Server -> 消费者 是一对一的关系,保证同一个id的消息发送到同一个队列

优点:简单易行,容易理解
缺点:

  1. 并行度会成为消息系统的瓶颈(由于都是一比一导致吞吐量不足)
  2. 只要消费端出现问题,会导致整个系统流程阻塞(因为消息直接都相互依赖)

RocketMQ如何保证消息不丢失

Producer端
草去send()同步发消息,发送结果是同步感知的。发送失败后可重试,设置重试次数。默认三次

Broker端
修改刷盘策略为同步刷盘。默认情况下是异步刷盘的。集群部署

Consumer端
消费完全正常后,再进行手动ack确认

RokectMQ如何实现分布式事务

  1. 生产者向MQ服务器发送half消息
  2. half消息发送成功后,MQ服务器返回确认消息给到生产者。
  3. 生产者开始执行本地事务;
  4. 根据本地事务执行结果(UNKNOW,COMMIT,ROLLBACK)向MQ Server发送提交或回滚消息
  5. 如果错过了(可能因为网络异常、生产者突然宕机等导致异常的情况)提交/回滚消息,则MQ服务器将向同一组中的每个生产者发送回查消息以获取事务状态。
  6. 回查生产者本地事务状态
  7. 生产者根据本地事务状态发送提交/回滚消息。
  8. MQ服务器将丢弃恢复的消息,但已提交(进行过二次确认的half消息)的消息将投递给消费者进行消费。

Half Message:预处理消息,当broker收到此类消息后,会存储到RMQ_SYS_TRANS_HALF_TOPIC的消息消费队列中

检查事务状态:Broker会开启一个定时任务,消费RMQ_SYS_TRANS_HALF_TOPIC队列中的消息,每次执行任务会向消息发送者确认事务执行状态(提交、回滚、未知),如果是未知,Broker会定时去毁掉在重新检查。

超时:如果超过回查次数,默认消息回滚。也就是消息并未真正进入Topic的queue,而是用了临时queue来放所谓的half Message,等提交事务后,才会真正将half message转移到topic下的queue。

RokectMQ的消息堆积如何处理

  1. 如果可以添加消费者解决,就添加消费者的数据量
  2. 如果出现queue,但消费者多的情况。可以准备一个临时topic,同时创建一些queue,在临时创建一个消费者来把这些消息转移到topic中,让消费者消费。

RokectMQ消息持久化处理

RokectMQ是一款高性能、高可靠的分布式消息中间件,高性能和高可靠是很难兼得的。因为要保证高可靠,那么数据必须持久化到磁盘上,将数据持久化到磁盘,那么可能就不能保证高性能了。

RokectMQ在兼容这两方面做的不错,先从磁盘说起,现代的磁盘都是高性能的,些速度并不一定比网络的数据传输速度慢。

RokectMQ在持久化的设计上,采取的是消息顺序写、随机读的策略,利用次哦按顺序写的速度,让磁盘的些速度不会成为系统的瓶颈。并且采用的是MMPP这种“零拷贝”技术,提高消息存盘和网络发送的速度。极力满足RokectMQ的高性能、高可靠要求。

RocketMQ 持久化机制的架构图
在RokectMQ持久化机制中,涉及到了三个角色:

  • CommitLog:消息真正的存储文件,所有消息都存储再CommitLog文件中
  • CosumeQueue:消息消息逻辑队列,类似数据库的索引文件
  • IndexFile:消息索引文件,主要存储消息key与offset对应关系,提升消息检索速度

CmmitLog文件是存放消息数据的地方,所有消息都将存入到commitlog中。生产者将消息发送到Rocket的broker后,broker服务器会将消息顺序写入到Commitlog文件中,这也就是RocketMQ高性能的原因。

但是消费者消费消息的时候,可能就会遇到麻烦,每一个消费者只能订阅一个主题,消费者关心的是订阅主题下的所有消息,但是同一主题的消息再commitLog文件中可能是不连续的,那么消费者消费消息的时候,需要将CommitLog文件加载到内存中,遍历查找订阅主题下的消息,频繁的IO操作,性能就会急速下降

为了解决上述问题,RocketMQ引入了comsumequeue文件。Comsumequeue文件可以看作是索引文件,类似于MySQL中的二级索引。在存放了同一主题下的所有消息,消费者消费的时候只需要去对应的consumeQueue组中取消息即可,consumequeue文件不会存储消息的全量信息,具体存储的字段,已在上图中标注。这样做也可以带来以下两个好处:

  • 由于consumequeue文件内容较小,可以尽可能的保证consumequeue文件全部读入到内存,提高消费效率
  • consumequeue文件也是会持久化的,不存全量信息可以节约磁盘空间

IndexFile是RocketMQ为消息订阅构建的索引文件,用来提高根据主题与消息队列检索消息的速度

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值