RocketMQ 学习笔记

本文深入探讨RocketMQ的框架流程,包括生产者、消费者与消息服务器的交互,以及消息发送、接收的各种API用法。此外,文章还介绍了RocketMQ的集群特性、消息存储与传输机制,如消息的持久化、刷盘机制和主从数据复制。
摘要由CSDN通过智能技术生成

 一、概述

MQ ( Message Queue ):消息队列,是一种用来保存消息数据的队列

MQ的作用:应用解耦、快速应用变更维护、流量削峰(削峰填谷)

MQ的缺点:消息顺序性、消息丢失、消息一致性、消息重复使用(都有解决方案)

二、RocketMQ 框架流程

        生产者(Producer Cluster)发送消息,消息包括Message、Topic(主分类)、Tag(小分类)。该消息到达消息服务器(Broker Cluster),消息服务器根据不同的topic创建队列,相应topic的消息会存放到对应的队列中。由监听器监督Broker中是否有消息,有消息的话Broker直接向消费者(Consumer Cluster)推送消息(还可向Broker发送请求,获取消息)。

        Broker除了接收消息、发送消息以外,还支持消息的持久化,即存储消息。过滤消息,实现高可用,一台服务器挂了,还可以继续工作(集群的方式)。

        命名服务器(NameServer Cluster)(集群)统筹生产者、消费者以及消息服务器,建立相应的连接。将消息服务器注册到命名服务器,命名服务器得到所有的Broker IPs,生产者在发送消息时连接命名服务器,同时获取所有的Broker信息(在没有Broker信息时不可工作)。消费者在接收消息时,同样同时拉取Broker消息。由此,三者相互之间建立连接。

        由心跳机制保障命名服务器实时感知生产者、消息服务器、消费者的存在,每一个服务器每30s向命名服务器发送请求,由此命名服务器感知到他们的存在。只要有一段时间没有收到心跳连接数据,就视相应的服务器宕机,视作下线。通过心跳机制来保障服务器状态的有效性。如果因为网路的原因而被视作宕机,当重新发送心跳连接数据后,再次上线。到此命名服务器就能够识别生产者、消费者以及消息服务器了。

三、消息发送与消息接收的API使用

1. 消息发送与接收

生产者:Producer.java

//生产者,产生消息
public class Producer {
    public static void main(String[] args) throws Exception {
        //1.创建一个发送消息的对象Producer
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.设定发送的命名服务器地址
        producer.setNamesrvAddr("192.168.23.129:9876");
        //3.1启动发送的服务
        producer.start();
        //4.创建要发送的消息对象,指定topic,指定内容body
        Message msg = new Message("topic1","hello rocketmq".getBytes("UTF-8"));
        //3.2发送消息
        SendResult result = producer.send(msg);
        System.out.println("返回结果:"+result);
        //5.关闭连接
        producer.shutdown();
    }
}

消费者:Consumer.java

