rocketMQ —— 02(集群搭建、rocketmq工作原理)

本文详细介绍了RocketMQ的集群模式(单Master、多Master多Slave异步/同步)、搭建步骤、消息发送样例、工作原理(生产与消费过程、存储策略、Rebalance机制等),以及关键概念如消费幂等、offset管理。

一、相关推荐

rocketMQ —— 01(前言、安装、基础概念)

二、基本架构图:

在这里插入图片描述

Name server、producer、consumer是无状态的,集群中的每个节点都是一样的。
Broker有两种,master和slave。master主要复制写,slave只能读。
一个slave只能属于一个master。

三、集群模式

在这里插入图片描述

  • 复制策略:(主从复制)

复制策略是指将Master的数据复制到Slave。并且分为同步复制和异步复制。
同步复制:消息写入master后,master会等待slave同步数据成功后才向producer返回成功ACK(确认信息)
异步复制:消息写入master后,master立即向producer返回成功ACK,无需等待slave同步数据成功

  • 刷盘策略:(将消息刷入磁盘,实现持久化)
    刷盘策略指的是broker中消息的落盘方式,即消息发送到broker内存后消息持久化到磁盘的方式。分为同步刷盘与异步刷盘。

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

1)异步刷盘和异步复制都会降低系统的写入延迟,RT变小,提高了系统的吞吐量
2)消息写入到Broker的内存,一般是写入到了PageCache
3)对于异步 刷盘策略,消息会写入到PageCache后立即返回成功ACK。但并不会立即做落盘操作,而是当PageCache到达一定量时会自动进行落盘。

1、单Master模式(这种单节点的理论上不叫集群)

这种方式风险较大,可用性不高,一旦Broker重启或者宕机时,会导致整个服务不可用。不建议线上环境使用,可以用于本地测试。

2、多Master模式

一个集群无Slave,全是Master,例如2个Master或者3个Master,这种模式的优缺点如下:

优点:配置简单,单个Master宕机或重启维护对应用无影响,在磁盘配置为RAID10时,即使机器宕机不可恢复情况下,由于RAID10磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢),性能最高;
缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅消息实时性会受到影响。

3、多Master多Slave模式(异步)

每个Master配置一个Slave,有多对Master-Slave,HA采用异步复制方式,主备有短暂消息延迟(毫秒级),这种模式的优缺点如下:

优点:即使磁盘损坏,消息丢失非常少,且消息实时性不会受影响,同时Master宕机后,消费者仍然可以从Slave消费,可用性比较高,而且此过程对应用透明,不需要人工干预,性能同多Master模式几乎一样。
缺点:Master宕机,磁盘损坏情况下会丢失少量消息。(由于异步从机可能还未来得及复制,主机就宕机了。这样可能会丢失少量数据。)

4、多Master多Slave模式(同步)

每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写方式,即只有主、从都写成功,才向应用返回成功,这种模式的优缺点如下:

优点:数据与服务都无单点故障,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高;
缺点:性能比异步复制模式略低(大约低10%左右),发送单个消息的RT会略高,且目前版本在主节点宕机后,从机不具备反客为主,不能自动切换为主机。

5、开始搭建(多Master多Slave模式)

5.1、准备(!!搭建失败了)

这里因为只有一台主机,所有通过端口号区分不同的节点。

为了看起来更像两台主机,我们修改一下hosts。
在这里插入图片描述
修改etc/hosts
修改hosts有什么用
在这里插入图片描述
注册中心:nameserver
127.0.0.1:9876
127.0.0.1:9877

broker:
master:
127.0.0.1:10911
127.0.0.1:10912
slave:
127.0.0.1:11011
127.0.0.1:11012

producer:

consumer:

注意:理论上搭建集群,有以下原则。多个主节点分布在不同ip下,主节点和从节点,不在同一个ip下。这样保证一台主机宕机后,不会出现微服务集体失效。

5.2、修改Brocker配置文件

官方为我们提供了几种方式的实例配置文件。

