非常详细的RocketMQ

一、简介

在这里插入图片描述

1、常见的MQ协议

JMS:是java平台上有关MOM的技术规范,它便于消息系统中的java应用程序进行消息交换,并且通过提供标准的产生、发送、接收消息的接口,简化企业应用的开发。ActiveMQ是该协议的典型实现。

STOMP:是一种MOM设计的简单文本协议。STOMP提供一个可互操作的连接格式,允许客户端与任意STOMP消息代理进行交互。ActiveMQ是协议的典型实现,RabbitMQ通过插件可以支持该协议。

AMQP:一个提供统一消息服务的应用层标准,是应用层协议的一个开放标准,是一种MOM设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制。RabbitMQ是该协议的典型实现。

MQTT:是IBM开发的一个即使通信协议,是一种二进制协议,主要用于服务器和低功耗IoT设备间的通信。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和制动器的通信协议。RabbitMQ通过插件可以支持协议。

2、RocketMQ简介

RocketMQ是一个统一消息引擎,轻量级数据处理平台。

3、基本概念

1、消息

​ 消息是指,消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主体。

2、主题

image-20210913232422900

topic标识一类消息的集合,每个主题包含若干个消息,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位。

一个生产者可以同时发送多种topic的消息;而一个消费者只对某种特定的topic感兴趣,即只可以订阅消费一种topic的消息。

3、标签

为消息设置的标签,用于同一主题下区分不同类型的消息。来自同一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据tag实现对不同子主题的不同消费逻辑,实现更好的扩展性。

4、队列

存储消息的物理试题。一个topic可以包含多个Queue,每个Queue中存放的就是该topic的消息。一个topic的Queue也被称为一个Topic中消息的分区。

一一个Topic的Queue中的消息只能被一个消费者组中的一个消费者消费。

在这里插入图片描述

在这里插入图片描述

5、消息标识

RocketMQ中每一个消息拥有唯一的MessageId,且可以携带具有业务标识的key,以方便对消息的查询。不过需要注意的是,MessageId有两个,在生产者send()消息时会自动生成一个MessageId(msgId),当消息到达Broke后,Broker也会自动生成一个MessageId(offsetMsgId)。msgId、offsetMsgId与key都称为消息标识。

  • msgId:由producer端生成,其生成规则为:

    producerIp + 进程pid + MessageClientIDSetter类的ClassLoader的hashCode + 当前时间 + AutomicInteger自增计数器。

  • offsetMsgId:由broke端生成,其生成规则为:brokeIp + 物理分区的offset(Queue中的偏移量)

  • key:由用户指定的业务相关的唯一标识

4、系统架构

在这里插入图片描述

1、producer

​ 消息生产者,负责生产消息。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递,投递过程支持快速并且低延迟。

​ RocketMQ中的消息生产者都是以生产者组的形式出现的。生产者组是同一类生产者的集合,这类Producer发送相同Topic类型的消息。

2、consumer

​ 消息消费者,负责消费消息。一个消息消费者会从Broker服务器中获取到消息,并对消息进行相关业务处理。

​ RocketMQ中的消息消费者都是以消费者组的形式出现的。消费者组是同一类消费者的集合,这类Consumer消费的是同一个Topic类型的消息。消费者组使得消息消费方面,实现负载均衡(将一个topic中的不同的Queue平均分配给同一个Consumer Group的不同的Consumer)和容错的目标变得非常容易。

在这里插入图片描述

消费者组中的Consumer的数量应该小于等于订阅Topic的Queue数量。如果超出Queue数量,则多出的Consumer将不能消费消息。

在这里插入图片描述

一个topic类型的消息可以被多个消费者组同时消费。

注意:

1)消费者组只能消费一个topic的消息,不能同时消费多个topic消息

2)一个消费者组中的消费者必须订阅完全相同的Topic

3、Name Server

NameServer是一个Broker与Topic路由的注册中心,支持Broker的动态注册与发现。

主要包含两个功能:

  • Broker管理:接受Broker集群的注册信息并且保存下来作为路由信息的基本数据;提供心跳检测机制,检查Broker是否还存活。

  • 路由信息管理:每个NameServer中保存着Broker集群的整个路由信息和用于客户端查询的队列信息。Producer和Consumer通过NameServer可以获取整个Broker集群的路由信息,从而进行消息的投递和消费

路由注册

​ NameServer通常也是以集群的方式部署,不过,NameServer是无状态的,即NameServer集群中的各个节点间是无差异的,个节点间相互不进行信息通讯。那各节点中的数据是如何进行数据同步的呢?在Broker节点启动时,轮询NameServer列表,与每个NameServer节点建立长连接,发起注册请求。在NameServer内部维护这一个Broker列表,用来动态存储Broker的信息。

​ Broker节点为了证明自己是活着的,为了维护与NameServer间的长连接,会将最新的信息以心跳包的方式上报给NameServer,每30秒发送一次心跳。心跳包中包含BrokerId、Broker地址、Broker名称、Broker所属集群名称等等。NameServer在接受到心跳包后,会更新心跳时间戳,记录这个Broker的最新存活时间。

路由剔除

由于Broker关机、宕机或者网络抖动等原因,NameServer没有收到Broker的心跳,NameServer可能会将其从Broker列表中剔除。

NameServer中有一个定时任务,每隔10秒就会扫描一次Broker表,查看没一个Broker的最新心跳时间戳距离当前时间是否超过120秒,如果超过,则会判定Broker失效,然后将其从Broker列表中剔除。

路由发现

RocketMQ的路由发现采用的是Pull模型。当Topic路由信息出现变化时,NameServer不会自动推送给客户端,而是客户端定时拉取主题最新的路由。默认客户端没30秒会拉取一次最新的路由。

客户端NameServer选择策略

客户端在配置时必须要写上NameServer集群的地址,那么客户端到底连接的是哪个NameServer节点,客户端首先会生成一个随机数,然后再与NameServer节点数量取模,此时得到的就是所要连接的节点索引,然后就会进行连接。如果连接失败,则会采用round-robin策略,逐个尝试着去连接其他节点。

4、Broker

功能介绍

Broker充当着消息中转角色,负责存储消息、转发消息。Broker在RocketMQ系统中负责接收并存储从生产者发送来的消息,同时为消费者的拉取请求做准备。Broker同时也存储着消息相关的元数据,包括消费者组消费进度偏移offset、主题、队列等。

在这里插入图片描述

Remoting Module:整个Broker的实体,负责处理来自clients端的请求。而这个Broker实体则由一下模块构成。

Client Manager:客户端管理器。负责接收、解析客户端(Producer/Condumer)请求,管理客户端。例如,维护Consumer的Topic订阅信息。

Store Service:存储服务。提供方便简单的API接口,处理消息存储到物理硬盘和消息查询功能。

HA Service:高可用服务,提供Master Broker和SalveBroker之间的数据同步功能。

5、工作流程
  1. 启动NameServer,NameServer启动后开始家庭端口,等待Broker、Producer、Consumer连接。

  2. 启动Broker时,Broker会与所有的NameServer建立并保持长连接,然后每30秒向NameServer定时发送心跳包。

  3. 收发消息前,可以先创建Topic,创建Topic时需要制定该Topic要存储在哪些Broker上,当然,在创建Topic时也会将Topic与Broker的关系写入到NameServer中。不过,这步是可选的,也可以在发送消息是自动创建opic.

    手动创建Topic时,有两种模式:

    • 集群模式:该模式下创建的Topic在该集群中,所有Broker中的Queue数量是相同的。

    • Broker模式:该模式下创建的Topic在该集群中,每个Broker中的Queue数量可以不同。

    自动创建Topic时,默认采用的是Broker模式,会为每个Broker默认创建4个Queue

  4. Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取路由信息,即当前发送的Topic的Queue与Broker的地址(Ip+Port)的映射关系。然后根据算法策略选择一个Queue,与队列所在的Broker建立长连接。

  5. Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取其所订阅Topic的路由信息,然后根据算法策略从路由信息中获取到其所要消费的Queue,然后直接跟Broker建立长连接,开始消费其中的消息。Consumer在获取到路由信息后,同样也会每30秒从NameServer更新一次路由信息。不过不同于Producer的是,Consumer还会向Broker发送心跳,以确保Broker的存活状态。

5、读写队列

​ 读写队列从物理上来讲是同一个队列,当手动创建的topic中的队列数不一样的时候,创建数量大的个数个队列。

​ 当读队列大于写队列的时候,后几个多出来的队列中不会有数据,当写队列大于读队列的时候,好几个队列中的消息不会被消费到。

​ 这样设计的目的是为了方便topic中Queue的缩容。

perm:用于设置对当前创建爱你Topic的操作权限:2表示只写,4表示只读,6表示读写

二、工作原理

三、使用

