1.1消息存储
分布式队列因为有高可靠性的要求,所以数据要进行持久化存储。
1.1.1 存储介质
1)关系型数据库DB
通过关系型数据库进行持久化消息,但在数据量达到千万级时其IO性能会出现瓶颈且DB一旦出现故障则MQ的消息就无法落盘存储会导致线上故障。如ActiveMQ
2)文件系统
像RocketMQ、Kafka、RabbitMQ均采用消息刷盘至所部署虚拟机/物理机的文件系统来做持久化(刷盘方式一般可分为异步刷盘和同步刷盘两种)。消息刷盘则为消息存储提供了一种高效率、高可靠性和高性能的数据持久化方式。除非部署MQ机器本身或是本地磁盘挂了否则一般不会出现无法持久化的故障问题。
1.1.2性能对比
文件系统 > 关系型数据库DB
1.1.3 消息的存储和发送
消息存储时使用顺序写保证了消息存储的速度,可达600M/s,另一种为随机写速度可达100KB/S,RocketMQ采用顺序写。
数据在发送时使用“零拷贝”的技术提高消息存盘和网络发送的速度。需要注意的是采用MappedByteBuffer这种内存映射方式有限制,其中之一是一次只能映射1.5G~2G的文件至用户态的虚拟内存,这也是为何RocketMQ默认设置单个CommitLog日志数据文件为1G的原因。
1.1.4 消息存储结构
RocketMQ消息的存储是由ConsumeQueue(消息逻辑队列)和CommitLog配合完成的,消息真正的物理文件是CommitLog,ConsumeQueue是消息的逻辑队列,类似数据库的索引文件,存储的是指向物理存储的地址。每个Topic下的每个MessageQueue都有一个对应的ConsumeQueue文件。
CommitLog:存储消息的元数据
ConsumeQueue:存储消息在CommitLog的索引
IndexFile:索引文件,为消息查询提供了一种通过key或时间区间来查询消息的方式,这种通过IndexFile来查找消息的方式不影响发送与消费消息的过程。
消费顺序:消费者先从consumeQueue中找到消息的索引在去CommitLog中找到具体的消息进行读取消费
1.1.5 刷盘机制
RocketMQ的消息是存储到磁盘上的,这样既能保证断电后恢复又可以让存储的消息量超过内存的限制。RocketMQ为了提高性能,会尽可能的保证磁盘的顺序写,消息在通过producer写入RocketMQ的时候有两种写磁盘方式:同步刷盘和异步刷盘。
1) 同步刷盘:在返回写成功状态时,消息已被写入磁盘。
具体流程是,消息写入内存的pageCache后,立刻通知刷盘线程刷盘,然后等待刷盘完成,刷盘线程执行完后唤醒等待的线程,返回写成功的状态。
2) 异步刷盘:在返回写成功状态时,消息可能只是被写入了内存的pageCache,写操作的返回快,吞吐量大;当内存里的消息量积累到一定程度时统一触发写磁盘动作,快速写入。
3) 配置:同步还是异步刷盘,都是通过broker配置文件中的flushDiskType参数设置的,SYNC_FLUSH 同步,ASYNC_FLUSH 异步
1.2 高可用性机制
RocketMQ分布式集群是通过Master和Slave的配合达到高可用的。
Master和Slave的区别:在Broker配置文件中,参数brokerId的值为0表明这个Broker是Master,大于0表明这个Broker是Slave,同时BrokerRole也会说明这个Broker是Master还是Slave。
Master角色的Broker支持读和写,Slave角色的Broker仅支持读,也就是Producer只能和Master角色的Broker连接写入消息;Consumer可以连接Master角色的Broker也可以连接Slave角色的Broker来读取消息。
1.2.1 消息消费高可用
在Consumer的配置文件中并不需要设置从Master读还是从Slave读,当Master不可用或者繁忙的时候Consumer会被自动切换到从Slave读取。有了自动切换Consumer这种机制,当一个Master角色的机器出现故障后Consumer仍然可从Slave读取消息,不影响Consumer程序,这就达到了消费端的高可用性。
1.2.2 消息发送高可用
在创建Topic时将Topic的多个Message Queue创建在多个Broker组上(相同的Broker名称,不同brokerId的机器组成一个Broker组)。这样当一个Broker组的Master不可用后其他组的Master仍然可用Producer仍可以发送消息。RocketMQ目前还不支持将Slave自动转换成Master,如果机器资源不足需要将Slave转成Master,则要手动停止Slave角色的Broker更改配置文件,用新的配置文件启动Broker。
1.2.3 消息主从复制
如果一个Broker组有Master和Slave,消息需要从Master复制到Slave,有同步和异步两种复制方式。
1)同步复制
同步复制方式是等Master和Slave均写成功后才反馈给客户端写成功状态。
在同步复制的方式下,如果Msater出现故障,Slave上有全部的备份数据,容易恢复,但是同步复制会增大数据写入延迟,降低系统的吞吐量。
2)异步复制
异步复制方式是只要Master写入成功即可反馈给客户端写成功状态。
在异步复制方式下,系统拥有较低的延迟和较高的吞吐量,但是如果Master出现故障,有些数据因为没有被写入Slave有可能会丢失。
3)配置
同步复制和异步复制是通过Broker配置文件中的BrokerRole参数进行设置的,该参数可设置成SYNC_MASTER、ASYNC_MASTER、SLAVE三个值中的一个。
4)总结
实际应用中要结合业务场景,合理设置刷盘方式和主从复制方式,尤其是SYNC_FLUSH方式,由于频繁的出发磁盘写动作,会明显的降低性能。通常情况下,应该将Master和Slave配置成ASYNC_FLUSH的刷盘方式,主从之间配置成SYNC_MASTER的复制方式,这样即使有一台机器出现故障,仍然能保证数据不丢失。
1.3 负载均衡
1.3.1 Producer负载均衡
Producer端,每个实例在发送消息的时候,默认会轮询所有的message queue进行发送,以达到让消息平均落在不同的queue上,而由于queue可以散落在不同的broker上,所以消息就发送到不同的broker上。
1.3.2 Consumer负载均衡
1)集群模式
该种消费模式下,每个消费者实例是按照均摊消息队列的,如6个队列,三个消费者,1消负责borker-a的0-1队列,2消负责borker-a的2和borker-b的0队列,3消费负责borker-b的1-2队列,默认的分配算法是AllocateMessageQueueAveragely
另外一种平均的算法是AllocateMessageQueueAveragelyByCircle,也是平均分摊每一条queue,只是以环状轮流分queue的形式。如
如6个队列,三个消费者,1消负责borker-a的0和borke-b的0队列,2消负责borker-a的1和borke-b的1队列,3消费负责borker-a的2和borke-b的2队列
2)广播模式
每个consumer实例都分到所有的queue因为消费的是相同的。
1.4 消息重试
1.4.1 顺序消息的重试
对于顺序消息,当消费者消费消息失败后,消息队列MQ会自动不断的进行消息重试(每次间隔时间为1秒),这时应用会出现消息消费被阻塞的情况,因此,在使用顺序消息时务必保证应用能及时监控并处理消费失败的情况,避免阻塞现象的发生。
1.4.2 无序消息的重试
对于无序消息(普通、延时、事务),当消费者消费消息失败时可通过设置返回状态达到消息重试的结果。无序消息的重试只针对集群模式消费方式生效;广播方式不提供失败重试的特性,即消费失败后失败消息不在重试继续消费新的消息。
1)重试次数
RocketMQ默认允许每条消息最多重试16次,时间间隔从10s-2小时,总计4小时46分钟,若16次后仍失败消息将不再投递。注意:一条消息无论重试多少次,消息的msgId不会改变。