rocketMQ安装配置+与java交互API操作+集群搭建+高级特性

本文详细介绍了RocketMQ的安装、启动、API应用,包括单生产者单消费者、单生产者多消费者、多生产者多消费者、消息过滤、延时消息、批量消息等。还探讨了RocketMQ的高级特性,如集群配置、消息存储、刷盘机制、高可用性、主从数据复制等。此外,还提供了事务消息和死信队列的讲解,以及消息重复消费与消息幂等性的处理策略。
摘要由CSDN通过智能技术生成

文章目录

一、RocketMQ

1. MQ概述

MQ全称 Message Queue(消息队列)

存储消息的中间件 是在 消息的传输过程中保存消息的容器。
多用于分布式系统之间进行通信。
消息队列是典型例子: 生产者消费者模型。

MQ消息队列, 存储消息的中间件

分布式系统通信方式:
直接远程调用
借助第三方完成间接通信
发送方称为生产者, 接收方称为消费者

MQ 的作用
  1. 应用解耦:
    MQ中间件的加入,解开了两台服务器之间的强依赖
  2. 速应用变更维护 :
    生成者将消息存放到了MQ中,消费者从MQ中获取消息进行消费.
    可以根据消息的多少,在分配消费者服务器的数量
  3. 流量削锋(削峰填谷):
    生成者将消息存放到了MQ中,消费者从MQ中获取消息进行消费.
    消费者量力而行,在不宕机的情况下尽可能的消费消息
MQ的缺点

1.系统可用性降低: 集群
2.系统复杂度提高:(程序员提升水平)
3.异步消息机制(都有解决方案)
消息顺序性
消息丢失
消息一致性
消息重复使用

常见产品

ActiveMQ:java语言实现,万级数据吞吐量,处理速度ms级,主从架构,成熟度高
RabbitMQ :erlang语言实现,万级数据吞吐量,处理速度us级,主从架构,
RocketMQ :java语言实现,十万级数据吞吐量,处理速度ms级,分布式架构,功能强大,扩展性强
kafka :scala语言实现,十万级数据吞吐量,处理速度ms级,分布式架构,功能较少,应用于大数据较多

image-20210615234600073

image-20210615234752072

2. RocketMQ简介

RocketMQ是阿里开源的一款非常优秀中间件产品,脱胎于阿里的另一款队列技术MetaQ,
后捐赠给Apache基金会作为一款孵化技术,仅仅经历了一年多的时间就成为Apache基金会的顶级项目。并且它现在已经在阿里内部被广泛的应用,并且经受住了多次双十一的这种极致场景的压力
(2017年的双十一,RocketMQ流转的消息量达到了万亿级,峰值TPS达到5600万)

image-20210615235910627

Apache官网地址: https://www.apache.org/

rocketMQ下载地址: http://rocketmq.apache.org/dowloading/releases/

3. RocketMQ安装与启动

安装

注意: RocketMQ是使用java语言开发的,所以在安装RocketMQ前先安装JDK.

# 1、 Liunx安装JDK
a. 查看当前Linux系统是否已经安装java
	 rpm -qa | grep java
b. 将要安装的软件上传到linux服务器上
	/software (该目录存放我们上传的软件压缩包)
c. 将软件安装到 /usr/local/jdk(jdk目录需要自己创建)
	mkdir jdk
d. 将软件压缩包解压到 jdk目录下
	(进入jdk目录)
	tar -xvf /software/jdk....
# jdk软件已经安装完毕,接下来需要配置环境变量(修改配置文件)
e. 修改linux的配置文件 (/etc/profile)
	vim /etc/profile 
	编辑 profile文件在文件的最下方添加:
        export JAVA_HOME=/usr/local/jdk/jdk1.8.0_181 
        export PATH=$JAVA_HOME/bin:$PATH
f. 重写加载 profile 文件
	source /etc/profile
g. 验证是否安装成功
	java 
	java -version
	
# 2、安装RocketMQ
要求: 在Linux上必须有jdk环境(1.8以上)
# 先将RocketMQ压缩包上传到Linux服务器上
# 解压到根目录下(方便RocketMQ高级的集群配置)
unzip rocketmq-all-4.5.2-bin-release.zip
# 修改目录名称
mv rocketmq-all-4.5.2-bin-release rocketmq
------------------------
目录介绍:
    benchmark: rocketmq测试目录,用于测试MQ
    bin: 存放rocketmq可执行命令
        mqnamesrv: 启动命名服务器
        mqbroker: 启动代理服务器
    conf: 存放rocketmq配置文件
    lib: 存放依赖jar包
-------------------------
# 配置RocketMQ运行时占用的内存空间  改为下面
vim /rocketmq/bin/runbroker.sh
vim /rocketmq/bin/runserver.sh  
# 开发环境配置 JVM Configuration  
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"
启动命名服务器
# 进入rocketMQ安装目录下的bin目录
# 启动nameserv
sh mqnamesrv 
# 查询name server的状态
ps -ef | grep mqnamesrv
启动Broker

新开一个窗口,启动Broker代理服务器

# 进入rocketMQ安装目录下的bin目录
# 启动mq服务 -n 指定nameserv的地址
sh mqbroker -n localhost:9876  

注意:首次启动会报错,因为broker默认占用内存过大,我们需要调整参数
====需修改mqbroker对应的runbroker.sh文件,将占用内存改小====
设置为256M

再次重启
# 重启mq服务 -n 指定nameserv的地址
sh mqbroker -n localhost:9876  
image-20210616001200038
测试

再另起一个窗口测试

# 关闭防火墙
systemctl stop firewalld.service 

# 设置命名服务器位置
export NAMESRV_ADDR=localhost:9876
# 进入bin目录.执行测试程序  随时ctrl+c暂停
sh tools.sh org.apache.rocketmq.example.quickstart.Producer
sh tools.sh org.apache.rocketmq.example.quickstart.Consumer

