MQ消息中间件

RocketMQ

RocketMQ是一款由阿里巴巴开源、现为 Apache 顶级项目的分布式消息中间件,以高吞吐、低延迟、金融级可靠和丰富的高级特性著称,是电商、金融、大数据等核心业务场景的主流选择。

核心角色

RocketMQ有4个核心角色:

  • NameServer(路由注册中心)
    • 轻量级的无状态路由发现服务,可集群部署
    • 它的定位就是给消费者和生产者拉取broker的ip以及topic对应哪些broker、哪些队列的
    • 仅仅提供broker的信息,不承担消息转发任务,也就是,发送者和生产者,拿到broker以及topic、队列信息后,它们直接和broker建立长连接,发送和消费消息,不经过nameServer
  • Broker(消息存储与转发核心)
    • 最核心的角色,承担消息的存储、转发、高可用
    • 接收Producer的发来的消息,并按照配置将它们持久化,以及同步到从节点,返回消息接收结果
    • 向消费端提供消息拉取服务,并维护消费组里每个消费者对应队列的offset,offset只有存在Broker里才能实现持久化,以及恢复,消息不重新消费,而是在断点位置继续消费
    • 支持延迟消息、事务消息、死信队列、重试丢列
    • 支持高可用,主从同步、Dledger 自动选主、多主多从
  • Producer(消息生产者)
    • 生产者,发送消息端,从NameServer拉取路由,负载均衡选Broker/Queue
    • 支持同步、异步、单向发送
  • Consumer(消息消费者)
    • 消息的消费者,从NameServer拉取路由,建立和broker的连接
    • 集群消费:同组消费者负载均衡,一条消息消费一次
    • 广播消费:同组每个消费者都消费同一消息一次
    • 一个Consumer可配置线程池线程数量去拉取队列消息,也可以配置同一时间同个队列只能有一个线程进行消费

整体理念

  • Topic
    • 一个topic对应于同一类消息(比方订单消息、库存消息)
    • 一个topic会被分为4/8个消息队列,每个消息队列对应一个物理分片,即一个消息队列存储到一个broker里,一个topic下的所有消息队列会被均匀的分给集群里的所有broker,以实现高并发
  • ConsumerGroup
    • 一个ConsumerGroup内会有多个Consumer
    • 一个ConsumerGroup订阅一个Topic,这个Topic下的所有消息队列会被均匀的分给group下的consumer,一个consumer可能会被分到1个或者多个消息队列,取决于group内的consumer数量以及topic的消息队列数量
    • 当ConsumerGroup里只有一个consumer时,这个consumer承担ConsumerGroup订阅的topic的所有消息队列
    • topic内的同一条消息,只会给一个ConsumerGroup消费一次。当有多个ConsumerGroup订阅同一个Topic时,同一条消息会给每个ConsumerGroup都消费一次

总结就是:

1个topic → 4/8个消息队列 → 均匀的分给brokers

1个consumerGroup(订阅1个topic) → n个consumer → 均匀的承担这个topic下的消息队列

怎么保证消息的可靠性?

从以下3个方面来保证可靠性

  • 发送端
    • 保证消息发送成功,发送失败重试,重试一定次数后存本地消息表定时任务兜底
    • 使用MQ的事务消息,和本地事务保证原子性
    • 使用同步发送,等broker确认
  • broker
    • 开启同步刷盘,消息commitLog落盘后才算成功
    • 开启主从同步复制,消息等从节点同步成功后才返回
    • 主节点挂了,重启后从本地恢复,消息还在。主节点恢复不了,由于从节点是同步复制的,Dledger选主,从节点顶上,数据和主节点一致
  • 消费端
    • 保证消息至少消费一次
    • 消费失败 → 重试 → 丢死信队列
    • 消费幂等
    • 顺序消费要做好超时控制,避免阻塞整个队列

怎么保证消息的顺序性?

由于RocketMQ架构上已经决定了一个Topic会分成多个消息队列,然后一个消费组里的所有消费者均分这些消息队列,所以投递到这个topic的消息,必然会被多个消费者给消费,每个消费者消费完消息肯定是不确定顺序的。即使是在同一个队列里的消息,虽然它取出来是按顺序的,但是一个consumer去消费这个消息队列是可以配多个线程的,那每个线程执行完的顺序就是不确定的了。