[root@iz2zedg4ylq9iqtwm11wecz conf]# pwd
/my/rocketMQ/rocketmq-all-4.5.0-bin-release/conf
[root@iz2zedg4ylq9iqtwm11wecz conf]# ls
2m-2s-async(双主双从异步)  2m-noslave (双主)   dledger             logback_namesrv.xml  plain_acl.yml
2m-2s-sync(双主双从同步)    broker.conf  logback_broker.xml  logback_tools.xml    tools.yml

查看其中一个配置文件

[root@iz2zedg4ylq9iqtwm11wecz conf]# cd 2m-2s-sync/
[root@iz2zedg4ylq9iqtwm11wecz 2m-2s-sync]# ls
broker-a.properties(主a)  broker-a-s.properties(a的从)  broker-b.properties (主b) broker-b-s.properties(b的从)
[root@iz2zedg4ylq9iqtwm11wecz 2m-2s-sync]#
  1. 修改broker-a.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=0
#nameServer地址,分号分割,这里的rocketmq1和2是同一台主机,在etc/hosts中配置了ip的地址映射
namesrvAddr=rocketmq1:9876;rocketmq2:9877
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10911
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store
#commitLog 存储路径
storePathCommitLog=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store/consumequeue
#消息索引存储路径
storePathIndex=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store/index
#checkpoint 文件存储路径
storeCheckpoint=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store/checkpoint
#abort 文件存储路径
abortFile=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
  1. 修改broker-b.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=0
#nameServer地址,分号分割,这里的rocketmq1和2是同一台主机,在etc/hosts中配置了ip的地址映射
namesrvAddr=rocketmq1:9876;rocketmq2:9877
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10912
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store-b
#commitLog 存储路径
storePathCommitLog=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store-b/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store-b/consumequeue
#消息索引存储路径
storePathIndex=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store-b/index
#checkpoint 文件存储路径
storeCheckpoint=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store-b/checkpoint
#abort 文件存储路径
abortFile=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store-b/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
  1. 修改配置broker-a-s.properties

#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=1
#nameServer地址,分号分割
namesrvAddr=rocketmq1:9876;rocketmq2:9877
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=11011
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store-a
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store-a/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store-a/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store-a/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store-a/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store-a/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SLAVE
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
  1. 修改配置broker-b-s.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=1
#nameServer地址,分号分割
namesrvAddr=rocketmq1:9876;rocketmq2:9877
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=11012
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store-b
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store-b/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store-b/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store-b/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store-b/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store-b/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SLAVE
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
5.3、启动
  1. 启动nameserver
[root@iz2zedg4ylq9iqtwm11wecz ~]# nohup sh mqnamesrv &
[1] 10096
[root@iz2zedg4ylq9iqtwm11wecz ~]# nohup: ignoring input and appending output to ‘nohup.out’

[root@iz2zedg4ylq9iqtwm11wecz ~]# jps
4562 newideas-0.0.1-SNAPSHOT.jar
10099 NamesrvStartup
997 
10124 Jps

  1. 启动Broker
[root@iz2zedg4ylq9iqtwm11wecz conf]# nohup sh mqbroker -c /my/rocketMQ/rocketmq-all-4.5.0-bin-release/conf/2m-2s-sync/broker-a.properties &
[2] 10545
[root@iz2zedg4ylq9iqtwm11wecz conf]# nohup: ignoring input and appending output to ‘nohup.out’

[root@iz2zedg4ylq9iqtwm11wecz conf]# jps
4562 newideas-0.0.1-SNAPSHOT.jar
10099 NamesrvStartup
10549 BrokerStartup
997 
10616 Jps

四、消息发送样例

1、导入pom依赖

<dependencies>
<!--        导入rocketmq客户端依赖-->
   <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-client</artifactId>
        <version>4.4.0</version>
    </dependency>
</dependencies>

2、运行测试

package com.lihua.rocketmq.producer;

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;

/**
 * 生产者发送同步消息
 * @author 15594
 */
public class SyncProducer {

