RocketMQ 集群部署示意图
- broker 实际消息处理服务端 默认端口:10911
1、Broker面向producer和consumer接受和发送消息;
2、向nameserver提交自己的信息;
3、是消息中间件的消息存储、转发服务器;
4、每个Broker节点,在启动时,都会遍历NameServer列表,与每个NameServer建立长连接,注册自己的信息,之后定时上报。
- namesrv 中间代理协调消费者、生产者、以及broker 默认端口:9876
1、底层由netty实现,提供了路由管理、服务注册、服务发现的功能,是一个无状态节点;
2、NameServer是服务发现者,集群中各个角色(producer、broker、consumer等)都需要定时想NameServer上报自己的状态,以便互相发现彼此,超时不上报的话,NameServer会把它从列表中剔除;
3、nameserver可以部署多个,当多个NameServer存在的时候,其他角色同时向他们上报信息,以保证高可用;
4、NameServer集群间互不通信,没有主备的概念;
5、Nameserver内存式存储,NameServer中的broker、topic等信息默认不会持久化;
-
RocketMQ 中topic和Broker、Queue之间的关系
1、一个集群需要多个broker,每个broker上可以存储一个topic的多个分片。
2、可以通过rocketadmin来自定义一个topic的分片queue在不同broker中的分布。
启动服务
1、Start Name Server
nohup sh bin/mqnamesrv &
tail -f ~/logs/rocketmqlogs/namesrv.log
2、Start Broker
nohup sh bin/mqbroker -n localhost:9876 &
tail -f ~/logs/rocketmqlogs/broker.log
3、Send & Receive Messages
Before sending/receiving messages, we need to tell clients the location of name servers. RocketMQ provides multiple ways to achieve this. For simplicity, we use environment variable NAMESRV_ADDR
export NAMESRV_ADDR=localhost:9876
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
4、Shutdown Servers
sh bin/mqshutdown broker
sh bin/mqshutdown namesrv
消费模式(消费端控制消费模式)
消费模式分,集群消费、广播消费
消费组:定一个一个消费组,组下跟进所有分布节点;
集群消费:一条消息只会被一个消费组内的只有一个节点收到;
广播消费:一条消息可以被一个消费组内的所有节点收到(只广播一次),消费失败的消息不会重投;
consumer.setMessageModel(MessageModel.BROADCASTING);
consumer.setMessageModel(MessageModel.CLUSTERING);
消息体
Message
//带topic 、 tag以及消息内容;消费端可以使用tag来过滤
Message msg = new Message("TopicTest","TagA" ,("Hello RocketMQ " ).getBytes());
消息类型
普通消息、延迟消息、事务消息
延迟消息
RocketMQ使用messageDelayLevel可以设置延迟投递
默认配置为
messageDelayLevel 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
配置
在broker.conf 中添加配置
messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
这个配置项配置了从1级开始,各级延时的时间,可以修改这个指定级别的延时时间;
时间单位支持:s、m、h、d,分别表示秒、分、时、天;
使用
发送消息时设置
message.setDelayTimeLevel(1);
事务消息
事务消息
分布式系统中的事务可以使用TCC(Try、Confirm、Cancel)、2pc来解决分布式系统中的消息原子性
RocketMQ 4.3+提供分布事务功能,通过 RocketMQ 事务消息能达到分布式事务的最终一致
RocketMQ实现方式
Half Message:预处理消息,当broker收到此类消息后,会存储到RMQ_SYS_TRANS_HALF_TOPIC的消息消费队列中
检查事务状态:Broker会开启一个定时任务,消费RMQ_SYS_TRANS_HALF_TOPIC队列中的消息,每次执行任务会向消息发送者确认事务执行状态(提交、回滚、未知),如果是未知,等待下一次回调。
超时:如果超过回查次数,默认回滚消息
TransactionListener的两个方法
executeLocalTransaction
半消息发送成功触发此方法来执行本地事务
checkLocalTransaction
broker将发送检查消息来检查事务状态,并将调用此方法来获取本地事务状态
本地事务执行状态
LocalTransactionState.COMMIT_MESSAGE
执行事务成功,确认提交
LocalTransactionState.ROLLBACK_MESSAGE
回滚消息,broker端会删除半消息
LocalTransactionState.UNKNOW
暂时为未知状态,等待broker回查
发送消息的方式
同步发送、异步发送、单向消息、批量消息发送
1、同步发送
消息发送中进入同步等待状态,可以保证消息投递一定到达。
2、异步发送
想要快速发送消息,又不想丢失的时候可以使用异步消息
producer.send(message,new SendCallback() {
public void onSuccess(SendResult sendResult) {
System.out.println("ok");
}
public void onException(Throwable e) {
e.printStackTrace();
System.out.println("err");
}
});
3、单向消息
只发送消息,不等待服务器响应,只发送请求不等待应答。此方式发送消息的过程耗时非常短,一般在微秒级别。
producer.sendOneway(message);
4、批量消息发送
可以多条消息打包一起发送,减少网络传输次数提高效率。
producer.send(Collection c) 方法可以接受一个集合 实现批量发送
消费中的SQL92过滤
SQL表达式过滤
消费者将收到包含TAGA或TAGB或TAGB的消息. 但限制是一条消息只能有一个标签,而这对于复杂的情况可能无效。 在这种情况下,您可以使用SQL表达式筛选出消息.
配置
在broker.conf 中添加配置
enablePropertyFilter=true
启动broker 加载指定配置文件
../bin/mqbroker -n 192.168.150.113:9876 -c broker.conf
MessageSelector selector = MessageSelector.bySql("order > 5");
consumer.subscribe("xxoo3", selector);
>语法:
数字比较, 像 >, >=, <, <=, BETWEEN, =;
字符比较, 像 =, <>, IN;
IS NULL 或者 IS NOT NULL;
逻辑运算AND, OR, NOT;
>常量类型是:
数字, 像123, 3.1415;
字符串, 像‘abc’,必须使用单引号;
NULL, 特殊常数;
布尔常量, TRUE 或FALSE;
顺序消费
队列先天支持FIFO模型,单一生产和消费者下只要保证使用MessageListenerOrderly监听器即可
顺序消费表示消息消费的顺序同生产者为每个消息队列发送的顺序一致,所以如果正在处理全局顺序是强制性的场景,需要确保使用的主题只有一个消息队列。
并行消费不再保证消息顺序,消费的最大并行数量受每个消费者客户端指定的线程池限制。
那么只要顺序的发送,再保证一个线程只去消费一个队列上的消息,那么他就是有序的。
跟普通消息相比,顺序消息的使用需要在producer的send()方法中添加MessageQueueSelector接口的实现类,并重写select选择使用的队列,因为顺序消息局部顺序,需要将所有消息指定发送到同一队列中。
超时相关
1、生产者超时重试
默认超时时间
/**
* Timeout for sending messages.
*/
private int sendMsgTimeout = 3000;
// 异步发送时 重试次数,默认 2
producer.setRetryTimesWhenSendAsyncFailed(1);
// 同步发送时 重试次数,默认 2
producer.setRetryTimesWhenSendFailed(1);
// 是否向其他broker发送请求 默认false
producer.setRetryAnotherBrokerWhenNotStoreOK(true);
2、消费者超时设置
消费超时,单位分钟
consumer.setConsumeTimeout()
发送ack,消费失败
RECONSUME_LATER
3、失败消息重试投递规则
只有在消息模式为MessageModel.CLUSTERING集群模式时,Broker才会自动进行重试,广播消息不重试
重投使用messageDelayLevel
默认值messageDelayLevel 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
单词概念
- offset
每个broker中的queue在收到消息时会记录offset,初始化0,没记录一条消息offset就加1 - min offset、max offset
最小值、最大值,rocketmq commitlog 默认48小时会被过期删除,所以这个最小minoffset会从0变成其他再变成其他。 - consumer offset
消费者消费进度/位置
consumer长轮询获取消息
1、rocketmq采用的事长轮询,介于短轮询和长连接之间。
2、没有数据时挂起客户端连接,有消息时在推送消息给客户端,然后客户端再次发起下一次请求。
3、解决的是减少多次轮询检查是否有数据的压力和资源问题。
消息存储
- rocketmq使用文件系统作为消息持久化
- 数据零拷贝技术
- 内存映射MappedByteBuffer API
1、MappedByteBuffer使用虚拟内存,因此分配(map)的内存大小不受JVM的-Xmx参数限制,但是也是有大小限制的。
2、如果当文件超出1.5G限制时,可以通过position参数重新map文件后面的内容。
3、MappedByteBuffer在处理大文件时的确性能很高,但也存在一些问题,如内存占用、文件关闭不确定,被其打开的文件只有在垃圾回收的才会被关闭,而且这个时间点是不确定的。
javadoc中也提到:A mapped byte buffer and the file mapping that it represents remain* valid until the buffer itself is garbage-collected.
4、为了使用零拷贝技术,RocketMQ的文件存储大小默认每个1G,超过1G会重新建立一个新文件。
消息存储结构
CommitLog、ConsumerQueue、indexFilez主要由这三大文件组成
1、CommitLog
存储消息的详细内容,按照消息收到的顺序,所有消息都存储在一起。每个消息存储后都会有一个offset,代表在commitLog中的偏移量。
内部结构:
- MappedFileQueue -> MappedFile
源码: - 默认配置 MessageStoreConfig
- 核心方法 putMessage 写入消息
MappedFile 文件默认大小1G
// CommitLog file size,default is 1G
private int mappedFileSizeCommitLog = 1024 * 1024 * 1024;
2、ConsumerQueue
通过消息偏移量建立的消息索引
针对每个Topic创建,消费逻辑队列,存储位置信息,用来快速定位CommitLog中的数据位置
启动后会被加载到内存中,加快查找消息速度
以Topic作为文件名称,每个Topic下又以queue id作为文件夹分组
默认大小
// ConsumeQueue extend file size, 48M
private int mappedFileSizeConsumeQueueExt = 48 * 1024 * 1024;
3、indexFile
消息的Key和时间戳索引
刷盘机制
在CommitLog初始化时,判断配置文件 加载相应的service
if (FlushDiskType.SYNC_FLUSH == defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
this.flushCommitLogService = new GroupCommitService();
} else {
this.flushCommitLogService = new FlushRealTimeService();
}
不够消息存放的空间使用魔术字符代替,保证消息不回写入到两个MappedFile中而截断
// Determines whether there is sufficient free space
if ((msgLen + END_FILE_MIN_BLANK_LENGTH) > maxBlank) {
this.resetByteBuffer(this.msgStoreItemMemory, maxBlank);
// 1 TOTALSIZE
this.msgStoreItemMemory.putInt(maxBlank);
// 2 MAGICCODE
不够放下一个消息的时候,用魔术字符代替
this.msgStoreItemMemory.putInt(CommitLog.BLANK_MAGIC_CODE);
// 3 The remaining space may be any value
// Here the length of the specially set maxBlank
final long beginTimeMills = CommitLog.this.defaultMessageStore.now();
byteBuffer.put(this.msgStoreItemMemory.array(), 0, maxBlank);
return new AppendMessageResult(AppendMessageStatus.END_OF_FILE, wroteOffset, maxBlank, msgId, msgInner.getStoreTimestamp(),
queueOffset, CommitLog.this.defaultMessageStore.now() - beginTimeMills);
}
集群
- 单Master模式
只有一个 Master节点
优点:配置简单,方便部署
缺点:这种方式风险较大,一旦Broker重启或者宕机时,会导致整个服务不可用,不建议线上环境使用 - 多Master模式
一个集群无 Slave,全是 Master,例如 2 个 Master 或者 3 个 Master
优点:配置简单,单个Master 宕机或重启维护对应用无影响,在磁盘配置为RAID10 时,即使机器宕机不可恢复情况下,由与 RAID10磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢)。性能最高。多 Master 多 Slave 模式,异步复制
缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受到受到影响 - 多Master多Slave模式(异步复制)
每个 Master 配置一个 Slave,有多对Master-Slave, HA,采用异步复制方式,主备有短暂消息延迟,毫秒级。
优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,因为Master 宕机后,消费者仍然可以从 Slave消费,此过程对应用透明。不需要人工干预。性能同多 Master 模式几乎一样。
缺点: Master 宕机,磁盘损坏情况,会丢失少量消息。 - 多Master多Slave模式(同步双写)
每个 Master 配置一个 Slave,有多对Master-Slave, HA采用同步双写方式,主备都写成功,向应用返回成功。
优点:数据与服务都无单点, Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高
缺点:性能比异步复制模式略低,大约低 10%左右,发送单个消息的 RT会略高。目前主宕机后,备机不能自动切换为主机
主备切换转移
rocketMQ的主备切换是建立在冗余文件系统备份上的一种切换,而这种文件系统就是DLedger,DLedger —基于 raft 协议的 commitlog 存储库。
- 减少第三方引入影响所以不适用zookeeper之类
- raft协议选主
rocketmq broker启动时的配置
# DLedger
enableDLegerCommitLog = true
dLegerGroup = broker-a
dLegerPeers = n0-192.168.150.210:40911;n1-192.168.150.211:40911
dLegerSelfId = n0
sendMessageThreadPoolNums = 4
负载均衡
通过Topic在多broker种分布式存储实现
producer端
发送端指定Target message queue发送消息到相应的broker,来达到写入时的负载均衡
- 提升写入吞吐量,当多个producer同时向一个broker写入数据的时候,性能会下降
- 消息分布在多broker种,为负载消费做准备
每 30 秒从 nameserver获取 Topic 跟 Broker 的映射关系,近实时获取最新数据存储单元,queue落地在哪个broker中,在使用api中send方法的时候,可以指定Target message queue写入或者使用MessageQueueSelector
默认策略是随机选择:
- producer维护一个index
- 每次取节点会自增
- index向所有broker个数取余
- 自带容错策略
其他实现
- SelectMessageQueueByHash
- hash的是传入的args
- SelectMessageQueueByRandom
- SelectMessageQueueByMachineRoom 没有实现
也可以自定义实现MessageQueueSelector接口中的select方法
MessageQueue select(final List mqs, final Message msg, final Object arg);
consumer端
客户端完成负载均衡
- 获取集群其他节点
- 当前节点消费哪些queue
- 负载粒度直到Message Queue
- consumer的数量最好和Message Queue的数量对等或者是倍数,不然可能会有消费倾斜
- 每个consumer通过balanced维护processQueueTable
1、processQueueTable为当前consumer的消费queue
2、processQueueTable中有
* ProcessQueue :维护消费进度,从broker中拉取回来的消息缓冲
* MessageQueue : 用来定位查找queue
DefaultMQPushConsumer默认 使用AllocateMessageQueueAveragely(平均分配)
平均分配策略(默认)(AllocateMessageQueueAveragely)
环形分配策略(AllocateMessageQueueAveragelyByCircle)
手动配置分配策略(AllocateMessageQueueByConfig)
机房分配策略(AllocateMessageQueueByMachineRoom)
一致性哈希分配策略(AllocateMessageQueueConsistentHash)
靠近机房策略(AllocateMachineRoomNearby)