1、docker安装rocketMQ

  1. 查看镜像所有版本

    curl https://registry.hub.docker.com/v1/repositories/foxiswho/rocketmq/tags\ | tr -d '[\[\]" ]' | tr '}' '\n'\ | awk -F: -v image='foxiswho/rocketmq' '{if(NR!=NF && $3 != ""){printf("%s:%s\n",image,$3)}}'
    
  2. 拉取镜像

    #服务端镜像
    docker pull foxiswho/rocketmq:server-4.7.0
    #broke镜像
    docker pull foxiswho/rocketmq:broker-4.7.0
    

    3、创建数据挂载目录

    #创建服务端挂载目录
    mkdir -p /quse/rocketmq/server/logs
    mkdir -p /quse/rocketmq/server/store
    #创建broker挂载目录
    mkdir -p /quse/rocketmq/broker/logs
    mkdir -p /quse/rocketmq/broker/store
    mkdir -p /quse/rocketmq/broker/conf
    

    4、创建broker配置文件

    vim /quse/rocketmq/broker/conf/broker.conf
    
    # nameServer地址,分号分割
    namesrvAddr=42.192.36.182:9876
    brokerClusterName = DefaultCluster
    #broker名称,master和slave使用相同的名称,表明他们的主从关系
    brokerName = broker-a
    #0表示Master,大于0表示不同的slave
    brokerId = 0
    #表示几点做消息删除动作,默认是凌晨4点
    deleteWhen = 04
    #在磁盘上保留消息的时长,单位是小时,默认是48小时
    fileReservedTime = 48
    #有三个值:SYNC_MASTER,ASYNC_MASTER,SLAVE;同步和异步表示Master和Slave之间同步数据的机制;
    brokerRole = ASYNC_MASTER
    #刷盘策略,取值为:ASYNC_FLUSH,SYNC_FLUSH表示同步刷盘和异步刷盘;SYNC_FLUSH消息写入磁盘后才返回成功状态,ASYNC_FLUSH不需要;
    flushDiskType = ASYNC_FLUSH
    # 设置broker节点所在服务器的ip地址
    brokerIP1 = 42.192.36.182
    # 是否允许 Broker 自动创建 Topic,建议线下开启,线上关闭
    autoCreateTopicEnable=false
    # 是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
    autoCreateSubscriptionGroup=false
    # Broker 对外服务的监听端口
    listenPort=10911
    

    5、docker-compose创建容器

    version: '3.0'
    services:
      rmqnamesrv:
        image: foxiswho/rocketmq:server-4.7.0
        container_name: rmqnamesrv
        ports:
          - 9876:9876
        volumes:
          - ./quse/rocketmq/server/logs:/opt/logs
          - ./quse/rocketmq/serve/store:/opt/store
    
      rmqbroker:
        image: foxiswho/rocketmq:broker-4.7.0
        container_name: rmqbroker
        ports:
          - 10909:10909
          - 10911:10911
        volumes:
          - ./quse/rocketmq/broker/logs:/opt/logs
          - ./quse/rocketmq/broker/store:/opt/store
          - ./dquse/rocketmq/broker/conf/broker.conf:/etc/rocketmq/broker.conf
        environment:
            NAMESRV_ADDR: "rmqnamesrv:9876"
            JAVA_OPTS: " -Duser.home=/opt"
            JAVA_OPT_EXT: "-server -Xms128m -Xmx128m -Xmn128m"
        command: mqbroker -c /etc/rocketmq/broker.conf
        depends_on:
          - rmqnamesrv
    
      rmqconsole:
        image: styletang/rocketmq-console-ng:1.0.0
        container_name: rmqconsole
        ports:
          - 18080:8080
        environment:
            JAVA_OPTS: "-Drocketmq.namesrv.addr=rmqnamesrv:9876 -Dcom.rocketmq.sendMessageWithVIPChannel=false"
        depends_on:
          - rmqnamesrv
    

    注意事项:home目录下的文件不能挂载到属主机上,就算做了目录映射,文件因为不能写入

2、集群搭建方式

3、刷盘策略

​ 刷盘策略指的是broker中消息的落盘方式,即消息发送到broker内存消息持久化到磁盘的方式。分为同步刷盘和异步刷盘:

  • 同步刷盘:当消息持久化到broker的磁盘后才算是消息写入成功。

  • 异步刷盘:当消息写入到broker的内存后即表示消息写入成功,无序等待消息持久化到磁盘。

    1)异步刷盘策略会降低系统的写入延迟,RT变小,提高了系统的吞吐量。

    2)消息写入到Broker的内存,一般是写入到PageCache

    3)对于异步刷盘策略,消息会写入到PageCache后立即返回成功ACK.但并不会立即做落盘操作,而是当PageCache到达一定量时会自动落盘。

4、消息的生产

1、消息的生产过程

​ Produce可以将消息写入到某Broker的某Queue中,其经历如下过程:

  • Producer发送消息之前,会先向NameServer发出获取消息Topic的路由信息的请求。

  • NameServer返回该Topic的路由表及Broker列表.

  • Producer根据代码中指定的Queue选择策略,从Queue列表中选出一个队列,用于后续存储消息。

  • Produer对消息做一些特殊处理,例如,消息本身超过4M,则会对其进行压缩。

  • Producer向选择出的Queue所在的Broker发出RPC请求,将消息发送到选择的Queue

    路由表:实际是一个Map,key为topic名称,value是一个QueueData实例列表。QueueData并不是一个Queue对应一个QueueData,而是一个Broker中该Topic的所有Queue对应一个QueueData.即,只要涉及到该Topic的Broker,一个Broker对应一个QueueData。QueueData中包含brokerName.

    Broker列表:其实际也是一个Map。key为brokerName,value为BrokerData。一套brokerName名称相同的Master-Salve小集群对应一个BrokerData.BrokerData中包含brokerName及一个map。改map的key为brokerId,value为该broker对应的地址。brokerId为0标识为Master,非0表示为Salve

2、Queue选择算法

​ 对于无序消息,其Queue选择算法,也成消息投递算法,常见的有两种:

轮询算法

默认选择算法。改算法保证了每个Queue中可以均匀的获取到消息。

该算法存在一个问题:由于某些原因,在某些Broker上的Queue可能投递延迟较严重。从而导致Produer的缓存队列中出现较大的消息积压,影响消息的投递性能。

最小投递延迟算法

​ 该算法会统计每次消息投递的时间延迟,然后格局统计出的结果将消息投递到时间延迟最小的Queue.如果延迟相同,则采用轮询算法投递。该算法可以有效提升消息的投递性能。

费算法也存在一个问题:消息在Queue上的分配不均衡。投递延迟小的Queue其可能会存在大量的消息。而对于该Queue的消费者压力会增大,降低消息的消费能力,可能会导致MQ中的消息的堆积。

5、消息的存储

RocketMQ中的消息存储在本地文件系统中,这些相关文件默认在当前用户主目录下的store目录中。

在这里插入图片描述

  • abort:该文件在Broker启动后会自动创建,正常关闭Broker,该文件会自动消失。若在没有启动Broker的情况下,发现这个文件是存在的,则说明之前Broker的关闭时非正常关闭的。
  • checkpoint:其中存储着commitlog、consumequeue、index文件的最后刷盘时间戳。
  • commitlog:其中存放着commitlog文件,而消息是写在commitlog文件中的。
  • config:存放着Broker运行期间的一些配置数据。
  • consumequeue:其中存放着consumequeue文件,队列就存放在这个目录中。
  • index:其中存放着消息索引文件indexFile
  • lock:运行期间使用的全局资源锁。
1、commitlog文件

说明:在很多资料中commitlog目录中的文件简单就称为commitlog文件。但是在源码中,该文件被命名为mappedFile.

目录与文件

​ commitlog目录中存放着很多的mappedFile文件,当前Broker中所有消息都是落盘倒这些mappedFile文件中的。mappedFile文件大小为1G,文件名由20位十进制数构成,表示当前文件的第一条消息的起始位置偏移量。

第一个文件名一定是20位0构成的。因为第一个文件的第一条消息的偏移量commitlog offset为0

当第一个文件放满时,则会自动生成第二个文件继续存放消息,假设第一个文件大小是1073741820(1G = 1073741824),则第二个文件名就是00000000001073741820.

以此类推,第n个文件名应该是前n-1个文件大小之和。

一个Broker中所有mappedFile文件的commitlog offset是连续的

​ 需要注意的是,一个Broker中仅包含一个commitlog目录,所有的mappedFile文件都是存放在该目录中的。即无论当前Broker中存放着多少个Topic的消息,这些消息都是被顺序写入到了mappedFile文件中的。也就是说,这些消息在Broker中存放时并没有被按照Topic进行分类存放。

mappedFile文件是顺序读写的文件,所以其访问效率很高。

消息单元

在这里插入图片描述

mappedFile文件内容由一个个的消息单元构成。每个消息单元中包含消息总长度MsgLen、消息的物理位置physicalOffset、消息的内容Body、消息的体长度BodyLength、消息主体Topic、Topic长度TopicLength、消息生产者BronHost、消息发送时间戳BronTimestamp、消息所在队列QueueId、消息在Queue中存储的偏移量QueueOffset等近20余项相关$\textcolor{Orange}{属性} $。

需要注意到,消息单元中包含Queue相关属性的。

一个mappedFile文件中第m+1个消息单元的commitlog offset偏移量

​ L(m+1) = L(m) + MegLen(m) (m >= 0)

2、consumequeue

在这里插入图片描述

目录与文件

在这里插入图片描述

在这里插入图片描述

​ 为了提高效率,会为每个Topic在~/store/consuequeue中创建一个目录,目录名为Topic名称。在该Topic目录下,会再为每个该Topic的Queue建立一个目录,目录名为queueId。每个目录中存放若干个consumequeue文件,consumequeue文件是commitlog索引文件,可以根据consumequeue定位到具体的消息。

​ consumequeue文件名也由20位数字构成,表示当前文件的第一个索引条目的起始位置偏移量。与mappedFile文件名不同的是,其后续文件名是固定的。因为consumequeue文件大小是固定不变的。

索引条目

在这里插入图片描述

