文章目录
- 1. 介绍
- 2. 配置环境
- 3. 功能概括图
- 4. 消息收发模型:
- 1. 九大属性
- 2. RocketMQ的4个组件:
- 3. 集群部署结构
- 1. 从官网下载rocketMq
- 2. 把文件解压出来,如下图所示
- 1. 启动nameserver
- 2. 启动 broker
- 五、 rocketMq可视化视图 下载与安装
- 1. 下载项目,自己打包
- 2. 把下载的项目解压出来,找到rocketmq-console项目
- 3. 打包项目
- 4. 启动 打包好的项目
- 5. 启动后的页面
- 七、 RocketMQ顺序消息
- 八、 顺序消息生产、消费 测试
- 1. RocketMQ事务消息流程
- 2. 事务消息生产者
- 3. 事务消息 消费
- 4. RocketMQ实现分布式事务流程(不太明白)
- 1. 消息生产者
- 2. 消息消费者:A和B,在同一组下
- 1) 消费者BroadcastingConsumerTest_A
- 2) 消费者B;同一消费组。
- 1) 广播生产
- 2) 消费者A
- 3) 消费者B;
- 1) 单个Master
- 2) 多个Master
- 3) 多Master多Slave模式-异步复制
- 4) 多Master多Slave模式-同步双写
- 1) 环境准备
- 2) 安装配置(slave)
- 3) 主从配置
- 4) 控制台配置
- 5) 主从模式故障演练
- a) master宕机收消息演示
- b) master宕机发消息演示
- 3. ==RocketMQ集群搭建-双主双从:推荐使用==
- 1) 准备工作
- 2) RocketMQ安装
- 3) 修改映射路径
一、 初始RocketMQ
1. 介绍
- RocketMQ是一款分布式、队列模型的消息中间件,是阿里巴巴集团自主研发的专业消息中间件,借鉴参考了JMS规范的MQ实现,更参考了优秀的开源消息中间件KAFKA,实现了业务消峰、分布式事务的优秀框架。
- 其底层代码编写清晰优秀,采用Netty NIO框架进行数据通信
- 摒弃了Zookeeper,内部使用更轻量级的NameServer进行网络路由,提高服务性能,并且支持消息失败重试机制。
- 天然支持集群模型,消费者负载均衡、水平扩展能力,支持广播模式和集群模式。
- 采用零拷贝的原理、顺序写盘、支持亿级消息堆积能力。
- 提供丰富的消息机制,如顺序消息、事务消息等。
目前 RocketMQ 已经成为Apache 顶级项目 。 在阿里内部, RocketMQ很好地服务了集团大大小小上千个应用,在每年的双十一当天,更有不可思议的万亿级消息通过 RocketMQ 流转(在 2017 年的双十一当天,整个阿里巴巴集团通过 RocketMQ 流转的线上消息达到了 万亿级,峰值 TPS 达到 5600 万),在阿里中台策略上发挥着举足轻重的作用 。
此外, RocketMQ 是使用 Java语言开发的,比起 Kafka 的 Scala语言和 RabbitMQ 的 Erlang 语 言,更容易找 到技术人员进行定制开发 。
2. 配置环境
本文讲解下载并安装单机版RocketMq,win10 环境。
要求:
- Linux/Unix/Mac/(本博客 是基于Windows 开发)
- 64bit JDK 1.8+;
- Maven 3.2.x
3. 功能概括图

多协议接入
- 支持 MQTT 协议:支持主动推送模型,多级 Topic 模型支持一次触达 1000万+ 终端,可广泛应用于物联网和社交即时通信场景。
- 支持 TCP 协议:区别于 HTTP 简单的接入方式,提供更为专业、可靠、稳定的 TCP 协议的 SDK 接入。
特色功能
- 事务消息:实现类似 X/Open XA 的分布事务功能,以达到事务最终一致性状态。
- 定时(延时)消息:允许消息生产者指定消息进行定时(延时)投递,最长支持40天。
- 大消息:目前默认支持最大 256KB 消息,华北2 地域支持最大 4MB 消息。
- 消息轨迹:通过消息轨迹,用户能清晰定位消息从发布者发出,经由 MQ 服务端,投递给消息订阅者的完整链路,方便定位排查问题。
- 广播消费:允许一个 Consumer ID 所标识的所有 Consumer 都会各自消费某条消息一次。
- 顺序消息:允许消息消费者按照消息发送的顺序对消息进行消费。
- 重置消费进度:根据时间重置消费进度,允许用户进行消息回溯或者丢弃堆积消息。
4. 消息收发模型:

二、 MQ基本概念:
1. 九大属性
- Message:消息,消息队列中信息传递的载体。
- Message ID:消息的全局唯一标识,由 MQ 系统自动生成,唯一标识某条消息。
- Message Key:消息的业务标识,由**消息生产者(Producer)设置**,唯一标识某个业务逻辑。三级消息类型
- Topic:消息主题,一级消息类型,通过 Topic 对消息进行分类。
- Tag:消息标签,二级消息类型,用来进一步区分某个 Topic 下的消息分类。
- Producer:消息生产者,也称为消息发布者,负责生产并发送消息。
- Producer ID:一类 Producer 的标识,这类 Producer 通常生产并发送一类消息,且发送逻辑一致。
- Consumer:消息消费者,也称为消息订阅者,负责接收并消费消息。
- Consumer ID:一类 Consumer 的标识,这类 Consumer 通常接收并消费一类消息,且消费逻辑一致。
2. RocketMQ的4个组件:
分别是nameserver、broker、producer和consumer。
- nameserver: 存储当前集群**所有Brokers信息、Topic跟Broker的对应关系**。
- Broker: 集群最核心的模块,主要负责Topic消息存储、消费者的消费位点管理(消费进度)。
- Producer: 消息生产者,每个生产者都有一个ID(编号),多个生产者实例可以共用同一个ID。同一个ID下所有实例组成一个生产者集群。
- Consumer: 消息消费者,每个订阅者也有一个ID(编号),多个消费者实例可以共用同一个ID。同一个ID下所有实例组成一个消费者集群。
3. 集群部署结构

4. 工作流程
- 启动Nameserver,Nameserver起来后监听端口,等待Broker、Produer、Consumer连上来,相当于一个路由控制中心。
- Broker启动,跟所有的Nameserver保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有topic信息。注册成功后,Nameserver集群中就有Topic跟Broker的映射关系。
- 收发消息前,先创建topic,创建topic时需要指定该topic要存储在哪些Broker上。也可以在发送消息时自动创建Topic。
- Producer发送消息,启动时先跟Namesrv集群中的其中一台建立长连接,并从Nameserver中获取当前发送的Topic存在哪些Broker上,然后跟对应的Broker建立长连接,直接向Broker发消息。
- Consumer跟Producer类似。跟其中一台Nameserver建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息。
三、 下载与安装
1. 从官网下载rocketMq
http://rocketmq.apache.org/ 下载如图,最新版

2. 把文件解压出来,如下图所示

四、 启动与测试
1. 启动nameserver
在window(也就是用管理员方式运行)上启动:用cmd进入bin目录下执行启动命令
会弹出一个框,表示启动成功。
Bug1:赶紧去配置环境变量。

然后重新执行启动命令。弹出 成功的框框:

2. 启动 broker
然后在bin目录下再执行
start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true
//autoCreateTopicEnable=true 表示默认创建topic
回车后会出现下面的bug2。
bug2:修改bin目录下的runbroker.cmd文件。

最后一行中,CLASSPATH 加上双引号,变为“CLASSPATH”

然后重新执行启动命令。弹出 成功的框框:

总共三个界面

扩展:在linux中的启动命令:
nohup sh bin/mqnamesrv &
nohup sh bin/mqbroker -n 127.0.0.1:9876 &
五、 rocketMq可视化视图 下载与安装
1. 下载项目,自己打包
项目地址:https://github.com/apache/rocketmq-externals

2. 把下载的项目解压出来,找到rocketmq-console项目
修改rocketmq-console\src\main\resources文件下的application.properties文件
如下:
server.contextPath=
server.port=8088
#spring.application.index=true
spring.application.name=rocketmq-console
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
logging.config=classpath:logback.xml
#if this value is empty,use env value rocketmq.config.namesrvAddr NAMESRV_ADDR | now, you can set it in ops page.default localhost:9876
rocketmq.config.namesrvAddr=127.0.0.1:9876
#if you use rocketmq version < 3.5.8, rocketmq.config.isVIPChannel should be false.default true
rocketmq.config.isVIPChannel=false
#rocketmq-console's data path:dashboard/monitor
rocketmq.config.dataPath=/tmp/rocketmq-console/data
#set it false if you don't want use dashboard.default true
rocketmq.config.enableDashBoardCollect=true
#set the message track trace topic if you don't want use the default one
rocketmq.config.msgTrackTopicName=
3. 打包项目
进入 \rocketmq-externals\rocketmq-console 文件夹,执行maven命令。
mvn clean package -Dmaven.test.skip=true
跳过测试,直接打包。

打包成功


4. 启动 打包好的项目
首先启动rocketMq的服务
项目本身就是springboot项目,直接启动:
D:\mq\rocketmq-externals\rocketmq-console\target>java -jar rocketmq-console-ng-1.0.0.jar &
命令:java -jar rocketmq-console-ng-1.0.0.jar –server.port=12581
注意:不指定端口的话,默认8080(避免与Tomcat冲突,最好指定)但是我在application.propertise 配置文件中,已经配置了端口,则这里不用加参数
5. 启动后的页面
一共打开三个服务,如图:

在浏览器输入:127.0.0.1:9876

六. 测试:用Java代码发送和接受信息
1. 测试
生产者:
package feng.rocketmq.quickstart;
import org.apache.rocketmq.client.exception.MQBrokerException;
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;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.io.UnsupportedEncodingException;
public class ProducerTest {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
// while (true){
// 1. 创建 DefaultMQProducer
DefaultMQProducer mqProducer = new DefaultMQProducer("ProducerTest");
// 2. 设置 namesrv 地址
mqProducer.setNamesrvAddr("127.0.0.1:9876");
// 3. 开启 DefaultMQProducer
mqProducer.start();
// 4. 创建消息 Message String topic, String tags, String keys, byte[] body
Message message = new Message("TopicTest1", // 主题
"TagA", // 主要用于消息过滤
"key1", // 消息的唯一值
"HELLO WORLD".getBytes(RemotingHelper.DEFAULT_CHARSET));
// 5. 发送消息
SendResult sendResult = mqProducer.send(message);
System.out.printf(String.valueOf(sendResult));
// 6. 关闭 DefaultMQProducer
mqProducer.shutdown();
// }
}
}

