1.NameServer整体架构
消息中间件的设计思路-一般基于主题的订阅发布机制,消息生产者(Producer)发送某一主体的消息到消息服务器,消息服务器负责该消息的持久化存储,消息消费者(Consumer)订阅感兴趣的主体,消息服务器根据订阅信息(路由信息)将消息推送到消息消费者(PUSH模式)或者消息消费者主动向消息服务器拉取消息(PULL模式),从而实现消息生产者与消息消费者解耦。为了避免消息服务器的单点故障导致整个系统瘫痪,通常会部署多台消息服务器共同承担消息存储。那消息生产者如何知道消息要发往哪台消息服务器呢?如果某一台消息服务器宕机了,那么生产者如何在不重启服务的情况下感知呢?
NameServer就是为了解决上述问题而设计的。
RocketMQ的逻辑部署图如图2-1所示:
Broker消息服务器在启动时向所有NameServer注册,消息生产者(Producer)在发消息之前现从NameServer获取Broker服务器地址列表,然后根据负载算法从列表中选择一台消息服务器进行消息发送。NameServer与每台Broker服务器保持长链接,并间隔30S检测Broker是否存活,如果检测到Borker宕机,则从路由注册表中将其移除。但是路由变化不会马上通知消息生产者,为什么要这样设计呢?这是为了降低NameServer实现的复杂性,在消息发送端提供容错机制来保证消息发送的高可用性。
NameServer本身的高可用可通过部署多台NameServer服务器来实现,但是彼此间互不通信,也就是NameServer服务器之间在某一时刻数据并不会完全相同,但这对消息发送不会造成任何影响,这也是RocketMQ NameServer设计的一个亮点,RocketMQ设计追求简单而高效。
可以对比:zookeeper 和 调度框架 xxl-job 以及tb-schdule等横向对比过之后才会发现各个框架或者中间件的取舍。
1.Name Server 是一个几乎无状态的结点,Name Server 之间采取share Noting 设计,互相不通信。
2.对于一个Name Server集群列表,客户端连接NameServer 的时候,只会选择随机连接一个节点,以做到负载均衡。
3. Name Server 所有状态都从Broker 上报而来,本身不存储任何状态,所有数据均在内存中。
4.如果中途所有Name Server全部挂掉了,影响到了路由信息的更新,不会影响和Broker通信。
2.NameServer动态路由发现以及剔除机制
broker启动后会自动注册到NameServer上并通过固定的周期来请求NameServer更新心跳时间,NameServer通过定时任务来检测
哪个broker如果心跳超过 120s没有更新,就会把它剔除出去。
最主要的需要关注 RouteInfoManager对象中初始化的下列信息:
1.topicQueueTable: Topic消息队列路由信息,消息发送时候根据路由表信息进行负载均衡
2.brokerAddrTable: Broker基础信息,包含brokerName,所属集群名称,主备Broker地址
3.clusterAddrTable: Broker集群信息,存储集群中所有 Broker名称
4.brokerLiveTable : Broker 状态信息。NameServer每次收到心跳包时,会替换该信息
5.filterServerTable: Broker上的FilterServer列表,用于类模式消息过滤
RocketMQ基于订阅发布机制,一个Topic拥有多个消息队列,一个Broker为每个主题默认建立4个读队列,4个写队列。多个Broker组成一个集群,brokerName由相同的多台Broker组成Master-slave架构,brokerId为0的代表Master,大于0的表示Slave。
BrokerLiveInfo中的LastUpdateTimestamp存储上收到Broker心跳包的时间。
集群中主从结构示意图:
运行时数据结构图:
topicQueueTable
topicQueueTable{
"topic1"[
{
"brokerName":"broker-a",
"readQueueNums":4,
"writeQueueNums":4,
"perm":6 //读写权限,具体含义请参考permName后续会分析
"topicSynFlag":0// topic 同步标记,具体含义请参考TopicSysFlag
}
{
"brokerName":"broker-b",
"readQueueNums":4,
"writeQueueNums":4,
"perm":6 //读写权限,具体含义请参考permName后续会分析
"topicSynFlag":0// topic 同步标记,具体含义请参考TopicSysFlag
}
"topic......"[]
}
brokerAddrTable
brokerAddrTable:{
"broker-a":{
"cluster":"c1",
brokerName:"broker-a" ,
brokerAddrs:{
0:192.168.1.100:10000
1:192.168.1.101:10000
}
}
"broker-b":{
"cluster":"c1",
brokerName:"broker-b" ,
brokerAddrs:{
0:192.168.1.102:10000
1:192.168.1.103:10000
}}}}
上面的是TopicQueueTable ,brokerAddrTable运行内存结构
下面:
BrokerLiveTable
brokerLiveTable:{
"192.168.1.100:10000":{
"lastUpdateTimestamp": 1586579499035,
"dataVersion":"versionOb1" ,
"channel":channelObj,
"haServerAddr":"192.168.1.101:10000"
}
"192.168.1.101:10000":{
"lastUpdateTimestamp": 1586579499035,
"dataVersion":"versionOb1" ,
"channel":channelObj,
"haServerAddr":""
}
"192.168.1.102:10000":{
"lastUpdateTimestamp": 1586579499035,
"dataVersion":"versionOb1" ,
"channel":channelObj,
"haServerAddr":"192.168.1.103:10000"
}
"192.168.1.103:10000":{
"lastUpdateTimestamp": 1586579499035,
"dataVersion":"versionOb1" ,
"channel":channelObj,
"haServerAddr":""
}
}
clusterAddrTable
private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
clusterAddrTable:{
"c1":["broker-a","broker-b"]
}