目录
消息存储
为了保证RocketMQ的高可靠性要求,MQ会对消息进行持久化
- 生产者发送消息
- MQ收到消息后,对消息进行持久化,在储存中新增一条记录
- 返回ACK给生产者
- MQ推送消息给对应的消费者,然后等待消费者返回ACK
- 如果消费者返回了ACK,则MQ执行消息删除的操作,若超时未返回,MQ会认为消费失败重新推送消息,重复执行4,5,6步骤
- MQ删除消息
存储介质
- 关系型数据库
例如Apache的ActiveMQ就可以通过JDBC来进行消息的持久化,设置一些XML的配置信息即可。但是,普通的关系型数据库,如MySQL,在数据量达到千万级别后会造成IO的性能瓶颈。在可靠性方面,这种方式非常依赖于数据库的性能,一旦数据库宕机,就会导致消息无法存储的线上故障。
- 文件系统
目前业界较为常用的几款产品(RocketMQ/Kafka/RabbitMQ)均采用的是消息刷盘至所部署虚拟机/物理机的文件系统来做持久化(刷盘一般可以分为异步刷盘和同步刷盘两种模式)。消息刷盘为消息存储提供了一种高效率、高可靠性和高性能的数据持久化方式。除非部署MQ机器本身或是本地磁盘挂了,否则一般是不会出现无法持久化的故障问题。
所以在性能上:文件系统>关系型数据库
消息的存储与发送的性能保障
- 消息存储
我们知道,消息多是通过网络发送过来的,RocketMQ如何保证消息存储的速度匹配上网络传输速度呢。其实以目前大多数SATA3接口的SSD顺序写的速度能达到600MB/s(更不说M.2接口了),但随机写差不多只有100KB/s,相差了6000倍。所以RocketMQ在存储消息时采用顺序写的方式来保证效率。
- 消息发送(指MQ)
一台MQ服务器把本地消息发送至客户端分两步
- 从本地读取消息;
- 将读取的内容通过网络发送出去。
因linux系统分为【用户态】和【内核态】,文件操作、网络操作需要涉及这两种形态的切换,免不了进行数据复制。
看似只有两步,实际在系统内部进行了四次数据的复制,如果数据非常大的话,4次复制会导致读的速度非常慢。
- 从磁盘复制数据到内核态内存;
- 从内核态内存复制到用户态内存(也就是应用程序独占的内存);
- 然后从用户态内存复制到网络驱动的内核态内存;
- 最后是从网络驱动的内核态内存复 制到网卡中进行传输。
那么RocketMQ是如何提高读的效率的呢,它是利用了Java中的MappedByteBuffer来实现一个叫做“零拷贝”的技术,通过省去数据向用户态内存的复制,来提高读取速度。
消息存储结构
在之前的博客中,我们在搭建集群的配置里设置了一些关系消息存储的路径。
#存储路径
storePathRootDir=/opt/rocketmq/store-m
#commitLog 存储路径
storePathCommitLog=/opt/rocketmq/store-m/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/opt/rocketmq/store-m/consumequeue
#消息索引存储路径
storePathIndex=/opt/rocketmq/store-m/index
#checkpoint 文件存储路径
storeCheckpoint=/opt/rocketmq/store-m/checkpoint
#abort 文件存储路径
abortFile=/opt/rocketmq/store-m/abort
对应系统中
RocketMQ消息的存储是由ConsumeQueue和CommitLog配合完成的,消息真正的物理存储文件是CommitLog,ConsumeQueue是消息的逻辑队列,类似数据库的索引文件,存储的是指向物理存储的地址。每 个Topic下的每个Message Queue都有一个对应的ConsumeQueue文件。
CommitLog:存储消息的元数据,发送的消息的所有信息(Topic,QueueId,Message)都会存到这里面
我们可以看到,查看CommitLog,里面就有一个大小为1G,用于存储消息的文件(当前只有一个,消息多了会自动创建新的,大小也是1G)。
这是因为前面提到的,RocketMQ使用MappedByteBuffer这种内存映射的方式有几个限制,其中之一是一次只能映射1.5~2G 的文件至用户态的虚拟内存,所以每一个文件的大小是1G(小于2G即可)。
ConsumerQueue:存储的是CommitLog里每个消息的索引,为的是提升消息读取的效率
ConsumerQueue里面存了消息最小的偏移量,已经消费的偏移量,最大的偏移量,且丢失后能通过CommitLog还原。
可以看到每一个Topic都有四个消息队列(0,1,2,3),每一个消息队列会对应一个ConsumerQueue。其和CommitLog里的文件同名,大小不大。也就意味着Linux能够将其加载到内存中读取,进一步加快读消息的速度。
IndexFile:也是索引文件,与ConsumerQueue通过偏移量查找不同,IndexFile提供了通过时间区间来查,key来查的方式
刷盘机制
- 同步刷盘
在返回写成功状态时,消息已经被写入磁盘。具体流程是,消息写入内存的PAGECACHE后,立刻通知刷盘线程刷盘, 然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态。
- 异步刷盘
在返回写成功状态时,消息可能只是被写入了内存的PAGECACHE,写操作的返回快,吞吐量大;当内存里的消息量积累到一定程度时,统一触发写磁盘动作,快速写入。
- 配置
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH