消息存储的结构
1、RocketMQ消息的存储结构
2、存储特点
如上图所示:
- 1、消息主体以及元数据都存储在CommitLog文件当中,完全顺序写,随机读
- 2、Consume Queue相当于kafka中的partition,是一个逻辑队列,存储了这个Queue在CommiLog中的起始offset,log大小和MessageTag的hashCode。
- 3、每次读取消息队列先读取consumerQueue,然后再通过consumerQueue去commitLog中拿到消息主体。
3、为什么要这样设计?
rocketMQ的设计理念很大程度借鉴了kafka,所以有必要介绍下kafka的存储结构设计:
存储特点:
和RocketMQ类似,每个Topic有多个partition(queue),kafka的每个partition都是一个独立的物理文件,消息直接从里面读写。
RocketMQ这样做的优点:
- 对最终用户展现的队列实际只储存消息在Commit Log 的位置信息,并且串行方式刷盘
- 队列轻量化,单个队列数据量非常少
- 对磁盘的访问串行化,避免磁盘竞争,不会因为队列增加导致IOWait增高
每个方案都有优缺点,他的缺点是:
- 写虽然是顺序写,但是读却变成了随机读
- 读一条消息,会先读Consume Queue,再读Commit Log,增加了开销
- 要保证Commit Log 与 Consume Queue完全的一致,增加了编程的复杂度
以上缺点如何克服:
- 随机读,尽可能让读命中pagecache,减少IO操作,所以内存越大越好。如果系统中堆积的消息过多,读数据要访问硬盘会不会由于随机读导致系统性能急剧下降,答案是否定的。
- 访问pagecache时,即使只访问1K的消息,系统也会提前预读出更多的数据,在下次读时就可能命中pagecache
- 随机访问Commit Log 磁盘数据,系统IO调度算法设置为NOOP方式,会在一定程度上将完全的随机读变成顺序跳跃方式,而顺序跳跃方式读较完全的随机读性能高5倍
- 由于Consume Queue存储数量极少,而且顺序读,在pagecache的与读取情况下,Consume Queue的读性能与内存几乎一直,即使堆积情况下。所以可以认为Consume Queue完全不会阻碍读性能
- Commit Log中存储了所有的元信息,包含消息体,类似于MySQl、Oracle的redolog,所以只要有Commit Log存在, Consume Queue即使丢失数据,仍可以恢复出来
详细的消息存储:RocketMQ源码学习--消息存储篇
同步刷盘和异步刷盘
RocketMQ消息存储:内存+磁盘存储,两种刷盘方式
RocketMQ和Redis等其他存储系统类似,提供了同步和异步两种刷盘方式,同步刷盘方式能够保证数据被写入硬盘,做到真正的持久化,但是也会让系统的写入速度受制于磁盘的IO速度;而异步刷盘方式在将数据写入缓冲之后就返回,提供了系统的IO速度,却存在系统发生故障时未来得及写入硬盘的数据丢失的风险。
同步刷盘、异步刷盘
RocketMQ的消息是存储到磁盘上的,这样既能保证断电后恢复,又可以让存储的消息量超出内存的限制。
RocketMQ为了提高性能,会尽可能地保证磁盘的顺序写。消息在通过Producer写入RocketMQ的时候,有两种
- 异步刷盘方式:在返回写成功状态时,消息可能只是被写入了内存的PAGECACHE,写操作的返回快,吞吐量大;当内存里的消息量积累到一定程度时,统一触发写磁盘操作,快速写入
- 同步刷盘方式:在返回写成功状态时,消息已经被写入磁盘。具体流程是,消息写入内存的PAGECACHE后,立刻通知刷盘线程刷盘,然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态。
同步刷盘还是异步刷盘,是通过Broker配置文件里的flushDiskType参数设置的,这个参数被设置成SYNC_FLUSH、ASYNC_FLUSH中的一个
详细的刷盘解读:rocketmq刷盘过程
同步复制和异步复制
前提:
同一组broker中有Master和Slave,消息需要从Master复制到Slave上,那么有同步和异步两种复制方式。
同步复制:是等Master和Slave均写成功后才反馈给客户端写成功状态
异步复制:是只要Master写成功即可反馈给客户端写成功状态
两种复制方式对比:
- 异步复制方式下,系统拥有较低的延迟和较高的吞吐量,但是如果Master出了故障,有些数据因为没有被写入Slave,有可能会丢失;
- 同步复制方式下,如果Master出故障,Slave上有全部的备份数据,容易恢复,但是同步复制会增大数据写入延迟,降低系统吞吐量。
配置方式:
同步复制和异步复制是通过Broker配置文件里的brokerRole参数进行设置的,这个参数可以被设置成ASYNC_MASTER、SYNC_MASTER、SLAVE三个值中的一个。
实际应用中要结合业务场景,合理设置刷盘方式和主从复制方式,尤其是SYNC_FLUSH方式,由于频繁的触发写磁盘动作,会明显降低性能。
通常情况下,应该把Master和Slave设置成ASYNC_FLUSH的刷盘方式,主从之间配置成SYNC_MASTER的复制方式,这样即使有一台机器出故障,仍然可以保证数据不丢。
高可用机制
- 当Master节点繁忙,可自动切换到Slave节点读取信息
- 当Master节点down机或不可用时,rocketmq基于raft 协议支持主从切换,引入了多副本机制,即DLedger,支持主从切换,即当一个复制组内的主节点宕机后,会在该复制组内触发重新选主,选主完成后即可继续提供消息写功能。
RocketMQ高可用机制详细解读:Apache RocketMQ Producer解析文章中的2、RocketMQ主从同步机制解析
NameServer协调者
Namesrv功能介绍
Namesrv的功能,就相当于RPC或微服务中的注册中心。对于MQ而言,broker启动,将自身创建的topic等信息注册到Namesrv上。consumer和producer需要配置namesrv的地址,启动后,首先和namesrv建立长连接,并获取相应的topic信息(比如,哪些broker有topic路由信息),然后再和broker建立长连接。Namesrv本身无状态,可集群横向扩展部署。所有的注册信息,都保存在namesrv的类似map内存数据结构中。
Namesrv启动流程:
Namesrv的数据都保存在RouteInfoManager类中:
public class RouteInfoManager {
private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
}
参考:
https://www.cnblogs.com/toUpdating/p/10021372.html
https://www.iteye.com/blog/technoboy-2368379
https://www.cnblogs.com/shoshana-kong/p/10914353.html