中间件知识点-消息中间件(RocketMq)二

RocketMq

RocketMQ介绍
RocketMq是阿里开源的项目。分开源和商用版。

RocketMQ的发展历程
早期阿里使用ActiveMQ,但是,当消息开始逐渐增多后,ActiveMQ的IO性能很快达到了瓶颈。于是,阿里开始关注Kafka。但是Kafka是针对日志收集场景设计的,当他的Topic过多时,由于Partition文件也会过多,会严重影响IO性能。于是阿里才决定自研中间件,最早叫做MetaQ,后来改名成为RocketMQ。最早他所希望解决的最大问题就是多Topic下的IO性能压力。但是产品在阿里内部的不断改进,RocketMQ开始体现出一些不一样的优势。
总结:
Kafka适合日志收集场景,吞吐量高,但是多topic下它的并发性能没有rocketmq好。
RocketMQ适合多Topic多业务场景,因为多Topic下性能好过kafka。
为什么?
1.文件存储的差异
Kafka采用了分区和分布式日志的设计,每个主题(Topic)一般分成多个分区存储不同的Broker上。所以topic多的话,分区会很多。kafka和rocketMq都使用文件存储,kafka是一个分区一个文件,当topic过多,分区的总量也会增加,kafka中存在过多的文件,当对消息刷盘时,就会出现文件竞争磁盘,出现性能的下降。
rocketMq所有的队列(对应Kafka Topic的分区) 不分主题一律顺序写入一个commitlog 文件中,即所有主题的消息都写到一个文件。这样的好处是可以减少查找目标文件的时间,让消息以最快的速度落盘。对比Kafka存文件时,需要寻找消息所属的Partition文件,再完成写入,当Topic比较多时,这样的Partition寻址就会浪费比较多的时间。所以rocketMq适合存在topic比较多的场景。
磁盘IO竞争:大量的日志文件需要频繁地进行写入操作,这会导致磁盘IO的竞争,可能限制系统的吞吐能力。
2.并发消费能力
Kafka的消费者默认是单线程的,Kafka一个分区只能被一个消费组中的一个消费者进行消费,因此可以同时消费的消费并行度比较少,比如有10个消费者进行消费那么它的并行度就是10。而rocketmq消费者会分配一个线程池,也就是所一个消费者可以开多个线程进行消费,比如开了10个线程,那么它的并行度就是10*10=100。所以rocketmq的并发度要比kafka高。

RocketMQ产品特点比较
RocketMQ的消息吞吐量虽然依然不如Kafka,但是却比RabbitMQ高很多。在阿里内部,RocketMQ集群每天处理的请求数超过5千亿次,支持的核心应用超过3000个。
RocketMQ天生就为金融互联网而生,因此他的消息可靠性相比Kafka也有了很大的提升,而消息吞吐量相比RabbitMQ也有很大的提升。
RocketMQ的高级功能也越来越全面,广播消费、延迟队列、死信队列等等高级功能一应俱全,甚至某些业务功能比如事务消息,已经呈现出领先潮流的趋势。
RocketMQ的源码是用Java开发的,这也使得很多互联网公司可以根据自己的业务需求做深度定制。而RocketMQ经过阿里双十一多次考验,源码的稳定性是值得信赖的,这使得功能定制有一个非常高的起点。

RocketMQ的组件结构:
NameServer : 提供轻量级的Broker路由服务。
Broker:实际处理消息存储、转发等服务的核心组件。
Producer:消息生产者集群。通常是业务系统中的一个功能模块。
Consumer:消息消费者集群。通常也是业务系统中的一个功能模块。
Producer发送消息时需要先从NameServer找一个Broker,然后再向该Broker发送消息。
我们要启动RocketMQ服务,需要先启动NameServer。

RocketMQ集群架构(一般生产环境上用的是集群)

在这里插入图片描述RocketMQ集群架构解析
一个完整的RocketMQ集群中,有如下几个角色
a.Producer:消息的发送者;举例:发信者
b.Consumer:消息接收者;举例:收信者
c.Broker:暂存和传输消息;举例:邮局
d.NameServer:管理Broker;举例:各个邮局的管理机构。
e.Topic:区分消息的种类;一个发送者可以发送消息给一个或者多个Topic;一个消息的接收者可以订阅一个或者多个Topic消息。
f.Message Queue:相当于是Topic的分区(Partition);用于并行发送和接收消息

NameServer类似于Nacos,主要是用于服务发现,Broker会将其信息注册到NameServer中。和Nacos/Zookeeper集群不一样的地方是各NameServer服务节点是相互独立,没有任何信息交换的。
一台机器可以部署多个Broker或NameServer。

RocketMQ集群搭建与优化
rocketmq没有管理控制台,需要自己搭建
java -jar rocketmq-dashboard-1.0.1-SNAPSHOT.jar

搭建Dledger高可用集群–了解
rocketMq集群中如果master宕机了,那么不会重新选举master。即没有容灾功能,只做数据备份。和kafka不一样。需要借助Dledger这些中间件来搭建高可用集群。Dledger选举原理也是raft协议。使用Dledger搭建高可用集群处理需要配置Broker节点,还需要配置Dledger节点。
Dledger需要在RocketMQ4.5以后的版本才支持。

系统参数调优 – 重要
1、配置RocketMQ的JVM内存大小(即jvm参数调优)
2、RocketMQ的其他一些核心参数(即修改rocketmq配置文件的参数)
例如在conf/dleger/broker-n0.conf中有一个参数:
sendMessageThreadPoolNums=16。这一个参数是表明RocketMQ内部用来发送消息的线程池的线程数量是16个,其实这个参数可以根据机器的CPU核心数进行适当调整,例如如果你的机器核心数超过16个,就可以把这个参数适当调大。
3、Linux内核参数定制
RocketMQ的bin目录下有个os.sh里面设置了RocketMQ建议的系统内核参数,可以根据情况进行调整。

RocketMQ消息转发模型
消息模型(Message Model)
Producer、Broker、Consumer、Topic、Message Queue、Name Server、Message。

消息生产者(Producer)
同一组的Producer被认为是发送同一类消息且发送逻辑一致。
消息消费者(Consumer)
RocketMQ 支持两种消息模式:集群消费(Clustering)和广播消费(Broadcasting)。
集群消费模式下, 相同Consumer Group的每个Consumer实例平均分摊消息。在集群消费模式下,如果你的应用程序启动了多个消费者实例,并且这些实例都加入了同一个消费组,RocketMQ会自动将这些实例均匀地分配到该消费组的所有消息队列上。每个消息队列在任何时刻只会被一个消费者实例消费(这是和kafka一样的地方,每个分区/队列都只能被一个消费者处理,区别是rocketmq每个消费者可以开多个线程)。
广播消费模式下,相同Consumer Group的每个Consumer实例都接收全量的消息。这是RocketMq消费模式和kafka是不一样的地方。