# 接收的消息如下
MessageExt [
    queueId=1, 
    storeSize=179, 
    queueOffset=256, 
    sysFlag=0, 
    bornTimestamp=1616681027046, 
    bornHost=/192.168.190.143:48824, 
    storeTimestamp=1616681027048, 
    storeHost=/192.168.190.143:10911, 
    msgId=C0A8BE8F00002A9F000000000002D147, 
    commitLogOffset=184647, 
    bodyCRC=1392906658, 
    reconsumeTimes=0, 
    preparedTransactionOffset=0, 
    toString()=Message{
      topic='TopicTest', 
      flag=0, 
      properties={
          MIN_OFFSET=0, 
          MAX_OFFSET=422, 
          CONSUME_START_TIME=1616761308096, 
          UNIQ_KEY=C0A8BE8F265163947C6B805495E6004E, 
          WAIT=true, 
          TAGS=TagA
      }, 
      body=[72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 55, 56], 
      transactionId='null'
	}
]

brokerName:broker名称
queueId:记录MessageQueue编号,消息在Topic下对应的MessageQueue中被拉取
storeSize:记录消息在Broker存盘大小
queueOffset:记录在ConsumeQueue中的偏移
sysFlag:记录一些系统标志的开关状态,MessageSysFlag中定义了系统标识
bornTimestamp:消息创建时间,在Producer发送消息时设置
bornHost:记录发送改消息的producer地址
storeTimestamp:消息存储时间
storeHost:记录存储该消息的Broker地址
msgId:消息Id
commitLogOffest:记录消息在Broker中存储偏移
bodyCRC:消息内容CRC校验值
reconsumeTimes:消息重试消费次数
body:Producer发送的实际消息内容,以字节数组(ASCII码)形式进行存储。Message消息有一定大小限制。
transactionId:事务消息相关的事务编号
preparedTransactionOffset:
message
	topic:话题
	flag:网络通信层标记
	properties
      MIN_OFFSET:最小偏移
      MAX_OFFSET:最大偏移
      CONSUME_START_TIME:消费拉取时间
      CLUSTER:集群
      TAGS:消息标签
      UNIQ_KEY:
      WAIT:
      body: 消息内容
关闭服务
先关broker: sh mqshutdown broker
再关namesrv: sh mqshutdown namesrv

二、API应用

1. 单生产者单消费者

创建maven项目导入jar包坐标

<!-- 导入rocketMQ的jar包坐标 -->
<dependencies>
    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-client</artifactId>
        <version>4.5.2</version>
    </dependency>
</dependencies>
生产者端
package com.ahcfl.demo1_one2one;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

/**
 * TODO:单生产,单消费
 * 生产者代码
 */
public class ProducerDemo {
    public static void main(String[] args) throws Exception {
        //1.创建生产者对象
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.设置命名服务器的路径
        producer.setNamesrvAddr("192.168.190.129:9876");
        //3.启动生产者对象
        producer.start();
        System.out.println("==========生产者启动了==============");
        //4.创建消息(同步)
        // 参数1: topic,主题
        // 参数2: 存放的消息信息
        Message msg = new Message("topic1","hello rocketMQ !".getBytes("utf-8"));
        //5.发送消息
        SendResult sendResult = producer.send(msg);
        System.out.println(sendResult);
        //6.关闭生产者客户端
        //producer.shutdown();
    }
}

消费者端
package com.ahcfl.demo1_one2one;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

/**
 * TODO:单生产者,单消费者
 * 消费者代码
 */
public class ConsumerDemo {
    public static void main(String[] args) throws Exception {
        //1.创建消费者对象
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        //2.设置命名服务器的地址
        consumer.setNamesrvAddr("192.168.190.129:9876");
        //3.设置订阅的topic和小标记
        consumer.subscribe("topic1","*");
        //4.设置监听,当topic1下有内容时获取对应的消息
        consumer.registerMessageListener(
                // 设置同步消息监听
                new MessageListenerConcurrently() {
                    @Override
                    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                        for (MessageExt msg : msgs) {
                            System.out.println("获取的消息为: "+msg);
                            System.out.println("消息ID为: "+msg.getMsgId());
                            System.out.println("队列ID为: "+msg.getQueueId());
                            System.out.println("消息内容: "+new String(msg.getBody()));
                        }
                        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                    }
                }
        );
        //5.启动消费者
        consumer.start();
        System.out.println("==========消费者启动了============");
    }
}

2. 单生产者多消费者

生产者
package com.ahcfl.demo2_one2many;

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

/**
 * 测试一个生产者多个消费者
 * 编写生产者代码
 */
public class ProducerDemo {
    public static void main(String[] args) throws Exception {
        //1.创建生产者对象,用于生产消息
        DefaultMQProducer producer = new DefaultMQProducer("group2");
        //2.设置命名服务器地址和端口
        producer.setNamesrvAddr("192.168.190.129:9876");
        //3.启动生产者
        producer.start();
        //4.创建消息
        for (int i = 1; i <=10 ; i++) {
            Message msg = new Message("topic2",("hello rocketMQ "+i).getBytes("utf-8"));
            SendResult result = producer.send(msg);
            System.out.println("生产消息返回值: "+result);
        }
        //5.关闭生产者
        producer.shutdown();
    }
}

消费者
负载均衡

多个消费者平均分配消息数量

package com.ahcfl.demo2_one2many;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;

import java.util.List;

/**
 * TODO:测试一个生产者多个消费者
 *      编写消费者代码
 *      测试时先启动多个消费者,再启动生产者
 */
public class ConsumerDemo {
    public static void main(String[] args) throws Exception {
        //1.创建消费者对象,用于消费消息
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group2");
        //2.设置命名服务器的地址和端口
        consumer.setNamesrvAddr("192.168.190.129:9876");
        //3.设置消费者订阅
        consumer.subscribe("topic2","*");
        // TODO: 设置消费模式
        // MessageModel.CLUSTERING : 负载均衡模式,该模式是默认的
        consumer.setMessageModel(MessageModel.CLUSTERING);
        //4.设置监听,监听topic2主题下的所有消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            // 同步状态监听
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.println("获取的消息为: "+new String(msg.getBody()));
                }
                // 返回状态
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        System.out.println("=========消费者1111111111启动了==========");
        //5.启动消费者
        consumer.start();
    }
}

广播模式
package com.ahcfl.demo2_one2many;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;