​ 每个consumequeue文件可以包含30w个索引条目,每个索引条目包含了三个消息重要属性:消息在mappedFile文件中的偏移量CommitLog Offset、消息长度、消息Tag的hashcode值。这三个属性占20个字节,所以么个文件的大小是固定的30w*20字节。

一个consumequeue文件中所有消息的Topic一定是相同的。但每条消息的Tag可能是不同的。

3、对文件的读写

在这里插入图片描述

消息的写入

一条消息进入到Broker后经历了一下几个过程才最终被持久化。

  • Broker根据queueId,获取到该消息对应索引条目要在consumequeue目录中写入偏移量。即QueueOffset。

    消费offset即消费进度,consumer对某个Queue的消费offset,即消费到了该Queue的第几条消息。

  • 将queueId、queueOffset等数据,与消息一起封装为消息单元

  • 将消息单元写入到commitlog

  • 形成消息索引条目

  • 将消息索引条目分发到相应的consumerqueue

消息的拉取

当Consumer来拉取消息时会经历一下几个步骤:

  • Consumer获取到其要消费消息所在Queue的消息偏移量offset,计算出其要消费消息的消费offset

  • Consumer向Broker发送拉取请求,其中会包含要拉取消息的Queue、消息offset及消息Tag。

  • Broker计算在该comsumequeue中的queueOffset.

    queueOffset = 消息Offset*20字节

  • 从该queueOffset处开始向后查找第一个指定Tag的索引条目。

  • 解析该索引条目的前8个字节,即可定位到该消息在commitlog中的commitlog offset

  • 从对应commitlog offset中读取消息单元,并发送给Consumer

性能提升

​ RocketMQ中,无论是消息本身还是消息索引,都是存储在磁盘上的。

​ 首先,RocketMQ对文件的读写操作时通过mmap零拷贝进行的,将对文件的操作转化未直接对内存地址进行操作,从而极大地提高了文件的读写效率。

​ 其次,consumequeue中的数据时顺序存放的,还引入了PageCache的预读取机制,使得对consumequeue文件的读取几乎接近于内存读取,计师在有消息堆积情况下也不会影响性能。

PageCache机制,页缓存机制,是OS对文件的缓存机制,用于加速对文件的读写操作。一般来说,程序对文件进行顺序读写的速度几乎接近于内存读写速度,主要原因是由于OS使用PageCache机制对读写访问操作进行性能优化,将一部分的内存用作PageCache。

  • 写操作:OS会先将数据写入到PageCache中,随后以异步方式由pdflush(page dirty flush)内核线程将Cache中的数据刷盘到物理磁盘
  • 读操作:若用户要读取数据,其首先会从PageCache中读取,若没有命中,则OS在从物理磁盘上加载该数据到PageCache的同时,也会顺序对其相邻数据块中的数据进行预读取。

​ RocketMQ中可能会影响性能的时对commitlog文件的读取。因为对commitlog文件来说,读取消息是会产生大量的随机访问,而随机访问会严重影响性能。不过,如果选择核是的系统IO调度算法,比如设置调度算法为Deadline(采用SSD固态硬盘的话),随机读的性能也会有所提升。

4、与Kafka的对比

​ RocketMQ的很多思想来源与Kafka,其中commitlog与consumequeue就是。

​ RcoketMQ中的commitlog目录与consumequeue的结合就类似与Kafka中的Partition分区目录。mappedFile文件就类似与Kafka中的segment段。

Kafka中的Topic的消息被分割为一个或多个partition.partition是一个物理概念,对应到系统上就是topic目录下的一个或多个目录。每个partition中包含的文件称为segment,是具体存放消息的文件。

Kafka中消息存放的目录结构是:topic目录下有partition目录,partition目录下有segment文件。

Kafka中没有二级分类标签Tag这个概念

6、indexFile

除了通过通常的指定Topic进行消息消费外,RocketMQ还提供了根据key进行消息查询的功能。该查询时通过store目录中的index子目录中的indexFile进行索引实现的快速查询。当然,这个indexFile中的索引数据时包含了key的消息被发送到Broker时写入的。如果消息中没有包含key,则不会写入。

1、索引条目结构

​ 每个Broker中包含一组indexFile,每个indexFile都是一个时间戳命名的。每个indexFile文件由三部分构成;indexHeader,slots槽位,indexs索引数据。每个IndexFile文件中包含500w个slot槽。而每个slot槽又可能会挂载很多的index索引单元。

在这里插入图片描述

indexHeader固定40个字节,其中存放着如下数据:

在这里插入图片描述

  • beginTimestamp:该indexFile中第一条消息的存储时间
  • endYimestamp:该indexFile中最后一条消息存储时间
  • beginPhyoffset:该indexFile中第一条消息在commitlog中的偏移量commitlog offset
  • endPhyoffset:该index中最后一条消息在commitlog中的偏移量commitlogs offset
  • hashSlotCount:已经填充有index的slot数量
  • indexCount:该indexFile中包含的索引个数

indexFile中最复杂的是Slots与INdexes间的关系。在实际存储时,Indexes是在SLots后面的,但为了便于理解,将他们的关系展示如下形式:

在这里插入图片描述

key的hash值%500w的结果即为slot槽位,然后将该slot值修改为该index索引单元的indexNo,根据这个indexNo可以计算出该index单元在index中的位置。不过,该取模结果的重复率是很高的,为了解决该问题,在每个index索引单元中增加了preIndexNo,用于指定该slot中当前index索引单元的前一个index索引单元。而slot中始终存放的是其下罪行的index索引单元的index,这样的话,只要找到slot就可以找到其最新的index索引单元,而通过这个index索引单元就可以找到其之前的所有索引单元。

indexNo是一个在indexFile中的流水号,从0开始一次递增。即一个indexFile中所有indexNo是依次递增的。

​ index索引单元默写20个字节,其中存放着一下四个属性:

在这里插入图片描述

  • keyHash:消息中指定的业务key的hash值
  • phyOffset:当前key对应的消息在commitlog中的偏移量commitlog offset
  • timeDiff:当前key对应消息的存储时间与当前indexFile创建时间的时间差
  • preIndexNo:当前slot下当前index索引单元的前一个Index索引单元的indexNo.
2、indexFile的创建

​ indexFile的文件名为当前文件被创建时的时间戳。根据业务Key进行查询时,查询条件除了key之外,还需要指定一个要查询的时间戳,表示要查询不大于该时间戳的最新消息。这个时间戳文件名可以简化查询,提高查询效率。

​ indexFile文件创建的条件有两个:

  • 当第一条带key的消息发送来后,系统发现没有indexFile,此时会创建第一个indexFile文件

  • 当一个indexFile中挂载的Index索引单元数量超出2000w时,会创建新的indexFile。当带key的消息发送到来后,系统会找到最新的indexFile,并从其indexHeader的最后4字节中读取到IndexCount。若indexCount >= 2000w时,会创建新的indexFile.

    由此可以推算出,一个indexFile的最大大小时:(40+500w4+2000w20)字节

3 查询流程

当消费者通过业务key来查询相应的消息时,其需要经过一个相对复杂的查询流程。以下有几个定位计算式子:

  1. 计算指定消息key的solt槽位序号:slot槽位序号 = key的hash%500w

  2. 计算槽位序号为n的slot的indexFile的起始位置:slot(n)位置 = 40 + (n-1)* 4

  3. 计算IndexNO为m的index在indexFile中的位置:

    index(m)位置 = 40 + 500w * 4 + (m-1)*20

在这里插入图片描述
在这里插入图片描述

7、消息的消费

​ 消费者从Broker中获取消息的方式有两种;pull拉取方式和Push推动方式。消费者组对于消息消费的模式又分为两种:集群消费clustering和广播broadcasting.

1、推拉消息类型
拉取式消费

​ Consumer主动从Broker中拉取消息,主动权由Consumer控制。一旦获取了批量消息,就会启动消费过程。不过,该方式的实时性较弱,即Broker中有了新的消息时消费者并不能及时发现并消费。

推动式消费

​ 该模式下Broker收到数据后会主动推动给Consumer。改消费模式一般实时性较高。

​ 该消费类型是典型的发布-订阅模式,即COnsumer向其关联的Queue注册了监听器,一旦发现有新的消息到来就会触发回调的执行,回调方法是Consumer向Queue中拉取消息。而这些都是基于COnsumer与Broker间的长连接的。长连接的维护是需要消耗系统资源的。

对比
  • ​ Pull:需要应用去实现对关联Queue的遍历,实时性差;但便于应用控制消息的拉取。
  • Push:封装了对关联Queue的遍历,实时性强,但会占用较多的系统资源。
2、消费模式
广播模式

在这里插入图片描述

广播模式下,相同COnsumer Groupd的每个Consumer实例都会接受到同一个Topic的全量消息。即每条消息都会被发送到COnsmerCroup中的每个Consumer.

集群消费

在这里插入图片描述

集群模式下,相同COnsumerGroup的每个COnsumer实例平均分摊同一个Topic的消息。即每条消息只会被发送到Consumer Group中的某个COnsumer.

消息进度保存
  • 广播模式:消费进度保存在consumer端。因为广播模式下consumer group中每个consumer都会消费所有消息,但他们的消费进度是不同。所以consumer各自保存各自的消费进度。
  • 集群模式:消费进度保存在broker中。consumer grooup的所有consumer共同消费同一个Topic中的消息,同一条消息只会被消费一次。消费进度会参与到了消费的负载均衡中,故消费进度是需要共享的。
3、Rebalance机制
什么是Rebalance机制

​ Rebalance即再均衡,指的是,将一个Topic下的多个Queue在同一个Consumer Group中的多个Consumer间进行重新分配的过程。