    public static void main(String[] args) throws Exception {
        //实例化消息生产者producer,group1表示这个生产者属于哪个集群
        DefaultMQProducer producer = new DefaultMQProducer("group1");

        //注册到注册中心
        producer.setNamesrvAddr("39.96.52.225:9876");
        //启动producer实例
        producer.start();
        for (int i = 0; i < 100; i++) {
            //创建消息,并指定Topic,Tag和消息体。topic:主题,标志了消息的类型 tags:标签,第二主题,子主题 keys:消息关键词。messagebody:具体的消息内容。
            //注意:在rocketmq里面所有消息都必须以二进制传输
            Message msg = new Message("TopicTest" , "TagA" , ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
            // 发送消息到一个Broker
            SendResult sendResult = producer.send(msg);
            // 通过sendResult返回消息是否成功送达
            System.out.printf("%s%n", sendResult);
        }
        // 如果不再发送消息,关闭Producer实例。
        producer.shutdown();
    }


}

在这里插入图片描述

五、rocketmq的工作原理

在这里插入图片描述
rocketmq的工作原理分三个部分(消息生产过程、消息存储过程、消息消费过程)讲解。

1、消息生产过程

  1. 首先 Producer或者Producer Group(生产者集群)向Name Server(注册中心)发出请求获得消息Topic的路由信息。简而言之:Producer 只能跟Name Server联系,然后会得到向Name Server中注册了的Broker的信息。有了这些信息(路由表的信息,因为Broker不可能只有一台,一般是集群)才能向Broker存储Topic消息。
  2. 联系上Name Server后,会从返回Topic的路由表Broker列表
  3. Producer根据代码中指定的Queue选择策略,从Queue列表中选出一个队列,用于后续存储消息。
  4. Producer向选择出的Queue所在的Broker发出RPC请求,将消息发送到选择出的Queue。

**注意:
1、是先选出Queue(这个过程在Producer进行),在根据Queue所在的Broker发出RPC请求,将消息发送到选择出的Queue
2、Produer对消息做一些特殊处理,例如,消息本身超过4M,则会对其进行压缩

路由表:
实际是一个Map,key为Topic名称,value是一个QueueData实例列表。QueueData并不是一个Queue对应一个QueueData,而是一个Broker中该Topic的所有Queue对应一个QueueData。即,只要涉及到该Topic的Broker,一个Broker对应一个QueueData。QueueData中包含brokerName。简单来说,路由表的key为Topic名称,value则为所有涉及该Topic的BrokerName列表。
Broker列表:
其实际也是一个Map。key为brokerName,value为BrokerData。一个Broker对应一个BrokerData实例,对吗?不对。一套brokerName名称相同的Master-Slave小集群对应一个BrokerData。BrokerData中包含brokerName及一个map。该map的key为brokerId,value为该broker对应的地址。brokerId为0表示该broker为Master,非0表示Slave。

概念很抽象,图解:

在这里插入图片描述

1)Queue选择算法

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

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

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

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

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

2、消息的存储过程

注意:消息存储的过程是发生在单个Broker中,不要搞混了!!

[root@iz2zedg4ylq9iqtwm11wecz store]# pwd
/root/store
[root@iz2zedg4ylq9iqtwm11wecz store]# ll
total 24
-rw-r--r-- 1 root root    0 Aug 31 10:47 abort
-rw-r--r-- 1 root root 4096 Aug 31 21:36 checkpoint
drwxr-xr-x 2 root root 4096 Aug 30 09:55 commitlog
drwxr-xr-x 2 root root 4096 Aug 31 21:37 config
drwxr-xr-x 3 root root 4096 Aug 29 19:02 consumequeue
drwxr-xr-x 2 root root 4096 Aug 31 10:47 index
-rw-r--r-- 1 root root    4 Aug 31 10:47 lock
[root@iz2zedg4ylq9iqtwm11wecz store]# 

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

我们需要注意的是commitlog、consumequeue、index这三个文件夹

1)commitlog
[root@iz2zedg4ylq9iqtwm11wecz commitlog]# pwd
/root/store/commitlog
[root@iz2zedg4ylq9iqtwm11wecz commitlog]# ls
00000000000000000000  # 这个是文件,里面存放着消息的信息。这个文件一般被叫做mappedFile
[root@iz2zedg4ylq9iqtwm11wecz commitlog]#

commitlog这个文件夹是用来存放消息的。Producer发送(生产的消息)会按照顺序写入commitlog文件夹里面的文件中(00000000000000000000,也就是mappedFile)。00000000000000000000 这个文件是有大小规定的,最大只能是10G,超过10G后会创建一个新的mappedFile,这个新的mappedFile的名字也是有规定的,规则如下:

第一个文件名一定是20位0构成的。因为第一个文件的第一条消息的偏移量commitlog offset为0当第一个文件放满时,则会自动生成第二个文件继续存放消息。假设第一个文件大小是1073741820字节(1G = 1073741824字节),则第二个文件名就是00000000001073741824。以此类推,第n个文件名应该是前n-1个文件大小之和。一个Broker中所有mappedFile文件的commitlog offset是连续的。

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

  • 消息单元
    在这里插入图片描述
    mappedFile文件内容由一个个的消息单元构成(每一行)。每个消息单元中包含消息总长度MsgLen、消息的物理位置physicalOffset、消息体内容Body、消息体长度BodyLength、消息主题Topic、Topic长度TopicLength、消息生产者BornHost、消息发送时间戳BornTimestamp、消息所在的队列QueueId、消息在Queue中存储的偏移量QueueOffset等近20余项消息相关属性。
2) consumequeue
[root@iz2zedg4ylq9iqtwm11wecz consumequeue]# pwd
/root/store/consumequeue
[root@iz2zedg4ylq9iqtwm11wecz consumequeue]# ls
TopicTest  TopicTest1  TopicTest2
[root@iz2zedg4ylq9iqtwm11wecz consumequeue]#

如果里面什么的没有可以通过Producer去生产几个 topic

package com.lihua.rocketmq.producer;

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;

/**
 * 生产者发送同步消息
 * @author 15594
 */
public class SyncProducer {

    public static void main(String[] args) throws Exception {
        //实例化消息生产者producer,group1表示这个生产者属于哪个集群
        DefaultMQProducer producer = new DefaultMQProducer("group1");

        //注册到注册中心
        producer.setNamesrvAddr("39.96.52.225:9876");
        //启动producer实例
        producer.start();
        for (int i = 0; i < 10; i++) {
            //创建消息,并指定Topic,Tag和消息体。topic:主题,标志了消息的类型 tags:标签,第二主题,子主题 keys:消息关键词。messagebody:具体的消息内容。
            //注意:在rocketmq里面所有消息都必须以二进制传输
            Message msg = new Message("TopicTest2" , "TagA2" , ("Hello RocketMQ2 " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
            // 发送消息到一个Broker
            SendResult sendResult = producer.send(msg);
            // 通过sendResult返回消息是否成功送达
            System.out.printf("%s%n", sendResult);
        }
        // 如果不再发送消息,关闭Producer实例。
        producer.shutdown();
    }


}

在这里插入图片描述

consumequeue(也就是00000这个文件)文件名也由20位数字构成,表示当前文件的第一个索引条目的起始位移偏移量。与mappedFile文件名不同的是,其后续文件名是固定的。因为consumequeue文件大小是固定不变的。每个consumequeue文件可以包含30w个索引条目,每个索引条目包含了三个消息重要属性:消息在mappedFile文件中的偏移量CommitLog Offset、消息长度、消息Tag的hashcode值。这三个属性占20个字节,所以每个文件的大小是固定的30w * 20字节。
  • 索引条目
    在这里插入图片描述
3)消息的读写(存取)

在这里插入图片描述

queueOffset : 写入队列时,写入哪个位置 消费offset :Consumer消费到这个Queue的哪个位置了。(读取到哪个位置) 消息offset = 消费offset + 1 (也就是下一个消费的消息的谁)
  • 消息写入
    先将详细写入commitLog,然后再写入consumequeue
  1. Broker根据queueId(这个队列id在producer中通过指定的算法确定),获取到该消息对应索引条目要在consumequeue目录中的写入偏移量,即QueueOffset
  2. 将queueId、queueOffset等数据,与消息一起封装为消息单元
  3. 将消息单元写入到commitlog
  4. 同时,形成消息索引条目
  5. 将消息索引条目分发到相应的consumequeue
  • 消息读取
  1. Consumer获取到其要消费消息所在Queue的消费偏移量offset,计算出其要消费消息的消息offset。
    消费偏移量offset,即消费进度,即consumer消费到了该Queue的第几条消息。
消息offset = 消费offset + 1
可以查看一下消费偏移量(消费进度): ```powershell [root@iz2zedg4ylq9iqtwm11wecz config]# pwd /root/store/config [root@iz2zedg4ylq9iqtwm11wecz config]# ll total 36 -rw-r--r-- 1 root root 27 Sep 1 09:24 consumerFilter.json -rw-r--r-- 1 root root 27 Sep 1 09:24 consumerFilter.json.bak -rw-r--r-- 1 root root 189 Sep 1 09:24 consumerOffset.json -rw-r--r-- 1 root root 189 Sep 1 09:24 consumerOffset.json.bak -rw-r--r-- 1 root root 21 Sep 1 09:24 delayOffset.json -rw-r--r-- 1 root root 21 Sep 1 09:24 delayOffset.json.bak -rw-r--r-- 1 root root 2505 Aug 29 19:05 subscriptionGroup.json -rw-r--r-- 1 root root 2627 Aug 31 22:19 topics.json -rw-r--r-- 1 root root 2445 Aug 31 22:19 topics.json.bak [root@iz2zedg4ylq9iqtwm11wecz config]# cat consumerOffset.json { "offsetTable":{ "TopicTest@please_rename_unique_group_name_4":{0:249,1:250,2:249,3:249 # 消费进度 }, "%RETRY%please_rename_unique_group_name_4@please_rename_unique_group_name_4":{0:0 } } }[root@iz2zedg4ylq9iqtwm11wecz config]# ```
  1. Consumer向Broker发送拉取请求,其中会包含其要拉取消息的Queue、消息offset及消息Tag。
  2. Broker计算在该consumequeue中的queueOffset。
queueOffset = 消息offset * 20字节
  1. 从该queueOffset处开始向后查找第一个指定Tag的索引条目。

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

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

4) indexFile(这个文件的作用根据用户指定的key和时间查询消息)