//消费者,接收消息
public class Consumer {
    public static void main(String[] args) throws Exception {
        //1.创建一个接收消息的对象Consumer
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        //2.设定接收的命名服务器地址
        consumer.setNamesrvAddr("192.168.23.129:9876");
        //3.设置接收消息对应的topic,对应的sub标签为任意*
        consumer.subscribe("topic1", "*");


        //设置当前消费者的消费模式(默认模式:负载均衡)
      //consumer.setMessageModel(MessageModel.CLUSTERING);
        //设置当前消费者的消费模式为广播模式:所有客户端接收的消息都是一样的
        consumer.setMessageModel(MessageModel.BROADCASTING);


        //3.开启监听,用于接收消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                //遍历消息
                for (MessageExt msg : list) {
                    System.out.println("收到消息:"+msg);
                    System.out.println("消息:" + new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //4.启动接收消息的服务
        consumer.start();
        System.out.println("接收消息服务已开启运行");
    }
}

默认模式:负载均衡,利用MQ内部的负载均衡模式来平均分配消息

广播模式:所有客户端接收的消息都是一样的

广播模式的现象:

        (1):如果生产者先发送消息,后启动消费者,消息只能被消费一次

        (2):如果多个消费者先启动(广播模式),后发消息,才有广播效果

 2. 发送者发送消息的三种类型

  • 同步消息:即时性较强,重要的消息,且必须有回执的消息,例如短信,通知

prodecer.send ( Message msg )

// 同步消息发送
Message msg = new Message("topic2",("同步消息:hello rocketmq "+i).getBytes("UTF-8"));
SendResult result = producer.send(msg);
System.out.println("返回结果:"+result);
  • 异步消息:即时性较弱,但需要有回执的信息,例如订单中的某些信息

producer.send( Message msg, SendCallback sendCallback )

//异步消息发送(回调处理结果必须在生产者进程结束前执行,否则回调无法正确执行)
Message msg = new Message("topic2",("异步消息:hello rocketmq "+i).getBytes("UTF-8"));
producer.send(msg, new SendCallback() {
//表示成功返回结果
public void onSuccess(SendResult sendResult) {
    System.out.println(sendResult);
}
//表示发送消息失败
public void onException(Throwable t) {
        System.out.println(t);
    }
});
  • 单向消息:不需要回执的信息,例如日志类消息

sendOneway( Message msg )

//单向消息
Message msg = new Message("topic2",("单向消息:hello rocketmq "+i).getBytes("UTF-8"));
producer.sendOneway(msg);

3. 延时消息:msg.setDelayTimeLevel(3);

消息发送时并不直接发送到消息服务器,而是根据设定的等待时间到达,起到延时到达的缓冲作用

Message msg = new Message("topic3",("非延时消息:hello rocketmq "+i).getBytes("UTF-8"));
//设置当前消息的延时效果,顺序3表示30s
//消息时间等级依次为
//1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
msg.setDelayTimeLevel(3);
SendResult result = producer.send(msg);
System.out.println("返回结果:"+result);

4. 批量消息:send ( Collection<Message> msgs )

//创建一个集合保存多个消息
List<Message> msgList = new ArrayList<Message>();

Message msg1 = new Message("topic5",("批量消息:hello rocketmq "+1).getBytes("UTF-8"));
Message msg2 = new Message("topic5",("批量消息:hello rocketmq "+2).getBytes("UTF-8"));
Message msg3 = new Message("topic5",("批量消息:hello rocketmq "+3).getBytes("UTF-8"));

msgList.add(msg1);
msgList.add(msg2);
msgList.add(msg3);

//发送批量消息(每次发送的消息总量不得超过4M)
//消息的总长度包含4个信息:
//topic(字符串长度),body(字符串长度)
//消息的属性(key与value对应字符串字节数),日志(20字节)
SendResult send = producer.send(msgList);

5. 消息过滤(分类过滤)

生产者:Message( String topic, String tags,byte[] body )

//创建消息的时候除了制定topic,还可以指定tag
Message msg = new Message("topic6","tag2",
                ("消息过滤按照tag:hello rocketmq 2").getBytes("UTF-8"));

消费者

//接收消息的时候,除了制定topic,还可以指定接收的tag,*代表任意tag
consumer.subscribe("topic6","tag1 || tag2");

6. 消息过滤(SQL过滤、属性过滤、语法过滤)

生产者

//为消息添加属性
msg.putUserProperty("vip","1");
msg.putUserProperty("age","20");

消费者

//使用消息选择器来过滤对应的属性,语法格式为类SQL语法
consumer.subscribe("topic7", MessageSelector.bySql("age >= 18"));

注意:在开启Broker服务时,要开启对SQL语法的支持

配置文件中添加属性:enablePropertyFilter=true

启动命令:sh mqbroker -n localhost:9876  -c ../conf/broker.conf

7. 顺序消息

        问题引出:生产者发出具有顺序的业务消息,存放于不同的消息队列中,消费者接收的消息会发生顺序错乱,导致业务前置工作没做完,后置工作已经启动了。

        解决方案:指定相同业务的消息按照先后顺序存储在同一队列中,消息的消费者应该一个队列开启一个线程进行接收(会影响速度)。

生产者:producer.send( Message msg, MessageQueueSelector selector, Object arg )

//设置消息进入到指定的消息队列中
for(final Order order : orderList){
    Message msg = new Message("orderTopic",order.toString().getBytes());
    //发送时要指定对应的消息队列选择器
    SendResult result = producer.send(msg, new MessageQueueSelector() {
        //设置当前消息发送时使用哪一个消息队列
        public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
            //根据发送的信息不同,选择不同的消息队列
            //根据id来选择一个消息队列的对象,并返回->id得到int值
            int mqIndex = order.getId().hashCode() % list.size();
            return list.get(mqIndex);
        }
    }, null);

    System.out.println(result);
}