在这里插入图片描述

Rebalance机制的本意是为了提升消息的并行消费能力。例如,一个Topic下5个队列,在1个消费者的情况下,这个消费者将负责消费这5个队列的消息。如果此时我们增加一个消费者,那么就可以给其中一个消费者分配2个队列,给另一个分配3个队列,从而提升消息的并行消费能力。

Rebalance限制

由于一个队列最多分配给一个消费者,因此当某个消费者组下的消费者实例数量大于队列的数量时,多余的消费者实例将分配不到任何队列。

Rebalance危害

Rebalance在提升消费能力的同时,也带来一些问题:

消费暂停:在只有一个Consumer时,其负责消费所有队列,在新增了一个Consumer后会触发Relbalance的发生。此时原Consumer就需要暂停部分队列的消费,等到这些队列分配给新的Consumer后,这些暂停消费的队列才能据需被消费。

消费重复:consumer在消费新分配给自己的队列时,必须接着之前COnsumer提交的消费进度的offset继续消费。然而默认情况下,offset是异步提交的,这个有性导致提交到Broker的offset与COnsumer实际消费的消息并不一致。这个不一致的差值就是可能会重复消费的消息。

同步提交:consumer提交了其消费完毕的一批消息的Offset给broker后,需要等待broker的成功ACK。当收到ACK后。consumer才会据需获取并消费下一篇消息。在等待ACK期间。consumer是阻塞的。

异步提交:consumer提交了其消费完毕的一批消息的Offset给broker后,不需要等待broker的成功ACK.consumer可以直接获取并消费下一批消息。

消费突刺:由于Rebalance可能导致重复消费,如果重复消费的消息过多,或者因为Rebalance暂停时间过长从而导致积压了部分消息。那么可能会导致在Rebalance结束之后瞬间需要消费很多消息。

Rebalance产生的原因

导致Rebalance产生的原因:消费者锁定月的Queue数量发生变化,或消费组中消费者数量发生变化。

Rebalance过程

在Broker中维护着多个Map集合,这些集合中动态存放着当前Topic中Queue的信息、Consumer Group中COnsumer实例的信息。一旦发现消费者所订阅的Queue数量发生变化,或消费者组中消费者的数量发生变化,立即向Consumer Group 中每个实例发出Rebalance通知。

Consumer实例在接收到通知后会采用Queue分配算法自己获取到相应的Queue,即由Consumer实例自主进行Rebalance.

TopicConfigManager:key是topic名称,value是TopicConfig。TopicConfig中维护着该Topic中所有Queue的数据。

COnsumerManager:key是COnsumer Group Id,value是COnsumerGroupInfo。ConsumerGroupInfo中维护着该Group中所有COnsumer实例数据。

ConsumerOffsetManager:key为Topic与订阅该Topic的Group的组合,value是一个内层Map.内层Map的key为QueueID,内层Mao的value为该Queue的消费进度offset。

与Kafka对比

​ 在Kafka中,一旦发现出现了Rebalance条件,BROker会调用Group Coordinator来完成Rebalanace.Coordinator是Broker中的一个进程。Coordinator会在COnsumer Group中选一个Group Leader。由这个Leader根据自己本身组情况完成Patition分区的再分配。这个再分配结果会上报给Coordinator,并由Coordinator同步给Group中的所有COnsumer实例。

​ Kafka中的Rebalance是由COnsumer Leader完成的。而rocketMQ中的Rebalance是由每个COnsumer自身完成的,Group中不存在Leader.

4、Queue分配算法

一个Topic中的Queue只能由COnsumer Group 中的一个Consumer进行消费,他们之间的的配对有四种常见的策略,这些策略是通过创建Consumer时的构造器进去的。

平均分配算法

在这里插入图片描述

该算法是根据avg = QueueCount/COnsumerCount的计算结果进行分配的。如果能够整除,则按顺序将avg个Queue逐个分配COnsumer;如果不能整除,则将多余的Queye安装consumer顺序逐个分配。

该算法是先算好每个consumer分配的队列数,然后进行逐个分配。

环形平均策略

在这里插入图片描述

环形平均算法是指,根据消费者的顺序,一次在有Queue队列组成的环形图中逐个分配。

该算法不需要计算每个消费者分配几个,直接一个个分配过去。

一致性hash策略

在这里插入图片描述

该算法会将consumer的hash值作为Node节点存放到hash环上,然后将queue的hash值也放到hash环上,通过顺时针方向,距离queue最近的那个consumer就是该queue要分配的consumer.

该算法存在的问题:分配不均

同机房策略

在这里插入图片描述

该算法会根据queue的部署机房位置和consumer位置,过滤出当前consumer相同机房的queue,按照姘居分配策略或环形平均策略对所有queue进行分配。

对比

一致性hash算法存在的问题:

两种平均分配策略的分配效率较高,一致性hash策略的较低。因为一致性hash算法较复杂。另外,一致性hash策略分配的结果也很大可能上存在不平均的情况。

一致性算法存在的意义:

其可以有效减少由于消费者组扩容或缩容所带来的大量Rebalance。即队列与消费者的分配关系。

在这里插入图片描述

在这里插入图片描述

5、至少一次原则

RocketMQ有一个原则:每条消息必须要被成功消费一次。

Consumer在消费完消息后会向其消费进度记录器提交其消费消息的offset,offset被成功记录到记录器中,那么这条消息就被消费了。

注:对于集群模式来说,消费者本身就是消费进度记录器,对于广播模式来说,Broker就是消费进度记录器。

8、订阅关系的一致性

订阅关系的一致性值的是,同一个消费者组(Group ID相同)下所有Consumer实例所订阅的Topic与Tag及对消息的处理逻辑必须完全一致。否则,消息消费的逻辑就会混乱,甚至导致消息丢失。

1、正确订阅关系

多个消费者组订阅了多个Topic,并且每个消费者组里的多个消费者实例的订阅关系保持了一致。

在这里插入图片描述

2、错误订阅关系

一个消费者组订阅了多个Topic,但是该消费者组里的多个COnsumer实例的订阅关系并没有保持一致。

在这里插入图片描述

9、offset管理

消费进度offset是用来记录每个Queue的不同消费者组的消费进度。根据消费进度记录器的不同,可以分为两种模式:本地模式和远程模式。

1、 offset本地管理模式

​ 当消费者模式为广播消息时,offset使用本地模式存储。因为每条消息会被所有的消费者消费,每个消费者管理自己的消费进度,各个消费者之间不存在消费进度的交集。

​ Consumer在广播消费模式下offset相关数据以json的形式持久化到COnsumer本地磁盘文件中,默认存储路径为当前用户主目录下的.rocketmq_offsets/ c l i e n t I d / {clientId}/ clientId/{group}/Offsets.json。其中${clientId}为当前消费者id,默认为ip@DEFAULT;group为消费者组名称。

2、offset远程管理模式

​ 当消费模式为集群消费时,offset使用远程模式管理。因为所有COnsumer实例对消息采用的是均衡消费,所有COnsumer共享Queue为消费进度。

​ Consumer在集群消费模式下offset相关数据以json的形式持久化到Broker磁盘文件中,文件路径为当前用户目录下的store/config/confumerOffset.json。

​ Broker启动时会加载这个文件,并写入到一个双层Map.外层map的key为topic@group,value为内容map。内层map的key为queueId,value为offset。当发生Rebalance时,新的Consumer会从该Map中获取到相应的数据来继续消费。

3、offset用途

​ 消费者要消费的第一条消息的起始位置是通过用户孫通过consumer.setConsumerFromWhere()方法指定的。

​ 在Consumer启动后,器要消费的第一条消息的起始位置常用的有三种,这三种位置可以通过枚举类型常量设置。这个枚举类型为ConsumerFromWhere。

在这里插入图片描述

CONSUME_FROM_LAST_OFFSET:从queue的当前最后一条消息开始消费

CONSUME_FROM_FIRST_OFFSET:从queue的第一条消息开始消费

CONSUMER_FROM_TIMESTAMP:从指定的具体时间戳位置的消息开始消费。这个具体时间戳是通过另一个语句指定的。

consumer.setConsumerTimestamp(“20211204000000”)yyyyMMddHHmmss

​ 当消费完一批消息后,Consumer会提交其消息进度offset给Broker,Broker在收到消息进度后会将其更新到那个双层Map及consumerOffset.json文件中,然后向该COnsumer进行ACK,而ACK内容中包含三项数据:当前消费队列的最小offset(minOffset)、最大offset(maxOffset)及下次消息的起始offset(nextBeginOffset).

4、重试队列

在这里插入图片描述

​ 当rockerMQ对消息的消费出现异常时,会将发生异常的消息的offset提交到Broker中的重试队列。系统在发生消息消费异常时会为当前的Topic创建一个重试队列,该队列以%RETYT%开头,到达重试时间后进行消费重试。

5、offset的同步提交与异步提交

​ 集群模式下,Consumer消费完消息后会向Broker提交消费进度offset,其提交方式分为两种:

同步提交:消费者在消费完一批消息后想broker提交这些消息的offset,然后等待broker的成功响应。若在等待超时之前收到成功响应,则继续读取下一批消息进行消费。若没有收到响应,则会重新提交,直到获取到相应。而这个等待过程中,消费者是阻塞的。其严重影响了消费者的吞吐量。

异步提交:消费者在消费完一批消息后broker提交offset,但无需等待Broker的成功响应,可以继续读取并消费下一批。这种方式增加了消费者的吞吐量。但是,broker收到提交的offset后,还是会向消费者进行响应的。

