【消息中间件】--- RocketMQ核心概念介绍 + 生产者简介 + 消息存储简介

本文对应源码地址:https://github.com/nieandsun/rocketmq-study
rocketmq官网:https://rocketmq.apache.org/docs/quick-start/
rocketmq github托管地址(这里直接给出的是中文docs地址):https://github.com/apache/rocketmq/tree/master/docs/cn

唉。。。加班好累啊,都没怎么有时间写博客了!!!!

1 RocketMQ核心概念介绍

基本全部摘自https://github.com/apache/rocketmq/tree/master/docs/cn

在这里插入图片描述

1.1 RocketMQ架构相关的概念

RocketMQ架构上主要分为四部分,如上图所示:


Producer
消息发布的角色,支持分布式集群方式部署。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递,投递的过程支持快速失败并且低延迟。


Consumer
消息消费的角色,支持分布式集群方式部署。支持以push推,pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费,它提供实时消息订阅机制,可以满足大多数用户的需求。


NameServer
NameServer是一个非常简单的Topic路由注册中心,其角色类似Dubbo中的zookeeper,支持Broker的动态注册与发现。

主要包括两个功能:

  • Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;
  • 路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。

然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。

NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息。


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


1.2 其他基本概念


消息(Message)
消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题。RocketMQ中每个消息拥有唯一的Message ID,且可以携带具有业务标识的Key(Message Key)。系统提供了通过Message ID和Key查询消息的功能。


主题(Topic)— 生产上肯定不允许自动创建主题
表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位。


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


生产者组(Producer Group) — 生产上肯定不允许自动创建
同一类Producer的集合,这类Producer发送同一类消息且发送逻辑一致。如果发送的是事务消息且原始生产者在发送之后崩溃,则Broker服务器会联系同一生产者组的其他生产者实例以提交或回溯消费。


消费者组(Consumer Group) — 生产上肯定不允许自动创建
同一类Consumer的集合,这类Consumer通常消费同一类消息且消费逻辑一致。消费者组使得在消息消费方面,实现负载均衡和容错的目标变得非常容易。要注意的是,消费者组的消费者实例必须订阅完全相同的Topic。RocketMQ 支持两种消息模式:集群消费(Clustering)和广播消费(Broadcasting)。


集群消费(Clustering)
集群消费模式下,相同Consumer Group的每个Consumer实例平均分摊消息。


广播消费(Broadcasting)
广播消费模式下,相同Consumer Group的每个Consumer实例都接收全量的消息。


拉取式消费(Pull Consumer)
Consumer消费的一种类型,应用通常主动调用Consumer的拉消息方法从Broker服务器拉消息、主动权由应用控制。一旦获取了批量消息,应用就会启动消费过程。


推动式消费(Push Consumer)
Consumer消费的一种类型,该模式下Broker收到数据后会主动推送给消费端,该消费模式一般实时性较高。


普通顺序消息(Normal Ordered Message) — 本篇文章暂不涉及
普通顺序消费模式下,消费者通过同一个消费队列收到的消息是有顺序的,不同消息队列收到的消息则可能是无顺序的。


严格顺序消息(Strictly Ordered Message) — 本篇文章暂不涉及
严格顺序消息模式下,消费者收到的所有消息均是有顺序的。


2 消息发送

本节内容基本全部摘自官网:https://github.com/apache/rocketmq/tree/master/docs/cn

  • 使用RocketMQ发送三种类型的消息:同步消息、异步消息和单向消息。其中前两种消息是可靠的,因为会有发送是否成功的应答。

2.1 单向发送消息

2.1.1 示例代码

这种方式主要用在不特别关心发送结果的场景,例如日志发送。

