一、简介
1、定义
MQ : 消息队列,是用来保存消息数据的队列
队列 : 数据结构的一种,特征为“先进先出”
消息 : 服务器间的业务请求
2、作用
优点:
应用解耦(异步消息发送)
快速应用变更维护
流量削峰
缺点:
系统可用性降低
系统复杂度提高
异步消息机制
消息顺序性
消息丢失
消息一致性
消息重复使用
3、产品介绍
4、结构
生产者
消费者
消息服务器
命名服务器
消息:
主题
标签
5、搭建
官网安装
http://rocketmq.apache.org/docs/quick-start/
docker安装
mkdir -p
/Users/weixingyu/Applications/devlop_tools/rocketmq/data/namesrv/logs
/Users/weixingyu/Applications/devlop_tools/rocketmq/data/namesrv/store
/Users/weixingyu/Applications/devlop_tools/rocketmq/conf
/Users/weixingyu/Applications/devlop_tools/rocketmq/data/broker/logs
/Users/weixingyu/Applications/devlop_tools/rocketmq/data/broker/store
配置文件
vi broker.conf
brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
brokerIP1 = {本地外网 IP}
拉去镜像
docker pull rocketmqinc/rocketmq:4.4.0
docker pull styletang/rocketmq-console-ng
创建nameserver容器
docker run -d -p 9876:9876 -v /root/rocketmq/data/namesrv/logs:/root/logs -v /root/rocketmq/data/namesrv/store:/root/store --name rmqnamesrv -e "MAX_POSSIBLE_HEAP=100000000" rocketmqinc/rocketmq:4.4.0 sh mqnamesrv
创建broker容器
docker run -d -p 10911:10911 -p 10909:10909 -v /root/rocketmq/data/broker/logs:/root/logs -v /root/rocketmq/data/broker/store:/root/store -v /root/rocketmq/conf/broker.conf:/opt/rocketmq-4.4.0/conf/broker.conf --name rmqbroker -e "NAMESRV_ADDR=namesrv:9876" -e "MAX_POSSIBLE_HEAP=200000000" rocketmqinc/rocketmq:4.4.0 sh mqbroker -c /opt/rocketmq-4.4.0/conf/broker.conf
创建控制台
docker run -d --name rmqconsole -p 9800:8080 -e "JAVA_OPTS=-Drocketmq.namesrv.addr=物理机ip:9876 -Dcom.rocketmq.sendMessageWithVIPChannel=false" -t styletang/rocketmq-console-ng
二、消息消费模式
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- <dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
<scope>test</scope>
</dependency>-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</version>
</dependency>
1、one2one(基础发送与基础接收)
public class Producer {
public static void main(String[] args) throws Exception {
//创建发送消息的对象
DefaultMQProducer mqProducer = new DefaultMQProducer("group1");
//设定发送消息的命名服务器
mqProducer.setNamesrvAddr("localhost:9876");
//启动发送的服务
mqProducer.start();
Message msg = new Message("topic1","hello rocketmq".getBytes(StandardCharsets.UTF_8));
SendResult result = mqProducer.send(msg);
System.out.println(result);
mqProducer.shutdown();
}
}
public class Consumer {
public static void main(String[] args) throws Exception {
//1\创建消费消息对象
DefaultMQPushConsumer mqConsumer = new DefaultMQPushConsumer("group1");
//2、设定接收消息的命名服务器地址
mqConsumer.setNamesrvAddr("localhost:9876");
//订阅消息,
mqConsumer.subscribe("topic1","*");
//3、设定监听器
mqConsumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt messageExt : list) {
System.out.println(new String(messageExt.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
mqConsumer.start();
System.out.println("接受消息服务已开启运行");
}
}
2、one2many(负载均衡与广播模式)
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer mqProducer = new DefaultMQProducer("group");
mqProducer.setNamesrvAddr("127.0.0.1:9876");
mqProducer.start();
for (int i = 0; i < 10; i++) {
Message msg = new Message("topic1",("message"+i).getBytes(StandardCharsets.UTF_8));
mqProducer.send(msg);
}
mqProducer.shutdown();
}
}
public class Consumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer mqConsumer = new DefaultMQPushConsumer("group1");
mqConsumer.setNamesrvAddr("127.0.0.1:9876");
mqConsumer.subscribe("topic1","*");
//广播模式
//mqConsumer.setMessageModel(MessageModel.BROADCASTING);
//负载均衡
mqConsumer.setMessageModel(MessageModel.CLUSTERING);
mqConsumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt message : list) {
System.out.println(new String(message.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
mqConsumer.start();
}
}
3、many2many()
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer mqProducer = new DefaultMQProducer("group1");
mqProducer.setNamesrvAddr("localhost:9876");
mqProducer.start();
for (int i = 0; i < 10; i++) {
Message msg = new Message("topic1",("消息二:"+"message"+i).getBytes(StandardCharsets.UTF_8));
SendResult result = mqProducer.send(msg);
System.out.println("返回结果"+result);
}
mqProducer.shutdown();
}
}
public class Consumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("topic1","*");
consumer.setMessageModel(MessageModel.CLUSTERING);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt message : list) {
System.out.println(new String(message.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("已经消费消息");
}
}
三、消息类别
同步消息:即时性较强,重要的消息,且必须有回执的消息,例如短信,通知(转账成功);
异步消息:即时性较弱,但需要有回执的消息,例如订单中的某些信息
单向消息:不需要有回执的消息,例如日志消息
1、同步消息
/**
* 测试消息的种类
*/
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer mqProducer = new DefaultMQProducer("group2");
mqProducer.setNamesrvAddr("localhost:9876");
mqProducer.start();
for (int i = 0; i < 5; i++) {
Message msg = new Message("topic2",("同步消息:"+"message"+i).getBytes(StandardCharsets.UTF_8));
SendResult result = mqProducer.send(msg);
System.out.println("返回结果"+result);
}
mqProducer.shutdown();
}
}
public class Consumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group2");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("topic2","*");
consumer.setMessageModel(MessageModel.CLUSTERING);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt message : list) {
System.out.println(new String(message.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("已经消费消息");
}
}
2、异步消息
/**
* 异步消息
*/
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer mqProducer = new DefaultMQProducer("group2");
mqProducer.setNamesrvAddr("localhost:9876");
mqProducer.start();
for (int i = 0; i < 5; i++) {
Message msg = new Message("topic2",("异步消息:"+"message"+i).getBytes(StandardCharsets.UTF_8));
mqProducer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("返回结果"+sendResult);
}
@Override
public void onException(Throwable throwable) {
System.out.println(throwable);
}
});
}
//添加休眠操作,确保异步消息返回后能够输出
TimeUnit.SECONDS.sleep(10);
mqProducer.shutdown();
}
}
public class Consumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group2");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("topic2","*");
consumer.setMessageModel(MessageModel.CLUSTERING);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt message : list) {
System.out.println(new String(message.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("已经消费消息");
}
}
3、单向消息
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer mqProducer = new DefaultMQProducer("group2");
mqProducer.setNamesrvAddr("localhost:9876");
mqProducer.start();
for (int i = 0; i < 5; i++) {
Message msg = new Message("topic2",("单向消息:"+"message"+i).getBytes(StandardCharsets.UTF_8));
mqProducer.sendOneway(msg);
}
mqProducer.shutdown();
}
}
public class Consumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group2");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("topic2","*");
consumer.setMessageModel(MessageModel.CLUSTERING);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt message : list) {
System.out.println(new String(message.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("已经消费消息");
}
}
4、延时消息
/**
* 延时消息
*/
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer mqProducer = new DefaultMQProducer("group2");
mqProducer.setNamesrvAddr("localhost:9876");
mqProducer.start();
for (int i = 0; i < 5; i++) {
Message msg = new Message("topic2",("延时消息:"+i).getBytes(StandardCharsets.UTF_8));
//设置当前消息的延时间隔
msg.setDelayTimeLevel(3);
mqProducer.send(msg);
}
mqProducer.shutdown();
}
}
5、批量消息
注意: 规定每次批量发送的消息不得超过4M
总长度 = topic(字符串字节数) +body(字节数组长度)+ 消息的属性(key与value对应字符串字节数)+日志(固定20字节)
/**
* 批量消息
*/
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer mqProducer = new DefaultMQProducer("group3");
mqProducer.setNamesrvAddr("localhost:9876");
mqProducer.start();
ArrayList<Message> messages = new ArrayList<>();
Message msg1 = new Message("topic3", ("批量消息:" + 1).getBytes(StandardCharsets.UTF_8));
Message msg2 = new Message("topic3", ("批量消息:" + 2).getBytes(StandardCharsets.UTF_8));
Message msg3 = new Message("topic3", ("批量消息:" + 3).getBytes(StandardCharsets.UTF_8));
messages.add(msg1);
messages.add(msg2);
messages.add(msg3);
mqProducer.send(messages);
mqProducer.shutdown();
}
}
6、过滤消息
- tag过滤消息
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer mqProducer = new DefaultMQProducer("group1");
mqProducer.setNamesrvAddr("localhost:9876");
mqProducer.start();
Message msg = new Message("topic6","tag1", ("tag过滤消息:" + "message").getBytes(StandardCharsets.UTF_8));
SendResult result = mqProducer.send(msg);
System.out.println("返回结果" + result);
mqProducer.shutdown();
}
}
public class Consumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("topic6","tag1");
consumer.setMessageModel(MessageModel.CLUSTERING);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt message : list) {
System.out.println(new String(message.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("已经消费消息");
}
}
- sql过滤消息
配置文件中配置属性过滤器
enablePropertyFilter=true
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer mqProducer = new DefaultMQProducer("group1");
mqProducer.setNamesrvAddr("localhost:9876");
mqProducer.start();
Message msg = new Message("topic6","tag1", ("tag过滤消息:" + "message").getBytes(StandardCharsets.UTF_8));
//为消息添加属性
msg.putUserProperty("vip","1");
msg.putUserProperty("age","20");
SendResult result = mqProducer.send(msg);
System.out.println("返回结果" + result);
mqProducer.shutdown();
}
}
public class Consumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("topic6","*");
//使用消息选择器来过滤对应的属性,语法格式为类sql语法
consumer.subscribe("topic6", MessageSelector.bySql("vip=1"));
consumer.setMessageModel(MessageModel.CLUSTERING);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt message : list) {
System.out.println(new String(message.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("已经消费消息");
}
}
7、顺序消息
- 生产者
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer mqProducer = new DefaultMQProducer("group1");
mqProducer.setNamesrvAddr("localhost:9876");
mqProducer.start();
//创建需要执行的任务队列
ArrayList<Order> orders = new ArrayList<>();
Order order11 = new Order();
order11.setId(1);
order11.setMsg("主单-1");
orders.add(order11);
Order order12 = new Order();
order12.setId(1);
order12.setMsg("子单-2");
orders.add(order12);
Order order13 = new Order();
order13.setId(1);
order13.setMsg("支付-3");
orders.add(order13);
Order order14 = new Order();
order14.setId(1);
order14.setMsg("推送-4");
orders.add(order14);
Order order21 = new Order();
order21.setId(2);
order21.setMsg("主单-1");
orders.add(order21);
Order order22 = new Order();
order22.setId(2);
order22.setMsg("子单-2");
orders.add(order22);
Order order31 = new Order();
order31.setId(3);
order31.setMsg("主单-1");
orders.add(order31);
Order order32 = new Order();
order32.setId(3);
order32.setMsg("子单-2");
orders.add(order32);
Order order41 = new Order();
order41.setId(4);
order41.setMsg("主单-1");
orders.add(order41);
Order order42 = new Order();
order42.setId(4);
order42.setMsg("子单-2");
orders.add(order42);
//设置消息进入指定的队列
for (Order order : orders) {
Message msg = new Message("orderTopic",order.toString().getBytes(StandardCharsets.UTF_8));
SendResult result = mqProducer.send(msg, new MessageQueueSelector() {
//设置当前消息发送时使用哪一个消息队列
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
//根据发送的消息不同,选择不同的消息队列
//根据id来选择一个消息队列的对象,并返回
int mgIndex = (int) order.getId() % list.size();
return list.get(mgIndex);
}
}, null);
System.out.println(result);
}
mqProducer.shutdown();
}
}
- 消费者
public class Consumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer mqConsumer = new DefaultMQPushConsumer("group1");
mqConsumer.setNamesrvAddr("localhost:9876");
mqConsumer.subscribe("orderTopic","*");
//使用单线程的模式从消息队列中获取数据,一个线程绑定一个队列
mqConsumer.registerMessageListener(new MessageListenerOrderly() {
/**
*使用MessageListenerOrderly接口后,对消息队列的处理由一个消息队列多个线程服务,
* 转化为一个消息队列一个线程服务
*/
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
for (MessageExt messageExt : list) {
System.out.println(Thread.currentThread().getName()+"消息:"+new String(messageExt.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
mqConsumer.start();
System.out.println("消费开始");
}
}
8、事务消息
事务补偿过程
三种状态
提交状态:允许进入队列,此消息与非事务消息无区别
回滚状态:不允许进入队列,此消息等同于未发送
中间状态:完成了half消息的发送,未对MQ进行二次状态确认
事务仅与生产者有关,与消费者无关
-生产者
public class Producer {
public static void main(String[] args) throws Exception {
TransactionMQProducer mqProducer = new TransactionMQProducer("group1");
mqProducer.setNamesrvAddr("localhost:9876");
//添加本地事务对应的监听
mqProducer.setTransactionListener(new TransactionListener() {
//正常事务过程
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
//return LocalTransactionState.ROLLBACK_MESSAGE;
return LocalTransactionState.UNKNOW;
//return LocalTransactionState.COMMIT_MESSAGE;
}
//事务补偿过程
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
return LocalTransactionState.COMMIT_MESSAGE;
}
});
mqProducer.start();
Message msg = new Message("topic1","hello,transaction message".getBytes(StandardCharsets.UTF_8));
mqProducer.send(msg);
//事务补偿的过程一定要保证服务器在正常运行中,否则将无法进行正常的事务补偿;
// mqProducer.shutdown();
}
}
- 消费者
public class Consumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer mqConsumer = new DefaultMQPushConsumer("group1");
mqConsumer.setNamesrvAddr("localhost:9876");
mqConsumer.subscribe("topic1","*");
mqConsumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt message : list) {
System.out.println(new String(message.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
mqConsumer.start();
System.out.println("接收消息服务已开启运行");
}
}
四、集群
1、集群分类
单机:一个broker提供服务(宕机后服务瘫痪)
集群:多个broker提供服务(单机宕机后消息无法及时被消费)
多个master多个slave
master到slave消息同步方式为同步(较异步消息性能略低,消息无延迟)
master到slave消息同步方式为异步
brokerName/名称相同是一组,brokerId为0的是master,brokerId不为0的是slave
2、集群模式
- 多Master多Slave模式(异步)
每个Master配置一个Slave,有多对Master-Slave,HA采用异步复制方式,主备有短暂消息延迟(毫秒级),这种模式的优缺点如下:
- 优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,同时Master宕机后,消费者仍然可以从Slave消费,而且此过程对应用透明,不需要人工干预,性能同多Master模式几乎一样;
- 缺点:Master宕机,磁盘损坏情况下会丢失少量消息。
- 多Master多Slave模式(同步)
每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写方式,即只有主备都写成功,才向应用返回成功,这种模式的优缺点如下:
- 优点:数据与服务都无单点故障,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高;
- 缺点:性能比异步复制模式略低(大约低10%左右),发送单个消息的RT会略高,且目前版本在主节点宕机后,备机不能自动切换为主机。
3、集群搭建
- 总体架构
- 集群工作流程
1. 启动NameServer,NameServer起来后监听端口,等待Broker、Producer、Consumer连上来,相当于一个路由控制中心。
2. Broker启动,跟所有的NameServer保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信息。注册成功后,NameServer集群中就有Topic跟Broker的映射关系。
3. 收发消息前,先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。
4. Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic存在哪些Broker上,轮询从队列列表中选择一个队列,然后与队列所在的Broker建立长连接从而向Broker发消息。
5. Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息。
- 搭建
vim /etc/hosts:
# nameserver
10.211.55.10 rocketmq-nameserver1
10.211.55.11 rocketmq-nameserver2
# broker
10.211.55.10 rocketmq-master1
10.211.55.10 rocketmq-slave2
10.211.55.11 rocketmq-master2
10.211.55.11 rocketmq-slave1
配置完成后, 重启网卡
systemctl restart network
# 关闭防火墙
systemctl stop firewalld.service
# 查看防火墙的状态
firewall-cmd --state
# 禁止firewall开机启动
systemctl disable firewalld.service
环境变量配置
vim /etc/profile
#set rocketmq
ROCKETMQ_HOME=/usr/local/rocketmq/rocketmq-all-4.4.0-bin-release
PATH=$PATH:$ROCKETMQ_HOME/bin
export ROCKETMQ_HOME PATH
source /etc/profile
broker配置文件
master1
#所属集群名字
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=/usr/local/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/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
slave2
#所属集群名字
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=/usr/local/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/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
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
- 修改启动脚本文件
vi /usr/local/rocketmq/bin/runbroker.sh
需要根据内存大小进行适当的对JVM参数进行调整:
#===================================================
# 开发环境配置 JVM Configuration
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"
vim /usr/local/rocketmq/bin/runserver.sh
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
- 服务启动
启动NameServe集群
分别在192.168.25.135和192.168.25.138启动NameServer
cd /usr/local/rocketmq/bin
nohup sh mqnamesrv &
启动Broker集群
master1:
cd /usr/local/rocketmq/bin
nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-a.properties &
slave2:
cd /usr/local/rocketmq/bin
nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-b-s.properties &
master2
cd /usr/local/rocketmq/bin
nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-b.properties &
slave1
cd /usr/local/rocketmq/bin
nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-a-s.properties &
4、消息持久化和持久化介质
-
消息存储
-
存储介质
5、顺序写和零拷贝
6、消息存储结构
- store (commitlog consumerQueue index config)
7、刷盘机制
- 同步刷盘
- 异步刷盘
- 总结
8、高可用与主从方案
-
高可用
-
主从复制
9、负载均衡
10、消息重试和死信队列
- 消息重试
如果消息重试 16 次后仍然失败,消息将不再投递。如果严格按照上述重试时间间隔计算,某条消息在一直消费失败的前提下,将会在接下来的 4 小时 46 分钟之内进行 16 次重试,超过这个时间范围消息将不再重试投递。
注意: 一条消息无论重试多少次,这些重试消息的 Message ID 不会改变。
消息队列 RocketMQ 默认允许每条消息最多重试 16 次,每次重试的间隔时间如下:
消费失败后,重试配置方式
public class MessageListenerImpl implements MessageListener {
@Override
public Action consume(Message message, ConsumeContext context) {
//处理消息
doConsumeMessage(message);
//方式1:返回 Action.ReconsumeLater,消息将重试
return Action.ReconsumeLater;
//方式2:返回 null,消息将重试
return null;
//方式3:直接抛出异常, 消息将重试
throw new RuntimeException("Consumer Message exceotion");
}
}
- 死信队列
11、消息幂等
- producer
public class Producer {
public static void main(String[] args) throws Exception {
Jedis jedis = new Jedis("localhost",6379);
jedis.auth("root");
//创建发送消息的对象
DefaultMQProducer mqProducer = new DefaultMQProducer("group1");
//设定发送消息的命名服务器
mqProducer.setNamesrvAddr("localhost:9876");
//启动发送的服务
mqProducer.start();
Message msg = new Message("topic1","hello rocketmq".getBytes(StandardCharsets.UTF_8));
String messageID = UUID.randomUUID().toString();
msg.setKeys(messageID);
jedis.sadd("rocketmq",messageID);
jedis.expire("rocketmq",60*60);
SendResult result = mqProducer.send(msg);
System.out.println(result);
jedis.close();
mqProducer.shutdown();
}
}
- consumer
public class Consumer {
public static void main(String[] args) throws Exception {
Jedis jedis = new Jedis("localhost",6379);
jedis.auth("root");
//1\创建消费消息对象
DefaultMQPushConsumer mqConsumer = new DefaultMQPushConsumer("group1");
//2、设定接收消息的命名服务器地址
mqConsumer.setNamesrvAddr("localhost:9876");
//订阅消息,
mqConsumer.subscribe("topic1","*");
//3、设定监听器
mqConsumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
try {
for (MessageExt messageExt : list) {
System.out.println(new String(messageExt.getBody()));
String messageID = messageExt.getKeys();
System.out.println("messageID"+messageID);
Set<String> smembers = jedis.smembers("rocketmq");
for (String smember : smembers) {
if(messageID.equals(smember)){
System.out.println("redis中存在messageID");
}
}
jedis.close();
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} catch (Exception e) {
e.printStackTrace();
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
});
mqConsumer.start();
System.out.println("接受消息服务已开启运行");
}
}