我们在无法打破这个架构的情况下,要怎么保证消息消费的有序性呢?

首先,消息是一定要投递到同一个消息队列里,这样取出来才是按顺序的,其次消费端去消费的时候,要保证同一时间,只有一个线程取了消息,并且在消费消息,等到这个线程消费成功了,才允许线程池取下一个消息,这样在保证了取消息有序性以及消费消息有序性后,最终保证了消息消费得顺序性。

所幸,我们不需要自己实现这个逻辑,RocketMQ已经帮我们想好了。

发送端把消息发到同一个消息队列,通过orderId进行哈希取模,相同orderId的消息,选了同一个消息队列存,不同orderId可能存到不同的消息队列里,既保证单订单顺序,又保证整体并发(不同orderId仍能交给不同的consumer消费):

java 复制代码
Message msg = new Message("order_topic", tags, orderId, content.getBytes());

// 手动选择队列:相同 orderId 进入同一个队列
producer.send(msg, new MessageQueueSelector() {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        String orderId = (String) arg;        // 订单ID
        int index = orderId.hashCode() % mqs.size(); // 固定哈希
        return mqs.get(index);
    }
}, orderId);

消费端开启同一时间只能有一个线程消费同一个topic下的消息

java 复制代码
consumer.registerMessageListener(new MessageListenerOrderly() {
    @Override
    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
        // 单队列内严格顺序消费
        // 一个队列同一时间只被一个线程消费
        return ConsumeOrderlyStatus.SUCCESS;
    }
});

缺点也很明显,同一队列里的消息是可能存在不同订单的(hash取模后归到一个队列),只要有一个异常订单卡住,那这个队列里,后面的消息就无法消费了,只能通过尽量增大队列数量,以及控制超时时间、重试次数进死信队列来缓解这个问题。

RocketMQ实现延迟队列底层逻辑?

5.x以下版本给Message 设置setDelayTimeLevel可实现延迟消息,level从1秒、5秒到2个小时,总共18个级别,底层是建了18个队列,分别存储不同级别,以及每个级别都有一个线程去盯着,每个线程只看队列的头部,假如头部的消息3秒后到时间,那线程就会休眠3秒后,去取消息。

使用18个级别去做的好处是添加消息时,永远往队列最后添加即可,不需要排序,取消息时,永远只需要关注队头的消息到期时间即可,时间复杂度时O(1).

取到到时间的消息,就会往正常topic的队列里放,然后被消费掉,偷天换日,其实一开始没往我们的topic队列里放,而是放到了18个级别的延迟队列里

5.x以上版本可以设置任意时间戳,底层是定长数据+链表,通过时间轮算法实现的,投递消息时,会计算这个消息属于哪个槽,每 1ms/100ms/1s 跑一次(按精度配置)取下一个槽,当前槽存的是个链表,链表里是当前轮和下n个周期轮的消息,会遍历链表取出过期的消息,投到正常topic的消息队列里,其他没到期的,等时间轮走完一周后,回到这个位置,再重新计算时间是否到期。

相关推荐
简单点了2 小时前
mac安装idea
java·macos·intellij-idea
橘子编程2 小时前
软件测试全流程实战指南
java·功能测试·测试工具·junit·tomcat·压力测试·可用性测试
计算机学姐3 小时前
基于SpringBoot的在线学习网站平台【个性化推荐+数据可视化+课程章节学习】
java·vue.js·spring boot·后端·学习·mysql·信息可视化
焦糖玛奇朵婷3 小时前
盲盒小程序开发,盲盒小程序怎么做
java·大数据·服务器·前端·小程序
喵了几个咪3 小时前
Go 语言 CMS 横评:风行 GoWind 对比传统 PHP/Java CMS 核心优势
java·golang·php
星晨雪海3 小时前
Spring Boot 常用注解
java·spring boot·后端
whatever who cares3 小时前
java/android中单例模式详解
android·java
rrrjqy3 小时前
深入浅出 RAG:基于 Spring AI 的文档分块 (Chunking) 策略详解与实战
java·人工智能·后端·spring
96773 小时前
mybatis的作用+sql怎么写
java·开发语言·mybatis