rockeqMQ 消息存储机制整理

1.rocketMq消息存储结构
RocketMQ消息存储结构主要包括消息队列文件、消息索引文件和消息文件。

消息队列文件是指存储具有相同属性(如Topic、队列ID等)的消息的文件,一个 Topic 包含多个消息队列,每个消息队列对应一个消息队列文件,RocketMQ采用了顺序写入、随机读取的方式存储每个消息队列中的消息,确保高效的写入速度和随机读取速度。

消息索引文件存储的是消息在消息队列中的偏移量和消息物理偏移量对应关系,使用 Hash 索引方式来加速定位,使用 DoubleBuffer 实现零拷贝刷盘,保证高效扫描和持久化速度。

消息文件存储的是所有消息的实际数据,包括消息头和消息体,RocketMQ采用内存映射的方式进行文件读写,确保高效的数据访问速度和快速的持久化速度。

总之,RocketMQ消息存储采用了一种三文件结构的方式,从而达到高效、可靠的存储效果。同时,RocketMQ还支持对消息的预写日志进行压缩、刷盘策略的配置等一系列优化措施,进一步提升了消息存储的效率和可靠性。

RocketMQ消费消息的过程可以分为Push模式和Pull模式两种模式。在这里,我为您介绍一下RocketMQ的Pull模式下的拉取消息的过程:
2. 拉取模式
消费者发送拉取请求
在Pull模式下,消费者需要主动向Broker发送拉取请求。消费者首先需要从NameServer中获取到指定topic所在的Broker地址,然后发送拉取请求到其中的一个Broker节点。

统计未拉取超时时间,选择消息队列
Broker接收到拉取请求后,会根据请求中的参数统计未拉取的时间,如果超时则会将当前消息队列的消费状态设置为pull_request,并返回给消费者

接着,Broker会选择合适的消息队列返回给消费者。当一个消费者启动时,它需要确定它将从哪个消息队列中拉取消息。RocketMQ默认的分配策略是随机分配分区,消费者以平均速率从各个分区中拉取消息。

返回拉取请求
Broker返回的消息队列信息包含该消费群组在该队列上的消费偏移量以及该队列当前存储的最大偏移量。

消费者根据拉到的偏移量,确定起始位置,并向Broker发送消息拉取请求。

Broker处理拉去请求
Broker端接收到拉去请求后,会根据拉去请求中的消费偏移量进行校验,然后将请求的消息批量返回给消费者。消费者可以逐条消费这些消息,或打包后进行批量处理。

消费者处理消息
消费者从Broker中拉到消息后,将其存储到本地的消息缓存区中,然后进行后续的业务处理,对消息进行消费、解析、持久化等处理,并更新消费偏移量。消费偏移量的更新需要消费者确保本地成功消费后才更新消费偏移量,以保证消息不被重复消费或丢失。

3.推送模式 push
创建生产者和消费者
首先,需要在代码中创建两个对象:生产者Producer和消费者Consumer。Producer用于发送消息,Consumer用于接收消息。

生产者发送消息
生产者通过send方法,将消息发送给Broker,消息被存储在Broker中的指定队列中。Producer在发送消息时,需要指定topic和tag。

Broker选择合适的消费者
Broker收到消息后,会检查该消息是哪个topic和tag,然后根据配置的消费者组的订阅信息,选择合适的消费者消费该消息。RocketMQ严格遵循先进先出的原则,对于一个消息,只有一个消费者可以消费。

消费者消费消息
被Broker选择的消费者,从相应的队列中取出该消息并进行消费。消费者在消费时,需要注册Listener监听器,在监听器中进行业务操作。

消息消费成功
当消费者消费成功后,需要向Broker发送确认消息,表示该消息已经被成功消费。如果消费失败,则可以返回重试指令或丢弃该消息。

消费者状态更新
Broker会记录每个消费者消费消息的偏移量,用于下次继续消费。如果消费者异常退出,则会立即通知Broker,使消息队列中待消费消息的状态被置为未消费状态。

4.rocketmq如何保证发送消息是顺序的
1.顺序消息
在RocketMQ中,可以使用顺序消息机制来保证消息的顺序性。具体来说,就是将一个业务流程中的所有消息发送到同一个消息队列中,并且该消息队列只由一个消费者(称为顺序消费者)进行消费。这样就可以保证消息的消费顺序和发送顺序一致。

2.有序生产者
在RocketMQ中,还可以使用有序生产者机制来保证消息的顺序性。具体来说,就是通过设置MessageQueueSelector,将消息发送到同一队列中。顺序消费者则消费队列中的消息,从而保证消息的消费顺序和发送顺序一致。

java
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.common.RemotingHelper;

import java.util.List;

public class OrderedProducer {
    public static void main(String[] args) throws Exception {
        // 创建一个 RocketMQ 生产者实例,并指定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("order_producer");
        // 指定 RocketMQ 地址
        producer.setNamesrvAddr("localhost:9876");
        // 启动生产者实例
        producer.start();
        // 发送消息
        // 模拟一批订单
        List orders = Order.generateOrders();
        for (Order order : orders) {
            Message message = new Message("order_topic", "order_tag", order.toString().getBytes(RemotingHelper.DEFAULT_CHARSET));
            // 将订单发送到同一个队列
            producer.send(message, new OrderQueueSelector(), order.getOrderId());
        }
        // 关闭生产者实例
        producer.shutdown();
    }
}

// 订单消息队列选择器
class OrderQueueSelector implements MessageQueueSelector {
    @Override
    public MessageQueue select(List list, Message message, Object o) {
        // 将订单信息的 ID hashCode 值与队列总数取余,得到目标队列 ID
        int queueIndex = (Integer)o % list.size();
        return list.get(queueIndex);
    }
}

// 订单类
class Order {
    private String orderId;
    private String orderName;

    public Order(String orderId, String orderName) {
        this.orderId = orderId;
        this.orderName = orderName;
    }

    public String getOrderId() {
        return orderId;
    }

    public String getOrderName() {
        return orderName;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderId='" + orderId + '\'' +
                ", orderName='" + orderName + '\'' +
                '}';
    }

    // 生成订单数据
    public static List generateOrders() {
        return List.of(
                new Order("2022010100001", "订单1"),
                new Order("2022010100002", "订单2"),
                new Order("2022010100003", "订单3"),
                new Order("2022010100004", "订单4"),
                new Order("2022010100005", "订单5")
        );
    }
}

需要注意的是,在使用顺序消息或有序生产者的场景下,需要对消息进行分片存储,并且每个分片只由一个节点进行消费,否则分片的消费可能会导致消息的乱序。另外,在实际使用中,还需要考虑消息的存储容量、消费者处理能力等因素,来避免消息积压和阻塞,从而保证发送的消息是顺序的。
5.rocketmq topic,消息队列和分区 之间的关系
Topic:在RocketMQ中,topic是用来标识一类消息的名称,类似于Kafka中的topic概念。所有的消息都是按照topic进行发布和订阅的。

消息队列:在RocketMQ中,Topic下的消息会被存储在多个消息队列中。每个消息队列可以看作是一个逻辑上的单元,它包含了一组连续的、有序的消息。可以把一个Topic看作是一个消息队列的集合。

分区:在RocketMQ中,Topic下的每个消息队列可以由多个分区组成,以实现更好的并发处理和容错性,类似于Kafka中的partition概念。多个消息消费者可以独立的消费这些分区上的消息,从而提高系统的并发处理能力。

分区的个数是可以动态调整的,例如可以根据消息队列的使用情况进行扩容或缩减。RocketMQ使用的是基于“Hash”算法的负载均衡机制,将消息均匀地放置到不同的分区中,以实现分布式处理和负载均衡。

总的来说,RocketMQ中的topic、消息队列和分区之间的关系是:topic下包含多个消息队列,每个消息队列下又包含多个分区,每个分区包含有序的消息,并由消息消费者进行并发消费和处理。这种分层的架构模型可以提高RocketMQ的性能和可靠性。

rocketmq中的消息多列是最小的逻辑存储单位,一个消息队列最多对应一个消费者线程,一个消息队列可以对应多个分区,分区的作用是为了保证高可用和吞吐量

你可能感兴趣的:(java,spring,分布式)