import java.util.List;

/**
 * TODO:测试一个生产者多个消费者
 *      编写消费者代码
 *      测试时先启动多个消费者,再启动生产者
 */
public class ConsumerDemo {
    public static void main(String[] args) throws Exception {
        //1.创建消费者对象,用于消费消息
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group2");
        //2.设置命名服务器的地址和端口
        consumer.setNamesrvAddr("192.168.190.129:9876");
        //3.设置消费者订阅
        consumer.subscribe("topic2","*");
        // TODO: 设置消费模式
        // MessageModel.CLUSTERING : 负载均衡模式,该模式是默认的
        //consumer.setMessageModel(MessageModel.CLUSTERING);
        // MessageModel.BROADCASTING: 广播模式
        consumer.setMessageModel(MessageModel.BROADCASTING);
        //4.设置监听,监听topic2主题下的所有消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            // 同步状态监听
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.println("获取的消息为: "+new String(msg.getBody()));
                }
                // 返回状态
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        System.out.println("=========消费者222222222222启动了==========");
        //5.启动消费者
        consumer.start();
    }
}

广播模式的现象
  1) 如果 生产者先发送消息, 后启动消费者, 消息只能被消费一次
  2) 如果多个消费者先启动(广播模式),后发消息,才有广播的效果
结论:
 	 必须先启动消费者再启动生产者才有广播的效果

3. 多生产者多消费者

生产者
package com.ahcfl.demo3_many2many;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

/**
 * 测试一个生产者多个消费者
 * 编写生产者代码
 */
public class ProducerDemo {
    public static void main(String[] args) throws Exception {
        //1.创建生产者对象,用于生产消息
        DefaultMQProducer producer = new DefaultMQProducer("group3");
        //2.设置命名服务器地址和端口
        producer.setNamesrvAddr("192.168.190.129:9876");
        //3.启动生产者
        producer.start();
        //4.创建消息
        for (int i = 1; i <=20 ; i++) {
            Message msg = new Message("topic3",("hello rocketMQ "+i).getBytes("utf-8"));
            SendResult result = producer.send(msg);
            System.out.println("生产消息返回值: "+result);
        }
        //5.关闭生产者
        producer.shutdown();
    }
}

消费者
package com.ahcfl.demo3_many2many;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;

import java.util.List;

/**
 * TODO:测试一个生产者多个消费者
 *      编写消费者代码
 *      测试时先启动多个消费者,再启动生产者
 */
public class ConsumerDemo {
    public static void main(String[] args) throws Exception {
        //1.创建消费者对象,用于消费消息
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group3");
        //2.设置命名服务器的地址和端口
        consumer.setNamesrvAddr("192.168.190.129:9876");
        //3.设置消费者订阅
        consumer.subscribe("topic3","*");
        //4.设置监听,监听topic2主题下的所有消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            // 同步状态监听
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.println("获取的消息为: "+new String(msg.getBody()));
                }
                // 返回状态
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        System.out.println("=========消费者2222222222222222启动了==========");
        //5.启动消费者
        consumer.start();
    }
}

4. 生产者三种类型发送

同步消息: 发送完消息后,必须立即返回结果(如:发送短信成功提示,转账成功提示)
即时性较强,重要的消息,且必须有回执的消息,例如短信,通知(转账成功)

异步消息: 即时性较弱,但需要有回执的消息,例如订单中的某些信息

单向消息: 不需要有回执的消息,例如日志类消息

生产者
package com.ahcfl.demo4_msg_type;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

/**
 * TODO:测试生产者 生产的消息类型
 *      同步消息:
 *          消息生产成功后,Broker必须立即返回结果
 *      异步消息
 *      单向消息
 * 编写生产者代码
 */
public class ProducerDemo {
    public static void main(String[] args) throws Exception {
        //1.创建生产者对象,用于生产消息
        DefaultMQProducer producer = new DefaultMQProducer("group4");
        //2.设置命名服务器地址和端口
        producer.setNamesrvAddr("192.168.190.144:9876");
        //3.启动生产者
        producer.start();
        //4.创建消息
        Message msg = new Message("topic4",("单向消息: hello rocketMQ ").getBytes("utf-8"));
        // 单向消息: 无返回结果
        producer.sendOneway(msg);

        System.in.read();
        //5.关闭生产者
        producer.shutdown();
    }

    // 异步消息
    public static void main2(String[] args) throws Exception {
        //1.创建生产者对象,用于生产消息
        DefaultMQProducer producer = new DefaultMQProducer("group4");
        //2.设置命名服务器地址和端口
        producer.setNamesrvAddr("192.168.190.144:9876");
        //3.启动生产者
        producer.start();
        //4.创建消息
        Message msg = new Message("topic4",("异步消息: hello rocketMQ ").getBytes("utf-8"));
        // 异步消息
        // 注意: 不能立即关闭发送者客户端
        producer.send(msg, new SendCallback() {
            // 发送成功后的回调方法
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.println("发送成功: "+sendResult);
            }
            // 发送失败后的回调方法
            @Override
            public void onException(Throwable e) {
                System.out.println("发送失败: "+e);
            }
        });
        System.in.read();
        //5.关闭生产者
        producer.shutdown();
    }

    // 同步消息
    public static void main1(String[] args) throws Exception {
        //1.创建生产者对象,用于生产消息
        DefaultMQProducer producer = new DefaultMQProducer("group4");
        //2.设置命名服务器地址和端口
        producer.setNamesrvAddr("192.168.190.144:9876");
        //3.启动生产者
        producer.start();
        //4.创建消息
        Message msg = new Message("topic4",("同步消息: hello rocketMQ ").getBytes("utf-8"));
        SendResult result = producer.send(msg);
        System.out.println("生产消息返回值: "+result);
        //5.关闭生产者
        producer.shutdown();
    }


}

5. 延时消息

立刻发送, 只是 告诉MQ ,消息隐藏一段时间再暴露

应用场景 
   下订单时,往mq发一个取消订单的消息 (取消订单这个消息延时30分钟)
   如果30分钟内,用户支付了该订单,则延时消息
   如果30分钟后,用户没有支付,则消费者能看到这个消息,开始处理取消订单(如果没付费)