indexFile里面存放了消息的索引,根据key进行消息查询的功能。(这里的key是生产者生产信息时由用户指定的)

  • key什么
    在这里插入图片描述
[root@iz2zedg4ylq9iqtwm11wecz index]# pwd
/root/store/index
[root@iz2zedg4ylq9iqtwm11wecz index]# ls
20210831104707863  # indexFile ,文件名为indexFile 文件创建时的时间
[root@iz2zedg4ylq9iqtwm11wecz index]#
  1. indexFile文件创建的时机
  • 第一条带key的消息发送来后,系统发现没有indexFile,此时会创建第一个indexFile文件
  • 当一个indexFile中挂载的index索引单元数量超出2000w个时,会创建新的indexFile。当带key的消息发送到来后,系统会找到最新的indexFile,并从其indexHeader的最后4字节中读取到indexCount。若indexCount >= 2000w时,会创建新的indexFile。
由于可以推算出,一个indexFile的最大大小是:(40 + 500w * 4 + 2000w * 20)字节
  1. indexFile文件 存储条目的结构
    在这里插入图片描述

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

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

indexNo是一个在indexFile中的流水号,从0开始依次递增。即在一个indexFile中所有indexNo是以此递增的。indexNo在index索引单元中是没有体现的,其是通过indexes中依次数出来的。

  1. 查询流程
    在这里插入图片描述

3、消息的消费

先获取消息,再消费消息。

1)获取消息的两种方式:
  1. 主动pull(拉取)
    Consumer主动从Broker中拉取消息,主动权由Consumer控制。一旦获取了批量消息,就会启动消费过程。不过,该方式的实时性较弱,即Broker中有了新的消息时消费者并不能及时发现并消费。
    注意:拉取时间间隔由用户指定,间隔短可能会空拉取(无用功),时间长实效性差
  2. 由Brokerpush(推送)
    该模式下Broker收到数据后会主动推送给Consumer。该获取方式一般实时性较高
    该获取方式是典型的发布-订阅模式,即Consumer向其关联的Queue注册了监听器,一旦发现有新的消息到来就会触发回调的执行,回调方法是Consumer去Queue中拉取消息。而这些都是基于Consumer与Broker间的长连接的。长连接的维护是需要消耗系统资源的

总结:

  • pull 实时性差,但便于应用控制消息的拉取。
  • push实时性强,但会占用较多的系统资源。