主题(Topic)
1,每条消息只能属于一个主题,topic是RocketMQ进行消息订阅的基本单位。
2.Topic只是一个逻辑概念,并不实际保存消息。 Topic 和 Broker 并没有直接的绑定关系,同一个Topic下的消息,会分片保存到不同的Broker上,而每一个分片单位,就叫做MessageQueue。
为什么Topic不在Broker下?因为Topic是一个逻辑概念,且他不只关联一个Broker,可能关联多个Broker。
3.MessageQueue(分区)是一个具有FIFO特性的队列结构,生产者发送消息与消费者消费消息的最小单位。

代理服务器(Broker Server)
1.消息中转角色,负责存储消息、转发消息。
2.代理服务器在RocketMQ系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。
3.Broker Server是RocketMQ真正的业务核心,包含了多个重要的子模块
a.Remoting Module:整个Broker的实体,负责处理来自clients端的请求。
b.Client Manager:负责管理客户端(Producer/Consumer)和维护Consumer的Topic订阅信息
c.Store Service:提供方便简单的API接口处理消息存储到物理硬盘和查询功能。
d.HA Service:高可用服务,提供Master Broker 和 Slave Broker之间的数据同步功能。
e.Index Service:根据特定的Message key对投递到Broker的消息进行索引服务,以提供消息的快速查询。
4.而Broker Server要保证高可用需要搭建主从集群架构。RocketMQ中有两种Broker架构模式
a.普通集群:
这种集群模式下会给每个节点分配一个固定的角色,master负责响应客户端的请求,并存储消息。slave则只负责对master的消息进行同步保存,并响应部分客户端的读请求。消息同步方式分为同步同步和异步同步。
这种集群模式下各个节点的角色无法进行切换,也就是说,master节点挂了,这一
组Broker就不可用了。
b.Dledger高可用集群:
Dledger是RocketMQ自4.5版本引入的实现高可用集群的一项技术。这个模式下的集群会随机选出一个节点作为master,而当master节点挂了后,会从slave中自动选出一个节点升级成为master。
Dledger技术做的事情:1、从集群中选举出master节点 2、完成master节点往slave节点的消息同步。3.接管CommitLog的写入操作。

名字服务(Name Server)
NameServer间相互独立,没有信息交换
名称服务充当路由消息的提供者。Broker Server会在启动时向所有的Name Server注册自己的服务信息,并且后续通过心跳请求的方式保证这个服务信息的实时性。生产者或消费者能够通过名字服务查找各主题相应的Broker IP列表。
这种特性也就意味着NameServer中任意的节点挂了,只要有一台服务节点正常,整个路由服务就不会有影响。当然,这里不考虑节点的负载情况。

消息(Message)
1.RocketMQ中每个消息拥有唯一的Message ID,且可以携带具有业务标识的Key。系统提供了通过Message ID和Key查询消息的功能。
2.Message上有一个为消息设置的标志,Tag标签。用于同一主题下区分不同类型的消息。
可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑,实现更好的扩展性。

在这里插入图片描述一个队列最多分配给一个消费者,但是一个消费者可以开启多个线程进行处理。而kafka一个消费者默认是开启一个线程处理。 一个队列只能存储同一个topic的消息。

同一个Topic下的消息,会分片保存到不同的Broker上的MessageQueue。那消费组订阅的时候,怎么知道从哪个MessageQueue中获取消息?
topic是RocketMQ进行消息订阅的基本单位,消费组订阅一个 Topic 时,会从 NameServer 获取该 Topic 的路由信息,包括该 Topic 在哪些 Broker 上以及每个 Broker 上有哪些 MessageQueue。消费者通过这些路由信息知道了如何从不同的 MessageQueue 中获取消息。
消费组在订阅 Topic 时并不需要关心具体从哪个 MessageQueue 中获取消息,RocketMQ 的负载均衡机制会自动分配 MessageQueue 给消费者实例。

------

通过上面kafka架构图和rocketmq消息转发模型图可以知道rocketmq和kafka的概念很多都是一样的。比如topic都可以关联多个Broker。topic只是一个逻辑概念。

默认每个broker会创建1个系统 Topic,每个topic会有4个队列。这个系统 Topic 主要用于存储一些系统元数据信息。

为什么RocketMQ不用Zookeeper而要自己实现一个NameServer来进行注册?
因为NameServer之间无需交互,不需要选举,所以无需用Nacos/Zookeeper。且Zookeeper只保证了CP,没有保证高可用,所以不用zookeeper。要用也是用Nacos。
如果用了Nacos/Zookeeper就大材小用了。

Consumer分组有什么用?Producer分组呢?
Consumer分组和Kafka一样。Producer分组,这里会涉及到事务。
在Apache RocketMQ中,Consumer和Producer分组都有特定的作用:
Consumer分组:
1.用于标识一组消费者,共同消费来自同一个主题(topic)的消息。
2.负载均衡。
3.通过水平扩展消费者,提高负载和高可用。
Producer分组:
1.用于标识一组生产者,共同向同一个主题发送消息。
2.实现生产者的负载均衡和故障转移。
3.可以用于消息发送的事务性管理。

RocketMQ如何保证集群高可用?
分两方面
a.NameServer集群
b.Broker集群+dledger高可用集群

Producer/Consumer会定时从NameServer中获取Broker列表信息然后缓存到本地。
生产者要发送消息到Broker的时候,需要先从NameServer中获取Broker列表的信息,如果有缓存则用缓存,当发消息发到Topic的时候会有一个队列(分区)选择的算法(即kafka讲的负载策略)来选择往哪个队列发。通过本地缓存判断队列/分区属于哪个Broker,然后就可以将消息发往到了Broker。
消费者消费消息的时候,如果是拉模式,也需要先从NameNameServer中获取Broker列表的信息,如果有缓存则用缓存。然后告诉Broker要消费哪些消息。推模式是由拉模式实现的。

rocketmq/kafka的队列就是rabbitmq的stream队列,是durable的。

消息生产者分别通过三种方式发送消息,同步发送、异步发送以及单向发送。

