文章目录
NameServer 的功能
- NameServer 是整个消息队列中的状态服务器,集群的各个组件通过它来了解全局的信息。各个角色的机器都要定期向NameServer 上报自己的状态,超时不上报的话, NameServer 会认为某个机器出故障不可用了,其他的组件会把这个机器从可用列表里移除
- NamServer 可以部署多个,相互之间独立,其他角色同时向多个NameServer机器上报状态信息,从而达到热备份的目的。
- NameServer 本身是无状态的, NameServer 中的Broker 、Topic 等状态信息不会持久存储,都是由各个角色定时上报并存储到内存中的( NameServer 支持配置参数的持久化, 一般用不到)。
集群状态的存储结构
org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager
类负责保存集群的状态信息
属性 | 描述 |
---|---|
HashMap<String/* topic */, List> topicQueueTable | topicQueueTable 这个结构的Key 是Topic 的名称,它存储了所有Topic的属性信息。Value 是个QueueData 队列, 队里的长度等于这个Topic数据存储的Master Broker 的个数, QueueData 里存储着Broker 的名称、读写queue 的数量、同步标识等 |
HashMap<String/* brokerName */, BrokerData> brokerAddrTable; | BrokerAddrTable以BrokerName 为索引,相同名称的Broker 可能存在多台机器, 一个Master 和多个Slave 。这个结构存储着一个BrokerName 对应的属性信息,包括所属的Cluster 名称, 一个Master Broker 和多个Slave Broker的地址信息 |
HashMap<String/* clusterName /, Set<String/ brokerName */>> clusterAddrTable; | ClusterAddrTable存储的是集群中C luster 的信息,结果很简单,就是一个Cluster 名称对应一个由BrokerName 组成的集合 |
HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable; | 这个结构和BrokerAddrTable 有关系,但是内容完全不同,这个结构的Key 是BrokerAddr ,也就是对应着一台机器, BrokerAddrTable 中的Key是BrokerName , 多个机器的BrokerName 可以相同。BrokerLiveTable存储的内容是这台Broker 机器的实时状态,包括上次更新状态的时间戳, NameServer 会定期检查这个时间戳,超时没有更新就认为这个Broker 无效了,将其从Broker 列表里清除 |
HashMap<String/* brokerAddr /, List/ Filter Server */> filterServerTable; | Filter Server 是过滤服务器,是RocketMQ 的一种服务端过滤方式,一个Broker 可以有一个或多个F ilter Server 。这个结构的Key 是Broker的地址, Value 是和这个Broker 关联的多个Filter Server 的地址 |
状态维护逻辑
- 其他角色会主动向NameServer上报状态,所以NameServer的主要逻辑在
org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor
类中,根据上报消息里的请求码做相应的处理,更新存储的对应信息。 - 连接断开的事件也会触发状态更新,具体逻辑在
org.apache.rocketmq.namesrv.routeinfo.BrokerHousekeepingService
类中
各个角色间的交互流程(Topic 的创建为例)
交互流程源码分析
-
创建Topic 的代码在
org.apache.rocketmq.tools.command.topic.UpdateTopicSubCommand
类中,创建Topic 的命令是updateTopic
,而在UpdateTopicSubCommand
类中的工作主要是TopicConfig
的组件工作。 -
在
updateTopic
的命令中,其中b 和c 参数比较重要, 而且他们俩只有一个会起作用( -b 优先), b 参
数指定在哪个Broker 上创建本Topic 的Message Queue , c 参数表示在这个Cluster 下面所有的Master Broker 上创建这个Topic 的Message Queue , 从而达.到高可用性的目的。具体的创建动作是通过发送命令触发的 -
真正的命令在
org.apache.rocketmq.client.impl.MQClientAPIImpl
类里面发送的,TopicConfig
会作为参数传入相应的方法中:
public void createTopic(final String addr, final String defaultTopic, final TopicConfig topicConfig, final long timeoutMillis) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
# 根据 TopicConfig 组装命令
CreateTopicRequestHeader requestHeader = new CreateTopicRequestHeader();
requestHeader.setTopic(topicConfig.getTopicName());
requestHeader.setDefaultTopic(defaultTopic);
requestHeader.setReadQueueNums(topicConfig.getReadQueueNums());
requestHeader.setWriteQueueNums(topicConfig.getWriteQueueNums());
requestHeader.setPerm(topicConfig.getPerm());
requestHeader.setTopicFilterType(topicConfig.getTopicFilterType().name());
requestHeader.setTopicSysFlag(topicConfig.getTopicSysFlag());
requestHeader.setOrder(topicConfig.isOrder());
# 创建命令
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_TOPIC, requestHeader);
# 发送命令
RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr),
request, timeoutMillis);
assert response != null;
switch (response.getCode()) {
case ResponseCode.SUCCESS: {
return;
}
default:
break;
}
throw new MQClientException(response.getCode(), response.getRemark());
}
- 创建Topic 的命令被发往对应的Broker , Broker 接到创建Topic 的请求后,执行具体的创建逻辑在
org.apache.rocketmq.broker.processor.AdminBrokerProcessor
类中。如updateAndCreateTopic
方法
- 接收传输的命令,更新本地的 topicConfig
- 方法最后一步是 向NameServer 发送注册信息,NameServer 完成创建Topic 的逻辑后,其他客户端才能发现新增的Topic
private synchronized RemotingCommand updateAndCreateTopic(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
final RemotingCommand response = RemotingCommand.createResponseCommand(null);
final CreateTopicRequestHeader requestHeader =
(CreateTopicRequestHeader) request.decodeCommandCustomHeader(CreateTopicRequestHeader.class);
log.info("updateAndCreateTopic called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()));
if (requestHeader.getTopic().equals(this.brokerController.getBrokerConfig().getBrokerClusterName())) {
String errorMsg = "the topic[" + requestHeader.getTopic() + "] is conflict with system reserved words.";
log.warn(errorMsg);
response.setCode(ResponseCode.SYSTEM_ERROR);
response.setRemark(errorMsg);
return response;
}
try {
response.setCode(ResponseCode.SUCCESS);
response.setOpaque(request.getOpaque());
response.markResponseType();
response.setRemark(null);
ctx.writeAndFlush(response);
} catch (Exception e) {
log.error("Failed to produce a proper response", e);
}
TopicConfig topicConfig = new TopicConfig(requestHeader.getTopic());
topicConfig.setReadQueueNums(requestHeader.getReadQueueNums());
topicConfig.setWriteQueueNums(requestHeader.getWriteQueueNums());
topicConfig.setTopicFilterType(requestHeader.getTopicFilterTypeEnum());
topicConfig.setPerm(requestHeader.getPerm());
topicConfig.setTopicSysFlag(requestHeader.getTopicSysFlag() == null ? 0 : requestHeader.getTopicSysFlag());
# 更新本地的 topicConfig
this.brokerController.getTopicConfigManager().updateTopicConfig(topicConfig);
# 向NameServer 发送注册信息
this.brokerController.registerIncrementBrokerData(topicConfig,this.brokerController.getTopicConfigManager().getDataVersion());
return null;
}
- NameServer 创建的操作在
org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager
的registerBroker()
方法中:
- 更新 Broker 信息
- 对每个Master 角色的Broker,创建一个QueueData 对象
- 如果是新建Topic,就是添加QueueData 对象;如果是修改Topic,就是把旧的QueueData 删除,加入新的QueueData 。
- 注意:写锁同步的实现,避免在代码中频繁的topic的创建销毁操作
public RegisterBrokerResult registerBroker(
final String clusterName,
final String brokerAddr,
final String brokerName,
final long brokerId,
final String haServerAddr,
final TopicConfigSerializeWrapper topicConfigWrapper,
final List<String> filterServerList,
final Channel channel) {
RegisterBrokerResult result = new RegisterBrokerResult();
try {
try {
this.lock.writeLock().lockInterruptibly();
# 更新 Broker 信息,配置的 brokerClusterName,获取 brokerName
Set<String> brokerNames = this.clusterAddrTable.get(clusterName);
if (null == brokerNames) {
brokerNames = new HashSet<String>();
this.clusterAddrTable.put(clusterName, brokerNames);
}
brokerNames.add(brokerName);
boolean registerFirst = false;
# BrokerData 内部维护 cluster、brokerName、 brokerAddrs
BrokerData brokerData = this.brokerAddrTable.get(brokerName);
if (null == brokerData) {
registerFirst = true;
brokerData = new BrokerData(clusterName, brokerName, new HashMap<Long, String>());
this.brokerAddrTable.put(brokerName, brokerData);
}
String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);
registerFirst = registerFirst || (null == oldAddr);
# 对每个Master 角色的Broker,创建一个QueueData 对象
if (null != topicConfigWrapper && MixAll.MASTER_ID == brokerId) {
if (this.isBrokerTopicConfigChanged(brokerAddr, topicConfigWrapper.getDataVersion()) || registerFirst) {
ConcurrentMap<String, TopicConfig> tcTable = topicConfigWrapper.getTopicConfigTable();
if (tcTable != null) {
for (Map.Entry<String, TopicConfig> entry : tcTable.entrySet()) {
this.createAndUpdateQueueData(brokerName, entry.getValue());
}
}
}
}
# 更新信息并且返回,主要是维护 brokerLiveTable 和 filterServerTable ,用于检查Broker的状态与过滤器
} finally {
this.lock.writeLock().unlock();
}
} catch (Exception e) {
log.error("registerBroker Exception", e);
}
return result;
}
为何不用ZooKeeper
RocketMQ 的架构设计决定了它不需要进行Master选举,用不到这些复杂的功能,只需要一个轻量级的元数据服务器就足够了
底层通信机制
Remoting 模块
- 类图如下:
- 最上层接
RemotingService
- void start();
- void shutdown();
- void registerRPCHook(RPCHook rpcHook);
-
RemotingClient
和RemotingServer
接口继承RemotingService
接口,并增加了自己特有的方法。NettyRemotingClient
和NettyRemotingServer
分别实现了RemotingCIient
和RemotingServer
,而且都继承了NettyRemotingAbstract
-
统一格式的自定义消息类:
RemotingCommand
,并且处理编解码的 -
比如NameServer 模块中
NamesrvController
的remotingServer
变量以及注册处理器处理器DefaultRequestProcessor
。NamesrvController
和DefaultRequestProcessor
部分代码如下:
# NamesrvController 部分代码
public class NamesrvController {
private RemotingServer remotingServer;
# 初始化 remotingServer
public boolean initialize() {
... ...
this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
this.registerProcessor();
... ...
}
# 注册处理器
private void registerProcessor() {
if (namesrvConfig.isClusterTest()) {
this.remotingServer.registerDefaultProcessor(new ClusterTestRequestProcessor(this, namesrvConfig.getProductEnvName()), this.remotingExecutor);
} else {
this.remotingServer.registerDefaultProcessor(new DefaultRequestProcessor(this), this.remotingExecutor);
}
}
}
# DefaultRequestProcessor 类部分代码,核心在于 processRequest()方法的逻辑处理
public class DefaultRequestProcessor implements NettyRequestProcessor {
# 拿到请求的 RemotingCommand ,然后处理
@Override
public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException {
# 业务处理
switch (request.getCode()) {
case RequestCode.PUT_KV_CONFIG:
return this.putKVConfig(ctx, request);
case RequestCode.GET_KV_CONFIG:
return this.getKVConfig(ctx, request);
case RequestCode.DELETE_KV_CONFIG:
return this.deleteKVConfig(ctx, request);
case RequestCode.QUERY_DATA_VERSION:
return queryBrokerTopicConfig(ctx, request);
省略 ... ...
default:
break;
}
return null;
}
}
协议设计和编解码
- 通信协议设计
- 第一部分是大端4 个字节整数,值等于第二、三、四部分长度的总和;
- 第二部分是大端4 个字节整数,值等于第三部分的长度;
- 第三部分是通过 Json 序列化的数据;
- 第四部分是通过应用自定义二进制序列化的数据。
- 信息的编码过程在
RemotingCommand
的encode()
方法中完成
public ByteBuffer encode() {
// 1> header length size
int length = 4;
// 2> header data length
byte[] headerData = this.headerEncode();
length += headerData.length;
// 3> body data length
if (this.body != null) {
length += body.length;
}
ByteBuffer result = ByteBuffer.allocate(4 + length);
// length
result.putInt(length);
// header length
result.put(markProtocolType(headerData.length, serializeTypeCurrentRPC));
// header data
result.put(headerData);
// body data;
if (this.body != null) {
result.put(this.body);
}
result.flip();
return result;
}
- 消息的编解码过程在
RemotingCommand
的decode()
方法中完成
public static RemotingCommand decode(final ByteBuffer byteBuffer) {
int length = byteBuffer.limit();
int oriHeaderLen = byteBuffer.getInt();
int headerLength = getHeaderLength(oriHeaderLen);
byte[] headerData = new byte[headerLength];
byteBuffer.get(headerData);
RemotingCommand cmd = headerDecode(headerData, getProtocolType(oriHeaderLen));
int bodyLength = length - 4 - headerLength;
byte[] bodyData = null;
if (bodyLength > 0) {
bodyData = new byte[bodyLength];
byteBuffer.get(bodyData);
}
cmd.body = bodyData;
return cmd;
}
Netty 库
RocketMQ 是基于Netty 库来完成 RemotingServer
和 RemotingClient
具体的通信实现的。主要在于 NettyRemotingServer
和NettyRemotingClient
两个类