@Slf4j
public class OneWayProducer {
    public static void main(String[] args) throws Exception {
        //Instantiate with a producer group name. --- 生产者组
        DefaultMQProducer producer = new DefaultMQProducer("nrsc-group");
        // Specify name server addresses.
        producer.setNamesrvAddr("localhost:9876");
        //Launch the instance.
        producer.start();
        for (int i = 0; i < 5; i++) {
            //Create a message instance, specifying topic, tag and message body.
            Message msg = new Message(
                    "Topic-NRSC" /* Topic */,
                    "TagA-NRSC" /* Tag */,
                    ("Hello RocketMQ " +
                            i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );
            //Call send message to deliver message to one of brokers.
            producer.sendOneway(msg);
            log.info("send msg is {}", msg);

        }
        //Shut down once the producer instance is not longer in use.
        producer.shutdown();
    }
}

2.1.2 示例代码(生成者)中涉及到的RocketMQ核心概念

上面这段代码包含了RocketMQ诸多的诸多核心概念:如生产者、生产者组 、消息 、主题 等,有兴趣的可以进行一一对应。

这里我们看一下Message的构造函数,如下:
在这里插入图片描述
可以看到:

  • 一个Message 必须要有一个主题(Topic)和一个消息主体(Message body) — 消息主体里存放了真正的消息内容;
  • 且消息主体必须为一个字节数组。

官网总结如下:https://github.com/apache/rocketmq/blob/master/docs/cn/best_practice.md

在这里插入图片描述


2.1.3 从可视化控制台看一下发送的消息

在这里插入图片描述
注意: 图中的MESSAGE KEY即2.1.2中Message构造函数中的keys,在我们的项目中这个东东是必须有的。
我看在官网的最佳实践(https://github.com/apache/rocketmq/blob/master/docs/cn/best_practice.md)里也写了关于该变量使用的好处:
在这里插入图片描述


2.2 发送同步消息


2.2.1 什么是同步???

其实很好理解,该模式工作原理基本如下,即发送一个消息,必须要等到响应结果,再发第二个消息。
在这里插入图片描述


2.2.2 示例代码

这种可靠性同步地发送方式使用的比较广泛,比如:重要的消息通知,短信通知。

    public static void main(String[] args) throws Exception {
        // 实例化消息生产者Producer
        DefaultMQProducer producer = new DefaultMQProducer("sync-group");
        // 设置NameServer的地址
        producer.setNamesrvAddr("localhost:9876");
        // 启动Producer实例
        producer.start();
        for (int i = 0; i < 5; i++) {
            // 创建消息,并指定Topic,Tag和消息体
            Message msg = new Message("Topic-sync" /* Topic */,
                    "TagA-sync" /* Tag */,
                    ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );
            // 发送消息到一个Broker
            SendResult sendResult = producer.send(msg);
            // 通过sendResult返回消息是否成功送达
            System.out.printf("%s%n", sendResult);
        }
        // 如果不再发送消息,关闭Producer实例。
        producer.shutdown();
    }
}

2.2.3 简单看一下发送同步消息获得的返回结果 — 在本文第3部分进行详解

SendResult [sendStatus=SEND_OK, msgId=C0A8007352B818B4AAC256417E8F0000, offsetMsgId=C0A8940100002A9F0000000000000E06, messageQueue=MessageQueue [topic=Topic-sync, brokerName=LAPTOP-V7UUU6JC, queueId=1], queueOffset=0]
SendResult [sendStatus=SEND_OK, msgId=C0A8007352B818B4AAC256417EA30001, offsetMsgId=C0A8940100002A9F0000000000000EBE, messageQueue=MessageQueue [topic=Topic-sync, brokerName=LAPTOP-V7UUU6JC, queueId=2], queueOffset=0]
SendResult [sendStatus=SEND_OK, msgId=C0A8007352B818B4AAC256417EA50002, offsetMsgId=C0A8940100002A9F0000000000000F76, messageQueue=MessageQueue [topic=Topic-sync, brokerName=LAPTOP-V7UUU6JC, queueId=3], queueOffset=0]
SendResult [sendStatus=SEND_OK, msgId=C0A8007352B818B4AAC256417EA70003, offsetMsgId=C0A8940100002A9F000000000000102E, messageQueue=MessageQueue [topic=Topic-sync, brokerName=LAPTOP-V7UUU6JC, queueId=0], queueOffset=0]
SendResult [sendStatus=SEND_OK, msgId=C0A8007352B818B4AAC256417EA90004, offsetMsgId=C0A8940100002A9F00000000000010E6, messageQueue=MessageQueue [topic=Topic-sync, brokerName=LAPTOP-V7UUU6JC, queueId=1], queueOffset=1]

2.3 发送异步消息

异步消息通常用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待Broker的响应。


2.3.1什么是异步???

有网络编程基础的,其实对同步、异步啥的我感觉理解起来应该都很轻松。
发送异步消息的工作原理基本如下,即消息发送方在发送了一条消息后,不等接收方发回响应,接着进行第二条消息发送。发送方通过回调接口的方式接收服务器响应,并对响应结果进行处理。
在这里插入图片描述


2.3.2 示例代码

public class AsyncProducer {
    public static void main(
            String[] args) throws MQClientException, InterruptedException {
        //生产者实例化
        DefaultMQProducer producer = new DefaultMQProducer("async");
        //指定rocket服务器地址
        producer.setNamesrvAddr("localhost:9876");
        //启动实例
        producer.start();
        //发送异步失败时的重试次数(这里不重试)
        producer.setRetryTimesWhenSendAsyncFailed(0);

        int messageCount = 10;
        final CountDownLatch countDownLatch = new CountDownLatch(messageCount);
        for (int i = 0; i < messageCount; i++) {
            try {
                final int index = i;
                Message msg = new Message("TopicTest",
                        "TagC",
                        "OrderID" + index,
                        ("Hello world " + index).getBytes(RemotingHelper.DEFAULT_CHARSET));
                //生产者异步发送
                producer.send(msg, new SendCallback() {
                    @Override
                    public void onSuccess(SendResult sendResult) {
                        countDownLatch.countDown();
                        System.out.printf("%-10d OK %s %n", index, new String(msg.getBody()));
                    }

                    @Override
                    public void onException(Throwable e) {
                        countDownLatch.countDown();
                        System.out.printf("%-10d Exception %s %n", index, e);
                        e.printStackTrace();
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //Thread.sleep(1000);
        countDownLatch.await(5, TimeUnit.SECONDS);
        producer.shutdown();
    }
}

2.3.3 简单看一下发送异步消息获得的返回结果

在这里插入图片描述
有兴趣的可以把整个sendResult打印出来看一下,这里不进行展示了。


2.4 简单总结

在这里插入图片描述


3 SendResult简单解读 + 消息存储简介

本节内容基本全部摘自官网:https://github.com/apache/rocketmq/blob/master/docs/cn/design.md


3.1 RocketMQ消息存储整体架构

从2.2.3打印出的SendResult内容可以看到里面有一个MessageQueue,而五条消息的queueId分别为:1,2,3,0,1 —> 这几个数字是什么意思呢??? —> 这就不得不提一下RocketMQ的整体存储架构了,其架构图如下:

在这里插入图片描述
首先来看一下官网的介绍:

消息存储架构图中主要有下面三个跟消息存储相关的文件构成。


(1) CommitLog:消息主体以及元数据的存储主体,存储Producer端写入的消息主体内容,消息内容不是定长的。单个文件大小默认1G ,文件名长度为20位,左边补零,剩余为起始偏移量,比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1073741824;当第一个文件写满了,第二个文件为00000000001073741824,起始偏移量为1073741824,以此类推。消息主要是顺序写入日志文件,当文件满了,写入下一个文件;


(2) ConsumeQueue:消息消费队列,引入的目的主要是提高消息消费的性能,由于RocketMQ是基于主题topic的订阅模式,消息消费是针对主题进行的,如果要遍历commitlog文件中根据topic检索消息是非常低效的。Consumer即可根据ConsumeQueue来查找待消费的消息。其中,ConsumeQueue(逻辑消费队列)作为消费消息的索引,保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量offset,消息大小size和消息Tag的HashCode值。consumequeue文件可以看成是基于topic的commitlog索引文件,故consumequeue文件夹的组织方式如下:topic/queue/file三层组织结构,具体存储路径为:$HOME/store/consumequeue/{topic}/{queueId}/{fileName}。同样consumequeue文件采取定长设计,每一个条目共20个字节,分别为8字节的commitlog物理偏移量、4字节的消息长度、8字节tag hashcode,单个文件由30W个条目组成,可以像数组一样随机访问每一个条目,每个ConsumeQueue文件大小约5.72M;


(3) IndexFile:IndexFile(索引文件)提供了一种可以通过key或时间区间来查询消息的方法。Index文件的存储位置是:$HOME \store\index\${fileName},文件名fileName是以创建时的时间戳命名的,固定的单个IndexFile文件大小约为400M,一个IndexFile可以保存 2000W个索引,IndexFile的底层存储设计为在文件系统中实现HashMap结构,故rocketmq的索引文件其底层实现为hash索引。


在上面的RocketMQ的消息存储整体架构图中可以看出,RocketMQ采用的是混合型的存储结构,即为Broker单个实例下所有的队列共用一个日志数据文件(即为CommitLog)来存储。RocketMQ的混合型存储结构(多个Topic的消息实体内容都存储于一个CommitLog中)针对Producer和Consumer分别采用了数据和索引部分相分离的存储结构,Producer发送消息至Broker端,然后Broker端使用同步或者异步的方式对消息刷盘持久化,保存至CommitLog中。只要消息被刷盘持久化至磁盘文件CommitLog中,那么Producer发送的消息就不会丢失。正因为如此,Consumer也就肯定有机会去消费这条消息。当无法拉取到消息后,可以等下一次消息拉取,同时服务端也支持长轮询模式,如果一个消息拉取请求未拉取到消息,Broker允许等待30s的时间,只要这段时间内有新消息到达,将直接返回给消费端。这里,RocketMQ的具体做法是,使用Broker端的后台服务线程—ReputMessageService不停地分发请求并异步构建ConsumeQueue(逻辑消费队列)和IndexFile(索引文件)数据。

由上面的介绍可知RocketMQ 收到消息后, 所有主题的消息都存储在 commitlog 文件中, 当消息到达 commitlog 后, 将会采用异步转发到消息队列, 也就是 consumerqueue,Queue 是数据分片的产物, 数据分片可以提高消费者的消费效率。


3.2 按照官方文档的描述看一下消息的物理存储

根据官网描述,我们其实可以在我们自己的电脑或服务器上找到这三个文件,比如我在win上安装了RocketMQ,这三个文件所在的目录如下:
在这里插入图片描述


接着我们来看一下consumequeue的子文件夹:
在这里插入图片描述
可以看到,consumequeue按照Topic又划分了许多子文件夹。


再来看一下某个Topic下的内容:
在这里插入图片描述
可以看到它又默认分了四个文件,其实到这里你肯定就知道为什么2.2.3中五条消息的queueId分别为:1,2,3,0,1了。 —> 即在默认情况下RocketMQ又将某个topic下的消息按照一定的算法将其分摊到了4个队列中。

这时候再看本节开头的那个消息存储架构图是不是就感觉轻松多了。


最后,这些东西其实都可以通过可视化控制台看到,我在工作中还是比较喜欢点这些东西的,有兴趣的可以研究一下,我们公司里控制台跟这个不一样(我这个的下载地址为:https://codeload.github.com/apache/rocketmq-externals/zip/master),可能是版本的原因,但是我看内容都差不多。
在这里插入图片描述
再看一下可视化控制台的Topic页面 —> 这些东西在真实开发场景下都是可以用得到的,有兴趣的可以多研究研究。
在这里插入图片描述


end!!!

猜你喜欢

转载自blog.csdn.net/nrsc272420199/article/details/106169923