消息类型
1.顺序消息
RocketMQ保证的是消息的局部有序,而不是全局有序。
消费者一般用push模式消费消息,pull模式已经过时了。
消费的时候,会锁定一个队列,然后消费该队列中所有消息,保证消费端的顺序消费。
RocketMQ消息有序的原理
要保证最终消费到的消息是有序的,需要从Producer、Broker、Consumer三个步骤都保证消息有序才行。
生产者端:将消息发送到同一个MessageQueue上
Broker端:Broker中一个队列内的消息是保证有序的
消费者端:消费者会从多个消息队列上去拿消息。这时虽然每个 消息队列上的消息是有序的,但是多个队列之间的消息仍然是乱序的。 消费者端要保证消息有序,就需要按队列一个一个来取消息,即取完一 个队列的消息后,再去取下一个队列的消息。而给consumer注入的 MessageListenerOrderly对象,在RocketMQ内部就会通过锁队列的方式保证消息是一个一个队列来取的。MessageListenerConcurrently这个消息监听器则不会锁队列,每次都是从多个队列中取一批数据 (默认不超过32条),因此无法保证消息有序。

2.广播消息
广播消息并没有特定的消息消费者样例,这是因为这涉及到消费者的集群消费模式。在集群状态(MessageModel.CLUSTERING)下,每一条消息只会被同一个消费者组中的一个实例消费到(这跟kafka和rabbitMQ的集群模式是一样的)。而广播模式则是把消息发给了所有订阅了对应主题的消费者,而不管消费者是不是同一个消费者组。

kafka消费模式和rocketmq消费模式区别?
a.kafka消费模式分queue模式和发布/订阅模式,能同时支持两种模式。
如果所有的消费者都属于同一个消费组,,那么所有的消息都会被均衡地投递给每一个消费
者,即每条消息只会被一个消费者处理,这就相当于点对点模式的应用。
如果所有的消费者都隶属于不同的消费组,那么所有的消息都会被广播给所有的消费者,即每
条消息会被所有的消费者处理,这就相当于发布/订阅模式的应用。所以说,对于有多个消费组
的情况,这两种模式都会有。
b.而rocketmq消费模式分广播消费和集群消费(默认)
集群消费模式类似kafka,同一个消费组只有一个消费者会接收消息。而广播消费会把所有消息发给同一个消费组内的所有消费者。
集群消费的offset由broker来管理,而广播消费的offset由消费者来管理。

3.延迟消息
只有rocketmq实现了延迟消息。rabbitmq需要ttl+死信队列(但是该队列是普通队列配置过后的,而rocketmq默认创建好了该队列,无需配置)才能实现。
开源版本的RocketMQ中,对延迟消息并不支持任意时间的延迟设定(商业版本中支持),而是只支持18个固定的延迟级别,1到18分别对应
messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h。

4.批量消息
批量消息是指将多条消息合并成一个批量消息,一次发送出去。这样的好处是可以减少网络IO,提升吞吐量。

5.过滤消息
a.RocketMQ的最佳实践中就建议,使RocketMQ时,一个应用可以就用一个Topic,而应用中的不同业务就用TAG来区分。
b.可以使用Message的Tag属性来简单快速的过滤信息。
c.消费者端:consumer.subscribe(“TagFilterTest”, “TagA ||TagC”); 这句表示只订阅TagA和TagC的消息。
d.服务端:Message()中只能传一个TAG
e.消费者使用Tag有一定的局限性,也可以使用SQL表达式筛选消息。
f.这个消息过滤是在Broker端进行的还是在Consumer端进行的?
明显是Broker端。

6.事务消息
虽说事务消息只保证了分布式事务的一半。但是即使这样,对于复杂的分布式事务,RocketMQ提供的事务消息也是目前业内最佳的降级方案。

事务消息的实现机制(使用半消息+2pc机制)
图。 事务消息机制的关键是在发送消息时,会将消息转为一个half半消息,并存入RocketMQ内部的一个 RMQ_SYS_TRANS_HALF_TOPIC 这个Topic,这样对消费者是不可见的。再经过一系列事务检查通过后,再将消息转存到目标Topic,这样对消费者就可见了。
如果本地事务执行慢没那么快返回的时候,会先返回个LocalTransactionState.UNKNOW状态,然后broker会过段时间去查询状态。即第5步骤,最多等15次,每次等待时间不一样。如果15次查询还不能查询到状态,就可以将其放到死信队列中。

半消息+2pc机制简要解释:
1.半消息(Half Message):
当生产者发送事务消息时,首先会发送一条称为半消息的消息到消息队列中,这个半消息在发送后处于“暂存”状态。
半消息在发送后,暂时不会被消费者消费。
2. 两阶段提交(Two-Phase Commit,2PC):
第一阶段:事务准备(Prepare)阶段。
在这个阶段,生产者发送半消息后,会执行一系列操作,准备事务的执行。
一旦事务准备完成(step3),生产者会向消息队列发送一个确认消息(Commit Message)(即完成step3后,会执行step4,step4也即下面的事务提交(Commit)或回滚(Rollback),事务完成可以是成功也可以是失败)。
第二阶段:事务提交(Commit)或回滚(Rollback)阶段。
在收到生产者发送的确认消息后,消息队列会执行最终的提交或回滚操作。
如果消息队列成功接收到确认消息,那么它会将之前的半消息标记为可投递状态,消费者可以正常消费这条消息。
如果发生了错误或者超时,消息队列会将之前的半消息标记为失败,并且进行相应的处理,比如丢弃消息或者将其放入死信队列。

使用半消息加两阶段提交机制的好处是可以确保消息在生产者和消息队列之间的可靠传输,并且保证了消息的一致性。如果在第二阶段发生错误,可以通过回滚操作将消息状态恢复到之前的状态,以保证系统的稳定性和可靠性。


事务消息的使用限制
1.事务消息不支持延迟消息和批量消息。
2.为了避免单个消息被检查太多次而导致半队列消息累积,我们默认将单个消息的检查次数限制为 15 次,但是用户可以通过 Broker 配置文件的transactionCheckMax参数来修改此限制。如果已经检查某条消息超过 N 次的话( N = transactionCheckMax ) 则 Broker 将丢弃此消息,并在默认情况下同时打印错误日志。用户可以通过重写AbstractTransactionCheckListener 类来修改这个行为。
3.事务消息将在 Broker 配置文件中的参数transactionMsgTimeout 这样的特定时间长度之后被检查。当发送事务消息时,用户还可以通过设置用户属性CHECK_IMMUNITY_TIME_IN_SECONDS 来改变这个限制,该参数优先于transactionMsgTimeout 参数。
4.事务性消息可能不止一次被检查或消费。
5.提交给用户的目标主题消息可能会失败,目前这依日志的记录而定。它的高可用性通过RocketMQ 本身的高可用性机制来保证,如果希望确保事务消息不丢失、并且事务完整性得到保证,建议使用同步的双重写入机制。
a.高可用性机制:
RocketMQ 本身具备的高可用性主要通过主备复制和消息重试机制来保证,可以在节点故障时自动切换到备份节点继续服务,从而保障消息的可靠性和可用性。
b.双重写入机制(Dual Write):
双重写入机制是一种常见的解决方案,用于确保消息的不丢失和事务的完整性。在 RocketMQ 中,这通常指的是将消息同时写入到两个独立的存储系统(如数据库和消息队列),从而保证消息即使在某个系统故障时也能被另一个系统接收和处理。
如果业务场景对消息的可靠性要求极高,确保消息不丢失,并且需要保证事务的完整性,建议使用双重写入机制。这种机制会增加系统的复杂性和成本,但可以提供更高的可靠性保障。
6.事务消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。与其他类型的消息不同,事务消息允许反向查询、MQ服务器能通过它们的生产者 ID 查询到消费者。