10、消息幂等

1、什么是消息的幂等

​ 当出现消费者对某条消息重复消费的情况时,重复消费的结果与消费一次的结果是相同的,并且多次消费并未对业务系统产生任何负面影响,那么这个消费过程就是消费幂等的。

​ 在互联网应用中,尤其在网络不稳定的情况下,消息很有可能会出现重复发送或重复消费。如果重复的消息可能会影响业务处理,那么就应该对消息做幂等处理。

2、消息重复的场景分析

出现消息被重复消费,最常见的有以下三种情况:

发送是消息重复

​ 当一消息已被成功发送到Broker并完成持久化,此时会出现网络闪断,从而导致Broker对Producer应答失败。如果此时Producer意识到消息发送失败并尝试再次发送消息,此时Broker中就可能会出现两条内容相同并且Message ID也相同的消息,那么后续COnsumer就一定会消费两次该消息。

消费是消息重复

​ 消息已经投递到COnsumer并完成业务处理,当COnsumer给Broker反馈应道是网络闪断,Broker没有接收到消息成功响应。为了保证消息至少被消费一次的原则,Broker将在网络恢复后再次尝试投递之前已被处理过的消息。此时消费者就会收到与之前处理过的内容相同、Message ID也相同的消息。

Rebalance时消息重复

​ 当Consumer Group中的COnusmer数量发生变化时,或其订阅的Topic的Queue数量发生变化时,会触发Rebalance,此时Consumer可能会收到曾经被消费过的消息。

3、通用解决方案
两要素

幂等解决方案的设计中涉及到两项要素:幂等令牌与唯一性处理。只要充分利用好这两要素,就可以设计出好的幂等解决方案。

  • 幂等令牌:是生产者和消费者两着中的既定协议,通常指具备唯一业务标识的字符串
  • 唯一性处理:服务端通过采用一定的算法策略,保证同一个业务逻辑不会被重复执行成功多次。
解决方案

对于常见的系统,幂等行操作的通用性解决方案是:

  1. 首先通过缓存去重。如果缓存中如果已经存在了某幂等等令牌,则说明本次操作是重复性操作;若缓存没有命中,则进入下一步。

  2. 在唯一性处理之前,现在数据库中查询幂等令牌作为做阴的数据是否存在。若存在,则说明本次操作作为重复性操作;若不存在,则进入下一步。

  3. 在同一事物中完成三项操作;唯一性处理后,将幂等令牌写入到缓存,并将幂等令牌作为唯一标识的数据写入到DB中。

举例

以支付场景举例:

  1. 当支付请求到达后,首先在Redis缓存中获取key为支付流水号的缓存value。若value不空,则说明本次支付是重复操作,业务系统直接返回调用侧重复支付标识;若value为空,则进入下一步操作。
  2. 到DBMS中根据支付流水号查询是否存在相应案例。若存在,则说明本次支付是重复操作,业务系统直接返回调用侧重复支付标识;若不存在,则说明本次操作是首次操作,进入下一步完成唯一性处理
  3. 在分布式事务中完成三项操作:
    • 完成支付任务
    • 将当Ian支付流水号作为key,任意字符串作为value,通过set(key,value,expriTime)将数据写入到Redis缓存
    • 将当前支付流水号作为主键,与其它相关数据共同写入到DBMS
4、消费幂等的实现

​ 消费幂等的解决方案很简单:为消息指定不会重复的唯一标识。因为Messagfe ID有可能出现重复的情况,所以真正安全的幂等处理,不建议以Message ID作为处理依据。最好的方式是以业务唯一标识作为幂等处理的关键依据,而业务的唯一标识可以通过消息Key设置。

11、消息堆积于消息延迟

1、概念

​ 消息处理流程中,如果COnsumer的消费速度跟不上Producer的发送速度,MQ中未处理的消息会越来越多,这部分消息就被称为堆积消息。消息出现堆积进而会造成消息的消费延迟。一下场景需要重点关注消息堆积和消费延迟问题:

  • 业务系统上下游能力不匹配造成的持续堆积,且无法自行恢复。
  • 业务系统对消息的消费实时性要求较高,即使是短暂的堆积造成的消息延迟也是无法接受。
2、产生的原因

在这里插入图片描述

Consumer使用长轮询Pull模式消费消息时,分为以下两个阶段:

拉取消息

​ Consumer通过长轮询Pull模式批量拉取的方式从服务端获取消息,将拉取到的消息缓存到本地缓冲队列中。对于拉取式消费,在内网环境下会有很高的吞吐量,所以这一阶段不会成为消息堆积的瓶颈。

消息消费

​ Consumer将本地缓存的消息提交到消费线程中,使用业务消费逻辑对消息进行处理,处理完毕后获取到一个结果。这是真正的消息消费过程。此时COnsumer的消费能力就完全依赖于消息的消耗时和消费并发度。如果由于业务处理逻辑复杂等原因,导致处理单挑消息的耗时较长,则整体的消息吞吐量肯定不会高,此时就会导致COnsumer本地缓冲队列达到上限,停止从服务端拉取消息。

3、消费耗时

​ 影响消息处理时长的代码逻辑,可能主要产生于两种类型的代码:CPU内部计算型代码和外部I/O操作型代码。

​ 通常情况下代码中如果没有负责的递归和循环的话,内部计算耗时相对应外部I/O操作来说几乎可以忽略。所以外部IO型代码是影响消息处理时长的主要症结所在。

外部IO操作型代码举例:

  • 读写外部数据库,例如对远程MySQL的访问
  • 读写外部缓存系统,例如对远程Redis的访问
  • 下游系统调用,例如Dubbo的RPC远程调用,Spring Cloud的对下游系统的Http接口调用

关于下游系统调用逻辑需要进行提前梳理,掌握每个调用操作预期的耗时,这样做是为了能够判断消费逻辑中IO操作的耗时是否合理。通常每个调用操作预期的耗时,通常消息堆积是由于下游系统出现了服务异常达到了DBMS容量限制,导致消费耗时增加、

服务异常,并不仅仅是系统中出现了类似500这样的代码错误,而可能是更加隐蔽的问题。例如,网络带宽问题。

达到了DBMS容量限制,其也会引发消息的消费耗时增加。

4、消费并发度

​ 一般情况下,消费者端的消费并发度由单节点线程数和节点数量共同决定,其值为单节点线程数*节点数量。不过,通常需要优先调节单节点的线程数,若单击硬盘资源达到上限,则需要通过很想扩展来提高消费并发度

对于普通消息、延时消息即事务消息,并发度计算都是单节点线程数*节点数量。但对于顺序消息则是不同的。顺序消息的消费并发度等于Topic的Queue分区数量

1)全局顺序消息:该类型消息的Topic只有一个Queue分区。其可以保证该Topic的所有消息被顺序消费。为了保证这个全局顺序行,COnsumer Group中在同一时刻只能有一个Conusmer的一个线程进行消费。所以并发度为1.

2)分区顺序消息:改类型消息的Topic有多个Queue分区。其仅可以保证该Topic分区中的消息被顺序消费,不能保证整个Topic中消息的顺序消费。为了保证这个分区顺序性,每个Queue分区中的消息在Consumer Group中的同一时刻只能有一个consumer的一个线程进行消费。即,在同一时刻最多会出现多个Queue分区有Consumer的多个线程并行消费。所以其并发度为Topic的分区数量。

5、单机线程数计算

​ 对于一台主机中线程池中线程数的设置需要谨慎,不能盲目直接调大线程数,设置过大的线程数反而会带来大量的线程切换的开销。理想环境下节点的最优线程数计算模型为:C1*(T1+T2)/T1

  • C:CPU内核数
  • T1:CPU内部逻辑计算耗时
  • T2:外部IO操作耗时
6、如何避免

​ 为了避免在业务使用时出现非预期的消息堆积和消费延迟问题,需要在前期设计阶段对整个业务逻辑进行完善的排查和数量。其中最重要的就是梳理消息的消费耗时和设置消息消费的并发度。

梳理消息的消费耗时

​ 通过压测获取消息的消费耗时,并对耗时较高的操作的代码逻辑进行分析。梳理消息的消费耗时时需要关注以下信息:

  • 消息消费逻辑的计算复杂度是否过高,代码是否存在无限循环和递归等缺陷。
  • 消息消费逻辑中的I/O操作是佛U币徐的,能否本地缓存等方案规避。
  • 消费逻辑中的负责耗时的操作是否可以做异步化处理。
设置消息消费并发度

对于消息消费并发度的计算,可以通过以下两步实施:

  • 逐步调大单个Consumer节点的线程数,并观测节点的系统指标,得到单个节点最优的消费线程数和消息吞吐量。
  • 根据上下游链路的流量峰值计算出需要设置的节点数

12、消息的清理

​ 消息是被顺序存储在commitlog文件的,且消息大小不定长,所以消息的清理是不可能以消息为单位进行清理的,而是以commitlog文件为单位进行清理的。否则会极具下降清理效率,并实现逻辑复杂。

​ commitlog文件存在一个国企时间,默认为72小时,即三天。除了用户手动清理外,在一下情况下也会被自动清理,无论文件中的消息是否被消费过:

  • 文件国企,且到达清理时间点(默认为凌晨4点)后,自动清理过期文件
  • 文件过期,且磁盘空间占用率已达到过期清理警戒线(默认75%)后,无论是否达到清理时间点,都会自动清理过期文件
  • 磁盘占用率达到清理警戒线(默认85%)后,开始按照设定好的规则清理文件,无论是否过期。默认会从最老的文件开始清理。
  • 自盘占用率达到系统危险警戒线(默认90%)后,Broker将拒绝消息写入。