生产者
package com.ahcfl.demo5_time;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

/**
 * TODO:测试延时消息
 * 编写生产者代码
 */
public class ProducerDemo {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("group5");
        producer.setNamesrvAddr("192.168.190.144:9876");
        producer.start();
        for (int i = 1; i <= 10; i++) {
            Message msg = new Message("topic5",("延时消息: hello rocketMQ "+i).getBytes("utf-8"));
            // 设置消息的延时 时长
            // 可取值: 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
            // 使用索引取值
            msg.setDelayTimeLevel(2);
            SendResult result = producer.send(msg);
            System.out.println("发送消息结果: "+result);
        }
        System.in.read();
        producer.shutdown();
    }
}

消费者
package com.ahcfl.demo5_time;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

/**
 * TODO:测试延时消息接收
 *      编写消费者代码
 */
public class ConsumerDemo {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group5");
        consumer.setNamesrvAddr("192.168.190.144:9876");
        consumer.subscribe("topic5","*");
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.println("获取的消息为: "+new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        System.out.println("=========消费者1111111111111启动了==========");
        consumer.start();
    }
}

6. 批量消息

生产者
package com.ahcfl.demo06_mul;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

import java.util.ArrayList;

/**
 * TODO:测试生产批量消息
 * 编写生产者代码
 */
public class ProducerDemo {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("group6");
        producer.setNamesrvAddr("192.168.190.144:9876");
        producer.start();
        // 定义封装消息的list集合
        ArrayList<Message> messageList = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            Message msg = new Message("topic6",("批量消息: hello rocketMQ "+i).getBytes("utf-8"));
            messageList.add(msg);
        }
        // 发送批量消息
        SendResult result = producer.send(messageList);
        System.out.println("发送结果: "+result);
        System.in.read();
        producer.shutdown();
    }
}

消费者
package com.ahcfl.demo06_mul;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

/**
 * TODO:测试生产批量消息
 *      编写消费者代码
 */
public class ConsumerDemo {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group6");
        consumer.setNamesrvAddr("192.168.190.144:9876");
        consumer.subscribe("topic6","*");
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.println("获取的消息为: "+new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        System.out.println("=========消费者1111111111111启动了==========");
        consumer.start();
    }
}

注意:

批量消息内容总长度不超过4M
 		消息内容总长度包含如下:
        topic(字符串字节数)
        body (字节数组长度)
        消息追加的属性(key与value对应字符串字节数)
        日志(固定20字节)

7. 消息过滤

【1】tag过滤
生产者
package com.ahcfl.demo7_tag;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

import java.util.ArrayList;

/**
 * TODO:测试消费者过滤消息 - tag过滤
 * 编写生产者代码
 */
public class ProducerDemo {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("group7");
        producer.setNamesrvAddr("192.168.190.144:9876");
        producer.start();
        for (int i = 1; i <= 10; i++) {
            // 参数1: topic,主题
            // 参数2: tag,标题,标记
            // 参数3: 消息内容
            Message msg = new Message(
                    "topic7",
                    "tags2",
                    ("tags2消息: hello rocketMQ "+i).getBytes("utf-8"));
            // 发送批量消息
            SendResult result = producer.send(msg);
            System.out.println("发送结果: "+result);
        }

        System.in.read();
        producer.shutdown();
    }
}

消费者
package com.ahcfl.demo7_tag;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

/**
 * TODO:测试消费者过滤消息 - tag过滤
 *      编写消费者代码
 */
public class ConsumerDemo {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group7");
        consumer.setNamesrvAddr("192.168.190.144:9876");
        // 参数1: topic,主题
        // 参数2: tags,标题,标记
        consumer.subscribe("topic7","tags1 || tags2");
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.println("获取的消息为: "+new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        System.out.println("=========消费者1111111111111启动了==========");
        consumer.start();
    }
}

*代表任意tag
"tag1 || tag2" 代表两个  tag  那个都行
【2】sql/属性/语法过滤
生产者
package com.ahcfl.demo8_sql;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

/**
 * TODO:测试消费者过滤消息 - sql/语法/属性过滤
 * 编写生产者代码
 */
public class ProducerDemo {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("group8");
        producer.setNamesrvAddr("192.168.190.144:9876");
        producer.start();
        for (int i = 1; i <= 10; i++) {
            // 参数1: topic,主题
            // 参数2: tag,标题,标记
            // 参数3: 消息内容
            Message msg = new Message("topic8",("sql消息: hello rocketMQ "+i).getBytes("utf-8"));
            // 给消息设置属性值
            msg.putUserProperty("vip","1");
            msg.putUserProperty("age","20");
            // 发送批量消息
            SendResult result = producer.send(msg);
            System.out.println("发送结果: "+result);
        }

        System.in.read();
        producer.shutdown();
    }
}

消费者
package com.ahcfl.demo8_sql;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.MessageSelector;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

/**
 * TODO:测试消费者过滤消息 - sql/语法/属性过滤
 *      编写消费者代码
 */
public class ConsumerDemo {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group8");
        consumer.setNamesrvAddr("192.168.190.144:9876");
        // 参数1: topic,主题
        // 参数2: 使用sql语法对消息属性进行过滤
        consumer.subscribe("topic8",MessageSelector.bySql("age>18"));
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.println("获取的消息为: "+new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        System.out.println("=========消费者1111111111111启动了==========");
        consumer.start();
    }
}

注意:SQL过滤需要依赖服务器的功能支持,在broker配置文件中添加对应的功能项,并开启对应功能

修改conf/broker.conf配置文件,在最后开启sql语法支持
		enablePropertyFilter=true

启动服务器

# 使用配置文件中的配置启动Broker,并将Broker注册给nameServer
sh mqbroker -n localhost:9876 -c ../conf/broker.conf

8. 顺序消息

默认情况下,MQ 开启了多个队列, 同时发送多个消息的的话,发送给那个队列是不确定的,同时消息的消费者读取消息,每读取一个消息开启一个线程,也不能保证消息的顺序性,

image-20210622083421248

