存储消息的中间件 是在 消息的传输过程中保存消息的容器。
多用于分布式系统之间进行通信。
消息队列是典型例子: 生产者消费者模型。
分布式系统通信方式:
直接远程调用
借助第三方完成间接通信
发送方称为生产者, 接收方称为消费者
1.系统可用性降低: 集群
2.系统复杂度提高:(程序员提升水平)
3.异步消息机制(都有解决方案)
消息顺序性
消息丢失
消息一致性
消息重复使用
ActiveMQ:java语言实现,万级数据吞吐量,处理速度ms级,主从架构,成熟度高
RabbitMQ :erlang语言实现,万级数据吞吐量,处理速度us级,主从架构,
RocketMQ :java语言实现,十万级数据吞吐量,处理速度ms级,分布式架构,功能强大,扩展性强
kafka :scala语言实现,十万级数据吞吐量,处理速度ms级,分布式架构,功能较少,应用于大数据较多
RocketMQ是阿里开源的一款非常优秀中间件产品,脱胎于阿里的另一款队列技术MetaQ,
后捐赠给Apache基金会作为一款孵化技术,仅仅经历了一年多的时间就成为Apache基金会的顶级项目。并且它现在已经在阿里内部被广泛的应用,并且经受住了多次双十一的这种极致场景的压力
(2017年的双十一,RocketMQ流转的消息量达到了万亿级,峰值TPS达到5600万)
Apache官网地址: https://www.apache.org/
rocketMQ下载地址: http://rocketmq.apache.org/dowloading/releases/
注意: 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代理服务器
# 进入rocketMQ安装目录下的bin目录
# 启动mq服务 -n 指定nameserv的地址
sh mqbroker -n localhost:9876
注意:首次启动会报错,因为broker默认占用内存过大,我们需要调整参数
====需修改mqbroker对应的runbroker.sh文件,将占用内存改小====
设置为256M
再次重启
# 重启mq服务 -n 指定nameserv的地址
sh mqbroker -n localhost:9876
再另起一个窗口测试
# 关闭防火墙
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
创建maven项目导入jar包坐标
<dependencies>
<dependency>
<groupId>org.apache.rocketmqgroupId>
<artifactId>rocketmq-clientartifactId>
<version>4.5.2version>
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("==========消费者启动了============");
}
}
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) 如果多个消费者先启动(广播模式),后发消息,才有广播的效果
结论:
必须先启动消费者再启动生产者才有广播的效果
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();
}
}
同步消息: 发送完消息后,必须立即返回结果(如:发送短信成功提示,转账成功提示)
即时性较强,重要的消息,且必须有回执的消息,例如短信,通知(转账成功)
异步消息: 即时性较弱,但需要有回执的消息,例如订单中的某些信息
单向消息: 不需要有回执的消息,例如日志类消息
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();
}
}
立刻发送, 只是 告诉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();
}
}
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字节)
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 那个都行
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
默认情况下,MQ 开启了多个队列, 同时发送多个消息的的话,发送给那个队列是不确定的,同时消息的消费者读取消息,每读取一个消息开启一个线程,也不能保证消息的顺序性,
想要保证消息的有序性,需要指定消息的队列,同时 消息的消费者应该一个队列开启一个线程进行接收而不是一个消息一个线程)
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;
}
RocketMQ 也允许我们像mysql 一样发送具有事务特征的消息
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使用:
多个broker提供服务
多个master多个slave
master到slave消息同步方式为同步(较异步方式性能略低,消息无延迟)
master到slave消息同步方式为异步(较同步方式性能略高,数据略有延迟)
# 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: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
准备两台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 查看后台进程
便于通过域名查找对应的服务器,配置如下
# 两台服务器都有配置,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
安装前检查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
主节点创建四个目录/ 从节点四个目录
# 在真实开发中一台服务器上就部署一个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
配置文件名称: 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
配置文件名称: 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
配置文件名称: 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
配置文件名称: 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运行时占用内存
rocketmq-console是一款基于java环境开发的(springboot)的管理控制台工具
获取地址github: https://github.com/apache/rocketmq-externals
获取地址码云: https://gitee.com/mirrors/RocketMQ-Externals
需要在SpringBoot项目的配置文件中,配置nameServer的地址和端口
ActiveMQ 使用了数据库的消息存储,
缺点:数据库瓶颈将成为MQ瓶颈
RocketMQ/Kafka/RabbitMQ
不用数据库,直接用文件存储(如)
生产者将消息发送给RocketMQ后,RocketMQ会将数据同步或异步刷盘到硬盘文件上
1) 通过启动时初始化话文件大小来保证 占用固定的磁盘空间,保证磁盘读写速度
2) 零拷贝”技术
数据传输由传统的4次复制简化成3次复制(如下图),减少1次复制过程
Java语言中使用MappedByteBuffer类实现了该技术
要求:预留存储空间,用于保存数据(1G存储空间起步)
消息数据存储区域
topic
queueId
message
消费逻辑队列
minOffset
maxOffset
consumerOffset
索引
key索引
创建时间索引
……
将内存中的消息写入到磁盘:
同步刷盘
异步刷盘
1)生产者发送消息到MQ,MQ接到消息数据
2)MQ挂起生产者发送消息的线程
3)MQ将消息数据写入内存
4)内存数据写入硬盘
5)磁盘存储后返回SUCCESS
6)MQ恢复挂起的生产者线程
7)发送ACK到生产者
1)生产者发送消息到MQ,MQ接到消息数据
2)MQ将消息数据写入内存
3)发送ACK到生产者
--等消息量多了--
4)内存数据写入硬盘
同步刷盘:安全性高,效率低,速度慢(适用于对数据安全要求较高的业务)
异步刷盘:安全性低,效率高,速度快(适用于对数据处理速度要求较高的业务)
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
nameserver
nameserver ,通过无状态+全服务器注册 来保证即使一个宕机了也能提供所有的服务
消息服务器
主从架构(2M-2S) ,即使又一台服务器宕机, 服务依旧可以正常提供
注意: master 一旦宕机,slave 只提供消费服务,不能写入新的消息(slave 不会升级为master)
消息生产(开发人员写代码时保障)
生产者将相同的topic绑定到多个group组,保障master挂掉后,其他master仍可正常进行消息接收
消息消费
RocketMQ自身会根据master的压力确认是否由master承担消息读取的功能,当master繁忙时候,自动切换由slave承担数据读取的工作(RocketMQ主变从,阈值为超过内存的40%)
master接到消息后,先复制到slave,然后反馈给生产者写操作成功
优点:数据安全,不丢数据,出现故障容易恢复
缺点:影响数据吞吐量,整体性能低
master接到消息后,立即返回给生产者写操作成功,当消息达到一定量后再异步复制到slave
优点:数据吞吐量大,操作延迟低,性能高
缺点:数据不安全,会出现数据丢失的现象,一旦master出现故障,从上次数据同步到故障时间的数据将丢失
#Broker 的角色
#- ASYNC_MASTER 异步Master
#- SYNC_MASTER 同步Master
#- SLAVE
brokerRole=SYNC_MASTER
Producer负载均衡
内部实现了不同broker集群中对同一topic对应消息队列的负载均衡
Consumer负载均衡
平均分配
循环平均分配
当消息消费后未正常返回消费成功的信息将启动消息重试机制
当消费者消费消息失败后,RocketMQ会自动进行消息重试(每次间隔时间为 1 秒)
注意:应用会出现消息消费被阻塞的情况,因此,要对顺序消息的消费情况进行监控,避免阻塞现象的发生
无序消息包括普通消息、定时消息、延时消息、事务消息
无序消息重试仅适用于负载均衡(集群)模型下的消息消费,不适用于广播模式下的消息消费
为保障无序消息的消费,MQ设定了合理的消息重试间隔时长
当消息消费重试到达了指定次数(默认16次)后,MQ将无法被正常消费的消息称为死信消息(Dead-Letter Message)
死信消息不会被直接抛弃,而是保存到了一个全新的队列中,该队列称为死信队列(Dead-Letter Queue)
- 归属某一个组(Gourp Id),而不归属Topic,也不归属消费者
- 一个死信队列中可以包含同一个组下的多个Topic中的死信消息
- 死信队列不会进行默认初始化,当第一个死信出现后,此队列首次初始化
- 不会被再次重复消费
- 死信队列中的消息有效期为3天,达到时限后将被清除
在监控平台中,通过查找死信,获取死信的messageId,然后通过id对死信进行精准消费
1 生产者发送了重复的消息
网络闪断
生产者宕机
2 消息服务器投递了重复的消息
网络闪断
3 动态的负载均衡过程
网络闪断/抖动
broker重启
订阅方应用重启(消费者)
客户端扩容
客户端缩容
问题:对同一条消息,无论消费多少次,结果保持一致,称为消息幂等性
解决方案
- 使用业务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,修改运行时占用的内存空间,配置环境变量