图解RocketMQ之主题Topic详解

大家好,我是苍何。

上一篇 图解RocketMQ之初识MQ 已经介绍了消息队列的基础概念、常用的消息队列框架以及 RocketMQ 的整体设计架构。

rocketmq架构图

还没看过或者已经忘记的小伙伴可以看看上一篇哈。

我们只有先建立整体的框架和初步的概念,才能更加深入的学习他的技术细节。(学任何技术或知识都是如此,ps:这可不是苍何说的,书本上都这么说。)

上一篇中讲了 RocketMQ 的四大核心概念,也即生产者、消费者、broker、nameserver。作为消息队列的宠儿,可不止这 4 个概念,还有其他比较重要的概念和模型,只有充分理解了这些核心的理念,才能更好的理解和使用 RocketMQ。

好,你现在先闭上眼,看能想到哪些概念或者名词呢?

我想你第一个想到的应该就是 topic 了(反正我是最先想到了他),OK,那我们这一篇的主题也就是主题 topic,当然此主题非彼主题,不过概念上相差不大,这就是中文的强大之处了。

Topic 概念定义

我们先来看看官方定义:

主题是 RocketMQ 中消息传输和存储的顶层容器,用于标识同一类业务逻辑的消息。

我的理解主题就是消息的分类,比如上一篇说到的鸡毛的餐厅中订单消息是一类主题消息,顾客下单的消息都往订单消息这个主题里面发,那么同样鸡鸡毛的朋友狗毛约他去打球,就属于打球主题消息,依此类推,不难发现,主题消息的分类其实是受业务所决定的,你要发什么业务消息最好单独是一个 Topic 主题,大家相互之间互不干扰。

现如今垃圾都要做好干湿分类,消息可不能一个 topic 干到底哦。

topic概念图解

Topic 主要有 2 大核心作用:

  • 定义数据的分类隔离:将不同业务类型的数据拆分到不同的主题中管理,通过主题实现存储的隔离性和订阅隔离性。
  • 定义数据的身份和权限:RocketMQ 的消息本身是匿名无身份的,同一分类的消息使用相同的主题来做身份识别和权限管理。

模型关系

在 RocketMQ 中主题 Topic 所处的流程和位置如下:

topic所处流程好位置

其实 Topic 是一个逻辑的概念,并不是一个实际的消息容器,每个 Topic 包含多个消息队列(Queue),队列是消息的实际存储单元,每个队列独立存储消息,队列数量在创建 Topic 时可以配置,后续也可以动态调整。

生产者发送消息时,消息会根据一定策略分配到不同的队列中,常见的分配策略包括轮询(Round Robin)和哈希(Hashing)。消费者消费消息时,同样是从这些队列中拉取消息。

Topic 属性和配置

Topic 主要有以下内部属性,并且可以用来配置。

主题名称

主题名称是在创建主题的时候,我们自己定义的,主要是用于标识主题,名称需要满足集群内唯一。虽然理论上可以按照自己所想随便定义,但官方还是苦口婆心的给了做了我们一些限制,看看也无妨:

Topic 命名应该尽量使用简短、常用的字符,避免使用特殊字符。特殊字符会导致系统解析出现异常,字符过长可能会导致消息收发被拒绝。

类别具体建议或限制
字符建议字母az或AZ、数字0~9以及下划线(_)、短划线(-)和百分号(%)
长度建议1~64个字符
系统保留字符Topic名称不允许使用以下保留字符或含有特殊前缀的字符命名
保留字符TBW102, BenchmarkTest, SELF_TEST_TOPIC, OFFSET_MOVED_EVENT, SCHEDULE_TOPIC_XXXX, RMQ_SYS_TRANS_HALF_TOPIC, RMQ_SYS_TRACE_TOPIC, RMQ_SYS_TRANS_OP_HALF_TOPIC
特殊前缀rmq_sys, %RETRY%, %DLQ%, rocketmq-broker-

所以,大家还是老老实实的命名吧,别触犯人官方的底线,小心消息都收不到哦。

队列列表

队列既是主题的组成单元,又是消息的最小存储单元,一个主题内包含一个或多个队列,消息实际存储在主题的各队列内。队列需要创建主题的时候指定,且一个主题内至少包含一个队列。

队列的主要作用如下:

  • 存储顺序性:队列天然具备顺序性,即消息按照进入队列的顺序写入存储,同一队列间的消息天然存在顺序关系,队列头部为最早写入的消息,队列尾部为最新写入的消息。消息在队列中的位置和消息之间的顺序通过位点(Offset)进行标记管理。(这其实也是队列出生就带的属性之一)
  • 流式操作语义:RocketMQ 基于队列的存储模型可确保消息从任意位点读取任意数量的消息,以此实现类似聚合读取、回溯读取等特性,这些特性是 RabbitMQ、ActiveMQ 等非队列存储模型不具备的。(划重点🔔)

消息类型

消息类型是 RocketMQ 主题所支持的消息类型,在创建主题时需要选择消息类型。主要包括以下消息类型:

1、Normal

顾名思义就是普通消息,是 RocketMQ 默认的消息类型,消息本身无特殊语义,消息之间也没有任何关联。

普通消息没有复杂的处理逻辑,适用于大多数常见的消息传递场景。消息从生产者发送到 Broker,然后由消费者拉取并处理。PmHub 中采用的也是这种类型消息。

由于比较常用,苍何贴出一个简单实例,直接就可以发送普通消息啦:

// 初始化消息生产者
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
producer.setNamesrvAddr("127.0.0.1:9876");

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

try {
    // 创建一个消息实例,指定主题、标签和消息体
    Message msg = new Message("TopicTest", "TagA", ("Hello RocketMQ").getBytes(RemotingHelper.DEFAULT_CHARSET));

    // 发送消息到 Broker
    SendResult sendResult = producer.send(msg);

    // 打印发送结果
    System.out.printf("%s%n", sendResult);
} catch (Exception e) {
    e.printStackTrace();
}

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

2、FIFO

也即是顺序消息,消息按照发送的顺序被严格地消费。适用于对消息顺序有严格要求的场景,比如数据实时增量同步。RocketMQ 支持两种类型的顺序消息:全局顺序消息和分区顺序消息。

全局顺序消息保证同一个 Topic 下的所有消息按照严格的顺序被消费。这意味着所有消息按照它们被发送的顺序依次被消费。这种模式比较少用,因为它限制了消费并发性。

分区顺序消息保证某一个分区(即某一个队列)内的消息按顺序消费,但不同分区之间的消息消费顺序不做保证。分区顺序消息通过将消息分配到不同的队列,实现了更高的并发性能。

3、Delay

定时/延时消息,通过指定延时时间控制消息生产后不要立即投递,而是在延时间隔后才对消费者可见。

这种消息类型很有意思,适用于延迟任务、任务超时处理、分布式定时调度等场景。这种场景我们后面会详细介绍。

分布式延时调度(图自官网)

4、Transaction

这种消息类型为事务消息,也是基于消息队列来实现分布式事务,保证事务最终一致性的较好解决方案,具体这里我们先不展开,在后面的章节中会有详细介绍哦。

先贴一张图大家感受下:

事务消息(来自管网)

在消息类型选择时候有一些限制约束:

RocketMQ 从 5.0 版本开始,支持强制校验消息类型,即每个主题只允许发送一种消息类型的消息,这样可以更好的运维和管理生产系统,避免混乱。为保证向下兼容 4.x 版本行为,强制校验功能默认开启。也即:

  • 消息类型必须一致:发送的消息的类型,必须和目标主题定义的消息类型一致。
  • 主题类型必须单一:每个主题只支持一种消息类型,不允许将多种类型的消息发送到同一个主题中。

使用场景和最佳实践

这里以 PmHub 为例,使用场景是项目任务提醒,企微消息提醒,所以在 PmHub 中,我们定义了消费者组 PMHUB_GROUP,并定义了 Topic:pmhub_local。

由于场景为消息通知,消息类型我们使用的是默认的普通消息。下面是 Topic 创建的实践:

- 消费者组创建

在控制台直接创建消费者组:

消费者组创建

消费者组创建

创建好后在列表会有显示:

列表显示消费者组

创建 Topic

同样是在控制台新建:

控制台新建

控制台新建topic

创建好后在列表会有显示信息:

列表显示topic

启动 pmhub-project,将 topic 注入到消费组中。建立消息通道:

pmhub-project建立消息通道

成功启动,再去控制台看看,我们一定能看到惊喜,点击主题,找到我们配置的主题 pmhub_topic,点击 consumer 管理。

启动成功后列表

建立成功

Topic 常见问题及解决方案

Topic 拆分过细

有些小伙伴看到主题是用来区分场景的,于是乎,把主题拆的很细,比如发送企微消息一个主题,发送公众号消息一个主题,更有卧龙凤雏按照用户 ID 拆分,一个用户一个 Topic,成千上万个用户就对应成千上万个 Topic,那可真是太壮观啦。

苍何觉得,大可不必,因为 Topic 也是消耗资源的,拆的过细,会消耗大量主题资源,造成系统负载过重。

Topic 拆分过粗

那是不是拆分的越粗越好呢,甚至不拆分?那也不好,会导致业务隔离性差,不利于独立运维和故障处理。比如 MQ 阻塞,排查起来都得半天,非常不适合 21 世纪新时代程序员。

苍何就曾经见过有人把所有交易消息和物流消息都共用一个主题。有天线上出故障了,从排查问题到解决花了几个小时,最主要原因还是 Topic 没有好好拆分。

Topic 管理线上开启自动化

RocketMQ 提供了自动创建主题的功能,在测试开发环境用的真是一个爽,但到线上忘记关了,哦豁,过一段时间发现产生大量垃圾主题,这样是无法管理和回收并浪费系统资源的。

主题属于顶层资源和容器,应该拥有独立的权限管理、可观测性指标采集和监控等能力,创建和管理主题会占用一定的系统资源。因此,生产环境需要严格管理主题资源,请勿随意进行增、删、改、查操作。也建议控制好权限,修改主题操作走审批和 CR,防止事故的发生。

总结

Topic 是 RocketMQ 中核心的模型概念,用于标识同一类业务逻辑的消息,主题的拆分既不能太细又不能太粗,讲究的是一个合适为好,可以从以下角度考虑拆分粒度:

  • 消息类型是否一致:不同类型的消息,如顺序消息和普通消息需要使用不同的主题。

  • 消息业务是否关联:如果业务没有直接关联,比如,淘宝交易消息和盒马物流消息没有业务交集,需要使用不同的消息主题;同样是淘宝交易消息,女装类订单和男装类订单可以使用同一个订单。当然,如果业务量较大或其他子模块应用处理业务时需要进一步拆分订单类型,你也可以将男装订单和女装订单的消息拆分到两个主题中。

  • 消息量级是否一样:数量级不同或时效性不同的业务消息建议使用不同的主题,例如某些业务消息量很小但是时效性要求很强,如果跟某些万亿级消息量的业务使用同一个主题,会增加消息的等待时长。

我是苍何,这是图解 RocketMQ 教程的第二篇,我们下篇见~

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员苍何

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值