路由中心
NameServer架构设计
- Broker消息服务器在启动的时候向所有NameServer注册
- NameServer中的路由表变化不会通知生产者,这样降低了NameServer实现的复杂性,通过消息发送端提供的容错机制来保证消息发送的高可用
- NameServer集群之间多台NameServer之间互不通信
- NameServer每隔10s扫描一次Broker,移除处于不激活状态的Broker
NameServer配置
public class NamesrvConfig {
// rocketmq主目录,可以通过-Drocketmq.home.dir=path设置
private String rocketmqHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV));
// NameServer存储KV配置属性的持久化路径
private String kvConfigPath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "kvConfig.json";
// 默认配置文件路径,不生效
private String configStorePath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "namesrv.properties";
private String productEnvName = "center";
private boolean clusterTest = false;
// 是否支持顺序消费,默认不支持
private boolean orderMessageEnable = false;
}
NettyServer配置
public class NettyServerConfig implements Cloneable {
// NameServer监听端口,该值默认会被初始化为9876
private int listenPort = 8888;
// Netty业务线程池个数
private int serverWorkerThreads = 8;
// Netty public任务线程池个数
private int serverCallbackExecutorThreads = 0;
// IO线程池个数,主要是NameServer、Broker端解析请求、返回相应的线程个数。这类线程主要是处理网络请求,解析请求包,
然后转发到各个业务线程池完成具体的业务操作,然后将结果再返回给调用方
private int serverSelectorThreads = 3;
// send oneway消息请求并发度(Broker端参数)
private int serverOnewaySemaphoreValue = 256;
// 异步消息发送最大并发度(Broker端参数)
private int serverAsyncSemaphoreValue = 64;
// 网络连接最大空闲时间,如果空闲时间超过该值,连接将被关闭
private int serverChannelMaxIdleTimeSeconds = 120;
// 网络Socket发送缓存区大小,默认64K
private int serverSocketSndBufSize = NettySystemConfig.socketSndbufSize;
// 网络Socket接收缓存区大小,默认64K
private int serverSocketRcvBufSize = NettySystemConfig.socketRcvbufSize;
// ByteBuffer是否开启缓存
private boolean serverPooledByteBufAllocatorEnable = true;
// 是否启用Epoll
private boolean useEpollNativeSelector = false;
}
路由元信息
路由元信息就是NameServer存储的信息
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;
}
- topicQueueTable:Topic消息队列路由消息,消息发送时根据路由表进行负载均衡
- brokerAddrTable:Broker基本信息,包括brokerName、所属集群名字、主备Broker地址
- clusterAddrTable:集群中所有Broker名称
- brokerLiveTable:Broker状态信息
- filterServerTable:Broker上的FilterServer列表,用于类模式消息过滤
RocketMQ基于订阅发布机制,一个Topic拥有多个消息队列,一个Broker为每一个主题默认创建4个读队列4个写队列。多个Broker组成一个集群,BrokerName由相同的多台Broker组成Master-Slave架构,brokerId为0代表Master,大于0表示Slave。BrokerLiveInfo中的lastUpdateTimestamp存储上次收到Broker心跳包的时间
路由注册
RocketMQ路由注册是通过Broker与NameServer的心跳功能实现的。Broker启动时向集群中所有的NameServer发送心跳语句,每隔30s向集群中所有NameServer发送心跳包,NameServer收到Broker心跳包时会更新brokerLiveTable缓存中BrokerLiveInfo的lastUpdateTimestamp,然后NameServer每隔10s扫描brokerLiveTable,如果连接120s没有收到心跳包,NameServer将移除该Broker的路由信息同时关闭Socket连接
心跳包
- brokerAddr
- brokerId:0为master,大于0为slave
- brokerName
- clusterName
- haServerAddr:master地址,初次请求时该值为空,slave向NameServer注册后返回
- requestBody 设计亮点:路由表使用了锁粒度较少的读写锁,允许多个Producer并发读,保证消息发送时的高并发。但同一时刻NameServer只处理一个Broker心跳包,多个心跳包请求串行执行
路由删除
NameServer收到Broker心跳包时会更新brokerLiveTable缓存中BrokerLiveInfo的lastUpdateTimestamp,然后NameServer每隔10s扫描brokerLiveTable,如果连接120s没有收到心跳包,NameServer将移除该Broker的路由信息同时关闭Socket连接
路由发现
RocketMQ路由发现是非实时的,当Topic路由出现变化后,NameServer不主动推送给客户端,而是由客户端定时拉取主题最新的路由
消息发送
RocketMQ支持三种消息发送方式:同步、异步、单向
同步:发送者向MQ执行发送消息API时,同步等待,直到消息服务器返回发送结果
异步:发送消息时,生产者指定消息发送成功后的回调函数,然后调用发送API后,立即返回,消息发送者线程不阻塞,直到线程运行结束,消息发送成功或失败的回调任务在一个新的线程执行。
单向:发送消息时,直接返回,不等待消息服务器的结果,也不注册回调函数。
消息体
生产者启动流程
消息发送基本流程
消息长度验证
发送消息的最大长度4M(maxMessageSize=1024 *1024 *4)
查找主题路由信息
如果生产者中缓存了topic的路由信息,如果该路由信息中包含了消息队列,则直接返回该路由信息,如果没有缓存或没有包含消息队列,则向NameServer查询该topic的路由消息。如果最终未找到路由信息,则抛出异常
选择消息队列
选择消息队列有两种方式
- sendLatencyFaultEnable=false 默认不启用Broker故障延迟机制
- sendLatencyFaultEnable=true 启用Broker故障延迟机制 消息生产者每隔30s更新一次路由信息
什么是Broker故障延迟机制
如果不采用故障延迟机制,那么在broker出现异常时,生产者在重试时会不断的访问失效的队列。采用故障延迟机制后发送消息的步骤如下:
- 在消息发送失败,mq根据消息发送耗时来预测该broker不可用的时长,并将broker名称,及”预计恢复时长“,存储于ConcurrentHashMap<String, FaultItem> faultItemTable中
- 在开启消息容错后,选择消息队列时,会根据当前时间与FaultItem中该broker的预计恢复时间做比较,若(System.currentTimeMillis() - startTimestamp) >= 0,则预计该broker恢复正常,选择该broker的消息队列
- 若所有的broker都预计不可用,随机选择一个不可用的broker,从路由信息中选择下一个消息队列,重置其brokerName,queueId,进行消息发送
消息发送
- 根据MessageQueue获取Broker的网络地址
- 为消息分配全局唯一ID
- 如果注册了消息发送钩子函数,则执行消息发送之前的增强逻辑
- 构建消息发送包
- 根据消息发送方式,同步、异步、单向方式进行网络传输
批量消息发送
批量消息发送是将同一主题的多条消息一起打包发送到消息服务端,减少网络调用次数,提高网络传输效率。单批次消息发送总长度不能超过DefaultMQProducer#maxMessageSize=4M
消息存储
CommitLog:消息存储文件,所有消息主题的消息都存储在CommitLog文件中
CommitLog文件存储目录为${ROCKET_HOME}/store/commitlog目录,每个文件默认1G。第一个文件的初始偏移量为0,
第二个文件1073741824,代表该文件第一条消息的物理偏移量为1073741824,这样根据物理偏移量快速定位到消息。
MappedFileQueue可以看做${ROCKET_HOME}/store/commitlog文件夹,MappedFile看作该文件夹下的一个文件
broker配置文件 storePathRootDir修改路径
mapedFileSizeCommitLog修改文件大小
ConsumeQueue:消息消费队列,消息到达CommitLog文件后,将异步转发到消息消费队列,供消息消费者消费
IndexFile:消息索引文件,主要存储消息Key与Offset的对应关系
abort:如果存在该文件说明Broker非正常关闭,该文件默认启动时创建,正常退出之前删除
checkpoint:文件检测点,存储commitLog文件最后一次刷盘时间戳、consumequeue最后一次刷盘时间、index索引文件最后一次刷盘时间戳
事务状态服务:存储每条消息的事务状态
定时消息服务:每一个延迟级别对应一个消息消费队列,存储延迟队列的消息拉取进度
消息发送存储流程
存储文件组织与内存映射
对于commitlog、consumequeue、index三类大文件进行磁盘读写操作,均是通过MapedFile类来完成
RocketMQ使用MappedFile、MappedFileQueue来封装存储文件
RocketMQ存储文件
ConsumeQueue
由于不同topic的消息可能存在相同的commitLog中,那么也就是说同一topic的数据在commitLog中也可能是不连续的,那么对于数据的查找非常低效,ConsumeQueue就是为了满足消费的检索需求
单个ConsumeQueue文件中默认包含30w个条目,单个文件的长度为30w*20字节
Index索引文件 为了根据key找到消息数据的offset,快速完成数据检索
checkpoint文件 checkpoint的作用是记录CommitLog、ConsumeQueue、Index文件的刷盘时间点,文件固定长度为4K,其中只用该文件的前面24个字节
实时更新消息消费队列与索引文件
当生产者提交消息到commitLog时,Broker通过开启一个线程ReputMessageService来实时转发CommitLog文件更新事件,及时更新ConsumeQueue、IndexFile文件