事务消息示例:订单处理系统

  1. 场景描述:
    用户在商店下单后,订单系统需要将订单信息发送给库存系统和支付系统。
    订单发送和本地事务(如更新订单状态)需要保持原子性,即订单消息要么成功发送给消息队列,要么本地事务回滚。
  2. 实现步骤:
    步骤 1:创建 RocketMQ 生产者
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.client.producer.TransactionListener;

public class OrderProducer {

    public static void main(String[] args) throws Exception {
        // 创建事务生产者
        TransactionMQProducer producer = new TransactionMQProducer("transaction_producer_group");
        // 设置 NameServer 地址
        producer.setNamesrvAddr("localhost:9876");

        // 设置事务监听器
        producer.setTransactionListener(new TransactionListenerImpl());

        // 启动生产者
        producer.start();

        // 模拟创建订单并发送订单消息
        String orderInfo = "Order ID: 123456";
        Message msg = new Message("OrderTopic", "OrderTag", orderInfo.getBytes());

        // 发送事务消息
        TransactionSendResult sendResult = producer.sendMessageInTransaction(msg, null);

        System.out.println("Send Transaction Message Result: " + sendResult);

        // 关闭生产者
        producer.shutdown();
    }
}

步骤 2:实现事务监听器

import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.LocalTransactionExecuter;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.client.producer.TransactionSendResult;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class TransactionListenerImpl implements TransactionListener {

    private AtomicInteger transactionIndex = new AtomicInteger(0);
    private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<>();

    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        int value = transactionIndex.getAndIncrement();
        int status = value % 3;

        localTrans.put(msg.getTransactionId(), status);

        // 执行本地事务逻辑,返回事务状态
        // 此处省略实际的本地事务执行过程

        if (status == 0) {
            // 本地事务执行成功,提交消息
            return LocalTransactionState.COMMIT_MESSAGE;
        } else if (status == 1) {
            // 本地事务执行失败,回滚消息
            return LocalTransactionState.ROLLBACK_MESSAGE;
        } else {
            // 未知状态,需要 RocketMQ 查询事务状态
            return LocalTransactionState.UNKNOW;
        }
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        Integer status = localTrans.get(msg.getTransactionId());
        if (status != null) {
            switch (status) {
                case 0:
                    return LocalTransactionState.COMMIT_MESSAGE;
                case 1:
                    return LocalTransactionState.ROLLBACK_MESSAGE;
                default:
                    return LocalTransactionState.UNKNOW;
            }
        }
        return LocalTransactionState.UNKNOW;
    }
}
  1. 示例说明:
    OrderProducer 类负责创建 RocketMQ 的事务生产者,并发送订单消息。在 sendMessageInTransaction 方法中,将消息发送到名为 “OrderTopic” 的 Topic。
    TransactionListenerImpl 类实现了 RocketMQ 的 TransactionListener 接口,处理本地事务的执行和事务状态的检查。
    在 executeLocalTransaction 方法中,模拟了订单的本地事务执行过程,并根据执行结果返回事务状态。
    在 checkLocalTransaction 方法中,根据之前执行的本地事务状态来检查并返回事务的最终状态。
    通过以上实现,当 executeLocalTransaction 方法返回 COMMIT_MESSAGE 时,RocketMQ 将提交消息;返回 ROLLBACK_MESSAGE 时,RocketMQ 将回滚消息;返回 UNKNOW 时,RocketMQ 将查询事务状态来决定消息的最终处理。
    这样,订单系统就可以利用 RocketMQ 的事务消息确保订单信息在发送和本地事务执行中的原子性和一致性。

rocketmq kafka各适合使用场景,为什么?
Rocketmq适合多Topic多业务场景、多topic性能比kafka高。
Rocketmq适合对消息顺序性要求高的场景,如金融交易、订单处理等。可以通过事务消息来实现。
Rocketmq适合数据可靠性要求特别高的场景,RocketMQ提供了多种级别的数据可靠性保证,包括异步实时刷盘、同步刷盘、同步复制和异步复制。其中,同步刷盘功能可以在消息写入后立即将其持久化到磁盘,确保即使在操作系统崩溃的情况下,消息也不会丢失。而Kafka主要使用异步刷盘方式和异步/同步复制。异步复制可以提供较高的吞吐量,但在极端情况下可能会导致数据丢失。虽然Kafka也支持同步复制,但其默认配置更倾向于异步复制以提高性能。
Kafka适合处理大数据海量数据流的场景,比如日志收集。其采用异步刷盘,使得吞吐量高。且它与许多大数据处理框架(如Spark、Flink等)有良好的集成,方便进行实时数据分析和流式处理。