想要保证消息的有序性,需要指定消息的队列,同时 消息的消费者应该一个队列开启一个线程进行接收而不是一个消息一个线程)

消费者
package com.ahcfl.demo9_order;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.MessageSelector;
import org.apache.rocketmq.client.consumer.listener.*;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class ConsumerDemo {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group9");
        consumer.setNamesrvAddr("192.168.190.144:9876");
        consumer.subscribe("orderTopic","*");
        // 限制一个线程访问同一个队列
        consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext context) {
                for (MessageExt msg : list) {
                    System.out.println(Thread.currentThread().getId()+"-消息:" + new String(msg.getBody()));
                }
                return ConsumeOrderlyStatus.SUCCESS;

            }
        });
        consumer.start();
        System.out.println("22222222222接收消息服务已开启运行");
    }
}

生产者
package com.ahcfl.demo9_order;

import com.itheima.demo9_order.pojo.Order;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;

import java.util.ArrayList;
import java.util.List;

public class ProducerDemo {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("group9");
        producer.setNamesrvAddr("192.168.190.144:9876");
        producer.start();

        //创建要执行的业务队列
        List<Order> orderList = new ArrayList<Order>();
        //------------------第一个订单-----------
        Order order11 = new Order();
        order11.setId(1);
        order11.setMsg("第一个订单-主单-1");
        orderList.add(order11);

        Order order12 = new Order();
        order12.setId(1);
        order12.setMsg("第一个订单-子单-2");
        orderList.add(order12);

        Order order13 = new Order();
        order13.setId(1);
        order13.setMsg("第一个订单-支付-3");
        orderList.add(order13);

        Order order14 = new Order();
        order14.setId(1);
        order14.setMsg("第一个订单-推送-4");
        orderList.add(order14);
        //--------------------第二个订单--------------
        Order order21 = new Order();
        order21.setId(2);
        order21.setMsg("第二个订单-主单-1");
        orderList.add(order21);

        Order order22 = new Order();
        order22.setId(2);
        order22.setMsg("第二个订单-子单-2");
        orderList.add(order22);
        //-------------------第三个订单----------
        Order order31 = new Order();
        order31.setId(3);
        order31.setMsg("第三个订单-主单-1");
        orderList.add(order31);

        Order order32 = new Order();
        order32.setId(3);
        order32.setMsg("第三个订单-子单-2");
        orderList.add(order32);

        Order order33 = new Order();
        order33.setId(3);
        order33.setMsg("第三个订单-支付-3");
        orderList.add(order33);

        //设置消息进入到指定的消息队列中
        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) {
                    // list: 存放一个topic下所有的队列对象 (默认情况下:一个topic中有4个队列)
                    // 数量只能通过修改 mq 的配置来改变(阿里开发团队认为,这个是敏感资源需要服务器管理员控制,而不是编码人员控制)
                    //根据发送的信息不同,选择不同的消息队列
                    //根据id来选择一个消息队列的对象,并返回->id得到int值
                    int mqIndex = order.getId().hashCode() % list.size();
                    return list.get(mqIndex);
                }
            }, null);
            System.out.println(result);
        }
        //5.关闭连接
        producer.shutdown();
    }
}


Order实体

package com.ahcfl.demo09.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Order {
    private Integer id;
    private String msg;
}

9. 事务消息

事务消息介绍

RocketMQ 也允许我们像mysql 一样发送具有事务特征的消息

image-20210622083130344

MQ 消息的三种状态

提交状态:允许进入队列,此消息与非事务消息无区别
回滚状态:不允许进入队列,此消息等同于未发送过
中间状态:完成了 half 消息的发送,未对 MQ 进行二次状态确认(未知状态)

注意:事务消息仅与生产者有关,与消费者无关
生产者
正常事务提交
package com.ahcfl.demo10_transaction;

import org.apache.rocketmq.client.producer.*;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;

public class ProducerDemo {
    public static void main(String[] args) throws Exception {
        //DefaultMQProducer producer = new DefaultMQProducer("group1");
        TransactionMQProducer producer = new TransactionMQProducer("group1");
        producer.setNamesrvAddr("192.168.190.144:9876");
        // 添加本地事务监听
        producer.setTransactionListener(new TransactionListener() {
            // 正常事务
            @Override
            public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
                return LocalTransactionState.COMMIT_MESSAGE;
            }
            // 补偿事务-事务的补偿过程
            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt msg) {
                return null;
            }
        });
        producer.start();
        Message msg =
                new Message("topic3",
                        "事务消息 : hello rocketmq".getBytes("UTF-8"));
        SendResult result = producer.sendMessageInTransaction(msg,null);
        System.out.println("返回结果:"+result);
        System.in.read();
        //5.关闭连接
        producer.shutdown();
    }
}

事务回滚
package com.ahcfl.demo10_transaction;

import org.apache.rocketmq.client.producer.*;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;

public class ProducerDemo {
    public static void main(String[] args) throws Exception {
        //DefaultMQProducer producer = new DefaultMQProducer("group1");
        TransactionMQProducer producer = new TransactionMQProducer("group1");
        producer.setNamesrvAddr("192.168.190.144:9876");
        // 添加本地事务监听
        producer.setTransactionListener(new TransactionListener() {
            // 正常事务
            @Override
            public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
                return LocalTransactionState.ROLLBACK_MESSAGE;
            }
            // 补偿事务-事务的补偿过程
            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt msg) {
                return null;
            }
        });
        producer.start();
        Message msg =
                new Message("topic3",
                        "事务消息 : hello rocketmq".getBytes("UTF-8"));
        SendResult result = producer.sendMessageInTransaction(msg,null);
        System.out.println("返回结果:"+result);
        System.in.read();
        //5.关闭连接
        producer.shutdown();
    }
}

事务补偿
package com.ahcfl.demo10_transaction;

import org.apache.rocketmq.client.producer.*;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;