 消费者:consumer.registerMessageListener(  MessageListenerOrderly messageListener)

//使用单线程的模式从消息队列中取数据,一个线程绑定一个消息队列
consumer.registerMessageListener(new MessageListenerOrderly() {
//使用MessageListenerOrderly接口后,
//对消息队列的处理由一个消息队列多个线程服务,转化为一个消息队列一个线程服务
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
    for(MessageExt msg : list){
        System.out.println(Thread.currentThread().getName()+"  消息:"+new String(msg.getBody()));
        }
    return ConsumeOrderlyStatus.SUCCESS;
    }
});

8. 事务消息

MQ的事务流程(本地代码正常执行)

        生产者首先发送一半的消息(half消息),消息服务器返回接收状态ok,由本地事务的处理结果来决定消息是否发送。如若发送,则生产者提交完整消息进入队列;如果不发送则回滚,half消息被删除。

 MQ的消息补偿过程(当本地代码执行失败时)

 

事务消息状态:

提交状态:允许进入队列,此消息与非事务消息没有区别

回滚状态:不允许进入队列,此消息等同于未发送过

中间状态:完成了half消息的发送,未对MQ进行二次状态确认

生产者

  • 事务消息使用的生产者是TransactionMQProducer
  • 事务发送使用的是 producer.sendMessageInTransaction( Message msg, Object arg )
  • 对于事务的处理过程,需要添加本地事务监听器
//事务消息使用的生产者是TransactionMQProducer
TransactionMQProducer producer = new TransactionMQProducer("group1");
producer.setNamesrvAddr("192.168.184.128:9876");
//添加本地事务对应的监听
producer.setTransactionListener(new TransactionListener() {
    //正常事务过程
    public LocalTransactionState executeLocalTransaction(Message message, Object o) {
        //事务提交状态
        return LocalTransactionState.UNKNOW;
    }
    //事务补偿过程
    public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
         System.out.println("事务补偿过程执行");
         return LocalTransactionState.COMMIT_MESSAGE;
    }
});
producer.start();

Message msg = new Message("topic8",("事务消息:hello rocketmq ").getBytes("UTF-8"));
SendResult result = producer.sendMessageInTransaction(msg,null);
System.out.println("返回结果:"+result);
producer.shutdown();

由正常事务过程的返回值来决定事务状态

提交状态:LocalTransactionState.COMMIT_MESSAGE

回滚状态:LocalTransactionState.ROLLBACK_MESSAGE

中间状态:LocalTransactionState.UNKNOW

四、集群

单机:一个broker提供服务(宕机后服务瘫痪)

多个broker提供服务:有效解决了宕机后服务瘫痪的问题,但单机宕机后消息无法及时消费

多个master多个slave:(根据数据同步的时机不同,会有两种方式)

        master到slave消息同步方式为同步(较异步方式性能略低,消息无延迟)

        master到slave消息同步方式为异步(较同步方式性能略高,数据略有延迟)

1. 集群特征

        命名服务器(NameServer)集群部署:无状态节点,即各命名服务器之间互不通信;全服务器注册,即所有的broker在注册时注册了所有的命名服务器,命名服务器之间不存在信息差,通过任一服务器都能找到对应topic的broker及其所在队列。

        Broker集群部署:Broker分为Master与Slave,一个Master可以对应多个Slave,但是一个Slave只能对应一个Master;BrokerId为0表示Master,非0表示Slave,Master与Slave的对应关系通过指定相同的BrokerName。Master也可以部署多个。由master进行消息的读写操作,只有当master负载过大时,才由slave读取消息。每个Broker与NameServer集群中的所有节点建立长连接,定时注册Topic信息到所有NameServer。

        Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。

        Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定。