2)消息的两种消费模式:
  • 广播消费(一个消息能被所有消费者消费)
    一个队列中,所有消息都被消费组(集群)中的所有消费者消费
    在这里插入图片描述

  • 集群消费(一个消息,只能被消费一次)
    一个队列中,一个消息只能被消费组(集群)里的一个消费者消费
    在这里插入图片描述

3)两种消费模式的进度保存:
  • 广播模式
    消费进度保存在consumer端。 因为广播模式下consumer group中每个consumer都会消费所有消息,但它们的消费进度是不同。所以consumer各自保存各自的消费进度。

  • 集群模式
    消费进度保存在broker中。 consumer group中的所有consumer共同消费同一个Topic中的消息,同一条消息只会被消费一次。消费进度会参与到了消费的负载均衡中,故消费进度是需要共享的。
    集群模式下消费进度保存在broker的consumerOffset.json文件下

[root@iz2zedg4ylq9iqtwm11wecz config]# pwd
/root/store/config
[root@iz2zedg4ylq9iqtwm11wecz config]# ls
consumerFilter.json      consumerOffset.json.bak  subscriptionGroup.json
consumerFilter.json.bak  delayOffset.json         topics.json
consumerOffset.json      delayOffset.json.bak     topics.json.bak
[root@iz2zedg4ylq9iqtwm11wecz config]# cat consumerOffset.json
{
	"offsetTable":{
		"TopicTest@please_rename_unique_group_name_4":{0:249,1:250,2:249,3:249  #  这个就是集群的消费进度
		},
		"%RETRY%please_rename_unique_group_name_4@please_rename_unique_group_name_4":{0:0
		}
	}
}

4、Queue分配算法

1、Queue分配算法在消费者客户端进行,Queue选择算法在Producer 生产者客户端进行。

2、一个Topic中的Queue只能由Consumer Group中的一个Consumer进行消费,而一个Consumer可以同时消费多个Queue中的消息。

1) 平均分配

根据avg = QueueCount / ConsumerCount的计算结果进行分配的。如果能够整除,则按顺序将avg个Queue逐个分配Consumer;如果不能整除,则将多余出的Queue按照Consumer顺序逐个分配。

2) 环形分配

环形平均算法是指,根据消费者的顺序,依次在由queue队列组成的环形图中逐个分配。
先将queue队列组成的环形图。然后消费者绕着环进行分配

在这里插入图片描述

5、Rebalance(再平衡)

  1. Rebalance意义:
    Rebalance存在的意义是为了提高并行消费能力。 。经过Queue分配算法后,已近将Queue和Consumer分配好了(相当于达到了最大的并发消费能力)。当Queue和Consumer发生变化(新增、减少)时,我们就要重新调用Queue分配算法进行再平衡,以保证最大的并发消费能力。

注意:Rebalance针对集群消费模式,因为广播消费Queue和Consumer变化时不受影响。广播消费需要消费者遍历所有的Queue,然后然后消费说有的消息。

  1. Rebalance带来的问题:
    Rebalance机制虽然提升了并发消费的性能,但是也带来了一下问题。
  • 消费暂停
    在新增了一个Consumer后,会触发Rebalance,此时原Consumer就需要暂停部分队列的消费。
  • 消费重复
    集群消费模式,消费进度是保存在Broker上,当offset(消费进度)是异步提交的,如果消息已近被消费了,异步返回消费成功的ACK,但是由于正在进行Rebalance,Broker可能会及收不到ACK,也就是没有修改消费进度,但是消息却被消费了。下一次就会发生重复消费。
  • 消费突刺
    由于Rebalance可能导致重复消费,如果需要重复消费的消息过多,或者因为Rebalance暂停时间过长从而导致积压了部分消息。消息积压,那么有可能会导致在Rebalance结束之后瞬间需要消费很多消息

6、至少消费一次的原则

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

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

什么是消费进度记录器?

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

7、消费幂等

1)什么是消费幂等

幂等:若某操作执行多次与执行一次对系统产生的影响是相同的,则称该操作是幂等的。

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

2)消费幂等出现的原因

1、 网络不稳定,已近处理的请求,由于网络故障没有返回确认ACK。那么发送端就会再次发送消息。保证消息至少被消费一次的原则。
2、 同一时间用户多次发起相同的请求,就会产生几次相同的请求。