public class ProducerDemo {
    public static void main(String[] args) throws Exception {
        //DefaultMQProducer producer = new DefaultMQProducer("group1");
        TransactionMQProducer producer = new TransactionMQProducer("group1");
        producer.setNamesrvAddr("192.168.190.144:9876");
        // 添加本地事务监听
        producer.setTransactionListener(new TransactionListener() {
            // 正常事务
            @Override
            public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
                //return LocalTransactionState.COMMIT_MESSAGE;
                //return LocalTransactionState.ROLLBACK_MESSAGE;
                return LocalTransactionState.UNKNOW;
            }
            // 补偿事务-事务的补偿过程
            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt msg) {
                System.out.println("事务补偿执行...");
                return LocalTransactionState.COMMIT_MESSAGE;
            }
        });
        producer.start();
        Message msg =
                new Message("topic3",
                        "事务消息 : hello rocketmq".getBytes("UTF-8"));
        SendResult result = producer.sendMessageInTransaction(msg,null);
        System.out.println("返回结果:"+result);
        System.in.read();
        //5.关闭连接
        producer.shutdown();
    }
}

小结

MQ: 消息队列(中间件)
  作用:
			应用解耦
      快速应用变更与维护
      削峰填谷
	相关产品:
			ActiveMQ
      RabbitMQ
      RocketMQ
      kafka
RocketMQ执行原理:
		角色:
				命名服务器: 共享broker的地址
        Broker(经纪人/代理): 消息队列
        生产者: 生产消息,将生产的消息存放到Broker中
          	生产者会访问命名服务器获取Broker地址
        消费者: 消费消息,从Broker中获取消息并消费
          	消费者会访问命名服务器获取Broker地址
    消息::
						topic: 主题
            tag: 标题
            message: 消息
安装:
		RocketMQ是使用java语言开发的,安装之前需要保证计算机上有jdk环境(jdb1.8以上)
    解压缩即可:
				unzip 压缩包名
        配置RocketMQ运行时占用的内存空间
    启动name-server: 命名服务器
      	作用: 共享Broker地址
        sh mqnamesrv
    启动Broker:  启动broker的同时将broker注册给nameServer
        sh mqbroker -n localhost:9876
    测试: ....
javaAPI使用:

三、RocketMQ高级特性

image-20210622192708511

1. 集群

  • 多个broker提供服务

  • 多个master多个slave

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

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

1.1 集群特征
# 1.NameServer是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。
		每一个broker都需要将自己注册给集群中的每一个NameServer
# 2.Broker部署相对复杂,Broker分为Master(主节点)与Slave(从节点).
		Master(主节点): 写数据,往rocketMQ中存放数据
				主节点变从节点: 当内存消耗40%(阈值)后
		Slave(从节点): 读数据,消费者从Slave中读取数据
		一个Master(主节点)可以对应多个Slave(从节点),但是一个Slave(从节点)只能对应一个Master(主节点)
		Master(主节点)与Slave(从节点)的对应关系通过指定相同的BrokerName来确定,不同的BrokerId来定义,BrokerId为0表示Master,非0表示Slave,Master也可以部署多个
		每个Broker与NameServer集群中的所有节点建立长连接,定时注册Topic信息到所有NameServer。
# 3.Producer连接集群中的每一个NameServer
		Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由(路径)信息,并向提供Topic服务的Master建立长连接,且定时向Master发送心跳。
		Producer完全无状态,可集群部署。
# 4.Consumer连接集群中的每一个NameServer
		Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定。

总结:

#1. nameserver有多个(集群方式部署)
#2. master多个,每个master都有多个slave
#3. master的brokerid =0,slave的brokerid非0 
#4. 多个master和多个slave,如果brokername相同则为一组
#5. master和slave会将自己注册到每一台nameserver上 
1.2 集群的工作流程
步骤1:NameServer启动,开启监听,等待broker、producer与consumer连接
步骤2:broker启动,根据配置信息,连接所有的NameServer,并保持长连接
		如果broker中有现存数据, NameServer将保存topic与broker关系
步骤3:producer发信息,连接某个NameServer,并建立长连接
步骤4:producer发消息
    如果topic存在,由NameServer直接分配
    如果topic不存在,由NameServer创建topic与broker关系,并分配
步骤5:producer在broker的topic选择一个消息队列(从列表中选择)
步骤6:producer与broker建立长连接,用于发送消息
步骤7:producer发送消息

comsumer工作流程同producer
1.3 集群搭建
前置说明
准备两台Linux
  	两台服务器上分别启动一个NameServer,形成NameServer集群
  	两台服务器上分布启动两个Broker,一主一从,主从对应,形成Broker集群
  	分配如下:
  			第一台: itcast,192.168.190.129
     				启动一个NameServer
          	部署rocketmq-master1  (broker-1)
          	部署rocketmq-slave2		(broker-2)
    		第二台: itcast1,192.168.190.130
          	启动一个NameServer
          	部署rocketmq-master2	(broker-2)
          	部署rocketmq-slave1   (broker-1)
查看nameServer和Broker的运行情况
     ps -ef | grep mqnamesrv
     ps -ef | grep mqbroker
     jps   查看后台进程
配置域名映射

image-20210622214215036

便于通过域名查找对应的服务器,配置如下
# 两台服务器都有配置,ip地址根据自己的实际情况,做相关调整
# 编辑 /etc/hosts文件,添加域名映射,便于查找对应的ip地址
vim /etc/hosts
# ----------------------------------------------
# nameserver的ip映射
192.168.190.129 rocketmq-nameserver1
192.168.190.130 rocketmq-nameserver2
# broker的ip映射
192.168.190.129 rocketmq-master1
192.168.190.129 rocketmq-slave2
192.168.190.130 rocketmq-master2
192.168.190.130 rocketmq-slave1

配置完毕后重启网卡,应用配置

systemctl restart network

关闭防火墙

# 关闭防火墙
systemctl stop firewalld.service 
# 查看防火墙的状态
firewall-cmd --state 
# 禁止firewall开机启动
systemctl disable firewalld.service
安装rocketmq
安装前检查JDK环境

将rocketmq 解压至跟目录 /

# 为了方便配置和查找,将rocketmq安装在根目录下(熟练后可自行调整)
# 解压
unzip rocketmq-all-4.5.2-bin-release.zip
# 修改目录名称
mv rocketmq-all-4.5.2-bin-release rocketmq
# 修改mq执行时占用内存大小  在bin目录下
vim runbroker.sh
vim runserver.sh
# 修改位置
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"
# 修改sql语法支持
# 编辑 /conf/broker.conf 在文件最后添加
enablePropertyFilter=true