四、RocketMQ的应用

1、普通消息

1、消息发送分类

Producer对于消息的发送方式也有多种选择,不同的方式会产生不同的系统效果。

同步发送消息

​ 同步发送消息是指,Producer发出一条消息后,会收到MQ返回的ACK之后才发下一条消息。该方式的消息可靠性最高,单消息发送效率太低。

在这里插入图片描述

异步发送消息

异步发送消息是指,Producer发出消息后无需等待MQ返回ACK,直接发送下一条消息。该方式的消息可靠性得到保障,消息发送效率也可以。在这里插入图片描述

单向发送消息

​ 单向发送消息是指,Producer仅负责发送消息,不等待、不处理MQ的ACK。该发送方式时MQ也不返回ACK.该方式的消息发送效率最高,但可靠性较差。

在这里插入图片描述

代码讲解

发送请求的结果状态枚举类SendStatus

public enum SendStatus {
    SEND_OK,   //发送成功
    FLUSH_DISK_TIMEOUT,  //刷盘超时。当Broker设置的刷盘策略为同步刷盘时才可能出现这种异常状态。异步刷盘不会出现
    FLUSH_SLAVE_TIMEOUT,  //Slave同步超时。当Broker集群设置的Master-Slave的复制方式为同步复制时才可能出现这种异常状态。异步复制不会出现
    SLAVE_NOT_AVAILABLE;  //没有可用的Slave。当Broker集群设置为Master-Slave的复制方式为同步复制时才可能出现这种异常状态。异步复制不会出现

    private SendStatus() {
    }
}

2、顺序消息

1、什么是顺序消息

​ 顺序消息指的是严格按照消息的发送顺序进行消费的消息。

​ 默认情况下生产者会把消息以Round Robin轮询方式发送到不同的Queue分区队列;而消费消息是会从多个Queue上拉取消息,这种情况下的发送和消费是不能保证顺序的。如果将消息仅发送到同一个队列中,消费时也只从这个Queue上拉取消息,就严格保证了消息的顺序性。

2、有序性分类

​ 根据有序范围的不同,RocketMQ可以严格地保证两种消息的有序性:分区有序全局有序

全局有序

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当发送和消费参与的Queue只有一个时所保证的有序是整个Topic中消息的顺序,称为全局有序。

分区有序

在这里插入图片描述

如果有多个Queue参与,其保证在该Queue分区队列上的消息顺序,则称为分区有序。

在定义Producer时我们可以指定消息队列选择器,而这个选择器是我们自己实现了MessageQueueSelector接口定义的。

在定义选择器的选择算法时,一般需要使用选择key。这个选择key可以是消息key,也可以是其他数据,但是最重要的是,都不能重。

一般性的选择算法是,让选择key与该topic与太Topic与该Topic所包含的Queue的数量去模,其结果即为选择出的Queue的QueueId.

取模算法存在一个问题:不同选择key与Queue数量取模结果可能会是相同的,即不同选择key的消息可能会出现在相同Queue,即同一个consumer可能会消费到不同选择key的消息。这个问题如何解决?一般性的做法是,从消息中获取到选择key,对其进行判断。若是当前COnsumer需要消费的消息,则直接消费,否则,什么也不做处理。这种做法要求选择key要能够随着消息一起呗COnsumer获取到。此时使用消息key作为选择key是比较好的做法。

以上做法会不会出现新的问题呢?不属于那个COnsumer的消息被拉取走了,那么应该消费该消息的Consumer是否还能再消费该消息呢?同一个Queue中的消息不可能被同一个Group中的不同Consumer同时消费。所以,消费先一个Queue的不同选择key的消息的Consumer一定属性不同的Group.而不同的Group中COnsumer间的消费是相互隔离的,互不影响的。

3、延迟消息

1、什么是延时消息

当消息写入达到Broker后,在指定的市场后才可被消费处理掉,称为延时消息。

采用RcketMQ的延时消息可以实现定时任务的功能,而无需使用定时器。典型的应用场景是,电商中超时未支付关闭订单的场景。

2、延时等级

延时消息的延时时长不支持随意时长的延迟,是通过特定的延迟等级来指定的。延时等级定义在RocketMQ服务端的MessageStoreConfig类中的如下变量中:

在这里插入图片描述

即,若指定的延时等级为3,则表示延迟时长为10s。

当然,如果需要自定义延时等级,可以通过在broker加载的配置中新增如下配置(例如下面增加了1甜这个等级),配置文件在ROcketMQ安装目录下的conf目录中

在这里插入图片描述

3、延时消息实现原理

在这里插入图片描述

Producer将消息发送到Broker后,Broker会首先将消息写入到commitlog文件,然后需要将其分发到相应的consumequeue.不过,在分发之前,系统会先判断消息中是否带有延时等级。若没有,则直接正常分发;若有则需要经历一个复杂的过程:

  • 修改消息的Topic为SCJEDULE_TOPIC
  • 根据延时等级,在consumequeue目录中SCHEDULE_TOPIC_XXX主题下创建相应的queueId目录与consumequeue文件(如果没有这些目录与文件的话)。

延迟等级delayLevel与queueId的对应关系为queueId=delayLevel-1

在这里插入图片描述

  • 修改消息索引单元内容。索引单元中的MEssage TAg HashCode部分原本存放的是消息的Tag的Hash值。现修改为消息的投递时间。投递时间是指该消息被重新修改为原Topic后再次被写入到commitlog中的时间,投递时间=消息存储时间+延时等级时间,消息时间指的是消息被发送到Broker时的时间戳。
  • 将消息索引写入到SCHEDULE_TOPIC_XXX主题下相应的consumequeue中

SCHDEULE_TOPIC_XXXX目录中各个延时等级Queue中的消息是如何排序的?

是按照消息投递时间排序的。一个Broker中同一等级的所有延时消息会被写入到consumequeue目录中SCHEDULE_TOPIC_XXXX目录下相同Queue中。即一个Queue中消息投递时间的延迟等级时间是相同的。那么投递是啊金就取决于消息存储时间了。即按照消息被发送到Broker的是啊金进行排序的。

投递延时消息

Broker内部有一个延迟消息服务类SchelueMessageService,其会消费SCHEDULE_TOPIC_XXX中的消息,即按照每条消息的投递时间,将延时消息投递到目标Topic中。不过在投递之前会从commitlog中将原来写入的消息再次读出。并将原来的延时等级设置为0,即原消息变为一条不延迟的普通消息。然后再次将消息投递到目标Topic中.

SchelueMessageService在Broker启动时,会创建并启动一个定时器Timer。用于执行相应的定时任务。系统会根据延时等级的个数,定义相应数量的TimerTask,每个TimerTask负责一个延迟等级消息的消费与投递。每个TimerTask都会检测相应Queue队列的第一条消息是否到期。若第一条消息未到期,则后面的所有消息更不会到期(消息是按照投递时间排序的);若第一条消息到期了,则将该消息投递到姆博阿Topic,即消费该消息。

将消息重新如写入到commitlog

延迟消息服务类将延迟消息再次发送给commitlog,并再次形成新的消息索引条目,分发到相应Queue.

4、事务消息

1、问题引入

在这里插入图片描述

2、解决思路

解决思路是,让第1、2、3步具有原子性,要么全部成功,要么全部失败。即消息发送成功后,必须要 保证扣款成功。如果扣款失败,则回滚发送成功的消息。而该思路即使用事务消息。这里要使用分布 式事务解决方案。

在这里插入图片描述

使用事务消息来处理该需求场景:

  1. 事务管理器TM向事务协调器TC发起指令,开启全局事务

  2. 工行系统发一个给B增款1万元的事务消息M给TC

  3. TC会向Broker发送半事务消息prepareHalf,将消息M预提交到Broker。此时的建行系统是看 不到Broker中的消息M的

  4. Broker会将预提交执行结果Report给TC。

  5. 如果预提交失败,则TC会向TM上报预提交失败的响应,全局事务结束;如果预提交成功,TC会 调用工行系统的回调操作,去完成工行用户A的预扣款1万元的操作

  6. 工行系统会向TC发送预扣款执行结果,即本地事务的执行状态

  7. TC收到预扣款执行结果后,会将结果上报给TM。

    预扣款执行结果存在三种可能性:

    // 描述本地事务执行状态

    public enum LocalTransactionState {

    ​ COMMIT_MESSAGE, // 本地事务执行成功

    ​ ROLLBACK_MESSAGE, // 本地事务执行失败

    ​ UNKNOW, // 不确定,表示需要进行回查以确定本地事务的执行结果

    }

    8.TM会根据上报结果向TC发出不同的确认指令

    • 若预扣款成功(本地事务状态为COMMIT_MESSAGE),则TM向TC发送Global Commit指令
    • 若预扣款失败(本地事务状态为ROLLBACK_MESSAGE),则TM向TC发送Global Rollback指令
    • 若现未知状态(本地事务状态为UNKNOW),则会触发工行系统的本地事务状态回查操作。回 查操作会将回查结果,即COMMIT_MESSAGE或ROLLBACK_MESSAGE Report给TC。TC将结果上 报给TM,TM会再向TC发送最终确认指令Global Commit或Global Rollback

    9.TC在接收到指令后会向Broker与工行系统发出确认指令

    • TC接收的若是Global Commit指令,则向Broker与工行系统发送Branch Commit指令。此时 Broker中的消息M才可被建行系统看到;此时的工行用户A中的扣款操作才真正被确认

    • TC接收到的若是Global Rollback指令,则向Broker与工行系统发送Branch Rollback指令。此时 Broker中的消息M将被撤销;工行用户A中的扣款操作将被回滚

      以上方案就是为了确保消息投递与扣款操作能够在一个事务中,要成功都成功,有一个失败, 则全部回滚。

      以上方案并不是一个典型的XA模式。因为XA模式中的分支事务是异步的,而事务消息方案中的 消息预提交与预扣款操作间是同步的。