RocketMQ使用中常见的问题
使用RocketMQ如何保证消息不丢失
(生产者采用事务消息机制+主从架构主从同步配置+同步刷盘+消费者端采用手动应答且不要使用异步消费机制+整个MQ挂了之后准备降级方案(比如先将消息发到其他存储中间件))
(一般为了性能考虑,不会采用全部方法)
1.所有MQ消息框架消息丢失场景?
在这里插入图片描述
其中,1,2,4三个场景都是跨网络的,而跨网络就肯定会有丢消息的可能。
关于3这个环节,通常MQ存盘时都会先写入操作系统的缓存page cache中,然后再由操作系统异步的将消息写入硬盘。这个中间有个时间差,就可能会造成消息丢失。如果服务挂了,缓存中还没有来得及写入硬盘的消息就会丢失。
所以这个是MQ场景都会面对的通用的丢消息问题。
2.RocketMQ消息零丢失方案(一般为了性能考虑,不会采用全部方法)
1》步骤1: 生产者使用事务消息机制保证消息零丢失。rabbitmq采用的是同步发送确认+重试机制,或手动事务(这种会对channel产生阻塞,造成吞吐量下降)。
而rocketmq比较优雅,采用事务消息机制。
关于rabbitmq同步发送确认+重试机制:
当生产者发送消息到 RabbitMQ 时,可以选择启用发布确认。启用后,生产者会收到一个确认(acknowledgement),指示消息已经成功发送到 RabbitMQ 服务器。如果出现网络故障或其他原因导致消息发送失败,生产者可以根据返回的确认信息进行重试,确保消息最终被正确地发送到 RabbitMQ。
2》步骤2:Dledger主从架构保证MQ主从同步时不会丢消息
rabbitmq中使用镜像集群,数据会主动在集群中个节点当中同步,这时丢失消息的概率不会太高。
而rocketmq中:
a.如果使用了Dledger搭建高可用集群,其会使用Dledger的文件同步,其采用的是两阶段提交,原理是raft协议。
b.如果使用普通方式搭建主从集群,没有使用Dledger,rocketmq也提供了主从同步的配置。
建议使用方式a。
3》步骤3:RocketMQ配置同步刷盘
rabbitmq中采用的是将队列声明为持久化队列,但我认为持久化队列并不能保证数据不丢失。
rockketmq采用的是同步刷盘,把RocketMQ的刷盘方式flushDiskType配置成同步刷盘。
4》步骤4:消费者端不要使用异步消费机制
rabbitmq中采用的是手动应答。
rocketmq中也是,另外需要不使用异步消费机制。
正常情况下,消费者端都是需要先处理本地事务,然后再给MQ一个ACK响应,这时MQ就会修改Offset,将消息标记为已消费,从而不再往其他消费者推送消息,如果没有标记已消费才会推送。所以在Broker的这种重新推送机制下,消息是不会在传输过程中丢失的。
但是如果使用异步消费的方式(即使用了线程),就有可能造成消息状态返回后消费者本地业务逻辑处理失败造成消息丢失的可能。

RocketMQ特有的问题,NameServer挂了如何保证消息不丢失?
1.NameServer之前都了解过,集群中任意NameServer节点挂掉,都不会影响他提供的路由功能。
如果集群中所有的NameServer节点都挂了呢?
有很多人就会认为在生产者和消费者中都会有全部路由信息的缓存副本,那整个服务可以正常工作一段时间。其实这个问题大家可以做一下实验,当NameServer全部挂了后,生产者和消费者是立即就无法工作了的。因为producer或client会立即感知到NameServer节点宕机了。
2(降级方案).那再回到我们的消息不丢失的问题,在这种情况下,RocketMQ相当于整个服务都不可用了,那他本身肯定无法给我们保证消息不丢失了。我们只能自己设计一个降级方案来处理这个问题了。例如在订单系统中,如果多次尝试发送RocketMQ不成功,那就只能另外找给地方(Redis、文件或者内存等)把订单消息缓存下来,然后起一个线程定时的扫描这些失败的订单消息,尝试往RocketMQ发送。这样等RocketMQ的服务恢复过来后,就能第一时间把这些消息重新发送出去。整个这套降级的机制,在大型互联网项目中,都是必须要有的。

RocketMQ消息零丢失方案总结
完整分析过后,整个RocketMQ消息零丢失的方案其实挺简单
1.生产者使用事务消息机制。
2.Broker配置同步刷盘+Dledger主从架构
3.消费者端采用手动应答且不要使用异步消费机制(先发送ACK再开启线程做本地逻辑操作)。
4.整个MQ挂了之后准备降级方案

一般为了性能考虑,不会采用全部方法
(可以使用异步刷盘、异步消费、生产者端也不一定需要使用事务消息)
那这套方案是不是就很完美呢?其实很明显,这整套的消息零丢失方案,在各个环节都大量的降低了系统的处理性能以及吞吐量。在很多场景下,这套方案带来的性能损失的代价可能远远大于部分消息丢失的代价。所以,我们在设计RocketMQ使用方案时,要根据实际的业务情况来考虑。例如,如果针对所有服务器都在同一个机房的场景,完全可以把Broker配置成异步刷盘来提升吞吐量。而在有些对消息可靠性要求没有那么高的场景,在生产者端就可以采用其他一些更简单的方案来提升吞吐,而采用定时对账、补偿的机制来提高消息的可靠性。而如果消费者不需要进行消息存盘(即不需要持久化消息到本地存储),那使用异步消费的机制(异步消费允许消费者在接收到消息后立即进行下一轮消息拉取,而不必等待消息处理完成再处理下一个消息。这样可以最大程度地利用网络和系统资源,提高消费者处理消息的效率)带来的性能提升也是非常显著的。总之,这套消息零丢失方案的总结是为了在设计RocketMQ使用方案时的一个很好的参考。

使用RocketMQ如何保证消息顺序
(生产者把消息发送到同一个队列)
(如果都存入同一个队列时吞吐影响很大就没必要用mq了)

为什么要保证消息有序?
场景需要,比如积分加减累计。

如何保证消息有序?
MQ的顺序问题分为全局有序和局部有序。
全局有序:整个MQ系统的所有消息严格按照队列先入先出顺序进行消费。
局部有序:只保证一部分关键消息的消费顺序。
对于全局有序的场景:一般是将全局有序压缩成局部有序,比如将所有消息都存入一个队列中。如果都存入同一个队列时吞吐影响很大就没必要用mq了。
对于局部有序的场景:一般将有序的一组消息都存入同一个MessageQueue里。
其实在大部分的MQ业务场景,我们只需要能够保证局部有序就可以了。
例如我们用QQ聊天,只需要保证一个聊天窗口里的消息有序就可以了。而对于电商订单场景,也只要保证一个订单的所有消息是有序的就可以了。至于全局消息的顺序,并不会太关心。而通常意义下,全局有序都可以压缩成局部有序的问题。例如以前我们常用的聊天室,就是个典型的需要保证消息全局有序的场景。但是这种场景,通常可以压缩成只有一个聊天窗口的QQ来理解。即整个系统只有一个聊天通道,这样就可以用QQ那种保证一个聊天窗口消息有序的方式来保证整个系统的全局消息有序。
然后落地到RocketMQ。通常情况下,发送者发送消息时,会通过MessageQueue轮询的方式保证消息尽量均匀的分布到所有的MessageQueue上,而消费者也就同样需要从多MessageQueue上消费消息。而MessageQueue是RocketMQ存储消息的最小单元,他们之间的消息都是互相隔离的,在这种情况下,是无法保证消息全局有序的。
而对于局部有序的要求,只需要将有序的一组消息都存入同一个MessageQueue里,这样MessageQueue的FIFO设计天生就可以保证这一组消息的有序。
RocketMQ中,可以在发送者发送消息时指定一个MessageSelector对象,让这个对象来决定消息发入哪一个MessageQueue。这样就可以保证一组有序的消息能够发到同一个MessageQueue里。
另外,通常所谓的保证Topic全局消息有序的方式,就是将Topic配置成只有一个MessageQueue队列(默认是4个)。这样天生就能保证消息全局有序了。这个说法其实就是我们将聊天室场景压缩成只有一个聊天窗口的QQ一样的理解方式。而这种方式对整个Topic的消息吞吐影响是非常大的,如果这样用,基本上就没有用MQ的必要了。