3)消除幂等问题
幂等令牌:是生产者和消费者两者中的既定协议,通常指具备唯⼀业务标识的字符串。例如,订单号、流水号。一般由Producer随着消息一同发送来的。

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

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

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

  • 在同一事务中完成三项操作:唯一性处理后,将幂等令牌写入到缓存,并将幂等令牌作为唯一索引的数据写入到DB中。

2、解决方案举例

以支付场景为例:

  1. 当支付请求到达后,首先在Redis缓存中却获取key为支付流水号的缓存value。
    若value不空,则说明本次支付是重复操作,业务系统直接返回调用侧重复支付标识;
    若value为空,则进入下一步操作

  2. 到DBMS中根据支付流水号查询是否存在相应实例。
    若存在,则说明本次支付是重复操作,业务系统直接返回调用侧重复支付标识;
    若不存在,则说明本次操作是首次操作,进入下一步完成唯一性处理

  3. 在分布式事务中完成【唯一性处理】三项操作:
    完成支付任务
    将当前支付流水号作为key,任意字符串作为value,通过set(key, value, expireTime)将数据写入到Redis缓存
    将当前支付流水号作为主键,与其它相关数据共同写入到DBMS

8、消费进度offset

消费进度offset是用来记录每个Queue的不同消费组的消费进度的。根据消费进度记录器的不同,可以分为两种模式:本地模式远程模式。其中本地模式对应着广播消费模式,远程模式对应着集群消费模式

1)offset本地管理模式

因为每条消息会被所有的消费者消费,每个消费者管理自己的消费进度,各个消费者之间不存在消费进度的交集。所以每个消费者都将消费进度记录在自己本地。
offset相关数据以json的形式持久化到Consumer本地磁盘文件中,默认文件路径为当前用户主目录下的.rocketmq_offsets/ c l i e n t I d / {clientId}/ clientId/{group}/Offsets.json。其中c l i e n t I d 为 当 前 消 费 者 i d , 默 认 为 i p @ D E F A U L T ; {clientId}为当前消费者id,默认为ip@DEFAULT;clientId为当前消费者id,默认为ip@DEFAULT;{group}为消费者组名称。

2)offset远程管理模式

当消费模式为集群消费时,offset使用远程模式管理。因为所有Cosnumer实例对消息采用的是均衡消费,所有Consumer共享Queue的消费进度。消费进度被记录在broker中,文件路径为当前用户主目录下的store/config/consumerOffset.json。

4)在代码中使用offset

消费者是如何从最开始持续消费消息的?消费者要消费的第一条消息的起始位置是用户自己通过consumer.setConsumeFromWhere()方法指定的。

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

CONSUME_FROM_LAST_OFFSET:从queue的当前最后一条消息开始消费 CONSUME_FROM_FIRST_OFFSET:从queue的第一条消息开始消费 CONSUME_FROM_TIMESTAMP:从指定的具体时间戳位置的消息开始消费。这个具体时间戳是通过另外一个语句指定的 。consumer.setConsumeTimestamp(“20210701080000”) yyyyMMddHHmmss
当消费完一批消息后,Consumer会提交其消费进度offset给Broker,Broker在收到消费进度后会将其更新到那个双层Map(ConsumerOffsetManager)及consumerOffset.json文件中,然后向该Consumer进行ACK,而ACK内容中包含三项数据:当前消费队列的最小offset(minOffset)、最大offset(maxOffset)、及下次消费的起始offset(nextBeginOffset)。

六、消息的清理

消息被消费过后会被清理掉吗?不会的。

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

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

  1. 文件过期,且到达清理时间点(默认为凌晨4点)后,自动清理过期文件

  2. 文件过期,且磁盘空间占用率已达过期清理警戒线(默认75%)后,无论是否达到清理时间点,都会自动清理过期文件

  3. 磁盘占用率达到清理警戒线(默认85%)后,开始按照设定好的规则清理文件,无论是否过期。默认会从最老的文件开始清理

  4. 磁盘占用率达到系统危险警戒线(默认90%)后,Broker将拒绝消息写入

同类笔记参考1
集群搭建
RocketMQ工作原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值