3、基础

分布式事务

​ 对于分布式事务,通俗地说就是,一次操作由若干分支操作组成,这些分支操作分属不同应用,分布在不同服务器上。分布式事务需要保证这些分支操作要么全部成功,要么全部失败。分布式事务与普通事务依旧,就是为了保证操作结果的一致性。

事务消息

​ RocketMQ提供了类似X/Open XA的分布式事务功能,通过事务消息能达到分布式事务的最终一致。XA是一种分布式事务解决方案,一种分布式事务处理模式。

半实物消息

​ 暂不能投递的消息,发送方已经成功地将下次发送到Broker,但是Broker收到的最终确认指令,此时该消息被标记成“咱不能投递”状态,即不能被消费者看到。除于该种状态下的消息即半事务消息。

本地事务状态

​ Producer回调操作指定的结果为本地事务状态,其会发送给TC,而TC会再发送给TM。TM会根据TC发送来的本地事务状态来决定全局事务确认指令。

在这里插入图片描述

消息回查

在这里插入图片描述

消息回查,即重新查询本地事务的执行状态。本例就是重新到DB中查询预扣款操作是否执行成功

意,消息回查不是重新执行回调操作。回调操作是进行预扣款操作,而消息回查则是查看预 扣款操作执行的结果。

引发消息回查的原因最常见的有两个:

1)回调操作返回INKNWON

2)TC没有接收到TM的最终全局事务确认指令

RocketMQ中消息回查设置

关于消息回查,有三个最常见的属性设置。他们都在broker加载的配置文件中设置,例如:

  • transactionTimeout=20,指定TM在20秒内应将最终确认状态发送给TC,否则引发消息回查。默 认为60秒
  • transactionCheckMax=5,指定最多回查5次,超过后将丢弃消息并记录错误日志。默认15次。
  • transactionCheckInterval=10,指定设置的多次消息回查的时间间隔为10秒。默认为60秒。

4、XA模式

XA协议

​ XA(Unix Transaction)是一种分布式事务解决方案,一种分布式事务处理模式,是基于XA协议的。XA协议由Tuxedo(Transaction for Unix has been Extended for Distributed Operation,分布式操作扩 展之后的Unix事务系统)首先提出的,并交给X/Open组织,作为资源管理器与事务管理器的接口标准。

XA模式中有三个重要组件:TC、TM、RM。

TC

Transaction Coordinator,事务协调者。维护全局和分支事务的状态,驱动全局事务提交或回滚。

RocketMQ中Broker充当着TC.

TM

Transaction Manager,事务管理器。定义全局事务的范围:开始全局事务、提交或回滚全局事务。它实际是全局事务的发起者。

RockerMQ中事务消息的Producer充当着TM.

RM

Resource Manager,资源管理器。管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

RocketMQ中事务消息的Producer及Broker均是RM

5、XA模式架构

在这里插入图片描述

XA模式是一个典型的2PC,其执行原理如下:

  1. TM向TC发起指令,开启一个全局事务。
  2. 根据业务要求,各个RM会逐个向TC注册分支事务,然后TC会逐个向RM发出与执行指令。
  3. 各个RM在接受到指令后在进行本地事务与执行。
  4. RM将执行结果Report给TC。当然,这个结果可能是成功,也可能是失败。
  5. TC在接收到各个RM的report后会将汇总结果上报给TM,根据汇总结果TM会向TC发出确认指令
    • 若所有结果都是成功响应,则向TC发送Global Commit指令
    • 只要有结果是失败响应,则向TC发送Global Rollback.
  6. TC在接受到指令后再次向RM发送确认指令。

事务消息方案并不是一个典型的XA模式。因为XA模式中的分支事务是异步的,而事务消息方案中的消息预提交与预口看操作间是同步的。

6、注意

  • 事务消息不支持延时消息
  • 对于事务消息要做好幂等性检查,因为事务消息可能不止一次被消费(因为存在回滚后再提交的情况)

5、批量消息

1、批量发送消息

发送限制

生产者进行消息发送时可以一次发送多条消息,这可以大大提升Producer的发送效率。不过需要注意以下几点:

  • 批量发送的消息必须具有相同的Topic

  • 批量发送的消息必须具有胸痛的刷盘策略

  • 批量发送的消息不能是延时消息与事务消息。

批量发送大小

默认情况下,一批发送的消息总大小不能超过4MB字节。如果想超出该值,优良中解决方案:

  • 方案一:将批量消息进行拆分,拆分为若干不大于4M的消息集合分多次批量发送

  • 方案二:在Producer端与Broker端修改属性

    Producer端需要在发送之前设置Producer的maxMessageSize属性

    Broker端需要修改其加载的配置文件中的maxMessageSize属性

    生产者发送的消息大小

    在这里插入图片描述

生产者通过send()方法发送的Message,并不是直接将Message序列化后发送到网络上的,而是通过这个Message生成了一个字符串发送出去的。这个字符串由四部分构成:Topic、消息Body、消息日志(占20字节),及用于描述消息的一对属性Key-value。这些属性中包含例如生产者地址、生产时间、要发送的QueueId等。最终写入到Broker中消息单元中的数据都是来自于这些属性。

2、批量消息消费

修改批量属性

在这里插入图片描述

Consumer的MessageListenerConsurrently监听接口的consumeMessage()方法的第一个参数为消息列表,但默认情况下每次只能消费一条消息。若要使其一次可以消费多条消息。则可以通过修改COnsumer的consumeMessageBatchMax属性来执行。不过,该值不能超过32。因为默认情况下消费者每次可以拉取的消息最多是32条。若要修改一次拉取的最大值,则可以通过修改COnsumer的pullBatchSize属性来指定。

存在的问题

Consumer的pullBatchSize属性与consumeMessageBatchMaxSize属性是否设置的越大越好?当然不是。

  • pullBatchSize值设置的越大,COnsumer没拉取一次需要的时间就会越长,且在网络上传输出现问题的可能性就越高。若在拉取过程中出现了问题,那么本批次所有消息都需要全部重新拉取。
  • consumeMessageBatchMaxSize值越大,COnusmer的消息并发消费能力越低,且这批被消费的消息具有相同的消费结果。因为consumeMessageMaxSize指定的一批消息只会使用一次线程进行处理,且在处理过程中只要有一个消息处理异常,则这批消息需要重新再次消费处理。

6、消息过滤

​ 消费者在进行消息订阅时,除了可以指定要订阅的Topic外,还可以对指定Topic中的消息根据指定条件进行过滤,即可以订阅比Topic更加细粒度的消息类型。

​ 对于指定Topic消息的过滤有两种过滤方式:Tag过滤与SQL过滤。

1、Tag过滤

​ 通过consumer的subdcribe()方法指定要订阅消息的Tag.如果订阅多个Tag的消息,Tag间使用或运算符(||)连接

!在这里插入图片描述

2、SQL过滤

​ SQL过滤是一种通过特定表达式对事先埋入到消息中的用户属性进行筛选过滤的方式。通过SQL过滤,可以实现对消息的复杂过滤。不过,只有使用PUSH模式的消费者才能使用SQL过滤。

SQL过滤表达式中支持多种常量类型与运算符。

支持的常量类型:

  • 数值:比如:123,3.1415
  • 字符:必须用单引号包裹起来,比如:‘abc’
  • 布尔:TRUE或FALSE
  • NULL:特殊的常量,表示空

支持的运算符有:

  • 数值比较:>,>=,<,<=,BEWTEEN,=

  • 字符比较:=,<>,IN

  • 逻辑运算:AND,OR,NOT

  • NULL判断:IS NULL 或IS NOT NULL

默认情况下Broker没有开启消息的SQL过滤功能,需要Broker加载的配置文件中添加如下属性,以开启改功能:

在这里插入图片描述

在启动Broker时需要指定这个修改过的配置文件。例如对于单击Broker的启动,器修改的配置文件是conf/broker.conf,启动时使用如下命令:

在这里插入图片描述

7、消息发送重试机制

1、说明

​ Producer对发送失败的消息进行重新发送的机制,称为消息发送重试机制,也称为消息重投机制。

​ 对于消息重投,需要注意以下几点:

  • 生产者在发送消息时,若采用同步或异步发送方式,发送失败会重试,但oneway消息发送方式发送失败是没有重试机制的

  • 只有普通消息具有发送重试机制,顺序消息是没有的。

  • 消息重投机制可以保证消息尽可能发送成功、不丢失,单可能会造成消息重复。消息重复在RocketMQ中是无法避免的问题。

  • 消息重复在一般情况下不会发生,当出现消息量大、网络抖动,消息重复在RocketMQ中是无法避免的问题。

  • 消息重复在一般情况下不会发生,当出现消息量大、网络抖动,消息重复就会成为大概率时间

  • producer主动重发、consumer负载边变化(发生Rebalance,不会导致消息重复,但可能出现重复消费)也会导致重复消息

  • 消息重复无法避免,但要避免消息的重复消费

  • 避免消息重复消费的解决方案是,为消息添加唯一标识(例如消息Key),是消费者对消息进行消费判断来避免重复消费

  • 消息发送重试有三种策略可以选择:同步发送方失败策略、异步发送失败策略,消息刷盘失败策略

