RocketMQ是一个统一消息引擎、轻量级数据处理平台。RocketMQ是一款阿里巴巴开源的消息中间件。 2016 年 11 月 28 日,阿里巴巴向 Apache 软件基金会捐赠RocketMQ,成为 Apache 孵化项目。 2017 年 9 月 25 日,Apache 宣布 RocketMQ孵化成为 Apache 顶级项目(TLP ),成为国内首个互联网中间件在 Apache 上的顶级项目。
文章目录
目录
2、RocketMQ有几种消费类型?消费模式有几种?各自的优缺点,以及应用场景?
7、RocketMQ由哪些角色组成?每个角色的作用和特点是什么?
7.1NameServer节点中的数据是如何进行数据同步的呢?
7.2如果某Broker节点宕机,如何保证数据不丢失呢?(Broker集群)
7.3在Broker集群中,哪一个Slave会被切换成Master?
7.4 Producer或Consumer应该选择NameServer集群中的哪一个NameServer呢?
17、如果让你来动手实现一个分布式消息中间件,整体架构你会如何设计实现?
23、为什么RocketMQ不使用Zookeeper作为注册中心呢?
前言
RocketMQ的发展历程:
- 2007 年,阿里开始五彩石项目,Notify作为项目中交易核心消息流转系统,应运而生。Notify系统是RocketMQ的雏形。
- 2010 年,B2B大规模使用ActiveMQ作为阿里的消息内核。阿里急需一个具有海量堆积能力的消息系统。
- 2011 年初,Kafka开源。淘宝中间件团队在对Kafka进行了深入研究后,开发了一款新的MQ,MetaQ。
- 2012 年,MetaQ发展到了v3.0版本,在它基础上进行了进一步的抽象,形成了RocketMQ,然后就将其进行了开源。
- 2015 年,阿里在RocketMQ的基础上,又推出了一款专门针对阿里云上用户的消息系统Aliware MQ。
- 2016 年双十一,RocketMQ承载了万亿级消息的流转,跨越了一个新的里程碑。 11 月 28 日,阿里巴巴向 Apache 软件基金会捐赠 RocketMQ,成为 Apache 孵化项目。
- 2017 年 9 月 25 日,Apache 宣布 RocketMQ孵化成为 Apache 顶级项目(TLP ),成为国内首个互联网中间件在 Apache 上的顶级项目。
RocketMQ的知识点:
1、Broker中的消息被消费后会立即删除吗?
不会,因为消息是被顺序存放在CommitLog文件中的,且消息大小不定长,所以消息是以commitlog文件为单位被删除的,否则会影响清理的效率。在commitlog文件中有过期时间,默认为72小时,到时间后会自动进行清理。但是,以下几种情况下过期文件也会被清理:
- 指定时间点删除文件(默认每天凌晨4点,通过brker.conf配置文件参数:deleteWhen);
- 磁盘不足;
- 人工删除:调用excuteDeleteFilesManualy方法手工触发过期文件删除。
2、RocketMQ有几种消费类型?消费模式有几种?各自的优缺点,以及应用场景?
消费者从Broker中获取消息的类型主要有两种:1、pull拉取方式 。2、push推送方式。但这两种方式都有一定的缺陷,后来采用了一种折中的方法,采用”长轮询“的方式,它既可以拥有Pull的优点,又能达到保证实时性的目的。
消费者组的消费模式分为两种:1、集群消费。2、广播消费。
消费类型:
1、pull拉取式消费:
Consumer主动从Broker中拉取消息,一旦获取了消息,就启动消费过程。便于应用控制消息的获取,但拉取式消费方式的实时性较差,当Broker中有了新的消息时不能及时地发现并消费消息。在设置拉取时间间隔时应按照实际情况去设置。若间隔时间太短,则空请求比例较高;若间隔时间太长,则无法及时地获取消息。
2、push推送式消费:
在推送式消费的模式下,Broker会主动地向消费者发送最新的消息,实时性较高。push方式是典型的发布-订阅模式,即Consumer向其关联的queue注册了监听器,一旦发现有新的消息就会触发回调的执行,回调方法是Consumer去Queue中拉去消息。而这些都是基于Consumer与Broker的长连接,而长连接是需要消耗系统资源的。
push方式里,consumer把轮询过程封装了一层,并注册了MessageListener监听器。当轮询取到消息后,便唤醒MessageListener的consumeMessage()来消费,对用户而言,感觉好像消息是被推送过来的
3、长轮询
长轮询消费即消费者发送拉取消息的请求后,如果 Broker 上没有可用消息,Broker 将会持有拉取请求设定的一段时间(长轮询时间),直到有消息到达或者超时为止。可以有效减少无效的请求次数,降低网络开销,提高消息消费的效率。
消费模式:
1、广播消费:
相同Consumer Group中的每一个Consumer都会接收同一个Topic的全量消息。即每条消息都会被发送到Consumer Group中的每一个Consumer。在广播消费方式中,消费的进度保存在Consumer端。因为每个Consumer会消费Topic下的全部消息,每个Consumer的消费进度是不一样的,所以Consumer各自保存消费进度。
广播消费的优点:
- 能够实现高吞吐量:每个消费者都会独立地接收和处理消息,因此可以实现更高的消息处理并行度,提高系统的吞吐量。
- 无竞争消费:每个消费者都可以独立消费消息,不会发生消费进度的竞争和冲突。
- 灵活度高:消费者之间相互独立,一台机器上的消费者挂了不会影响其他机器上的消费者。
广播消费的缺点:
- 消息重复消费:由于每一个消费者都会收到完整的消息副本,所以会导致消息的重复消费。
- 不支持顺序消费:由于消息会被同时发送给多个消费者,所以不能保证消息的消费顺序。
广播消费的使用场景:
- 实时性强,对消息处理的并行度要求比较大的场景。
- 不在乎消费顺序,可以容忍一定程度上的消息重复消费的场景。
2、集群消费:
相同Consumer Group中的每一个Consumer会平均分摊同一个Topic的消息。即每条消息只被发送到Consumer Group中的某个Consumer。在集群消费方式中,消费进度保存在Broker中,因为消费进度会参与到消费的负载均衡中,所以消费进度是需要共享的。
集群消费的优点:
- 顺序消费:集群消费能够保证消息按照发送顺序进行消费,确保消息的有序性
- 高可靠性:由于多个消费者以集群形式消费消息,可以相互备份和容错,提高系统的可靠性和容灾能力。
集群消费的缺点:
- 可能会导致消费进度的竞争和冲突。
- 无法实现和广播消费一样高的并行度。
集群消费的使用场景:
- 对消息的顺序要求较高的业务场景,比如订单处理,流程审批
- 对高可靠性和容灾能力要求较高的场景。
2.1为什么要主动拉取消息而不使用事件监听方式?
因为如果使用事件监听方式(PUSH)方式作为消费方式,当消费速度比获取消息速度慢时,则可能会造成消息堆积的情况。
3、Broker如何处理拉取请求的?
- 消费者向Broker发送拉取请求。
- Broker会验证拉取请求的合法性,包括检查消费者的身份,订阅关系等,以确保该消费者具有访问该Topic或队列的权限。如果认证通过,Broker将根据偏移量offset确认消费者要拉取的位置。
- Broker根据位置获取对应的消息队列,从消息队列中获取待消费的消息。如果消息队列中有消息可供消费,则将这些消息返回给消费者。如果没有消息可以消费,则会进入到长轮询等待状态。
-
Broker将拉取的消息返回给消费者。同时Broker会记录消费者的拉取位置,以便下次拉取时,可以从该位置继续开始。
-
如果拉取请求过程中发生错误或者出现异常,Broker可能会进行重试操作,确保消息能够被正确地传输和消费。
4、拉取请求在长轮询等待后依旧无法获取消息怎么办?
-
重新检查拉取位置:当长轮询超时后,消费者可以重新检查拉取的位置信息,确保没有跳过任何消息。可能存在因网络或其他原因造成的消息漏传,重新检查拉取位置能够及时发现漏传的消息。
-
调整等待超时时间:增加等待超时时间,使得消费者能够更长时间地等待消息到达。通过适当延长等待时间,提高获取消息的概率,但需要注意避免过长等待时间导致消费的实时性下降。
-
检查消费者订阅关系和消费组状态:确保消费者的订阅关系正确且消费组状态正常。如果消费者的订阅关系配置出错或者消费组状态异常,可能会导致无法正确消费消息。
-
确认消息是否发送成功:检查消息的生产者端,确认消息是否成功发送到消息队列中。如果消息没有成功发送,消费者无法获取到消息进行消费。
5、MQ如何选型?
- RabbitMQ:基于erlang开发,每秒钟可以处理几万到十几万条消息。其吞吐量较Kafka与RocketMQ要低,且由于其不是Java语言开发,所以公司内部对其实现定制化开发难度较大。
- RocketMQ:基于JAVA开发,面向互联网集群化功能丰富,对在线业务的响应时延做了很多的优化,大多数情况下可以做到毫秒级的响应,每秒钟大概能处理几十万条消息。
- Kafka:基于Scala开发,面向日志功能丰富,性能很高。但是在低消息强度的时候,Kafka的时延反而会变高,不太适合在线业务场景。
- ActiveMQ:基于Java语言开发一款MQ产品。早期很多公司与项目中都在使用。但现在的社区活跃度已经很低。现在的项目中已经很少使用了。
6、为什么要使用MQ?
当项目比较大时,分布式系统的远程服务调用同步执行经常出问题,所以引入了MQ;
MQ的作用基本有三个:
- 限流削峰:MQ可以将系统中的超量请求暂存在其中,以便系统后期可以慢慢进行处理,从而避免请求的丢失或者压垮系统。
- 异步解耦:上游系统对下游系统的调用若为同步调用,则会大大降低系统的吞吐量与并发度,且系统耦合度太高。而异步调用则会解决这些问题。
- 数据收集:分布式系统会产生海量级别的数据,如:业务日志,监控数据,用户行为等。可以通过MQ将这些数据进行收集,方便进行大数据分析。
7、RocketMQ由哪些角色组成?每个角色的作用和特点是什么?
NameServer:
它是Broker和Topic的路由注册中心,支持Broker的动态注册与发现。
- Broker管理:接受Broker集群的注册信息,并且保存下来作为路由信息。还提供了心跳检测机制,检查Broker是否还存活。
- 路由信息管理:每个NameServer中都保存着Broker集群的整个路由信息和用于客户端查询的队列信息。生产者、消费者都可以通过NameServer获取整个Broker集群的路由信息,从而进行消息的投递和消费。
NameServer也可以建立集群,不过NameServer通常是无状态的,即NameServer的各个节点间是无差异的,互不进行通讯。
Producer:
消息生产者,负责发消息到Broker。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递,投递的过程支持快速失败并且低延迟。生产者组是同一类生产者的集合,这类Producer发送相同Topic类型的消息。一个生产者组可以同时发送多个同一主题下的消息。
Broker:
Broker充当着消息中转角色,负责存储消息、转发消息。Broker在RocketMQ系统中负责接收并存储从生产者发送来的消息,同时为消费者的拉取请求作准备。Broker同时也存储着消息相关的元数据,包括消费者组消费进度偏移offset、主题、队列等。
Consumer:
消息消费者,负责消费消息。一个消息消费者会从Broker服务器中获取到消息,并对消息进行相关业务处理。
RocketMQ中的消息消费者都是以消费者组(Consumer Group)的形式出现的。消费者组是同一类消费者的集合,这类Consumer消费的是同一个Topic类型的消息。消费者组使得在消息消费方面,实现负载均衡(将一个Topic中的不同的Queue平均分配给同一个Consumer Group的不同的Consumer,注意,并不是将消息负载均衡)和容错(一个Consmer挂了,该Consumer Group中的其它Consumer可以接着消费原Consumer消费的Queue)的目标变得非常容易
7.1NameServer节点中的数据是如何进行数据同步的呢?
路由注册:
在Broker启动的时候,会轮询NameServer列表,与每个NameServer建立长连接,发送注册请求。于是每一个NameServer中会维护一个Broker列表,用来动态存储Broker的信息。
Broker每 30 秒向NameServer发送一次心跳。心跳包中包含 BrokerId、Broker地址(IP+Port)、Broker名称、Broker所属集群名称等等。NameServer在接收到心跳包后,会更新心跳时间戳,记录这个Broker的最新存活时间。
路由剔除:
由于Broker关机、宕机或网络抖动等原因,NameServer没有收到Broker的心跳,NameServer可能会将其从Broker列表中剔除
NameServer中有一个定时任务,每隔 10 秒就会扫描一次Broker表,查看每一个Broker的最新心跳时间戳距离当前时间是否超过 120 秒,如果超过,则会判定Broker失效,然后将其从Broker列表中剔除。
路由发现:
RocketMQ的路由发现采用的是Pull模型。当Topic路由信息出现变化时,NameServer不会主动推送给客户端,而是客户端定时拉取主题最新的路由。默认客户端每 30 秒会拉取一次最新的路由。
7.2如果某Broker节点宕机,如何保证数据不丢失呢?(Broker集群)
扩展Broker集群。将Broker集群节点进行横向扩展,解决单点问题。还增加了Broker的性能和吞吐量。其次,Broker集群中,存在两种角色:Master和Slave。一个Master中可以有多个Slave两,者之间是主备关系。Master负责消息的读写操作,而Slave负责备份,当Master挂掉了,Slave则会自动切换为Master去工作。Master与Slave的对应关系是通过指定相同的BrokerName、不同的brokerId来确定的,BrokerId为0表示是Master节点,Broker非0表示是Slave节点。
7.3在Broker集群中,哪一个Slave会被切换成Master?
当 Master 节点宕机或不可用时,由 RocketMQ 自动选择一个 Slave 节点作为新的 Master 节点,以保证集群的高可用性。这个过程称为主节点切换(Master Failover)。
主节点切换的具体方式主要涉及以下两个因素:
-
心跳机制(Heartbeat Mechanism):RocketMQ 中的主从节点通过心跳机制进行通信和检测节点的可用性。每个 Slave 节点会向对应的 Master 节点定期发送心跳请求,如果 Master 节点在一定时间内没有响应,Slave 节点会尝试成为新的 Master 节点。
-
节点优先级(Broker Priority):当一个 Master 节点宕机时,对应的 Slave 节点会根据节点的优先级来决定是否参与竞选新的 Master 节点。RocketMQ 允许为每个 Broker 节点设置不同的优先级,优先级高的 Slave 节点在竞选新的 Master 节点时具有更高的优先权。
在主节点切换过程中,其他与宕机的 Master 节点保持一致的 Slave 节点会从节点状态切换为主节点状态,并负责处理之前 Master 节点所负责的消息存储和传递等任务。需要注意的是,主节点切换过程中可能会导致少量的消息重复消费或者消息延迟,因此在设计应用程序时应充分考虑这些情况,并采取相应的处理措施。
7.4 Producer或Consumer应该选择NameServer集群中的哪一个NameServer呢?
首先,无论是生产者还是消费者,在配置时一定要写上NameServer集群的地址。生产者或消费者首先会产生一个随机数,然后与NameServer的节点数量进行取模,得到的数就是要选择的NameServer索引。然后会进行连接,如果连接失败,则会采用轮询策略,尝试连接其他节点。
8、为什么要设置读/写队列?
设置读队列和写队列的数量主要是为了,Topic中queue的扩容。
例如,原来创建的Topic中包含 16 个Queue,如何能够使其Queue缩容为 8 个,还不会丢失消息?可以动态修改写队列数量为 8 ,读队列数量不变。此时新的消息只能写入到前 8 个队列,而消费都消费的却是16 个队列中的数据。当发现后 8 个Queue中的消息消费完毕后,就可以再将读队列数量动态设置为 8 。整个缩容过程,没有丢失任何消息。
9、RocketMQ如何做负载均衡?
对于Producer,Queue选择算法也叫做消息投递,主要有两种:
轮询算法:默认选择的算法,该算法能够保证每个Queue可以均匀的获取到消息。
缺点是,由于某些原因,一些Broker中的Queue可能投递延迟较为严重,从而导致produer缓存队列出现较大的消息积压,影响投递性能。
最小投递延迟算法:
该算法会统计每次消息投递的时间延迟,然后根据统计出来的结果将消息投递到时间延迟最小的Queue。如果延迟相同,则采用轮询算法。
这个算法也会有问题,比如消息在Queue上的分配不均匀。投递延迟小的Queue可能会存在大量的消息。queue的消费者消费压力增大,降低消费的能力,造成消息积压。
对于Consumer
存在一个Reblance机制,但前提是集群消费。
Rebalance即再均衡,将同一个Topic下的Queue分配给同一个Consumer Group中的消费者的过程。它的本意是为了提高消息的并行消费能力。但同时存在着以下问题:
- 消费暂停:一开始只有一个Consumer,后来又增加了一个Consumer,就会出发Rebalance机制。此时原Consumer就要暂停部分队列的消费,等这些队列分配给新的Consumer之后,才可继续被消费。
- 重复消费问题:Consumer 在消费新分配给自己的队列时,必须接着之前Consumer 提交的消费进度的offset继续消费。然而默认情况下,offset是异步提交的,这个异步性导致提交到Broker的offset与Consumer实际消费的消息并不一致。这个不一致的差值就是可能会重复消费的消息。
- 消费突刺:由于Rebalance可能导致重复消费,如果需要重复消费的消息过多,或者因为Rebalance暂停时间过长从而导致积压了部分消息。那么有可能会导致在Rebalance结束之后瞬间需要消费很多消息。
Reblance出现的场景:
- Queue数量发生变化
- 消费者数量发生变变化
Reblance的过程:
在Broker中维护着多个Map集合,这些集合中动态存放着当前Topic中Queue的信息、Consumer Group中Consumer实例的信息。一旦发现消费者所订阅的Queue数量发生变化,或消费者组中消费者的数量发生变化,立即向Consumer Group中的每个实例发出Rebalance通知。Consumer实例在接收到通知后会采用Queue分配算法自己获取到相应的Queue,即由Consumer实例自主进行Rebalance。
Queue分配算法:
一个Topic中的Queue只能由Consumer Group中的一个Consumer进行消费,而一个Consumer可以同时消费多个Queue中的消息。那么Queue与Consumer间的配对关系是如何确定的,即Queue要分配给哪个Consumer进行消费,也是有算法策略的。常见的有四种策略。这些策略是通过在创建Consumer时的构造器传进去的。
- 平均分配策略
- 环形平均策略
- 一致性hash策略
- 同机房策略
- 平均分配策略:
该算法是要根据avg = QueueCount / ConsumerCount
的计算结果进行分配的。如果能够整除,则按顺序将avg个Queue逐个分配Consumer;如果不能整除,则将多余出的Queue按照Consumer顺序逐个分配。
2.环形平均策略
环形平均算法是指,根据消费者的顺序,依次在由queue队列组成的环形图中逐个分配。
3.一致性hash策略
该算法会将consumer的hash值作为Node节点存放到hash环上,然后将queue的hash值也放到hash环上,通过顺时针方向,距离queue最近的那个consumer就是该queue要分配的consume 。有效减少由于消费者组扩容或缩容所带来的大量的Rebalance问题,但该算法存在分配不均的问题。适用于Consumer数量变化较频繁的场景。
4、同机房策略
该算法会根据queue的部署机房位置和consumer的位置,过滤出当前consumer相同机房的queue。然后按照平均分配策略或环形平均策略对同机房queue进行分配。如果没有同机房queue,则按照平均分配策略或环形平均策略对所有queue进行分配。
10、消息重复消费,重复投递等问题
生产者发送消息时重复投递:
当一条消息已被成功发送到Broker并完成持久化,此时出现了网络闪断,从而导致Broker对Producer应答失败。 如果此时Producer意识到消息发送失败并尝试再次发送消息,此时Broker中就可能会出现两条内容相同并且Message ID也相同的消息,那么后续Consumer就一定会消费两次该消息。
消费者重复消费:
消息已投递到Consumer并完成业务处理,当Consumer给Broker反馈应答时网络闪断,Broker没有接收到消费成功响应。为了保证消息至少被消费一次
的原则,Broker将在网络恢复后再次尝试投递之前已被处理过的消息。此时消费者就会收到与之前处理过的内容相同、Message ID也相同的消息。
Rebalance时消息重复:
当Consumer Group中的Consumer数量发生变化时,或其订阅的Topic的Queue数量发生变化时,会触发Rebalance,此时Consumer可能会收到曾经被消费过的消息。
解决方式:
-
幂等性处理:在消费者端,确保消费逻辑具有幂等性。即消费者对于同一条消息的重复处理,不会产生不一致的结果。例如,对于数据库操作,可以使用主键或唯一索引来保证数据的幂等性。
-
使用分布式事务:如果应用场景需要保证消息处理的原子性,可以使用分布式事务机制,确保消息的处理具有原子性和幂等性。
方案:
对于常见的系统,幂等性操作的通用性解决方案是:
- 首先通过缓存去重。在缓存中如果已经存在了某幂等令牌,则说明本次操作是重复性操作;若缓存没有命中,则进入下一步。
- 在唯一性处理之前,先在数据库中查询幂等令牌作为索引的数据是否存在。若存在,则说明本次操作为重复性操作;若不存在,则进入下一步。
- 在同一事务中完成三项操作:唯一性处理后,将幂等令牌写入到缓存,并将幂等令牌作为唯一索引的数据写入到DB中。
11、如何让RocketMQ保证消息的顺序消费
首先,创建顺序消息:
- 对于同一个生产者实例:在发送消息时,确保相关的有序消息发送到相同的queue。这样可以保证消息按照发送顺序进行消费。
- 对于多个生产者实例:使用消息键(Message Key)来保证消息的有序性。消息键可以是某个业务上下文相关的唯一标识。通过消息键,消息将路由到固定的消费队列。消费者在接收到消息时,根据消息键进行相应处理。
其次,在消费时:
- 单线程消费:在消费者端,为了保证消息的顺序消费,建议使用单线程消费器。这可以保证消息队列中的消息按照顺序进行处理,避免并发处理导致的顺序错乱。
- 使用顺序消息消费模式(orderly Message Consumption):是RocketMQ提供的特殊消费模式,用于确保消息的顺序消费。通过实现MessageListenerOrderly接口,并实现其onMessage方法,每个消费者实例将分配一个特定的消费队列进行消费,消费者在处理完一个消息后,再去处理下一个消息,避免并行处理导致的顺序混乱。
为了更好地保证消息的顺序消费,需要遵循以下几个建议:
- 合理设计 Topic、消息队列和消费者的数量,确保每个队列只由一个消费者实例消费。
- 设置消费者的并发度(ConsumeThreadMin 和 ConsumeThreadMax)为1,以保证单线程消费。
- 设置负载均衡策略为均匀分布,避免消费者之间的负载不均。
11.1如何保证消息发送到同一个Queue
Rocket MQ给我们提供了MessageQueueSelector接口,可以自己重写里面的接口,实现自己的算法,举个最简单的例子:判断 i % 2 == 0
,那就都放到queue1里,否则放到queue2里。
12、RocketMQ如何保证消息不丢失
首先在如下三个部分都可能会出现丢失消息的情况:
- Producer端
- Broker端
- Consumer端
在Producer端中:可采用两种消息发送方式
-
同步发送模式:生产者可以使用同步发送模式,即发送消息后等待 Broker 的响应。只有当 Broker 成功接收并持久化消息后,生产者才会收到成功响应,从而确保消息的可靠性。
-
异步发送模式:生产者可以使用异步发送模式,在发送消息后无需等待 Broker 的响应,而是通过回调函数来处理发送结果。在回调函数中,可以根据返回的发送结果判断消息是否成功发送。
在Broker端中:
- 将刷盘策略由异步刷盘改成同步刷盘。flushDiskType=SYNC_FLUSH
- Broker集群部署,主备模式,实现高可用
在Consumer端中:
-
在消息成功消费之后会返回ACK应答。
-
构建消费者组:RocketMQ 具有消费者组的概念,当某个消费者宕机时,其他消费者可以接管未完成的消息消费过程,确保消息不会丢失。
-
当处理消息时,消费者应该设计成具备幂等性。即使消费者重复消费相同的消息,也不会导致数据错误或不一致。
13、消费的堆积问题
消息处理流程中,如果Consumer的消费速度跟不上Producer的发送速度,MQ中未处理的消息会越来越多(进的多出的少),这部分消息就被称为堆积消息
。消息出现堆积进而会造成消息的消费延迟
。
以下场景需要重点关注消息堆积和消费延迟问题:
- 业务系统上下游能力不匹配造成的持续堆积,且无法自行恢复。
- 业务系统对消息的消费实时性要求较高,即使是短暂的堆积造成的消费延迟也无法接受。
解决方法:
1.关注消费耗时和消费并发度。
通过压测获取消息的消费耗时,并对耗时较高的操作的代码逻辑进行分析。通过增加线程数或调整线程池配置可以增加消息的并行处理能力。
2.增加消费者数量:
增加更多的消费者实例来并行处理消息。RocketMQ 会自动将未消费的消息均匀地分配给新增的消费者。
3.调整消息的存储策略:
如优化磁盘性能、增加存储容量等,以提高消息的读取速度和传输效率。
14.消息消费重试(可靠性)
对于需要重试消费的消息,并不是Consumer在等待了指定时长后再次去拉取原来的消息进行消费,而是将这些需要重试消费的消息放入到了一个特殊Topic的队列中,而后进行再次消费的。这个特殊的队列就是重试队列。每条消息默认最多重试 16 次,但每次重试的间隔时间是不同的,会逐渐变长。每次重试的间隔时间如下表。
Broker对于重试消息的处理是通过延时消息
实现的。先将消息保存到SCHEDULE_TOPIC_XXXX延迟队列中,延迟时间到后,会将消息投递到%RETRY%consumerGroup@consumerGroup重试队列中。
15、消息投递重试(可靠性)
Producer对发送失败的消息进行重新发送的机制,称为消息发送重试机制,也称为消息重投机制。
-
同步发送失败策略
对于普通消息,消息发送默认采用round-robin策略来选择所发送到的队列。如果发送失败,默认重试 2次。但在重试时是不会选择上次发送失败的Broker,而是选择其它Broker。当然,若只有一个Broker其也只能发送到该Broker,但其会尽量发送到该Broker上的其它Queue。
2.异步发送失败策略
异步发送失败重试时,异步重试不会选择其他broker,仅在同一个broker上做重试,所以该策略无法保证消息不丢
3.消息刷盘失败策略
消息刷盘超时(Master或Slave)或slave不可用(slave在做数据同步时向master返回状态不是SEND_OK)时,默认是不会将消息尝试发送到其他Broker的。不过,对于重要消息可以通过在Broker的配置文件设置retryAnotherBrokerWhenNotStoreOK属性为true来开启。
16、RocketMQ在分布式事务支持这块机制的底层原理?
对于分布式事务,通俗地说就是,一次操作由若干分支操作组成,这些分支操作分属不同应用,分布在不同服务器上。分布式事务需要保证这些分支操作要么全部成功,要么全部失败。分布式事务与普通事务一样,就是为了保证操作结果的一致性。
基础知识:
- RocketMQ是一种最终一致性的分布式事务,
-
半消息:是指暂不能被Consumer消费的消息。Producer 已经把消息成功发送到了 Broker 端,但此消息被标记为暂不能投递状态,处于该种状态下的消息称为半消息。需要 Producer对消息的二次确认后,Consumer才能去消费它。
-
消息回查:
由于网络闪段,生产者应用重启等原因。导致 Producer 端一直没有对 Half Message(半消息) 进行 二次确认。这是Brock服务器会定时扫描长期处于半消息的消息,会主动询问Producer端 该消息的最终状态(Commit或者Rollback),该消息即为 消息回查。
流程:
- A服务先向Brocker发送半消息,消息中携带者B服务的内容
- 当A服务知道半消息发送成功之后开始执行本地事务
- 执行本地事务有三种情况,成功失败还有Unkown,如果成功那么product向brocker发送Commit,这样B服务就可以消费这个消息。如果本地事务失败,那么producer向brocker发送rollback,删除掉这条半消息。如果因为未知原因返回unkown,则会进行rocketMQ的回调接口来进行事务的回查。之后再次进行判断。
发送半消息可以确认Brocker是否正常,还可以通过半消息来回查事务。
如果B服务执行失败,几乎就是代码有问题,因为消费可以进行多次重试。将异常记录下来,由人工进行处理,最终实现一致性。
17、如果让你来动手实现一个分布式消息中间件,整体架构你会如何设计实现?
设计和实现一个分布式消息中间件需要考虑许多方面,如消息的生产和消费、消息的可靠性、高可用性、扩展性等。中间件需要提供监控和管理功能,用于追踪和监视消息的生产和消费情况。实际的分布式消息中间件还需要根据具体需求和场景进行更详细的设计和实现。此外,还需要考虑安全性、性能优化、容量规划等方面的问题。
18.对RocketMQ源码的理解
里面比较典型的设计模式有单例、工厂、策略、门面模式。单例工厂无处不在,策略印象深刻比如发消息和消费消息的时候queue的负载均衡就是N个策略算法类,有随机、hash等,这也是能够快速扩容天然支持集群的必要原因之一。持久化做的也比较完善,采取的CommitLog来落盘,同步异步两种方式。
19、高吞吐量下如何优化生产者和消费者的性能?
开发:
-
同一group下,多机部署,并行消费
-
单个Consumer提高消费线程个数
-
批量消费
-
消息批量拉取
-
业务逻辑批量处理
运维:
- 网卡调优
- jvm调优
- 多线程与cpu调优
- Page Cache
20、RocketMQ 是如何保证数据的高容错性的?
-
主从架构:RocketMQ采用主从模式,主节点将消息异步地复制到从节点。其中主节点负责写入操作,而从节点用于备份和复制主节点的消息。当主节点发生故障时,系统会自动切换到可用的从节点,保证消息的持久性和可用性。
-
选举机制:RocketMQ采用基于选举的集群管理机制,用于选举主节点和从节点。选举过程中会考虑节点的可用性、状态和优先级等因素,以保证系统的稳定性和高可用性。
21、消息队列积压了大量数据怎么办?
因为消息发送之后才会出现积压的问题,所以和消息生产端没有关系,又因为绝大部分的消息队列单节点都能达到每秒钟几万的处理能力,相对于业务逻辑来说,性能不会出现在中间件的消息存储上面。毫无疑问,出问题的肯定是消息消费阶段,那么从消费端入手.
当RocketMQ中消息堆积时,可以采取以下几种方式来处理:
1. 扩容消费者:
增加消费者数量可以提高消息的消费速度,从而减少消息堆积。您可以根据实际情况增加消费者的数量,确保消费者能够及时处理消息。
2. 调整消费者配置:
检查消费者的配置参数,如消费线程数、消费批量大小等,确保其能够充分利用系统资源,提高消息的消费效率。
3. 优化消息生产者:
检查消息生产者的配置参数,如发送超时时间、发送重试次数等,确保消息能够及时发送到RocketMQ服务器。
4. 调整消息队列数量:
根据消息堆积的情况,可以考虑增加消息队列的数量。通过增加消息队列,可以提高消息的并发处理能力,加快消息的消费速度。
5. 增加消息消费的并发度:
如果消费者的处理逻辑允许并行处理消息,可以增加消息消费的并发度。通过增加并发度,可以提高消息的处理速度,减少消息堆积。
6. 监控和报警:
设置监控系统,实时监控RocketMQ的消息堆积情况。当消息堆积超过一定阈值时,触发报警机制,及时采取相应的处理措施。
7. 扩容RocketMQ集群:
如果以上方法无法解决消息堆积问题,可以考虑扩容RocketMQ集群,增加消息的处理能力和存储容量。
总之,处理RocketMQ消息堆积需要综合考虑消费者、生产者、队列、并发度等多个因素,并根据实际情况采取相应的优化和调整措施。
22.说说RocketMQ实现原理吧?
RocketMQ由NameServer注册中心集群、Producer生产者集群、Consumer消费者集群和若干Broker(RocketMQ进程)组成,它的架构原理是这样的:
- Broker在启动的时候去向所有的NameServer注册,并保持长连接,每30s发送一次心跳
- Producer在发送消息的时候从NameServer获取Broker服务器地址,根据负载均衡算法选择一台服务器来发送消息
- Conusmer消费消息的时候同样从NameServer获取Broker地址,然后主动拉取消息来消费.
23、为什么RocketMQ不使用Zookeeper作为注册中心呢?
- 基于性能的考虑,NameServer本身的实现非常轻量,而且可以通过增加机器的方式水平扩展,增加集群的抗压能力,而zookeeper的写是不可扩展的,而zookeeper要解决这个问题只能通过划分领域,划分多个zookeeper集群来解决,首先操作起来太复杂,其次这样还是又违反了CAP中的A的设计,导致服务之间是不连通的。
- zookeeper并不能保证服务的可用性,zookeeper在进行选举的时候,整个选举的时间太长,期间整个集群都处于不可用的状态,而这对于一个注册中心来说肯定是不能接受的,作为服务发现来说就应该是为可用性而设计。
- 消息发送应该弱依赖注册中心,而RocketMQ的设计理念也正是基于此,生产者在第一次发送消息的时候从NameServer获取到Broker地址后缓存到本地,如果NameServer整个集群不可用,短时间内对于生产者和消费者并不会产生太大影响。
24、那Broker是怎么保存数据的呢?
RocketMQ主要的存储文件包括commitlog文件、consumequeue文件、indexfile文件。
Broker在收到消息之后,会把消息保存到commitlog的文件当中,而同时在分布式的存储当中,每个broker都会保存一部分topic的数据,同时,每个topic对应的messagequeue下都会生成consumequeue文件用于保存commitlog的物理位置偏移量offset,indexfile中会保存key和offset的对应关系。
由于同一个topic的消息并不是连续的存储在commitlog中,消费者如果直接从commitlog获取消息效率非常低,所以通过consumequeue保存commitlog中消息的偏移量的物理地址,这样消费者在消费的时候先从consumequeue中根据偏移量定位到具体的commitlog物理文件,然后根据一定的规则(offset和文件大小取模)在commitlog中快速定位。
25、Master和Slave之间是怎么同步数据的呢?
消息在master和slave之间的同步是根据raft协议来进行的:
- 在broker收到消息后,会被标记为uncommitted状态
- 然后会把消息发送给所有的slave
- slave在收到消息之后返回ack响应给master
- master在收到超过半数的ack之后,把消息标记为committed
- 发送committed消息给所有slave,slave也修改状态为committed
26、 你知道RocketMQ为什么速度快吗?
是因为使用了顺序存储、Page Cache和异步刷盘。
- 我们在写入commitlog的时候是顺序写入的,这样比随机写入的性能就会提高很多
- 写入commitlog的时候并不是直接写入磁盘,而是先写入操作系统的PageCache
- 最后由操作系统异步将缓存中的数据刷到磁盘
27、它有哪几种部署类型?分别有什么特点?
RocketMQ有4种部署类型
1)单Master
单机模式, 即只有一个Broker, 如果Broker宕机了, 会导致RocketMQ服务不可用, 不推荐使用
2)多Master模式
组成一个集群, 集群每个节点都是Master节点, 配置简单, 性能也是最高, 某节点宕机重启不会影响RocketMQ服务
缺点:如果某个节点宕机了, 会导致该节点存在未被消费的消息在节点恢复之前不能被消费
3)多Master多Slave模式,异步复制
每个Master配置一个Slave, 多对Master-Slave, Master与Slave消息采用异步复制方式, 主从消息一致只会有毫秒级的延迟
优点是弥补了多Master模式(无slave)下节点宕机后在恢复前不可订阅的问题。在Master宕机后, 消费者还可以从Slave节点进行消费。采用异步模式复制,提升了一定的吞吐量。总结一句就是,采用多Master多Slave模式,异步复制模式进行部署,系统将会有较低的延迟和较高的吞吐量
缺点就是如果Master宕机, 磁盘损坏的情况下, 如果没有及时将消息复制到Slave, 会导致有少量消息丢失
4)多Master多Slave模式,同步双写
与多Master多Slave模式,异步复制方式基本一致,唯一不同的是消息复制采用同步方式,只有master和slave都写成功以后,才会向客户端返回成功
优点:数据与服务都无单点,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高
缺点就是会降低消息写入的效率,并影响系统的吞吐量
实际部署中,一般会根据业务场景的所需要的性能和消息可靠性等方面来选择后两种。
28、自己部署RocketMq的过程
我部署的是双master和双slave模式集群,并部署了两个nameserver节点
1)服务器分配
分配是两台服务器,A和B,其中A服务器部署nameserv1,master1,slave2;B服务器部署nameserv2,master2和slave1节点
2)broker的配置
分别配置rocketmq安装目录下四个配置文件:
master1:/conf/2m-2s-async/broker-a.properties
slave2:/conf/2m-2s-async/broker-b-s.properties
master2:/conf/2m-2s-async/broker-b.properties
slave1:/conf/2m-2s-async/broker-a-s.properties
a.master节点的brokerId为0,slave节点的brokerId为1(大于0即可);
b.同一组broker的broker-Name相同,如master1和slave1都为broker-a;
c.每个broker节点配置相同的NameServer;
d.复制方式配置:master节点配置为ASYNC-MASTER,slave节点配置为SLAVE即可;
e.刷盘方式分为同步刷盘和异步刷盘,为了保证性能而不去考虑少量消息的丢失,因此同意配置为异步刷盘
3)启动集群
a 检查修改参数
启动前分别检查修改runbroker.sh和runserver.sh两个文件中的JVM参数,默认的JAVA_OPT参数的值比较大,若直接启动可能会失败,需要根据实际情况重新配置
b 分别启动两个namerser节点
nohup sh bin/mqnamesrv > /dev/null 2>&1 &
查看日志
tail -f ~/logs/rocketmqlogs/namesrv.log
c 分别启动4个broker节点
maste1
nohup sh bin/mqbroker -c
/usr/local/rocketmq/conf/2m-2s-async/broker-a.properties &
slave1
nohup sh bin/mqbroker -c
/usr/local/rocketmq/conf/2m-2s-async/broker-a-s.properties &
maste2
nohup sh bin/mqbroker -c
/usr/local/rocketmq/conf/2m-2s-async/broker-b.properties &
slave2
nohup sh bin/mqbroker -c
/usr/local/rocketmq/conf/2m-2s-async/broker-b-s.properties &
查看日志:
tail -f ~/logs/rocketmqlogs/broker.log
29、rocketmq如何保证高可用性?
-
主从复制:RocketMQ采用主从模式,将消息分布在多个Broker节点上。主节点负责写入消息,从节点负责备份和复制主节点的消息。这种主从复制机制确保了数据的冗余和可用性。如果主节点发生故障,系统会自动选择一个可用的从节点接管服务。
-
高可用集群:通过在多个Broker之间构建集群,RocketMQ实现了高可用性。集群中的每个节点都是自洽的,即可以相互路由和接收消息。如果一个节点出现故障,消息会被自动路由到其他可用节点,确保消息的连续传递和消费。
-
健康检查与自动恢复:RocketMQ具备健康检查和自动恢复的能力。它会定期检查节点、网络和存储的状态,并监控消息的传递情况。如果检测到异常,系统会自动触发恢复机制来修复问题。
-
监控和管理工具:RocketMQ提供了丰富的监控指标和管理工具,可以监视和分析Broker的性能和状态。这些工具可以帮助识别潜在的故障点,采取相应的措施,以确保系统的高可用性。
-
数据持久化存储:RocketMQ使用消息日志的方式对消息进行持久化存储,以避免数据丢失。这种持久化存储机制在节点故障或系统重启后,可以恢复消息数据,保障数据的可靠性。
通过上述机制和措施,RocketMQ能够实现高可用性的消息传递服务,确保消息的可靠性和系统的连续性。这样,即使在面对节点故障、网络中断或其他异常情况下,RocketMQ仍然能够继续提供稳定和可靠的消息传递。
30、rocketmq的工作流程是怎样的?
RocketMq的工作流程如下:
1)首先启动NameServer。NameServer启动后监听端口,等待Broker、Producer以及Consumer连上来
2)启动Broker。启动之后,会跟所有的NameServer建立并保持一个长连接,定时发送心跳包。心跳包中包含当前Broker信息(ip、port等)、Topic信息以及Borker与Topic的映射关系
3)创建Topic。创建时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic
4)Producer发送消息。启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic所在的Broker;然后从队列列表中轮询选择一个队列,与队列所在的Broker建立长连接,进行消息的发送
5)Consumer消费消息。跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,进行消息的消费
31、RocketMq的存储机制了解吗?
RocketMq采用文件系统存储消息,并采用顺序写写入消息,使用零拷贝发送消息,极大得保证了RocketMq的性能
1)顺序写
我们知道,操作系统每次从磁盘读写数据的时候,都需要找到数据在磁盘上的地址,再进行读写。而如果是机械硬盘,寻址需要的时间往往会比较长。ROcketmq采用的是顺序写,直接追加数据到末尾。实际上,磁盘顺序写的性能极高,在磁盘个数一定,转数一定的情况下,基本和内存速度一致
2)零拷贝
那么采用零拷贝的方式发送消息,必定会大大减少读取的开销,使得RocketMq读取消息的性能有一个质的提升。
零拷贝技术采用了MappedByteBuffer内存映射技术,采用这种技术有一些限制,其中有一条就是传输的文件不能超过2G,这也就是为什么RocketMq的存储消息的文件CommitLog的大小规定为1G的原因
32、RocketMq如何进行消息的去重?
RocketMq本身并不保证消息不重复,这样肯定会因为每次的判断,导致性能打折扣,所以它将去重操作直接放在了消费端:
1)消费端处理消息的业务逻辑保持幂等性。那么不管来多少条重复消息,可以实现处理的结果都一样
2)还可以建立一张日志表,使用消息主键作为表的主键,在处理消息前,先insert表,再做消息处理。这样可以避免消息重复消费
33、MQ的缺点
- 复杂性增加,包括配置、部署、维护和监控MQ等方面的工作。
- 异步通信可能导致编程模型复杂化,需要考虑消息顺序、重复消息和消息丢失等问题。
- 消息传递可靠性和一致性难以保证,可能出现消息丢失或重复传递的风险。
- 系统复杂性增加,引入额外的组件和服务,增加维护成本。
- 网络传输开销增加,通过网络发送和接收消息可能带来性能影响。
- 部署和维护成本增加,需要配置和管理消息队列服务器以及应用程序。