配置rocketmq的环境变量

# 编辑系统配置文件
vim /etc/profile
# 配置rocketmq环境变量(安装路径配置成自己的rockmq路径)
ROCKETMQ_HOME=/rocketmq
PATH=$PATH:$ROCKETMQ_HOME/bin
export ROCKETMQ_HOME PATH
# 配置完毕后,应用配置
source /etc/profile
创建rockmq数据存储目录

主节点创建四个目录/ 从节点四个目录

# 在真实开发中一台服务器上就部署一个Broker,直接设置当前Broker数据存储路径即可
# 当前我们在部署时,我们在一台服务器上部署了2个Broker,一主一从,我们需要创建不用的目录,分别存放Broker生成的数据信息
# 主节点数据保存路径
mkdir /rocketmq/store
mkdir /rocketmq/store/commitlog
mkdir /rocketmq/store/consumequeue
mkdir /rocketmq/store/index

# 从节点数据保存路径
mkdir /rocketmq/store-slave
mkdir /rocketmq/store-slave/commitlog
mkdir /rocketmq/store-slave/consumequeue
mkdir /rocketmq/store-slave/index
注意master与slave如果在同一个虚拟机中部署,需要将存储目录区分开
集群配置

不同的节点,应该修改不同的配置,文件夹也应该不一样

双主双从配置文件所在目录 (同步)
/rocketmq/conf/2m-2s-sync
A的主节点配置

配置文件名称: broker-a.propertie

#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=0
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10911
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120

#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
A的从节点配置

配置文件名称: broker-a-s.properties

#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=1
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=11011
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/rocketmq/store-slave
#commitLog 存储路径
storePathCommitLog=/rocketmq/store-slave/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/rocketmq/store-slave/consumequeue
#消息索引存储路径
storePathIndex=/rocketmq/store-slave/index
#checkpoint 文件存储路径
storeCheckpoint=/rocketmq/store-slave/checkpoint
#abort 文件存储路径
abortFile=/rocketmq/store-slave/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SLAVE
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
#发消息线程池数量
sendMessageThreadPoolNums=128
#拉消息线程池数量
pullMessageThreadPoolNums=128
B的主节点配置

配置文件名称: broker-b.propertie

#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=0
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10911
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120

#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/rocketmq/store/abort


#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
B的从节点配置

配置文件名称: broker-b-s.propertie

#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=1
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=11011
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120

#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/rocketmq/store-slave
#commitLog 存储路径
storePathCommitLog=/rocketmq/store-slave/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/rocketmq/store-slave/consumequeue
#消息索引存储路径
storePathIndex=/rocketmq/store-slave/index
#checkpoint 文件存储路径
storeCheckpoint=/rocketmq/store-slave/checkpoint
#abort 文件存储路径
abortFile=/rocketmq/store-slave/abort


#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SLAVE
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
#发消息线程池数量
sendMessageThreadPoolNums=128
#拉消息线程池数量
pullMessageThreadPoolNums=128
启动集群

检查启动内存 (nameserver 和broker 均需要修改)

vim /rocketmq/bin/runbroker.sh
vim /rocketmq/bin/runserver.sh

# 开发环境配置 JVM Configuration
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"

启动(进入bin 目录)

nohup sh mqnamesrv &
	jps  查看
nohup sh mqbroker -c ../conf/2m-2s-sync/broker-a.properties &
nohup sh mqbroker -c ../conf/2m-2s-sync/broker-b-s.properties &
===============
nohup sh mqnamesrv &
nohup sh mqbroker -c ../conf/2m-2s-sync/broker-b.properties &
nohup sh mqbroker -c ../conf/2m-2s-sync/broker-a-s.properties &
小结
rocketMQ:
		生产者: 生产消息
      	默认支持集群 ----> 高可用
      	连接nameServer,获取Broker地址,往Broker中存放消息信息
    消费者: 消费消息
      	默认支持集群 ----> 高可用
      	连接nameServer,获取Broker地址,从Broker中获取消息信息
    命名服务器: nameServer  搭建集群
      	管理共享Broker地址
      	命名服务器集群:
						每一台服务器存储的内容是一样的,且彼此之间不通信
    Broker: 存放消息的位置
				存放消息的位置
      	Broker集群:
						读写分类:
								主服务器(主节点-Master): 写
                  	一个主服务器可以有多个从节点
                从服务器(从节点-slave): 读
                  	一个从节点,只能有一个主服务器
            		数据同步问题:
										主节点将写入的数据同步给从节点
                    同步方式: 主节点每写一条,立即将数据同步给从节点
										异步方式: 注解点写一段时间后,异步批量将数据同步给从节点
注意:
		域名映射
    防火墙
    Broker和Nameserver运行时占用内存

2. rocketmq-console

rocketmq-console是一款基于java环境开发的(springboot)的管理控制台工具
获取地址github: https://github.com/apache/rocketmq-externals
获取地址码云: https://gitee.com/mirrors/RocketMQ-Externals
需要在SpringBoot项目的配置文件中,配置nameServer的地址和端口

3 高级特性介绍

3.1 消息的存储(消息存在哪儿?)

ActiveMQ 使用了数据库的消息存储,
缺点:数据库瓶颈将成为MQ瓶颈

image-20210622214242822

RocketMQ/Kafka/RabbitMQ
不用数据库,直接用文件存储(如)

image-20210622214300124

生产者将消息发送给RocketMQ后,RocketMQ会将数据同步或异步刷盘到硬盘文件上
3.2 MQ 高效的消息存储与读写方式
1) 通过启动时初始化话文件大小来保证 占用固定的磁盘空间,保证磁盘读写速度
2) 零拷贝”技术
	数据传输由传统的4次复制简化成3次复制(如下图),减少1次复制过程
	Java语言中使用MappedByteBuffer类实现了该技术
	要求:预留存储空间,用于保存数据(1G存储空间起步)

image-20210622214332287

3.3 消息存储结构
消息数据存储区域
    topic
    queueId
    message