2、同步发送失败策略

​ 对于普通消息,消息发送方默认采用round-robin策略来选择所发送到的队列。如果发送失败, 偶人重试2次。但在重试时是不会选择上次发送失败的Broker,而是选择其他Broker.当然。若只有一个Broker其也只能发送到该Broker,但其会尽量发送方到该Broker上的其他Queue.

// 创建一个producer,参数为Producer Group名称
DefaultMQProducer producer = new DefaultMQProducer("pg");
// 指定nameServer地址
producer.setNamesrvAddr("rocketmqOS:9876");
// 设置同步发送失败时重试发送的次数,默认为2次
producer.setRetryTimesWhenSendFailed(3);
// 设置发送超时时限为5s,默认3s
producer.setSendMsgTimeout(5000);

同时,Broker还具有失败隔离功能,使Producer尽量选择未发生过发送失败的Broker作为目标 Broker。其可以保证其它消息尽量不发送到问题Broker,为了提升消息发送效率,降低消息发送耗时。

思考:让我们自己实现失败隔离功能,如何来做?

  1. 方案一:Producer中维护某JUC的Map集合,其key是发生失败的时间戳,value为Broker实 例。Producer中还维护着一个Set集合,其中存放着所有未发生发送异常的Broker实例。选择目 标Broker是从该Set集合中选择的。再定义一个定时任务,定期从Map集合中将长期未发生发送 异常的Broker清理出去,并添加到Set集合。
  2. 方案二:为Producer中的Broker实例添加一个标识,例如是一个AtomicBoolean属性。只要该 Broker上发生过发送异常,就将其置为true。选择目标Broker就是选择该属性值为false的 Broker。再定义一个定时任务,定期将Broker的该属性置为false。
  3. 方案三:为Producer中的Broker实例添加一个标识,例如是一个AtomicLong属性。只要该 Broker上发生过发送异常,就使其值增一。选择目标Broker就是选择该属性值最小的Broker。若 该值相同,采用轮询方式选择。

如果超过重试次数,则抛出异常,由Producer去保证消息不丢。当然当生产者出现 RemotingException、MQClientException和MQBrokerException时,Producer会自动重投消息。

3 异步发送失败策略

​ 异步发送失败重试时,异步重试不会选择其他broker,仅在同一个broker上做重试,所以该策略无法保 证消息不丢。

DefaultMQProducer producer = new DefaultMQProducer("pg");
producer.setNamesrvAddr("rocketmqOS:9876");
// 指定异步发送失败后不进行重试发送
producer.setRetryTimesWhenSendAsyncFailed(0);

4、消息刷盘失败策略

​ 消息刷盘超时(Master或Slave)或slave不可用(slave在做数据同步时向master返回状态不是 SEND_OK)时,默认是不会将消息尝试发送到其他Broker的。不过,对于重要消息可以通过在Broker 的配置文件设置retryAnotherBrokerWhenNotStoreOK属性为true来开启。

8、消息消费重试机制

1、顺序消息的消费重试

​ 对于顺序消息,当Consumer消费消息失败后,为了保证消息的顺序性,其会自动不断地进行消息重 试,直到消费成功。消费重试默认间隔时间为1000毫秒。重试期间应用会出现消息消费被阻塞的情 况。

DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("cg");
// 顺序消息消费失败的消费重试时间间隔,单位毫秒,默认为1000,其取值范围为[10,
30000]
consumer.setSuspendCurrentQueueTimeMillis(100);

由于对顺序消息的重试是无休止的,不间断的,直至消费成功,所以,对于顺序消息的消费, 务必要保证应用能够及时监控并处理消费失败的情况,避免消费被永久性阻塞。

注:顺序消息没有发送失败重试机制,但具有消费失败重试机制

2、无序消息的消费重试

​ 于无序消息(普通消息、延时消息、事务消息),当Consumer消费消息失败时,可以通过设置返回 状态达到消息重试的效果。不过需要注意,无序消息的重试只对集群消费方式生效,广播消费方式不 提供失败重试特性。即对于广播消费,消费失败后,失败消息不再重试,继续消费后续消息。

3、消息重试次数与间隔

​ 对于无序消息集群消费下的重试消费,每条消息默认最多重试16次,但每次重试的间隔时间是不同 的,会逐渐变长。每次重试的间隔时间如下表。

在这里插入图片描述

若一条消息在一直消费失败的前提下,将会在正常消费后的第4小时46分后进行第16次重试。 若仍然失败,则将消息投递到死信队列

修改消费重试次数

efaultMQPushConsumer consumer = new DefaultMQPushConsumer("cg");
// 修改消费重试次数
consumer.setMaxReconsumeTimes(10);

对于修改过的重试次数,将按照以下策略执行:

  • 若修改值小于16,则按照指定间隔进行重试

  • 若修改值大于16,则超过16次的重试时间间隔均为2小时

对于Consumer Group,若仅修改了一个Consumer的消费重试次数,则会应用到该Group中所有 其它Consumer实例。若出现多个Consumer均做了修改的情况,则采用覆盖方式生效。即最后被 修改的值会覆盖前面设置的值。

4、重试队列

​ 对于需要重试消费的消息,并不是Consumer在等待了指定时长后再次去拉取原来的消息进行消费,而 是将这些需要重试消费的消息放入到了一个特殊Topic的队列中,而后进行再次消费的。这个特殊的队 列就是重试队列。

​ 当出现需要进行重试消费的消息时,Broker会为每个消费组都设置一个Topic名称 为%RETRY%consumerGroup@consumerGroup 的重试队列。

1)这个重试队列是针对消息才组的,而不是针对每个Topic设置的(一个Topic的消息可以让多 个消费者组进行消费,所以会为这些消费者组各创建一个重试队列)

2)只有当出现需要进行重试消费的消息时,才会为该消费者组创建重试队列

在这里插入图片描述

注意,消费重试的时间间隔与延时消费的延时等级十分相似,除了没有延时等级的前两个时间 外,其它的时间都是相同的

Broker对于重试消息的处理是通过延时消息实现的。先将消息保存到SCHEDULE_TOPIC_XXXX延迟队 列中,延迟时间到后,会将消息投递到%RETRY%consumerGroup@consumerGroup重试队列中。

5、消息重试配置方式

在这里插入图片描述

群消费方式下,消息消费失败后若希望消费重试,则需要在消息监听器接口的实现中明确进行如下三 种方式之一的配置:

  • 方式1:返回ConsumeConcurrentlyStatus.RECONSUME_LATER(推荐)
  • 方式2:返回Null
  • 方式3:抛出异常

6、消费不充实配置方式

在这里插入图片描述

在这里插入图片描述

9、死信队列

1、什么是死信队列

​ 当一条消息初次消费失败,消息队列会自动进行消费重试;达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息,此时,消息队列不会立刻将消息丢弃,而是将其发送到该消费者对应的特殊队列中。这个队列就是死信队列(Dead-Letter Queue,DLQ),而其中的消息则称为死信消息(Dead-Letter Message,DLM)。

死信队列是用于处理无法被正常消费的消息的

2、死信队列的特征

死信队列具有如下特征:

  • 死信队列中的消息不会再被消费者正常消费,即DLQ对于消费者是不可见的
  • 死信存储有效期与正常消息相同,均为3天(commitlog文件的过期时间),3天后会被自动删除
  • 死信队列就是一个特殊的Topic,名称为%DLQ%consumerGroup@consumerGroup,即每个消费者组都有一个死信队列
  • 如果一个消费者组未产生死信消息,则不会为其创建相应的死信队列

3、死信消息的处理

​ 实际上,当一个消息进入死信队列,就意味着系统中某些地方出现了问题,从而导致消费者无法正常消费该消息,比如代码中原本就存在的Bug.因此,对于死信消息,通常需要开发人员进行特殊处理。最关键的不走是排查bug,然后将原来的死信消息重新进行消费

  • 20
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
详细了解RocketMQ的消息推送教程,你可以通过访问官方网站上的文档来获取更多信息。 在该文档中,你将找到详细的操作步骤和示例代码。 首先,你需要在项目的pom文件中引入RocketMQ的jar包。具体操作如下:将以下代码添加到pom文件的dependencies标签中: ```xml <!--rocketmq--> <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-client</artifactId> <version>4.2.0</version> </dependency> ``` 接下来,你需要设置RocketMQ的安装目录和RocketMQ的地址。你可以通过设置环境变量或在代码中进行设置。例如,在Windows系统中,你可以设置ROCKETMQ_HOME环境变量为RocketMQ的安装目录的上一级目录。同时,你需要设置NAMESRV_ADDR环境变量为RocketMQ的地址,例如localhost:9876。 一旦你完成了这些准备工作,你可以开始使用RocketMQ进行消息推送。你可以根据官方文档中的示例代码来编写你的应用程序。文档中详细介绍了如何创建Producer、发送消息和处理发送结果等操作。 通过这些步骤,你就可以开始使用RocketMQ进行消息推送了。记得参考官方文档以获取更多详细信息和示例代码。 <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [分布式消息队列RocketMQ详细下载安装教程](https://blog.csdn.net/weixin_45703155/article/details/126395300)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [springBoot项目部署rocketmq,使用mq消息对了实现微信公众号客服消息推送](https://blog.csdn.net/FengRenYuanDeFZ/article/details/114577203)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值