2. 集群的工作流程

  • 步骤1:NameServer启动,开启监听,等待broker、producer与consumer连接
  • 步骤2:broker启动,根据配置信息,连接所有的NameServer,并保持长连接
  • 步骤2补充:如果broker中有现存数据, NameServer将保存topic与broker关系
  • 步骤3:producer发信息,连接某个NameServer,并建立长连接
  • 步骤4:producer发消息
  • 步骤4.1若果topic存在,由NameServer直接分配
  • 步骤4.2如果topic不存在,由NameServer创建topic与broker关系,并分配
  • 步骤5:producer在broker的topic选择一个消息队列(从列表中选择)
  • 步骤6:producer与broker建立长连接,用于发送消息
  • 步骤7:producer发送消息

comsumer工作流程同producer

        读取消息时,判定broker当前是否处于高负载状态,broker负载状态低,读取消息,broker负载状态高,连接对应的slave读取消息,降低master工作压力

五、MQ内部存储及消息传输

1. 文件系统存储方案

        问题引入:broker中的数据如果不进行持久化存储,一旦broker停止服务,消息就丢失了。消息存储有两种方式,一种是数据库存储,但是数据库瓶颈将成为MQ瓶颈;一种是文件系统存储方案,采用消息刷盘机制进行数据存储。

 2. MQ 高效的消息存储与读写方式

1)磁盘的获取方式:通过启动时初始化文件大小来保证占用固定的磁盘空间,保证磁盘读写速度,在预留空间中实现顺序写。

2)零拷贝技术:数据传输由传统的4次复制简化成3次复制,减少1次复制过程,加快数据传输过程。

Linux网络数据传输过程:

  •  文件读写的重要指标,硬盘的读写速度,除了可以使用SSD之外,还可以通过顺序写来提高读写速度。由于磁盘存储有占用,会采取随机写的方式(100kb/s)。顺序写(600M/s)是在现有的内容尾部追加内容,因此要保证有足够的空余磁盘空间。
  • Java语言中使用 MappedByteBuffer 类实现了零拷贝技术

 3. 消息的存储结构

 消费逻辑队列:记录每个队列的使用情况,每个消费逻辑队列与每个消息队列一一绑定。

 索引:便于快速查找,每个消息队列都要创建自己的索引

4. 刷盘机制

消息存储最终存放在磁盘上,也就是文件系统中,这个过程被称为刷盘。

1)同步刷盘

  • 1) 生产者发送消息到MQ,MQ接到消息数据
  • 2) MQ挂起生产者发送消息的线程
  • 3) MQ将消息数据写入内存
  • 4) 内存数据写入硬盘
  • 5) 磁盘存储后返回SUCCESS
  • 6) MQ恢复挂起的生产者线程
  • 7) 发送ACK到生产者

2)异步刷盘

  • 1) 生产者发送消息到MQ,MQ接到消息数据
  • 2) MQ将消息数据写入内存
  • 3) 发送ACK到生产者
  • --等消息量多了--
  • 4) 内存数据写入硬盘

5. 主从数据复制

同步复制:master接到消息后,先复制到slave,然后反馈给生产者写操作成功

异步复制:master接到消息后,立即返回给生产者写操作成功,当消息达到一定量后再异步复制到slave

6. 负载均衡

Producer负载均衡:内部实现了不同broker集群中对同一topic对应消息队列的负载均衡

Consumer负载均衡:

平均分配

 循环平均分配

 7. 消息重试

当消息消费后未正常返回消费成功的信息将启动消息重试机制

1. 顺序消息重试

当消费者消费消息失败后,RocketMQ会自动进行消息重试(每次间隔时间为 1 秒)
注意:应用会出现消息消费被阻塞的情况,因此,要对顺序消息的消费情况进行监控,避免阻塞现象的发生

2. 无序消息重试 

无序消息包括普通消息、定时消息、延时消息、事务消息
无序消息重试仅适用于负载均衡(集群)模型下的消息消费,不适用于广播模式下的消息消费
为保障无序消息的消费,MQ设定了合理的消息重试间隔时长

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值