MQ: 消息队列:
提供消息(请求)服务的中间件 (消息中间件)
生产 存储 消费全过程的软件系统, 先进先出原则
使用场景: 限流削峰, 异步, 解耦, 数据收集, 大数据处理
大项目, 体量大 并发高
好处: 提高系统响应速度, 稳定性
产品: Kafka (多使用在大数据) 瞬时大流量应用 --> 大量的大文件
RocketMQ --> 大量的小文件
技术选型: 业务场景简单, 允许数据丢失 想要快速上线 --> Redis
大数据场景, 日志收集, 实时性要求高 --> Kafka
金融领域, 不能接受消息丢失或重复 --> RabbitMQ, RocketMQ
RocketMQ:
特征: 支持集群, 负载均衡, 水平扩展
NameServer代替Zookeeper,实现服务寻址和服务协调
底层通信框架采用Netty NIO
采用零拷贝的原理,顺序写盘,随机读
RocketMQ官网下载:
RocketMQ RocketMQ · 官方网站 | RocketMQ
下载地址:下载 | RocketMQ
上传到虚拟机
解压
unzip ./rocketmq-all-4.9.6-bin-release.zip -d /opt/module/
部署NameServer:
0.创建文件夹
mkdir backups --> cp ./bin/runserver.sh ./backups/
mkdir -p store store/commitlog store/consumequeue
1.修改jvm占用内存大小runserver.sh
-server -Xms512m -Xmx512m -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m
2.启动NameServer服务:
nohup sh ./bin/mqnamesrv > ./log/nameserver.log 2>&1 &
nohup --> 后台启动
sh ./bin/mqnamesrv --> 启动NameServer服务
> ./log/nameserver.log --> 信息记录到xx日志
2>&1 & --> 后台运行
部署Broker:
1.修改jvm占用内存大小runserver.sh
-server -Xms512m -Xmx512m
2.修改broke.conf
# 集群名称,同一个集群下的broker要求统一
brokerClusterName=DefaultCluster
# broker名称
brokerName=broker-a
# brokerId=0代表主节点,大于零代表从节点
brokerId=0
# 删除日志文件时间点,默认凌晨 4 点
deleteWhen=04
# 日志文件保留时间,默认 48 小时
fileReservedTime=48
# Broker 的角色
# - ASYNC_MASTER 异步复制Master
# - SYNC_MASTER 同步双写Master
brokerRole=SYNC_MASTER
# 刷盘方式
# - ASYNC_FLUSH 异步刷盘,性能好宕机会丢数
# - SYNC_FLUSH 同步刷盘,性能较差不会丢数
flushDiskType=SYNC_FLUSH
# 末尾追加,NameServer节点列表,使用分号分割
# 这里的IP需要根据自身情况修改
namesrvAddr=192.168.10.111:9876
# 是否开启自动创建Topic
autoCreateTopicEnable=true
# Broker监听的端口号
listenPort=10911
# 指定消息的存储路径,具体是下面两个
storePathRootDir=/opt/module/rocketmq-4.9.6/store
# commitLog的存储路径
storePathCommitLog=/opt/module/rocketmq-4.9.6/store/commitlog
# 消费队列的存储路径
storePathConsumerQueue=/opt/module/rocketmq-4.9.6/store/consumequeue
3.启动Broker服务:
nohup sh ./bin/mqbroker > ./log/broker.log 2>&1 &
消息的收发
1.设置环境变量:
export NAMESRV_ADDR=192.168.10.111:9876
2.启动消息生产者:
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
3.启动消息消费者:
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
关闭服务
1.关闭broker:
sh ./bin/mqshutdown broker
2.关闭namesrv
sh ./bin/mqshutdown namesrv
启动Rocket可视化界面的配置
1.配置broker.conf: 防止RocketMQ找不到Broker节点
brokerIP1=192.168.xx.111
2.启动Broker组件
nohup sh ./bin/mqbroker -c ./conf/broker.conf > ./log/broker.log 2>&1 &
nohup 后台运行
sh ./bin/mqbroker 启动nroker服务
-c ./conf/broker.conf 指定配置文件
> ./log/broker.log 指定输出的日志文件
2>&1 & 后台运行
3.尝试:手动添加topic
./bin/mqadmin updateTopic -n 127.0.0.1:9876 -b 127.0.0.1:10911 -t topic-test
./bin/mqadmin --> 启动添加组件
updateTopic --> 更新topic
-n ip地址:端口号 --> 指定nameserver所在的节点ip
-b ip地址:端口号 --> 指定broker所在的节点ip
-t 主题topic-test --> 指定主题的名称
3.查看所有的主题topic
sh ./bin/mqadmin topicList -n 192.168.10.111:9876
RocketMQ代码实现:
1.依赖: org.apache.rocketmq:rocketmq-client
2.生产者代码:
a. 创建生产producer组 :
new DefaultMQProducer("设置生产组名"); --> producer
创建事务消息生产者组: (事务消息的生产者组为这个)
new TransactionMQProducer("设置生产组名");--producer
b. 设置NameServer地址:
producer.setNamesrvAddr("ip地址:端口号9876")
c. 设置消息的长度:
producer.setMaxMessageSize(4096);
设置超时时间:
producer.setSendMsgTimeout(3000);
设置消息发送失败重试的次数:
producer.setRetryTimesWhenSendAsyncFailed(2);
事务消息需要设置, 事务消息监听器:
producer.setTransactionListener( new TransactionListener事务监听器接口 );
--> 事务监听器会重写两个方法: executeLocalTransaction( 执行的本地业务 ) 和 checkTransaction( MQ进行事务状态的回查 )
--> 这两个方法返回值共有三种情况的返回值:
return LocalTransactionState.COMMIT_MESSAGE;--> 提交
return LocalTransactionState.ROLLBACK_MESSAGE;--> 回滚
return LocalTransactionState.UNKNOW;--> 未知
d. 启动生产者producer
producer.start();
e. 创建消息对象:
new Message("主题topic-xxx","tag标签","内容".getBytes(RemotingHelper.DEFAULT_CHARSET));
--> msg 内容需要转换成字节码,否则接收不到内容
还可以给消息定义属性:
msg.setUserProperty("key","value")
f. 发送消息对象
(同步消息发送)
producer.send(msg);
(异步消息发送)
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.getMessage());
}
});
(单向消息发送)
producer.sendOneway(msg);
(全局顺序消息发送)
producer.send(msg, new MessageQueueSelector() {
@Override
// select方法的参数
// 第一个参数: 指该Topic下有的队列集合
// 第二个参数: 发送的消息
// 第三个参数: 消息将要进入的队列下标,它与send方法的第三个参数相同
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
return mqs.get((Integer) arg);
}
}, 1); --> 消息进入哪个消息队列
(部分有序消息发送)
--> 模板与全局顺序相同,有小改动
1.多创建几个不同内容的消息 例如:for支付:for发货
先发送支付消息,再发送发货消息 --> 支付0:发货0 , 支付1:发货1
2.返回的队列需要计算, 订单编号%队列数量, 返回队列, 表示该消息是进入哪个队列
这样相同id的支付和发货就放在同一个队列当中
send()方法个人理解:
1. object o 中的数据是从send()方法的第三个参数中拿取的
2. 第三个参数可以随意定义, 最终影响的的是队列的id, 一般定义为消息id 或 某个队列id
3. 内部类中的方法返回值, 返回的是消息队列id
4. send(参数1消息对象, 参数2队列, 参数3随意): 是将参数1,以参数3为下标放进参数2中
(延迟消息发送)
msg.setDelayTimeLevel(等级数);
-->分为18级, 可以通过修改配置来增加级别, 比如在mq安装目录的 broker.conf 文件中增加
producer.send(msg消息对象);
(批量消息发送)
1.创建消息集合
2.创建多条消息对象, 并放进集合当中
3.producer.send(消息集合)
(事务消息发送)
producer.sendMessageInTransaction(msg消息对象, Object针对什么消息进行控制参数)
g.关闭生产者producer实例
producer.shutdown();
返回值结果:
SendResult [
sendStatus=SEND_OK, -->状态OK
msgId=C0A8006516B018B4AAC270EF9D940000, -->发送者生成的id
offsetMsgId=C0A8006500002A9F0000000000008E1C, -->由Broker生成的消息id
messageQueue=MessageQueue [topic=syn-topic, brokerName=LAPTOP-20VLGCRC, queueId=3], -->队列信息
queueOffset=0 --> offset管理
]
3.消费者代码: -->
a. 设置消费condumer组 -->
new DefaultMQPushConsumer("设置消费组名");--consumer
b. 设置NameServer地址 -->
consumer.setNamesrvAddr("127.0.0.1:9876");
c. 设置消费线程的个数(最大,最小) -->
defaultMQPushConsumer.setConsumeThreadMax(1);
defaultMQPushConsumer.setConsumeThreadMin(1);
设置同时拉取pull多少个消息 -->
defaultMQPushConsumer.setPullBatchSize(1);
d. 设置消费位置 - 订阅主题 - 指定tag -->
consumer.subscribe("订阅的主题","指定tag标签 || tag标签 || * || …"); 指定消费某条信息
consumer.subscribe("订阅的主题",MessageSelector.bySql("表达式: i>2")); 指定消费某条信息
e. 设置消息监听器 - 处理消息
-->同步,异步,单项,批量,事务都可以消费:
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
-->顺序消费:
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
for (MessageExt mse : list) {
System.out.println("线程: " + Thread.currentThread().getName()
+ "消费了消息, queueId: " + mse.getQueueId()
+ "--> 消息内容为: " + new String(mse.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
e.启动消费者 -->
consumer.start();
f.关闭消费者 -->
consumer.shutdown();
RocketMQ 原理
1.生产者Producer: 发布消息
2.消费者Condumer: 消费消息
3.Broker: 负责消息的存储,投递,查询,服务的高可用保证
4.NameServer: 注册中心,支持动态注册与发现
主要包括:
-->会保存Broker整个的集群注册信息作为路由基本信息,
用于客户端查询队列的信息,
并控制生产者与消费者的消息发布与消费
-->提供心跳检测机制,检查Broker是否还存活
RocketMQ 核心概念
1.RocketMQ 网络部署特点:
-->Broker一般是以集群的方式出现的
-->Broker是主从集群master中负责处理处理请求, slave负责备份数据
-->当master节点挂掉时, slave会顶上去工作
-->一个master会包含多个slave, 一个slave只有一个master
-->通过相同的BrokerName不同的BrokerId来确定关系 0:主 !0:从
2.RabbitQ工作流程:
-->NameServer启动后会监听端口,等待Producer,Consumer,Broker连接,相当于路由控制中心
-->Broker启动后,会和所有的NameServer建立长连接,
定时发送心跳(Broker的信息:ip+端口+所有的topic信息),这样集群中就会有topic和Broker的映射关系
-->收发信息前会创建topic, 指定存储在哪些Broker上
-->Producer生产者发送消息,启动时先跟NameServer建立长连接, 并从中获取到当前发送的topic存在哪些Broker上,
并轮询从队列列表中选择队列,与队列所在的Broker建立长连接发消息
-->Consumer消费者与生产者类似,获取订阅的topic存在哪些Broker上,并建立长连接,开始消费信息
3.生产者的生产模式
a.Rocket具有多种发送方式: 同步, 异步, 单向, 顺序
--> 其中同步和异步需要返回确认信息
b.Producer生产者都是以组的形式出现 是一类的集合
--> 这类Producer生产者发送相同的topic类型的消息
--> 一个Prosucer组可以同时发送多个topic主题的消息
--> 会使用算法把消息发送到哪个master中的队列
4.消费者的消费模式
a.拉取式消费、推动式消费。(主动,被动)
b.Consumer消费者都是以组的形式出现 是一类的集合
--> 这类Consumer消费者消费相同的topic类型的消息
--> 一个Consumer组可以同时消费多个topic主题的消息
--> 一个队列是不能被同一个消费者组中的多个消费者进行消费的, 为了减少资源竞争,提升整体的性能
5.topic消息主题
表示一类消息的集合, 每个主题都包含若干条message消息,
--> 主题是消息订阅(消费)的基本单位
6.message消息
消息所传输的物理载体, 生产和消费的最小单位, 每条消息都必须属于一个主题
7.tag标签
为消息设置标记,区分同一主题下不同类型的消息
-->优化查询系统,可以扩展
-->主题是一级分类, 标签是二级分类
8.MessageQueue消息队列
一个主题包含多个队列,也被称之为主题中的消息分区
一个队列最多只能分配给一个消费者
一个消费者得到多个队列
-->避免消费过程中的多线程处理和资源锁定,有效提高消费者的处理效率
-->消费者组中的Consumer数量应该小于等于le主题中队列的数量,为了减少不必要的资源
-->为了防止消息紊乱一个消费者组中的Consumer都是订阅相同主题下的队列
9.消息,主题,队列,组之间的关系:
-->一个组可以消费或发送多个topic主题的消息
-->一个队列只能有一个消费者进行消费
-->一条消息只能属于一个主题
-->一个主题包含多个队列
-->一个消费者得到多个队列
10.读写队列
Queue分为 写队列 和 读队列
默认创建数量是都是4
Producer发送的消息进入写队列,Consumer从读队列获取数据,一般情况下读写队列数量是一样的
可以通过可视化界面修改Topic中的队列数量
perm: 设置对当前创建Topic的操作权限: 2可写 4可读 6可读写
11.MessageId/Key
每个消息拥有唯一的MessageId,方便查询
MessageId有两个,生产者发送时自动生成一个,到达Broker之后,Broker也会自动生成一个
12.Rebalance重新负载
当消费者的数量或者队列的数量修改,
他会把一个主题下的多个队列重新分配给消费者组下的Consumer, 目的增加消费能力
13.消息拉取模式
拉取式 pull ,和推送是 push
pull: 需要消费者间隔一定时间就去遍历关联的Queue,实时性差但是便于应用控制消息的拉取
Push: 推送式,封装了所有的Queue,实时性强,但是对系统资源占用比较多
14.消息消费模式: 针对同一个消费者组下的所有的消费者
广播模式: 都会受到同一个主题下的所有消息, 可能会出现脏读
集群模式: 平分同一个主题下的所有消息, 安全
15.Queue的分配算法: Queue是如何分配给Consumer的
平均分配【默认】: 队列数量除以消费者, 有余数再逐个分配.
环形平均策略: 根据消费者的顺序,一个一个的分配Queue即可类似于发扑克牌.
一致性Hash策略: 该算法将Consumer的Hash值作为节点放到Hash环上,然后将Queue的hash值也放入Hash环上,通过顺时针进行就近分配.
同机房策略:该算法会根据queue的部署机房位置和consumer的位置,过滤出当前consumer相同机房的queue。
然后按照平均分配策略或环形平均策略对同机房queue进行分配。
如果没有同机房queue,则按照平均分配策略或环形平均策略对所有queue进行分配。
-->平均分配性能比较高,一致性Hash性能不高,但是能减少Rebalance,如果Consumer数量变动频繁可以使用一致性Hash
16.Offset管理
用来维护Consumer的消费进度
-->consume_from_last_offset: 从队列的最后一条消息开始消费
-->consume_from_first_offset:从队列的第一条信息开始消费
-->consume_from_timestamp:从某个时间戳位置开始消费信息
消费者消费结束后,会向Consumer提交消费进度offset给Broker
offset信息存储分别为:
-->本地offset管理: json形式持久化到Consumer本地磁盘,用户目录下,适用于广播模式
-->远程offset管理: store/config/consumerOffset.json文件以json方式存储,适用于集群模式
集群模式下,消费者消费完信息后会向Broker提交消费进度:
提交的方式有两种:
-->同步提交:需要等待上一批消息的Broker的成功响应后,才可以继续读取下一批消息进行消费,
超时则重新提交,直到获取成功响应,没有收到响应,则会重新提交
严重影响消费者的吞吐量
-->异步提交:无需等待上一批的消息的Broker成功响应, 可以直接读取并消费下一批消息,
增加了消费者的吞吐量,
虽然不需要等待,但Broker还是会向消费者进行响应
17.消息的清理
消息不会被单独清理,消息是顺序存储到commitlog中,以commitlog为单位进行清理
默认72h后进行清理
到达时间清理点,自动清理过期文件: 默认凌晨4点
磁盘空间使用率达到过期清理阈值(75%)-自动清理过期文件,(85%)-按照设定的规则清理文件,(90%)-拒绝写入数据
18.延迟消息
1.Broker在存储生产者写入消息时,首先会将其写入到CommitLog中
判断有没有延时等级:
2.没有: 转发到目标队列
有: 将消息topic名字改为SCHEDULE_TOPIC_XXXX
根据延迟等级在consumequeue目录中SCHEDULE_TOPIC_XXXX主题下创建出相应的queueId
投递时间 = 消息存储时间 + 延时等级时间
等延迟时间到了, 消息还会进入commitlog文件中, 再提交到目标队列中