一、NameServer介绍
NameServer 是专为 RocketMQ 设计的轻量级名称服务,具有简单、可集群横向扩展、无状态,节点之间互不通信等特点。整个Rocketmq集群的工作原理如下图所示:
可以看到,RocketMQ架构上主要分为四部分, Broker、Producer、Consumer、NameServer,其他三个都会与NameServer进行通信:
-
NameServer: 一个简单的Topic路由注册中心,其角色类似Dubbo中的zookeeper,支持Broker的动态注册与发现。
主要包括两个功能:
-
Broker管理:NameServer接受Broker的注册请求,处理请求数据作为路由信息的基础数据。对broker进行心跳检测机制,检测是否还存活(120s);
-
topic路由信息管理:每个NameServer都保存整个Broker集群的路由信息,用于Producer和Conumser查询的路由信息,从而进行消息的投递和消费。
-
Producer: 消息发布的角色,可集群部署。通过NameServer集群获得Topic的路由信息,包括Topic下面有哪些Queue,这些Queue分布在哪些Broker上等。(Producer只会将消息发送到Master节点,因此只需与Master节点建立连接)。
-
Consumer: 消息消费的角色,可集群部署。通过NameServer集群获得Topic的路由信息,连接到对应的Broker上拉取和消费消息。(Master和Slave都可以拉取消息,因此Consumer会与Master和Slave都建立连接)。
-
Broker: 主要负责消息的存储、投递和查询以及服务高可用保证。
二、为什么要使用NameServer?
目前可以作为服务发现组件有很多,如etcd、consul、zookeeper、nacos等:
那么为什么rocketmq选择自己开发一个NameServer,而不是使用这些开源组件呢?原因如下:
-
RocketMQ的架构设计决定了只需一个轻量级的元数据服务器,只需保持最终一致,而不需要Zookeeper的强一致性解决方案,无需再依赖另一个中间件,从而减少整体维护成本。
-
NameServer互相独立,彼此没有通信关系,由于Broker向每个NameServer注册自己的路由信息,所以每个NameServer都保存一份完整的路由信息,单台NameServer挂掉,Broker仍然可以向其它NameServer同步路由信息,不影响其他NameServer,所以Producer,Consumer仍然可以动态感知Broker的路由的信息。
三、NameServer 内部解密
NameServer的路由数据来源是broker注册提供,然后内部加工处理,而路由的数据的使用者是producer和consumer,接下来将着重解析NameServer的路由数据结构,路由注册/查询、broker动态等检测核心逻辑(源码)。
3.1 路由数据结构
RouteInfoManager是NameServer核心逻辑类,其代码作用就是维护路由信息管理,提供路由注册/查询等核心功能,由于路由信息都是保存在NameServer应用内存里,其本质就是维护HashMap,而为了防止并发操作,添加了ReentrantReadWriteLock读写锁,简单代码描述如下:
public class RouteInfoManager {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
// NameServer 与 Broker 空闲时长,默认2分钟,在2分钟内 Nameserver 没有收到 Broker 的心跳包,则关闭该连接。
private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2;
//读写锁
private final ReadWriteLock lock = new ReentrantReadWriteLock();
// Topic,以及对应的队列信息 --消息发送时根据路由表进行负载均衡 。
private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
// 以BrokerName为单位的Broker集合(Broker基础信息,包含 brokerName、所属集群名称、主备 Broker地址。)
private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
// 集群以及属于该进群的Broker列表(根据一个集群名,获得对应的一组BrokerName的列表)
private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
// 存活的Broker地址列表 (NameServer 每次收到心跳包时会 替换该信息 )
private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
// Broker对应的Filter Server列表-消费端拉取消息用到
private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
...省略...
}
可以通过以下类图更清楚查看其关系:
QueueData 属性解析:
/**
* 队列信息
*/
public class QueueData implements Comparable<QueueData> {
// 队列所属的Broker名称
private String brokerName;
// 读队列数量 默认:16
private int readQueueNums;
// 写队列数量 默认:16
private int writeQueueNums;
//todo Topic的读写权限(2是写 4是读 6是读写)
private int perm;
/** 同步复制还是异步复制--对应TopicConfig.topicSysFlag
* {@link org.apache.rocketmq.common.sysflag.TopicSysFlag}
*/
private int topicSynFlag;
...省略...
}
map: topicQueueTable 数据格式demo(json):
{
"TopicTest":[
{
"brokerName":"broker-a",
"perm":6,
"readQueueNums":4,
"topicSynFlag":0,
"writeQueueNums":4
}
]
}
BrokerData 属性解析:
/**
* broker的数据:Master与Slave 的对应关系通过指定相同的BrokerName,不同的BrokerId来定义,BrokerId为0 表示Master,非0表示Slave。
*/
public class BrokerData implements Comparable<BrokerData> {
// broker所属集群
private String cluster;
// brokerName
private String brokerName;
// 同一个brokerName下可以有一个Master和多个Slave,所以brokerAddrs是一个集合
// brokerld=O表示 Master,大于 O表示从 Slave
private HashMap<Long/* brokerId */, String/* broker address */> brokerAddrs;
// 用于查找broker地址
private final Random random = new Random();
...省略...
}
map: brokerAddrTable 数据格式demo(json):
{
"broker-a":{
"brokerAddrs":{
"0":"172.16.62.75:10911"
},
"brokerName":"broker-a",
"cluster":"DefaultCluster"
}
}
BrokerLiveInfo 属性解析:
/**
* 存放存活的Broker信息,当前存活的 Broker,该信息不是实时的,NameServer 每10S扫描一次所有的 broker,根据心跳包的时间得知 broker的状态,
* 该机制也是导致当一个 Broker 进程假死后,消息生产者无法立即感知,可能继续向其发送消息,导致失败(非高可用)
*/
class BrokerLiveInfo {
//最后一次更新时间
private long lastUpdateTimestamp;
//版本号信息
private DataVersion dataVersion;
//Netty的Channel
private Channel channel;
//HA Broker的地址 是Slave从Master拉取数据时链接的地址,由brokerIp2+HA端口构成
private String haServerAddr;
...省略...
}
map: brokerLiveTable 数据格式demo(json):
{
"172.16.62.75:10911":{
"channel":{
"active":true,
"inputShutdown":false,
"open":true,
"outputShutdown":false,
"registered":true,
"writable":true
},
"dataVersion":{
"counter":2,
"timestamp":1630907813571
},
"haServerAddr":"172.16.62.75:10912",
"lastUpdateTimestamp":1630907814074
}
}
brokerAddrTable -Map 数据格式demo(json)
{"DefaultCluster":["broker-a"]}
从RouteInfoManager维护的HashMap数据结构和QueueData、BrokerData、BrokerLiveInfo类属性得知,NameServer维护的