使用RocketMQ如何快速处理积压消息?(增加消费者或topic和MessageQueue)
1、如何确定RocketMQ有大量的消息积压?
(通过查看管理界面是否有消息积压,看位点差值)
对于消息积压,如果是RocketMQ或者kafka,他们的消息积压不会对性能造成很大的影响。而如果是RabbitMQ的话(这里应该是说使用RabbitMq经典队列),那就惨了,大量的消息积压可以瞬间造成性能直线下滑。因为RabbitMQ经典队列会将数据都存在内存,更耗性能。而RocketMQ或者kafka是会存在本地的,需要的时候再读取到内存,没那么耗性能。
通过查看管理界面是否有消息积压,看位点差值
2.如何处理大量积压的消息?
和kafka一样,topic中的messagequeue/partion只能由一个消费组中的消费者来处理,且一个messagequeue/partion只能由一个消费者来处理。
a.如果Topic下的MessageQueue配置得是足够多就增加消费者。
b.如果Topic下的MessageQueue配置得不够多的,可以新建topic和MessageQueue,另外增加一组消费者(就1组),然后这一组消费者只负责将旧Topic的消息转存到新的topic,这个速度是可以很快的。然后在新的Topic上,就可以通过增加消费者个数来提高消费速度了。

RocketMQ的消息轨迹(可以查看消息轨迹)
RocketMQ默认提供了消息轨迹的功能,这个功能在排查问题时是非常有用的。

RocketMQ高性能原理
一、读队列与写队列
消息先写到写队列当中,然后持久化到磁盘,消费者拉取消息的时候,从读队列中获取相关的消息信息如offset,然后获取相应的消息。
在RocketMQ的管理控制台创建Topic时,可以看到要单独设置读队列和写队列。通常在运行时,都需要设置读队列数量=写队列数量。

rocketmq中读队列和写队列都会存一份数据吗?
写队列会真实的创建对应的存储文件,负责消息写入。而读队列会记录Consumer的Offset,负责消息读取。这其实是一种读写分离的思想。
即写队列和读队列存储的数据是不同的,写队列存储原始消息数据,而读队列存储消息的消费状态和索引信息。

rocketmq在往写队列里写Message时,会同步写入一条消息到一个对应的读队列中吗?
在RocketMQ中,消息的写入和读取是异步的过程,并不是在往写队列里写入消息时就会立即同步写入一条消息到对应的读队列中。
当生产者发送消息并将其写入写队列后,RocketMQ会根据消费者的消费进度和消费者所属的消费者组来决定消息的分发。消息会根据一定的策略被分发到不同的读队列中,以便消费者能够从对应的读队列中获取消息进行消费。
因此,写入消息和读取消息是两个独立的过程,在写入时并不会立即同步写入到读队列中,而是在消息需要被消费时,RocketMQ会将消息分发到对应的读队列中供消费者消费。

读队列和写队列分别存储在哪个文件
在RocketMQ中,写队列(Write Queue)和读队列(Read Queue)并不会各自存储一份完整的数据副本。它们实际上是逻辑上的概念,而不是物理上的存储结构。
在RocketMQ中,消息队列的数据存储主要包括两部分:CommitLog和ConsumeQueue。
CommitLog存写队列数据,ConsumeQueue存读队列信息。

二、消息持久化
RocketMQ消息直接采用磁盘文件保存消息,默认路径在${user_home}/store目录。这些存储目录可以在broker.conf中自行指定。