消费者:
package feng.rocketmq.quickstart;
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.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import java.io.UnsupportedEncodingException;
import java.util.List;
public class ConsumerTest {
public static void main(String[] args) throws MQClientException {
// 1. 创建 DefaultMQPushConsumer
DefaultMQPushConsumer mqPushConsumer = new DefaultMQPushConsumer("ConsumerTest");
// 2. 设置 namesrv
mqPushConsumer.setNamesrvAddr("127.0.0.1:9876");
// 3. 设置 subscribe ,这里是要读取的主题信息
mqPushConsumer.subscribe("TopicTest1", // 指定要消费的主题
"*" // 过滤规则
);
// 设置 消息拉取 最大数
mqPushConsumer.setConsumeMessageBatchMaxSize(2);
//设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
//如果非第一次启动,那么按照上次消费的位置继续消费
mqPushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
// 字面意思:消费线程最小值
mqPushConsumer.setConsumeThreadMin(1);
// 字面意思:消费线程最大值
mqPushConsumer.setConsumeThreadMax(1);
//设置消费模型,集群还是广播,默认为集群
mqPushConsumer.setMessageModel(MessageModel.CLUSTERING);
// 设置
mqPushConsumer.setPullThresholdForQueue(1);
// 4. 创新消息监听 MessageListener 匿名内部类方式实现
mqPushConsumer.registerMessageListener(new MessageListenerConcurrently(){
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
// 5. 获取消息信息
// 迭代 消息信息
for (MessageExt msg: list){
if (msg.getTopic().equals("TopicTest1")){
if (msg.getTags() != null && msg.getTags().equals("TagA")){
try {
// 获取主题
String topic = msg.getTopic();
// 获取标签
String tags = msg.getTags();
// 获取信息
byte[] body = msg.getBody();
// String result = new String(body);
String result = null;
result = new String(body, RemotingHelper.DEFAULT_CHARSET);
System.out.println("Consumer消费信息---topic:"+topic+",TagA:"+tags+",result"+result);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
//消息重试 消息消费失败之后,会重新再发一次。
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
}
}
// 6. 返回消息读取状态
// 消息消费完成
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 开启Consumer
mqPushConsumer.start();
// lambda
// mqPushConsumer.registerMessageListener();
}
}

2. 小总结
到目前为止,开启的服务有
1、 namesrv
2、 broker
3、 rockermq-console 可视化工具

还可以在可视化工具中查看**消息和消费者**。
七、 RocketMQ顺序消息
消息有序指的是可以**按照消息的发送顺序来消费**。 RocketMQ可以严格的保证消息有序。但这个顺序,不是全局顺序,只是分区(queue)顺序。要全局顺序只能一个分区。
如何保证顺序?
在MQ的模型中,顺序需要由3个阶段去保障:
1.消息被发送时保持顺序
2.消息被存储时保持和发送的顺序一致
3.消息被消费时保持和存储的顺序一致
发送时保持顺序意味着对于有顺序要求的消息,用户应该在同一个线程中采用同步的方式发送。存储保持和发送的顺序一致则要求在同一线程中被发送出来的消息A和B,存储时在空间上A一定在B之前。而消费保持和存储一致则要求消息A、B到达Consumer之后必须按照先A后B的顺序被处理。

八、 顺序消息生产、消费 测试
1. 消息生产者
我们创建一个消息生产者OrderProducer,这里每次发消息都会发到同一个队列中,代码如下:
package feng.rocketmq.order;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
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 org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.io.UnsupportedEncodingException;
import java.util.List;
/**
* 顺序消息生产
*/
public class OrderProducerTest {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
// 1. 创建 DefaultMQProducer
DefaultMQProducer mqProducer = new DefaultMQProducer("OrderProducerTest");
// 2. 设置 namesrv 地址
mqProducer.setNamesrvAddr("127.0.0.1:9876");
// 3. 开启 DefaultMQProducer
mqProducer.start();
for (int i = 1; i<=5; i++ ){
// 4. 创建消息 Message String topic, String tags, String keys, byte[] body
Message message = new Message("Order_Topic_Test", // 主题
"TagA", // 主要用于消息过滤
"key_"+i, // 消息的唯一值
("HELLO WORLD!-"+i).getBytes(RemotingHelper.DEFAULT_CHARSET));
// 5. 发送消息
// 第一个参数: 发送的消息信息
// 第二个参数: 选中指定的消息队列对象(会将所有信息队列传入进来)
// 第三个参数: 指定对应的队列下表
SendResult sendResult = mqProducer.send(
message,
new MessageQueueSelector() {
// 参数1 :有几个队列,会将队列传入到参数 list 中,如果是集群环境,此时这个队列就是集群个数存于队列的个数
// 参数2 :消息
// 参数3 :就是对应的下面的第三个参数,指定对应的队列下表
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
// 获取队列的下标
Integer index = (Integer) o;
// 获取对应下标的队列
return list.get(index);
}
},
1
);
System.out.println(String.valueOf(sendResult));
}
// 6. 关闭 DefaultMQProducer
mqProducer.shutdown();
}
}

2. 消息消费者
创建一个消息消费者OrderConsumer,消息监听用**MessageListenerOrderly**来实现顺序消息,代码如下:
package feng.rocketmq.order;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.*;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import java.io.UnsupportedEncodingException;
import java.util.List;
public class OrderConsumerTest {
public static void main(String[] args) throws MQClientException {
// 1. 创建 DefaultMQPushConsumer
DefaultMQPushConsumer mqPushConsumer = new DefaultMQPushConsumer("OrderConsumerTest");
// 2. 设置 namesrv
mqPushConsumer.setNamesrvAddr("127.0.0.1:9876");
// 3. 设置 subscribe ,这里是要读取的主题信息
mqPushConsumer.subscribe("Order_Topic_Test", // 指定要消费的主题
"*" // 过滤规则
);
// 设置 消息拉取 最大数
mqPushConsumer.setConsumeMessageBatchMaxSize(2);
// 4. 创新消息监听 MessageListener 匿名内部类方式实现
mqPushConsumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
for (MessageExt msg : list){
try {
// 获取主题
String topic = msg.getTopic();
// 获取标签
String tags = msg.getTags();
// 获取信息
byte[] body = msg.getBody();
// String result = new String(body);
String result = null;
result = new String(body, RemotingHelper.DEFAULT_CHARSET);
System.out.println("Order Consumer消费信息---topic:"+topic+",TagA:"+tags+",result"+result);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
//消息重试 消息消费失败之后,会重新再发一次。
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
// 开启Consumer
mqPushConsumer.start();
// lambda
// mqPushConsumer.registerMessageListener();
}
}

九、 RocketMQ事务消息、测试
在RocketMQ4.3.0版本后,开放了事务消息这一特性,对于分布式事务而言,最常说的还是二阶段提交协议。
1. RocketMQ事务消息流程
RocketMQ的事务消息,主要是通过消息的异步处理,可以保证本地事务和消息发送同时成功执行或失败,从而保证数据的最终一致性,这里我们先看看一条事务消息从诞生到结束的整个时间线流程:
这个图的流程一定要记住啊 !!!!,理解后,记到骨子里

事务消息的成功投递是需要经历三个Topic的,分别是:
Half Topic:用于记录所有的prepare消息
Op Half Topic:记录已经提交了状态的prepare消息
Real Topic:事务消息真正的Topic,在Commit后会才会将消息写入该Topic,从而进行消息的投递
2. 事务消息生产者
我们创建一个事务消息生产者TransactionProducerTest,事务消息发送消息对象是TransactionMQProducer,为了实现本地事务操作和回查,我们需要创建一个监听器,监听器需要实现TransactionListener接口,实现代码如下:
监听器TransactionListenerImpl,代码如下:
package feng.rocketmq.transaction;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.concurrent.ConcurrentHashMap;
public class TransactionImpl implements TransactionListener {
// 存储 当前线程 对应的 事务状态 key: 事务 ID, value : 当前事务执行的 状态
private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<String, Integer>();
/**
* 发送prepare消息成功后 回调该方法用于 执行本地事务
* @param message :回传的消息,利用transactionId即可获取到该消息的唯一Id
* 会记录当前消息的一个事务信息,这个事务不是本地事务,是Brocker 里面有一个 事务ID
* @param o 调用send方法时传递的参数,当send时候若有额外的参数可以传递到send方法中,这里能获取到
* @return
*/
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
// 获取线程 ID
String transactionId = message.getTransactionId();
/**
* 这里是 业务执行, 处理本地事务,可以调用 service层
*/
// System.out.println("hello! ----TEST ---Transaction");
// 0: 执行中,状态未知 1:本地事务执行成功 2: 本地事务执行失败
localTrans.put(transactionId, 0);
try {
// 此处 执行本地事务操作
System.out.println("....执行本地事务");
Thread.sleep(70000);
System.out.println("....执行完成本地事务");
} catch (InterruptedException e) {
e.printStackTrace();
// 发生异常 ,则回滚消息
localTrans.put(transactionId, 2);
return LocalTransactionState.ROLLBACK_MESSAGE;
}
// localTrans.put(transactionId, 1);
// System.out.println("executeLocalTransaction------状态为1");
return LocalTransactionState.COMMIT_MESSAGE;
}
/* 每次上面等待的时候, 下面 回查 一分钟一次:也就是一分钟执行一次,永不停止,知道项目结束 */
/**
* 消息 回查
* @param messageExt : 如果上面执行成功了,那么这个 参数 则可以获取上面的 事务状态
* @return
*/
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
// 获取事务 ID
String transactionId = messageExt.getTransactionId();
// 通过 事务ID 获取对应的 本地事务执行状态
Integer status = localTrans.get(transactionId);
System.out.println("消息回查---"+status);
switch (status){
case 0:
return LocalTransactionState.UNKNOW;
case 1:
return LocalTransactionState.COMMIT_MESSAGE;
case 2:
return LocalTransactionState.ROLLBACK_MESSAGE;
}
return LocalTransactionState.UNKNOW;
}
}
创建消息发送对象TransactionProducerTest, 代码如下:
package feng.rocketmq.transaction;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.*;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.*;
public class TransactionProducerTest {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
// 1. 创建 事务消息发送对象 TransactionMQProducer
TransactionMQProducer transactionMQProducer = new TransactionMQProducer("TransactionProducerTest");
// 2. 设置 namesrv 地址
transactionMQProducer.setNamesrvAddr("127.0.0.1:9876");
// 创建消息监听对象,用于执行本地事务和消息回查
TransactionListener transactionListener = new TransactionImpl();
// 设置监听器
transactionMQProducer.setTransactionListener(transactionListener);
// 线程池 用处: 消息回查(第七步) 和 消息发送(第三步) 是多线程的 所以要用到线程池。
ExecutorService executorService = new ThreadPoolExecutor(
2,
5,
100,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("client-transaction-msg-check-thread");
return thread;
}
}
);
// 设置线程池
transactionMQProducer.setExecutorService(executorService);
// 3. 开启 TransactionMQProducer
transactionMQProducer.start();
// 4. 创建消息 Message String topic, String tags, String keys, byte[] body
Message message = new Message("Transaction_Topic_Test", // 主题
"TagA", // 主要用于消息过滤
"key_T", // 消息的唯一值
"HELLO - Transaction!".getBytes(RemotingHelper.DEFAULT_CHARSET));
// 5. 发送 事务消息
TransactionSendResult transactionSendResult = transactionMQProducer.sendMessageInTransaction(message, "hello-transaction");
System.out.println(transactionSendResult);
// 休眠
Thread.sleep(120000);
// 6. 关闭 DefaultMQProducer
transactionMQProducer.shutdown();
}
}
3. 事务消息 消费
事务消息的消费者和普通消费者一样,这里我们就不做介绍了,直接贴代码:
package feng.rocketmq.transaction;
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.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import java.io.UnsupportedEncodingException;
import java.util.List;
public class TransactionConsumerTest {
public static void main(String[] args) throws MQClientException {
// 1. 创建 DefaultMQPushConsumer
DefaultMQPushConsumer mqPushConsumer = new DefaultMQPushConsumer("TransactionConsumerTest");
// 2. 设置 namesrv
mqPushConsumer.setNamesrvAddr("127.0.0.1:9876");
// 设置 每次拉去的 消息个数
mqPushConsumer.setConsumeMessageBatchMaxSize(5);
// 设置消费顺序
mqPushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
// 3. 设置 监听消息subscribe ,这里是要读取的主题信息
mqPushConsumer.subscribe("Transaction_Topic_Test", // 指定要消费的主题
"*" // 过滤规则
);
// 4. 创建 消息监听 registerMessageListener 匿名内部类方式实现
mqPushConsumer.registerMessageListener(new MessageListenerConcurrently(){
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
// 5. 获取消息信息
// 迭代 消息信息
for (MessageExt msg: list){
try {
String topic = msg.getTopic();
String tags = msg.getTags();
String keys = msg.getKeys();
String result = new String(msg.getBody(), RemotingHelper.DEFAULT_CHARSET);
System.out.println("Consumer消费信息---topic:"+topic+",Tags:"+tags+",keys:"+keys+",result"+result);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
//消息重试 消息消费失败之后,会重新再发一次。
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
// 6. 返回消息读取状态
// 消息消费完成
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 开启Consumer
mqPushConsumer.start();
}
}
4. RocketMQ实现分布式事务流程(不太明白)
MQ事务消息解决分布式事务问题,但第三方MQ支持事务消息的中间件不多,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。
以阿里的 RocketMQ 中间件为例,其思路大致为:
- 第一阶段Prepared消息,会拿到消息的地址。
- 第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。
也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

十、 消息广播/批量发送
上面发送消息,我们测试的时候,可以发现消息只有一个消费者能收到(消费者消费时,指定了消费组),如果我们想实现消息广播,让每个消费者都能收到消息也是可以实现的。而且上面发送消息的时候,每次都是发送单条Message对象,能否批量发送呢?答案是可以的。
1. 消息生产者
创建消息生产者BroadcastingProducer,代码如下:

代码如下:
package feng.rocketmq.broadcasting;
import org.apache.rocketmq.client.exception.MQBrokerException;
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;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
public class BroadcastingProducerTest {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
// 1. 创建 DefaultMQProducer
DefaultMQProducer mqProducer = new DefaultMQProducer("BroadcastingProducerTest");
// 2. 设置 namesrv 地址
mqProducer.setNamesrvAddr("127.0.0.1:9876");
// 3. 开启 DefaultMQProducer
mqProducer.start();
// 4. 创建消息 Message String topic, String tags, String keys, byte[] body
List<Message> messages = new ArrayList<Message>();
for (int i= 0; i<10;i++){
Message message = new Message("BroadcastingTest1", // 主题
"TagA", // 主要用于消息过滤
"keys", // 消息的唯一值
"HELLO WORLD".getBytes(RemotingHelper.DEFAULT_CHARSET));
messages.add(message);
// 5. 发送消息
SendResult sendResult = mqProducer.send(messages);
System.out.println(String.valueOf(sendResult));
}
// 6. 关闭 DefaultMQProducer
mqProducer.shutdown();
}
}
2. 消息消费者:A和B,在同一组下
广播消费模式其实就是每个消费者都能读取到消息,我们这里只需要将消费者的消费模式设置成广播模式即可。
consumer.setMessageModel(MessageModel.BROADCASTING);,默认是集群消费模式,集群消费模式:只有一个消费者能消费消息。
广播消费:则每个消息者都能消费到消息。
代码如下:
1) 消费者BroadcastingConsumerTest_A
package feng.rocketmq.broadcasting;
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 org.apache.rocketmq.remoting.common.RemotingHelper;
import java.io.UnsupportedEncodingException;
import java.util.List;
public class BroadcastingConsumerTest_A {
public static void main(String[] args) throws MQClientException {
// 1. 创建 DefaultMQPushConsumer
DefaultMQPushConsumer mqPushConsumer = new DefaultMQPushConsumer("BroadcastingConsumerTest");
// 2. 设置 namesrv
mqPushConsumer.setNamesrvAddr("127.0.0.1:9876");
// 默认是集群消费模式MessageModel.CLUSTERING , MessageModel.BROADCASTING: 广播模式。
mqPushConsumer.setMessageModel(MessageModel.BROADCASTING);
// 3. 设置 subscribe ,这里是要读取的主题信息
mqPushConsumer.subscribe("BroadcastingTest1", // 指定要消费的主题
"*" // 过滤规则
);
// 设置 消息拉取 最大数
mqPushConsumer.setConsumeMessageBatchMaxSize(2);
// 4. 创建消息监听 MessageListener 匿名内部类方式实现
mqPushConsumer.registerMessageListener(new MessageListenerConcurrently(){
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
// 5. 获取 消息 信息
// 迭代 消息 信息
for (MessageExt msg: list){
try {
// 获取主题
String topic = msg.getTopic();
// 获取标签
String tags = msg.getTags();
// 获取信息
byte[] body = msg.getBody();
// String result = new String(body);
String result = null;
result = new String(body, RemotingHelper.DEFAULT_CHARSET);
// 打印读取的消息
System.out.println("A---Consumer消费信息---topic:"+topic+",TagA:"+tags+",result"+result);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
//消息重试 消息消费失败之后,会重新再发一次。
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
// 6. 返回消息读取状态
// 消息消费完成
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 开启Consumer
mqPushConsumer.start();
}
}
2) 消费者B;同一消费组。
package feng.rocketmq.broadcasting;
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 org.apache.rocketmq.remoting.common.RemotingHelper;
import java.io.UnsupportedEncodingException;
import java.util.List;
public class BroadcastingConsumerTest_B {
public static void main(String[] args) throws MQClientException {
// 1. 创建 DefaultMQPushConsumer
DefaultMQPushConsumer mqPushConsumer = new DefaultMQPushConsumer("BroadcastingConsumerTest");
// 2. 设置 namesrv
mqPushConsumer.setNamesrvAddr("127.0.0.1:9876");
// 默认是集群消费模式MessageModel.CLUSTERING , MessageModel.BROADCASTING: 广播模式。
mqPushConsumer.setMessageModel(MessageModel.BROADCASTING);
// 3. 设置 subscribe ,这里是要读取的主题信息
mqPushConsumer.subscribe("BroadcastingTest1", // 指定要消费的主题
"*" // 过滤规则
);
// 设置 消息拉取 最大数
mqPushConsumer.setConsumeMessageBatchMaxSize(2);
// 4. 创建消息监听 MessageListener 匿名内部类方式实现
mqPushConsumer.registerMessageListener(new MessageListenerConcurrently(){
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
// 5. 获取 消息 信息
// 迭代 消息 信息
for (MessageExt msg: list){
try {
// 获取主题
String topic = msg.getTopic();
// 获取标签
String tags = msg.getTags();
// 获取信息
byte[] body = msg.getBody();
// String result = new String(body);
String result = null;
result = new String(body, RemotingHelper.DEFAULT_CHARSET);
// 打印读取的消息
System.out.println("B---Consumer消费信息---topic:"+topic+",TagA:"+tags+",result"+result);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
//消息重试 消息消费失败之后,会重新再发一次。
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
// 6. 返回消息读取状态
// 消息消费完成
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 开启Consumer
mqPushConsumer.start();
}
}
3. 测试走起来
1) 广播生产

2) 消费者A

3) 消费者B;

十一、 ROCKETMQ集群模式
1. RocketMQ集群模式
1) 单个Master
这是一种风险比较大的集群方式,因为一旦Borker重启或宕机期间,将会导致这个服务不可用,因此是不建议线上环境去使用的。
2) 多个Master
一个集群全部都是Master,没有Slave,它的优点和缺点如下:
优点:配置简单,单个Master宕机或者是重启维护对应用没有什么影响的,在磁盘配置为RAID10时,即使机器宕机不可恢复的情况下,消息也不会丢失(异步刷盘丢失少量消息,同步刷盘则是一条都不会丢失),性能最高
缺点:当单个Broker宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息的实时性会受到影响。
3) 多Master多Slave模式-异步复制
每个Master配置一个Slave,有多对的Master-Slave,HA采用的是异步复制方式,主备有短暂的消息延迟,毫秒级别的(Master收到消息之后立刻向应用返回成功标识,同时向Slave写入消息)。优缺点如下:
优点:即使是磁盘损坏了,消息丢失的非常少,且消息实时性不会受到影响,因为Master宕机之后,消费者仍然可以从Slave消费,此过程对应用透明,不需要人工干预,性能同多个Master模式机会一样。
缺点:Master宕机,磁盘损坏的情况下,会丢失少量的消息。
4) 多Master多Slave模式-同步双写
每个Master配置一个Slave,有多对的Master-Slave,HA采用的是同步双写模式,主备都写成功,才会向应用返回成功。
优点:数据与服务都无单点,Master宕机的情况下,消息无延迟,服务可用性与数据可用性都非常高
缺点:性能比异步复制模式略低,大约低10%左右,发送单个Master的RT会略高,目前主机宕机后,Slave不能自动切换为主机,后续会支持自动切换功能。
2. RocketMQ主从搭建
1) 环境准备
我们先准备2台centos虚拟机,ip:192.168.211.142/192.168.211.143,在hosts文件中配置地址与IP的映射关系。.
IP
| hostname
| mastername
|
192.168.211.142
| rocketmq-nameserver2
| rocketmq-master1-slave1
|
192.168.211.143
| rocketmq-nameserver1
| rocketmq-master1
|
修改2台机器的/etc/hosts文件,加入如下映射关系:
192.168.211.143 rocketmq-nameserver1
192.168.211.143 rocketmq-master1
192.168.211.142 rocketmq-nameserver2
192.168.211.142 rocketmq-master1-slave1
2) 安装配置(slave)
由于之前已经安装了1台RocketMQ,所以我们只需要安装从节点(192.168.211.142)即可。我们可以把安装文件上传到虚拟上,并解压安装
从192.168.211.143目录下降RocketMQ拷贝到192.168.211.142服务器上,如下代码:
scp rocketmq-all-4.4.0-bin-release.zip
192.168.211.142:/usr/local/server/software/
解压文件存放到/usr/local/server/mq目录下
unzip rocketmq-all-4.4.0-bin-release.zip -d /usr/local/server/mq
更改解压后的文件名
mv rocketmq-all-4.4.0-bin-release rocketmq
创建RocketMQ存储文件的目录,执行如下命令:
[root@localhost rocketmq]# mkdir logs
[root@localhost rocketmq]# mkdir store
[root@localhost rocketmq]# cd store/
[root@localhost store]# mkdir commitlog
[root@localhost store]# mkdir consumequeue
[root@localhost store]# mkdir index
进入conf目录,替换所有xml中的${user.home},保证日志路径正确
sed -i 's#${user.home}#/usr/local/server/mq/rocketmq#g' *.xml
注意:sed -i在这里起一个批量替换的作用
sed -i 's#原字符串#新字符#g' 替换的文件
RocketMQ对内存的要求比较高,最少1G,如果内存太少,会影响RocketMQ的运行效率和执行性能。我们需要修改bin目录下的runbroker.sh和runserver.sh文件
runbroker.sh
改前:
JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g"
改后:
JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn1g"
runserver.sh
改前:
JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
改后:
JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
3) 主从配置
我们这里设置192.168.211.143为主节点,192.168.211.142为从节点,我们先修改192.168.211.143服务器数据,再拷贝到192.168.211.142服务器来即可。
修改192.168.211.143机器的RocketMQ配置文件:broker-a.properties,主要增加了从节点的地址
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
修改192.168.211.143机器的RocketMQ配置文件:broker-a-s.properties,将主配置复制一份儿 即可。
terName=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=10911
#删除文件时间点,默认是凌晨4点
deleteWhen=04
#文件保留时间,默认48小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/server/mq/rocketmq/store
#commitLog存储路径
storePathCommitLog=/usr/local/server/mq/rocketmq/store/commitlog
#消费队列存储路径
storePathConsumeQueue=/usr/local/server/mq/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/server/mq/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/server/mq/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/server/mq/rocketmq/store/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=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageTreadPoolNums=128
#拉消息线程池数量
#pullMessageTreadPoolNums=128lushDiskType=ASYNC_FLUSH
主要改了brokerId=1和brokerRole=SLAVE
将配置文件拷贝到192.168.211.142服务器上,如下代码:
scp broker-a.properties broker-a-s.properties 192.168.211.142:/usr/local/server/mq/rocketmq/conf/2m-2s-async/
停止192.168.211.43服务
sh mqshutdown broker
sh mqshutdown namesrv
启动192.168.211.143的namesrv
启动192.168.211.142的namesrv
启动192.168.211.143的broker
nohup sh mqbroker -c /usr/local/server/mq/rocketmq/conf/2m-2s-async/broker-a.properties > /dev/null 2>&1 &
启动192.168.211.142的broker
nohup sh mqbroker -c /usr/local/server/mq/rocketmq/conf/2m-2s-async/broker-a-s.properties > /dev/null 2>&1 &
4) 控制台配置
修改rocketmq-console-ng控制台项目的application.properties配置文件,添加上主从地址
rocketmq.config.namesrvAddr=192.168.211.143:9876;192.168.211.142:9876
运行控制台项目,并打开控制台的集群,可以看到如下节点信息:

5) 主从模式故障演练
主从模式,即使Master宕机之后,消费者仍然可以从Slave消费,但不能接收新的消息,我们通过程序来演示一次该流程。
a) master宕机收消息演示
创建MasterSlaveProducer,实现消息发送,代码如下:

执行消息发送后,打开控制台,如下图:

停掉192.168.211.143的broker节点
创建MasterSlaveConsumer,实现消息消费,代码如下:


控制台结果如下:
topic:Test_Quick_Topic,tags:TagA,result:hello rocketmq master-slave
b) master宕机发消息演示
关掉192.168.211.143的broker后,再次尝试发消息,会出现如下错误:
3. RocketMQ集群搭建-双主双从:推荐使用
我们这里搭建一个双主双从的集群,采用同步赋值异步刷盘方式进行集群,在工作中,我们也推荐这么做,我们先把环境准备一下。
1) 准备工作
准备4台机器,如下:
IP
| hostname
| mastername
|
192.168.211.141
| rocketmq-nameserver1
| rocketmq-master1
|
192.168.211.142
| rocketmq-nameserver2
| rocketmq-master2
|
192.168.211.143
| rocketmq-nameserver3
| rocketmq-master1-slave
|
192.168.211.144
| rocketmq-nameserver4
| rocketmq-master2-slave
|
2) RocketMQ安装
在每台机器上安装RocketMQ,安装过程和上面单节点RocketMQ安装流程基本类似。
3) 修改映射路径
修改每台机器的/etc/hosts文件,添加如下映射路径
192.168.211.141 rocketmq-nameserver1
192.168.211.142 rocketmq-nameserver2
192.168.211.143 rocketmq-nameserver3
192.168.211.144 rocketmq-nameserver4
192.168.211.141 rocketmq-master1
192.168.211.142 rocketmq-master2
192.168.211.143 rocketmq-master1-slave
192.168.211.144 rocketmq-master2-slave
4) RocketMQ节点配置
4台机器一起解压rocketmq压缩文件,最后将解压文件存放在/usr/local/server/mq/rocketmq目录下。
分别在4台机器的rocketmq目录下执行如下操作
mkdir logs
mkdir store
cd store/
mkdir commitlog
mkdir consumequeue
mkdir index
进入141集群rocketmq/conf/2m-2s-sync目录下配置对应的配置文件,修改broker-a.properties配置文件,如下:
terName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示Master, > 0 表示slave
brokerId=0
#nameServer 地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876;rocketmq-nameserver3:9876;rocketmq-nameserver4: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=/usr/local/server/mq/rocketmq/store
#commitLog存储路径
storePathCommitLog=/usr/local/server/mq/rocketmq/store/commitlog
#消费队列存储路径
storePathConsumeQueue=/usr/local/server/mq/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/server/mq/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/server/mq/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/server/mq/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
# flushCommitLogLeastPages=4
# flushConsumeQueueLeastPages=2
# flushCommitLogThoroughInterval=10000
# flushConsumeQueueThoroughInterval=60000
# Broker 的角色
# - ASYNC_MASTER 异步复制Master
# - SYNC_MASTER 同步双写Master
# - SLAVE
brokerRole=YNC_MASTER
# 刷盘方式
# - ASYNC_FLUSH 异步刷盘
# - SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageTreadPoolNums=128
#拉消息线程池数量
#pullMessageTreadPoolNums=128
修改broker-b.properties文件,如下:
terName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示Master, > 0 表示slave
brokerId=0
#nameServer 地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876;rocketmq-nameserver3:9876;rocketmq-nameserver4: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=/usr/local/server/mq/rocketmq/store
#commitLog存储路径
storePathCommitLog=/usr/local/server/mq/rocketmq/store/commitlog
#消费队列存储路径
storePathConsumeQueue=/usr/local/server/mq/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/server/mq/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/server/mq/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/server/mq/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
# flushCommitLogLeastPages=4
# flushConsumeQueueLeastPages=2
# flushCommitLogThoroughInterval=10000
# flushConsumeQueueThoroughInterval=60000
# Broker 的角色
# - ASYNC_MASTER 异步复制Master
# - SYNC_MASTER 同步双写Master
# - SLAVE
brokerRole=YNC_MASTER
# 刷盘方式
# - ASYNC_FLUSH 异步刷盘
# - SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageTreadPoolNums=128
#拉消息线程池数量
#pullMessageTreadPoolNums=128
修改broker-a-s.properties文件,如下:
terName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示Master, > 0 表示slave
brokerId=1
#nameServer 地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876;rocketmq-nameserver3:9876;rocketmq-nameserver4: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=/usr/local/server/mq/rocketmq/store
#commitLog存储路径
storePathCommitLog=/usr/local/server/mq/rocketmq/store/commitlog
#消费队列存储路径
storePathConsumeQueue=/usr/local/server/mq/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/server/mq/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/server/mq/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/server/mq/rocketmq/store/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=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageTreadPoolNums=128
#拉消息线程池数量
#pullMessageTreadPoolNums=128
修改broker-b-s.properties文件,如下:
terName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示Master, > 0 表示slave
brokerId=1
#nameServer 地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876;rocketmq-nameserver3:9876;rocketmq-nameserver4: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=/usr/local/server/mq/rocketmq/store
#commitLog存储路径
storePathCommitLog=/usr/local/server/mq/rocketmq/store/commitlog
#消费队列存储路径
storePathConsumeQueue=/usr/local/server/mq/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/server/mq/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/server/mq/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/server/mq/rocketmq/store/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=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageTreadPoolNums=128
#拉消息线程池数量
#pullMessageTreadPoolNums=128
将节点信息拷贝到每台机器,在141机器执行如下命令
scp broker-*.properties 192.168.211.142:/usr/local/server/mq/rocketmq/conf/2m-2s-sync
scp broker-*.properties 192.168.211.143:/usr/local/server/mq/rocketmq/conf/2m-2s-sync
scp broker-*.properties 192.168.211.144:/usr/local/server/mq/rocketmq/conf/2m-2s-sync
效果如下图:

进入到每台机器的conf目录,替换日志文件路径,执行如下命令
sed -i 's#${user.home}#/usr/local/server/mq/rocketmq#g' *.xml
进入每台机器的bin目录下,需改runbroker.sh和runserver.sh文件
runbroker.sh
改前:
JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g"
改后:
JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn1g"
runserver.sh
改前:
JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
改后:
JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
5) RocketMQ集群启动测试
先在每台服务器启动namesrv
再启动broker
141服务器执行如下启动命令:
nohup sh mqbroker -c /usr/local/server/mq/rocketmq/conf/2m-2s-sync/broker-a.properties > /dev/null 2>&1 &
142服务器执行如下启动命令:
nohup sh mqbroker -c /usr/local/server/mq/rocketmq/conf/2m-2s-sync/broker-b.properties > /dev/null 2>&1 &
143服务器执行如下启动命令:
nohup sh mqbroker -c /usr/local/server/mq/rocketmq/conf/2m-2s-sync/broker-a-s.properties > /dev/null 2>&1 &
144服务器执行如下启动命令:
nohup sh mqbroker -c /usr/local/server/mq/rocketmq/conf/2m-2s-sync/broker-b-s.properties > /dev/null 2>&1 &
输入jps查看进程
[root@localhost bin]# jps
43761 NamesrvStartup
43803 BrokerStartup
44093 Jps
[root@localhost bin]#
启动后,修改控制台的namesrv地址,如下修改:
rocketmq.config.namesrvAddr=192.168.211.141:9876;192.168.211.142:9876;192.168.211.143:9876;192.168.211.144:9876
启动控制台服务,并访问控制台,如下:

这里如果想停掉服务,可以先停掉broker
再关闭namesrv