消费逻辑队列
    minOffset
    maxOffset
    consumerOffset
索引
    key索引
    创建时间索引
    ……

image-20210622214350015

3.4 刷盘机制
将内存中的消息写入到磁盘:
	同步刷盘
  异步刷盘
3.4.1 同步刷盘

image-20210622214402506

1)生产者发送消息到MQ,MQ接到消息数据
2)MQ挂起生产者发送消息的线程
3)MQ将消息数据写入内存
4)内存数据写入硬盘
5)磁盘存储后返回SUCCESS
6)MQ恢复挂起的生产者线程
7)发送ACK到生产者
3.4.2 异步刷盘
1)生产者发送消息到MQ,MQ接到消息数据
2)MQ将消息数据写入内存
3)发送ACK到生产者
--等消息量多了--
4)内存数据写入硬盘
3.4.3 同步刷盘/ 异步刷盘 优缺点对比
同步刷盘:安全性高,效率低,速度慢(适用于对数据安全要求较高的业务)
异步刷盘:安全性低,效率高,速度快(适用于对数据处理速度要求较高的业务)
3.4.4 配置方式
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH

4. 高可用性

nameserver

nameserver ,通过无状态+全服务器注册 来保证即使一个宕机了也能提供所有的服务

消息服务器

主从架构(2M-2S) ,即使又一台服务器宕机, 服务依旧可以正常提供
注意: master 一旦宕机,slave 只提供消费服务,不能写入新的消息(slave 不会升级为master)

消息生产(开发人员写代码时保障)

生产者将相同的topic绑定到多个group组,保障master挂掉后,其他master仍可正常进行消息接收

消息消费

RocketMQ自身会根据master的压力确认是否由master承担消息读取的功能,当master繁忙时候,自动切换由slave承担数据读取的工作(RocketMQ主变从,阈值为超过内存的40%)

5. 主从数据复制

5.1 同步复制
master接到消息后,先复制到slave,然后反馈给生产者写操作成功
优点:数据安全,不丢数据,出现故障容易恢复
缺点:影响数据吞吐量,整体性能低
5.2 异步复制
master接到消息后,立即返回给生产者写操作成功,当消息达到一定量后再异步复制到slave
优点:数据吞吐量大,操作延迟低,性能高
缺点:数据不安全,会出现数据丢失的现象,一旦master出现故障,从上次数据同步到故障时间的数据将丢失
5.3 配置方式
#Broker 的角色
#- ASYNC_MASTER 异步Master
#- SYNC_MASTER 同步Master
#- SLAVE
brokerRole=SYNC_MASTER

6 负载均衡

  • Producer负载均衡

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

  • Consumer负载均衡

    平均分配

    循环平均分配

7. 消息重试

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

7.1 顺序消息重试
当消费者消费消息失败后,RocketMQ会自动进行消息重试(每次间隔时间为 1 秒)
注意:应用会出现消息消费被阻塞的情况,因此,要对顺序消息的消费情况进行监控,避免阻塞现象的发生
7.2 无序消息重试
无序消息包括普通消息、定时消息、延时消息、事务消息
无序消息重试仅适用于负载均衡(集群)模型下的消息消费,不适用于广播模式下的消息消费
为保障无序消息的消费,MQ设定了合理的消息重试间隔时长

image-20210622214430948

8. 死信队列

8.1 概念
当消息消费重试到达了指定次数(默认16次)后,MQ将无法被正常消费的消息称为死信消息(Dead-Letter Message)
死信消息不会被直接抛弃,而是保存到了一个全新的队列中,该队列称为死信队列(Dead-Letter Queue)
8.2 死信队列特征
- 归属某一个组(Gourp Id),而不归属Topic,也不归属消费者
- 一个死信队列中可以包含同一个组下的多个Topic中的死信消息
- 死信队列不会进行默认初始化,当第一个死信出现后,此队列首次初始化

8.3 死信队列中消息特征
- 不会被再次重复消费
- 死信队列中的消息有效期为3天,达到时限后将被清除
8.4 死信处理
在监控平台中,通过查找死信,获取死信的messageId,然后通过id对死信进行精准消费

9. 消息重复消费与 消息幂等

9.1 消息重复消费原因
1 生产者发送了重复的消息
    网络闪断
    生产者宕机
2 消息服务器投递了重复的消息
	网络闪断
3 动态的负载均衡过程
    网络闪断/抖动
    broker重启
    订阅方应用重启(消费者)
    客户端扩容
    客户端缩容
9.2 消息幂等
问题:对同一条消息,无论消费多少次,结果保持一致,称为消息幂等性

解决方案

- 使用业务id作为消息的key
- 在消费消息时,客户端对key做判定,未使用过放行,使用过抛弃

注意:messageId由RocketMQ产生,messageId并不具有唯一性,不能作用幂等判定条件

学会分析哪些业务需要幂等操作:
	幂等: 同一个业务无论执行多少次,执行结果时一样的.
查询所有用户信息
	select * from user;  
将id为1的用户名改为 "张三"  幂等
  update user set name = "张三" where id = 1;
修改id为1的账户金额,在原来的基础上加100  
  update user set money=money+100 where id = 1;
....

总结

rocketMQ集群:
		作用: 提高服务器架构的高可用
    概念:
				nameserver集群:
					nameserver是无状态的,在搭建集群时,
					需要将各个Broker的信息注册给每一个nameserver
        Broker集群:: master
            主要负责写,当主服务器的内存被消耗到40%,可以作为从服务器进行读取数据
          从: slave
            主要负责读,消费者连接从服务器,获取mq中的消息进行消费
          主broker和从broker的服务器的名称保持一致,
					主broker的id为0,从broker的id为非0
    搭建双主,双从:
				准备两台Linux服务器:
						129: 
							部署一个nameserver
              部署一个主1broker
              部署一个从2slave
            130:
							部署一个nameserver
              部署一个主2broker
              部署一个从1slave
				配置域名映射
        安装RocketMQ,修改运行时占用的内存空间,配置环境变量
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

编程小栈

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

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

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

打赏作者

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

抵扣说明:

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

余额充值