存储文件主要分为三个部分:
1.CommitLog(存储消息):存储消息的元数据。所有消息都会顺序存入到CommitLog文件当中。CommitLog由多个文件组成,每个文件固定大小1G。以第一条消息的偏移量为文件名。
2.ConsumerQueue(是一个逻辑队列,存储了某个消息队列的消息在CommitLog的位置(offset)):存储消息在CommitLog的索引。一个MessageQueue一个文件。
从下面图中可以看到,comsumerqueue文件夹下为每个topic建立文件夹,topic文件夹下又为每个messagequeue建立的文件夹,每个messagequeue文件夹下才包含了messagequeue消息的文件。
3.IndexFile:为了消息查询提供了一种通过key或时间区间来查询消息的方法,这种通过IndexFile来查找消息的方法不影响发送与消费消息的主流程。
文件名以时间戳来命名
另外,还有几个辅助的存储文件:
4.checkpoint:数据存盘检查点。里面主要记录commitlog文件、ConsumeQueue文件以及IndexFile文件最后一次刷盘的时间戳。
5.config/*.json:这些文件是将RocketMQ的一些关键配置信息进行存盘保存。例如Topic配置、消费者组配置、消费者组消息偏移量Offset等等一些信息。
比如topic.json,包含了权限、读/写队列数等等
6.abort:这个文件是RocketMQ用来判断程序是否正常关闭的一个标识文件。正常情况下,会在启动时创建,而关闭服务时删除。但是如果遇到一些服务器宕机,或者kill -9这样一些非正常关闭服务的情况,这个abort文件就不会删除,因此RocketMQ就可以判断上一次服务是非正常关闭的,后续就会做一些数据恢复的操作。
7:lock:表示这个store文件夹已经被某一个rocketmq服务占用了,一个store文件夹只能被一个服务占用。即为了防止多个rocketmq服务同时操作文件夹的数据。

整体的消息存储结构
图中consumerOffset和CommitLogOffset的区别?
Consumer Offset 是指消费者在某个消息队列上消费消息的位置标识。
Commit Log Offset 是指消息在 RocketMQ 存储系统(Commit Log)中的物理存储位置。

怎么查看某个消费者组消费到哪个offset?
先从config/*.json文件中查找consumeroffset,再从ConsumerQueue中找到commitLogOffset。(下面第2点有说:消费者在ConsumeQueue文件当中的消费进度,会保存在config/consumerOffset.json文件当中。

整体的消息存储结构描述
1、CommitLog文件存储所有消息实体。所有生产者发过来的消息,都会无差别的依次存储到Commitlog文件当中(即所有topic、messagequeue/patition的消息都统一放到这个commitLog中)。这样的好处是可以减少查找目标文件的时间,让消息以最快的速度落盘。对比Kafka存文件时,需要寻找消息所属的Partition文件,再完成写入,当Topic比较多时,这样的Partition寻址就会浪费比较多的时间,所以Kafka不太适合多Topic的场景。而RocketMQ的这种快速落盘的方式在多Topic场景下,优势就比较明显。
总结:即顺序写,相比Kafka优势在于Kafka消息写入磁盘时不是只写入一个文件的,而是可能会写入到不同的文件(Partition文件),所以会有一个需要先确定其写到哪个Partition文件的步骤,然后再往对应的Partition文件写入。而RocketMq不需要,消息都是直接写到CommitLog。
从kafka的知识点可以知道kafka也是顺序写,kafka和rocketMq也都可以指定消息往哪个Patition/MessageQueue发消息。但是kafka是将消息存到不同的Patition文件,而RocketMq是将消息都存储到CommitLog文件中。所以说如果当Topic比较多时,Partition也会比较多,这样查找对应Partition文件会相对rocketmq比较耗时。所以之前就说在topic比较多的时候,建议使用rocketmq。
文件结构:CommitLog的文件大小是固定的,但是其中存储的每个消息单元长度是不固定的,具体格式可以参考org.apache.rocketmq.store.CommitLog
正因为消息的记录大小不固定,所以RocketMQ在每次存CommitLog文件时,都会去检查当前CommitLog文件空间是否足够,如果不够的话,就重新创建一个CommitLog文件。文件名为当前消息的偏移量。
2、ConsumeQueue文件主要是加速消费者的消息索引。他的每个文件夹对应RocketMQ中的一个MessageQueue,文件夹下的文件记录了每个MessageQueue中的消息在CommitLog文件当中的偏移量。这样,消费者通过ComsumeQueue文件,就可以快速找到CommitLog文件中感兴趣的消息记录。而消费者在ConsumeQueue文件当中的消费进度,会保存在config/consumerOffset.json文件当中。
文件结构:每个ConsumeQueue文件固定由30万个固定大小(20byte)的数据块组成,数据块的内容包括:msgPhyOffset(8byte,消息在文件中的起始位置)+msgSize(4byte,消息在文件中占用的长度)+msgTagCode(8byte,消息的
tag的Hash值)。
在ConsumeQueue.java当中有一个常量CQ_STORE_UNIT_SIZE=20,这个常量就表示一个数据块的大小。
3、IndexFile文件主要是辅助消息检索。消费者进行消息消费时,通过ConsumeQueue文件就足够完成消息检索了,但是如果要按照MeessageId或者MessageKey来检索文件,比如RocketMQ管理控制台的消息轨迹功能,ConsumeQueue文件就不够用了。IndexFile文件就是用来辅助这类消息检索的。他的文件名比较特殊,不是以消息偏移量命名,而是用的时间命名。但是其实,他也是一个固定大小的文件。
文件结构:他的文件结构由 indexHeader(固定40byte)+ slot(固定500W个,每个固定20byte) + index(最多500W*4个,每个固定20byte) 三个部分组成。

三、过期文件删除
消息既然要持久化,就必须有对应的删除机制。RocketMQ内置了一套过期文件的删除机制。
1.首先:如何判断过期文件:对于非当前写的文件(只有偏移量最大的文件才是当前写的文件),如果超过了一定的保留时间,那么这些文件都会被认为是过期文件,随时可以删除。这个保留时间就是在broker.conf中配置的fileReservedTime属性。
kafka默认是保持7天。
2.然后:何时删除过期文件:
RocketMQ内部有一个定时任务,对文件进行扫描,并且触发文件删除的操作。
用户可以指定文件删除操作的执行时间。在broker.conf(案例是在broker-a.properties)中deleteWhen属性指定。默认是凌晨四点。另外,RocketMQ还会检查服务器的磁盘空间是否足够,如果磁盘空间的使用率达到一定的阈值(diskMaxUsedSpaceRatio),也会触发过期文件删除。所以RocketMQ官方就特别建议,broker的磁盘空间不要少于4G。

四、高效文件写
4.1 零拷贝技术加速文件读写
引入了DMA,java层面使用的是sendFile方式实现零拷贝
4.2 顺序写加速文件写入磁盘(顺序写无需寻址)
kafka也使用了顺序写。
总结:顺序写会提前申请一块连续的磁盘空间,而随机写不会。
通常应用程序往磁盘写文件时,由于磁盘空间不是连续的,会有很多碎片。所以我们去写一个文件时,也就无法把一个文件写在一块连续的磁盘空间中,而需要在磁盘多个扇区之间进行大量的随机写。这个过程中有大量的寻址操作,会严重影响写数据的性能。
而顺序写机制是在磁盘中提前申请一块连续的磁盘空间,每次写数据时,就可以避免这些寻址操作,直接在之前写入的地址后面接着写就行。
Kafka官方详细分析过顺序写的性能提升问题。Kafka官方曾说明,顺序写的性能 基本能够达到内存级别。而如果配备固态硬盘,顺序写的性能甚至有可能超过写内 存。而RocketMQ很大程度上借鉴了Kafka的这种思想。
4.3 刷盘机制保证消息不丢失
(操作系统通过提供一 个系统调用,应用程序可以自行调用这个系统调用,完成PageCache的强制刷盘,使消息不丢失)
在操作系统层面,当应用程序写入一个文件时,文件内容并不会直接写入到硬件当中,而是会先写入到操作系统中的一个缓存PageCache中。PageCache缓存以4K 大小为单位,缓存文件的具体内容。这些写入到PageCache中的文件,在应用程序 看来,是已经完全落盘保存好了的,可以正常修改、复制等等。但是,本质上, PageCache依然是内存状态,所以一断电就会丢失。因此,需要将内存状态的数据 写入到磁盘当中,这样数据才能真正完成持久化,断电也不会丢失。这个过程就称 为刷盘。
PageCache是源源不断产生的,而Linux操作系统显然不可能时时刻刻往硬盘写 文件。所以,操作系统只会在某些特定的时刻将PageCache写入到磁盘。例如当我 们正常关机时,就会完成PageCache刷盘。另外,在Linux中,对于有数据修改的 PageCache,会标记为Dirty(脏页)状态。当Dirty Page的比例达到一定的阈值时, 就会触发一次刷盘操作。例如在Linux操作系统中,可以通过/proc/meminfo文件 查看到Page Cache的状态。
但是,只要操作系统的刷盘操作不是时时刻刻执行的,那么对于用户态的应用程 序来说,那就避免不了非正常宕机时的数据丢失问题。因此,操作系统也提供了一 个系统调用,应用程序可以自行调用这个系统调用,完成PageCache的强制刷盘。 在Linux中是fsync,同样我们可以用man 2 fsync 指令查看。
刷盘:即将内存状态的数据写入到磁盘当中。
rocketmq刷盘机制用于在服务宕机的时候也能将数据刷盘。
RocketMQ对于何时进行刷盘设计了两种刷盘机制:同步刷盘和异步刷盘。
1.同步刷盘:
在返回写成功状态时,消息已经被写入磁盘。具体流程是,消息写入内存的PAGECACHE后,立刻通知刷盘线程刷盘, 然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写 成功的状态。
2.异步刷盘:
在返回写成功状态时,消息可能只是被写入了内存的PAGECACHE,写操作的返回快,吞吐量大;当内存里的消息量积累到一定程度时,统一触发写磁盘动作,快速写入。

五、消息主从复制
(集群消息才会复制多一份,读队列不会)
如果Broker以一个集群的方式部署(即多个Broker),会有一个master节点和多个slave节点,消息需要从Master复制到Slave上。而消息复制的方式分为同步复制和异步复制。
1.同步复制:同步复制是等Master和Slave都写入消息成功后才反馈给客户端写入成功的状态。
在同步复制下,如果Master节点故障,Slave上有全部的数据备份,这样容易恢复数据。但是同步复制会增大数据写入的延迟,降低系统的吞吐量。
2.异步复制:
异步复制是只要master写入消息成功,就反馈给客户端写入成功的状态。然后再异步的将消息复制给Slave节点。
在异步复制下,系统拥有较低的延迟和较高的吞吐量。但是如果master节点故障,而有些数据没有完成复制,就会造成数据丢失。

六、负载均衡
A.Producer负载均衡
Producer发送消息时,默认会轮询目标Topic下的所有MessageQueue,并采用递增取模的方式往不同的MessageQueue上发送消息,以达到让消息平均落在不同的queue上的目的。而由于MessageQueue是分布在不同的Broker上的,所以消息也会发送到不同的broker上。
同时生产者在发送消息时,可以指定一个MessageQueueSelector。通过这个对象来将消息发送到自己指定的MessageQueue上。这样可以保证消息局部有序。
B.Consumer负载均衡
Consumer也是以MessageQueue为单位来进行负载均衡。分为集群模式和广播模式。
1、集群模式
在集群消费模式下,每条消息只需要投递到订阅这个topic的Consumer Group下的
一个实例即可。RocketMQ采用主动拉取的方式拉取并消费消息,在拉取的时候需
要明确指定拉取哪一条message queue。且每一个MessageQueue只能被消费组内的一个Consumer消费。
而每当实例的数量有变更,都会触发一次所有实例的负载均衡,这时候会按照queue的数量和实例的数量平均分配queue给每个实例。
每次分配时,都会将MessageQueue和消费者ID进行排序后,再用不同的分配算法
进行分配。内置的分配的算法共有六种(默认是平均分配策略,将所有MessageQueue平均分给每一个消费者)。
2、广播模式
广播模式下,每一条消息都会投递给订阅了Topic的所有消费者实例,所以也就没有消息分配这一说。而在实现上,就是在Consumer分配Queue时,所有Consumer都分到所有的Queue。
广播模式实现的关键是将消费者的消费偏移量不再保存到broker当中,而是保存到客户端当中,由客户端自行维护自己的消费偏移量。

七、消息重试
(广播模式消息消费失败或消费者宕机后不会进行消息重试。而集群模式才会进行重试,比如消费者宕机后会发送给其它消费者进行消费)

八、死信队列
当一条消息消费失败,RocketMQ就会自动进行消息重试。而如果消息超过最大重试次数,RocketMQ就会认为这个消息有问题。但是此时,RocketMQ不会立刻将这个有问题的消息丢弃,而会将其发送到这个消费者组对应的一种特殊队列:死信队列。
rabbitMq中该队列是需要配置的,而rocketmq默认会帮我们创建队列,无需配置。
但是rocketmq死信队列并不会一开始就创建,而是真正需要使用到的时候才会创建。
默认创建出来的死信队列perm是2,即不可读不可写,需要手动修改。只有RocketMQ可以写入该队列。

九、消息幂等(默认kafka/rocketmq只能保证at least once。)
幂等的概念:
在MQ系统中,对于消息幂等有三种实现语义:
at most once 最多一次:每条消息最多只会被消费一次(最容易实现)
at least once 至少一次:每条消息至少会被消费一次(rocketmq可以通过同步发送、事务消息保证)
exactly once 刚刚好一次:每条消息都只会确定的消费一次(最难保证,需要由业务系统自行保证)(参考kafka,用at least once 加上消费者幂等性可以实现)

rocketmq MessageId是无法保证全局唯一的,也会有冲突的情况。所以在一些对幂等性要求严格的场景,最好是使用业务上唯一的一个标识比较靠谱。例如订单ID。而这个业务标识可以使用Message的Key来进行传递。

十、Dledger集群(基于Raft)
RocketMQ如何保证集群高可用?
分两方面:a.NameServer集群 b.Broker集群+dledger高可用集群

Dledger是RocketMQ自4.5版本引入的实现高可用集群的一项技术。他基于Raft算法进行构建,在RocketMQ的主从集群基础上,增加了自动选举的功能。当master节点挂了之后,会在集群内自动选举出一个新的master节点。

Raft 算法主要用于解决 RocketMQ 集群中主从节点数据的一致性和高可用性问题。
高可用体现在每个节点(包括主节点)都能够参与 Raft 选举过程,并在主节点故障时自动选举出新的主节点。

RocketMQ中的Dledger集群主要包含三个功能:
1、从集群中选举产生master节点。
2、优化master节点往slave节点的消息同步机制(上节讲的消息零丢失-主从同步两阶段提交有讲过,即如果使用了Dledger搭建高可用集群,其会使用Dledger的文件同步,其采用的是两阶段提交,原理也是基于raft协议。)
Master节点在接收到生产者发送的消息后,会将消息写入自己的CommitLog,并且通过DLedger集群的机制将消息同步到Slave节点上。
RocketMQ使用两阶段提交的方式来保证消息在Master节点和Slave节点之间的同步,以确保消息不丢失。这是通过Raft协议来实现的。

rocketmq可以让消息100%不丢失消息(上面有讲到,但性能会不怎么好),而kafka无法做到。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值