Broker注册

在Broker的启动函数中,添加了定时向NameServer进行注册的任务,在启动后延迟10秒向NameServer进行注册,之后定时发送心跳包,关于发送周期,首先从Broker配置的周期与60000毫秒中选出最小的那个值,然后再与10000毫秒对比,选出最大的那个,所以最长10秒钟执行一次心跳发送

public class BrokerController {
public void start() throws Exception {
// ... // 定时向NameServer进行注册
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override
public void run() {
try {
// 向NameServer注册
BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister());
} catch (Throwable e) {
log.error("registerBrokerAll Exception", e);
}
}
}, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS); // ...
}
}

具体的注册逻辑是在BrokerOuterAPIregisterBrokerAll方法中实现的:

public class BrokerController {

    public synchronized void registerBrokerAll(final boolean checkOrderConfig, boolean oneway, boolean forceRegister) {
// ...
if (forceRegister || needRegister(this.brokerConfig.getBrokerClusterName(),
this.getBrokerAddr(),
this.brokerConfig.getBrokerName(),
this.brokerConfig.getBrokerId(),
this.brokerConfig.getRegisterBrokerTimeoutMills())) {
// 调用doRegisterBrokerAll
doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper);
}
} // 注册Broker
private void doRegisterBrokerAll(boolean checkOrderConfig, boolean oneway,
TopicConfigSerializeWrapper topicConfigWrapper) {
// 调用BrokerOuterAPI的registerBrokerAll进行注册
List<RegisterBrokerResult> registerBrokerResultList = this.brokerOuterAPI.registerBrokerAll(
this.brokerConfig.getBrokerClusterName(),
this.getBrokerAddr(),
this.brokerConfig.getBrokerName(),
this.brokerConfig.getBrokerId(),
this.getHAServerAddr(),
topicConfigWrapper,
this.filterServerManager.buildNewFilterServerList(),
oneway,
this.brokerConfig.getRegisterBrokerTimeoutMills(),
this.brokerConfig.isCompressedRegister()); // ...
}
}

发送注册请求

registerBrokerAll方法的处理逻辑如下:

  1. 封装请求头,设置当前Broker的IP、Name等信息
  2. 封装请求体,主要是设置主题配置信息和消息过滤服务器列表并对请求体的内容计算CRC32校验和,NameServer收到请求时会对数据进行校验
  3. 遍历所有的NameServer服务列表,对每一个NameServer进行注册,为了提升效率注册任务是放在线程池中开启多线程执行的,所以使用了CountDownLatch,调用await方法等待所有的NameServer都注册完毕
  4. 底层使用Netty进行网络通信,向NameServer发送注册请求,请求对应的类型为REGISTER_BROKER
  5. 处理请求响应数据,将结果封装到RegisterBrokerResult中返回
public class BrokerOuterAPI {

   public List<RegisterBrokerResult> registerBrokerAll(
final String clusterName,
final String brokerAddr,
final String brokerName,
final long brokerId,
final String haServerAddr,
final TopicConfigSerializeWrapper topicConfigWrapper,
final List<String> filterServerList,
final boolean oneway,
final int timeoutMills,
final boolean compressed) {
// 创建list,保存注册结果
final List<RegisterBrokerResult> registerBrokerResultList = new CopyOnWriteArrayList<>();
List<String> nameServerAddressList = this.remotingClient.getNameServerAddressList();
if (nameServerAddressList != null && nameServerAddressList.size() > 0) {
// 封装请求头
final RegisterBrokerRequestHeader requestHeader = new RegisterBrokerRequestHeader();
requestHeader.setBrokerAddr(brokerAddr);// 设置Broker地址
requestHeader.setBrokerId(brokerId); // 设置Broker Id
requestHeader.setBrokerName(brokerName); // 设置Broker Name
requestHeader.setClusterName(clusterName);// 设置集群名称
requestHeader.setHaServerAddr(haServerAddr);
requestHeader.setCompressed(compressed);
// 设置请求体
RegisterBrokerBody requestBody = new RegisterBrokerBody();
requestBody.setTopicConfigSerializeWrapper(topicConfigWrapper); // 设置主题配置
requestBody.setFilterServerList(filterServerList);// 设置消息过滤服务器列表
final byte[] body = requestBody.encode(compressed);
// 计算CRC32
final int bodyCrc32 = UtilAll.crc32(body);
requestHeader.setBodyCrc32(bodyCrc32);
final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size());
// 遍历NameServer服务列表
for (final String namesrvAddr : nameServerAddressList) {
brokerOuterExecutor.execute(new Runnable() {
@Override
public void run() {
try {
// 进行注册,底层通过Netty进行网络通信
RegisterBrokerResult result = registerBroker(namesrvAddr, oneway, timeoutMills, requestHeader, body);
if (result != null) {
// 将注册结果加入到列表
registerBrokerResultList.add(result);
} log.info("register broker[{}]to name server {} OK", brokerId, namesrvAddr);
} catch (Exception e) {
log.warn("registerBroker Exception, {}", namesrvAddr, e);
} finally {
countDownLatch.countDown();
}
}
});
} try {
// 等待所有的NameServer都注册完毕
countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
}
} return registerBrokerResultList;
} // 通过Netty发送注册请求
private RegisterBrokerResult registerBroker(
final String namesrvAddr,
final boolean oneway,
final int timeoutMills,
final RegisterBrokerRequestHeader requestHeader,
final byte[] body
) throws RemotingCommandException, MQBrokerException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
InterruptedException {
// 创建请求命令,这里发送的请求是REGISTER_BROKER类型
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.REGISTER_BROKER, requestHeader);
// 设置请求体
request.setBody(body); if (oneway) {
try {
this.remotingClient.invokeOneway(namesrvAddr, request, timeoutMills);
} catch (RemotingTooMuchRequestException e) {
// Ignore
}
return null;
}
// 发送请求
RemotingCommand response = this.remotingClient.invokeSync(namesrvAddr, request, timeoutMills);
assert response != null;
switch (response.getCode()) {
case ResponseCode.SUCCESS: {
// 获取注册结果
RegisterBrokerResponseHeader responseHeader =
(RegisterBrokerResponseHeader) response.decodeCommandCustomHeader(RegisterBrokerResponseHeader.class);
RegisterBrokerResult result = new RegisterBrokerResult();
// 设置Master Broker的地址
result.setMasterAddr(responseHeader.getMasterAddr());
result.setHaServerAddr(responseHeader.getHaServerAddr());
if (response.getBody() != null) {
result.setKvTable(KVTable.decode(response.getBody(), KVTable.class));
}
return result;
}
default:
break;
} throw new MQBrokerException(response.getCode(), response.getRemark(), requestHeader == null ? null : requestHeader.getBrokerAddr());
}
}

NameServer处理

NameServer在启动时注册了一个请求处理器DefaultRequestProcessor,当收到其他服务发送的请求时,会进入到processRequest方法中,通过Switch CASE对请求类型判断,进行不同的处理。

上文可知Broker注册时发送的请求类型为REGISTER_BROKER,对应的处理方法为registerBroker

public class DefaultRequestProcessor extends AsyncNettyRequestProcessor implements NettyRequestProcessor {

    // 处理请求
@Override
public RemotingCommand processRequest(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException { if (ctx != null) {
log.debug("receive request, {} {} {}",
request.getCode(),
RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
request);
}
// 判断请求类型
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);
case RequestCode.REGISTER_BROKER: // 如果是Broker注册请求
Version brokerVersion = MQVersion.value2Version(request.getVersion());
if (brokerVersion.ordinal() >= MQVersion.Version.V3_0_11.ordinal()) {
return this.registerBrokerWithFilterServer(ctx, request);
} else {
// 处理Broker的注册请求
return this.registerBroker(ctx, request);
}
case RequestCode.UNREGISTER_BROKER:
return this.unregisterBroker(ctx, request);
// ... default:
break;
}
return null;
}
}

Broker注册请求处理

对Broker请求注册的处理在registerBroker方法中:

  1. 对请求数据进行CRC32校验,检查数据的合法性
  2. 通过RouteInfoManager的registerBroker方法对Broker进行注册
  3. 将注册结果设置到响应数据中,返回给Broker
  public RemotingCommand registerBroker(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
// 创建响应命令对象
final RemotingCommand response = RemotingCommand.createResponseCommand(RegisterBrokerResponseHeader.class);
final RegisterBrokerResponseHeader responseHeader = (RegisterBrokerResponseHeader) response.readCustomHeader();
// 获取请求头
final RegisterBrokerRequestHeader requestHeader =
(RegisterBrokerRequestHeader) request.decodeCommandCustomHeader(RegisterBrokerRequestHeader.class);
// 进行crc32校验
if (!checksum(ctx, request, requestHeader)) {
response.setCode(ResponseCode.SYSTEM_ERROR);
response.setRemark("crc32 not match");
return response;
} TopicConfigSerializeWrapper topicConfigWrapper;
// 如果请求体不为空
if (request.getBody() != null) {
// 获取请求中的主题配置
topicConfigWrapper = TopicConfigSerializeWrapper.decode(request.getBody(), TopicConfigSerializeWrapper.class);
} else {
topicConfigWrapper = new TopicConfigSerializeWrapper();
topicConfigWrapper.getDataVersion().setCounter(new AtomicLong(0));
topicConfigWrapper.getDataVersion().setTimestamp(0);
} // 进行注册
RegisterBrokerResult result = this.namesrvController.getRouteInfoManager().registerBroker(
requestHeader.getClusterName(),
requestHeader.getBrokerAddr(),
requestHeader.getBrokerName(),
requestHeader.getBrokerId(),
requestHeader.getHaServerAddr(),
topicConfigWrapper,
null,
ctx.channel()
);
responseHeader.setHaServerAddr(result.getHaServerAddr());
responseHeader.setMasterAddr(result.getMasterAddr()); byte[] jsonValue = this.namesrvController.getKvConfigManager().getKVListByNamespace(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG);
// 设置响应内容
response.setBody(jsonValue);
response.setCode(ResponseCode.SUCCESS);
response.setRemark(null);
// 返回响应
return response;
}

注册处理

注册处理的主要逻辑在RouteInfoManager中,首先来看三个与Broker信息相关的Map集合。

brokerAddrTable

brokerAddrTable是一个Map集合,主要存储Broker相关信息,key为Broker名称,value为BrokerData:

private final HashMap<String, BrokerData> brokerAddrTable; // key为Broker名称,value为BrokerData

BrokerData对象中记录了Broker所属集群名称、Broker名称以及Broker地址集合,其中KEY为Broker ID, value为Broker地址,因为可以搭建主从模式的Broker集群,此时BrokerName一致但是Broker ID和地址不同,所以使用Map记录Broker ID和地址的对应关系:

public class BrokerData implements Comparable<BrokerData> {
private String cluster; // 集群名称
private String brokerName; // Broker名称
private HashMap<Long, String> brokerAddrs; // Broker地址集合,KEY为Broker ID, value为Broker 地址
}

clusterAddrTable

clusterAddrTable主要存储集群与Broker的对应关系,key为集群名称,value为该集群下的所有Broker Name集合:

private final HashMap<String, Set<String> clusterAddrTable; // key为集群名称,value为该集群下的所有Broker Name集合

brokerLiveTable

brokerLiveTable主要存储Broker的心跳发送信息,key为Broker地址,value为Broker发送心跳信息记录对象BrokerLiveInfo:

private final HashMap<String, BrokerLiveInfo> brokerLiveTable; // key为Broker地址,value为Broker发送心跳信息记录BrokerLiveInfo

BrokerLiveInfo中记录了NameServer收到Broker发送心跳包的时间、数据版本、与Broker网络通信的Channel以及HA Server地址:

class BrokerLiveInfo {
private long lastUpdateTimestamp; // 收到心跳发送的时间
private DataVersion dataVersion; // 版本
private Channel channel; // 通信Channel
private String haServerAddr; // HA Server地址,也就是所属Master Broker的地址
}

注册Broker

由于一个NameServer可能同时收到多个Broker的注册请求,所以在处理注册请求时使用了读写锁,在进行修改的时候添加写锁,处理逻辑如下:

  1. 根据集群名称从clusterAddrTable中查找对应的BrokerName集合,如果查找到,将当前的BrokerName加入到集合中,如果未查找到,新建集合将BrokerName加入到集合中,并添加到clusterAddrTable中。
  2. 根据Broker名称从brokerAddrTable获取BrokerData对象,如果获取为空,新建BrokerData对象并加入到brokerAddrTable
  3. 从BrokerData中获取同一BrokerName的所有地址信息,它是一个map集合,key为Broker ID, value为Broker地址
  4. 遍历Broker的地址信息集合,如果地址一致但是Broker ID不一致,删除旧的信息,保证同一个地址在map集合中只能有一条数据
  5. 将Broker加入到brokerLiveTable中,并记录收到注册请求的时间戳,在进行心跳检测的时候需要根据这个时间戳来判断是否在规定时间内未收到Broker的请求
  6. 如果发送请求的Broker不是Master,需要获取其所属的Master地址设置到注册结果中返回给Broker
public class RouteInfoManager {
// 读写锁
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
private final HashMap<String, BrokerData> brokerAddrTable; // key为Broker名称 value为对应的Broker相关数据对象BrokerData
private final HashMap<String, Set<String> clusterAddrTable; // key为集群名称,value为该集群下的所有Broker Name集合
private final HashMap<String, BrokerLiveInfo> brokerLiveTable; // key为Broker地址,value为Broker发送心跳信息记录BrokerLiveInfo
private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable; /**
* Broker注册
* @param clusterName 集群名称
* @param brokerAddr Broker地址
* @param brokerName Broker名称
* @param brokerId Broker ID
* @param haServerAddr 所属Master Broker的地址
* @param topicConfigWrapper 主题配置
* @param filterServerList 服务过滤列表
* @param channel 通信Channel
* @return
*/
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 Name集合
Set<String> brokerNames = this.clusterAddrTable.get(clusterName);
// 如果集合为空
if (null == brokerNames) {
// 新建Broker集合
brokerNames = new HashSet<String>();
// 加入clusterAddrTable中
this.clusterAddrTable.put(clusterName, brokerNames);
}
// 加入到Broker Name集合
brokerNames.add(brokerName); boolean registerFirst = false;
// 根据Broker名称从brokerAddrTable获取Broker信息
BrokerData brokerData = this.brokerAddrTable.get(brokerName);
// 如果获取为空
if (null == brokerData) {
registerFirst = true;
// 新建BrokerData对象
brokerData = new BrokerData(clusterName, brokerName, new HashMap<Long, String>());
// 加入到brokerAddrTable
this.brokerAddrTable.put(brokerName, brokerData);
}
// 获取Broker地址集合
Map<Long, String> brokerAddrsMap = brokerData.getBrokerAddrs();
// 遍历Broker地址集合
Iterator<Entry<Long, String>> it = brokerAddrsMap.entrySet().iterator();
while (it.hasNext()) {
// 获取Broker地址
Entry<Long, String> item = it.next();
// 如果地址一致但是Broker ID不一致,删除旧的信息,保证同一个地址在map集合中只能有一条数据
if (null != brokerAddr && brokerAddr.equals(item.getValue()) && brokerId != item.getKey()) {
it.remove();
}
}
// 将新的地址信息添加到Broker地址集合中,key为Broker ID, value为Broker地址
String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);
// 是否首次注册
registerFirst = registerFirst || (null == oldAddr);
// 处理主题配置
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());
}
}
}
}
// 将Broker加入到brokerLiveTable中,key为Broker地址
BrokerLiveInfo prevBrokerLiveInfo = this.brokerLiveTable.put(brokerAddr,
new BrokerLiveInfo(
System.currentTimeMillis(), // 记录收到心跳的时间
topicConfigWrapper.getDataVersion(),
channel,
haServerAddr));
if (null == prevBrokerLiveInfo) {
log.info("new broker registered, {} HAServer: {}", brokerAddr, haServerAddr);
}
// 处理服务过滤列表
if (filterServerList != null) {
if (filterServerList.isEmpty()) {
this.filterServerTable.remove(brokerAddr);
} else {
this.filterServerTable.put(brokerAddr, filterServerList);
}
}
// 如果发送请求的broker不是Master
if (MixAll.MASTER_ID != brokerId) {
// 获取Master Broker地址
String masterAddr = brokerData.getBrokerAddrs().get(MixAll.MASTER_ID);
if (masterAddr != null) {
BrokerLiveInfo brokerLiveInfo = this.brokerLiveTable.get(masterAddr);
if (brokerLiveInfo != null) {
// 设置HA Server地址
result.setHaServerAddr(brokerLiveInfo.getHaServerAddr());
// 将Broker集群中的Master地址设置到注册结果
result.setMasterAddr(masterAddr);
}
}
}
} finally {
this.lock.writeLock().unlock();
}
} catch (Exception e) {
log.error("registerBroker Exception", e);
} return result;
}
}

心跳检测

NameServer在启动时注册了定时检查处于不活跃状态Broker的任务:

   public boolean initialize() {

        // 定时扫描下线的Broker
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override
public void run() {
// 扫描下线的Broker
NamesrvController.this.routeInfoManager.scanNotActiveBroker();
}
}, 5, 10, TimeUnit.SECONDS);
}

brokerLiveTable保存了当前NameServer收到的心跳数据,遍历brokerLiveTable,获取每一个Broker最近一次发送心跳的时间,如果上一次发送心跳的时间 + 过期时间(120s) 小于 当前时间,也就是超过120s没有收到某个Broker的心跳包,则认为此Broker已下线,需要关闭该Broker的Channel,将Broker移除:

public class RouteInfoManager {
// Broker Channel过期时间:120s
private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2; public void scanNotActiveBroker() {
Iterator<Entry<String, BrokerLiveInfo>> it = this.brokerLiveTable.entrySet().iterator();
// 遍历所有的Broker
while (it.hasNext()) {
Entry<String, BrokerLiveInfo> next = it.next();
// 获取上一次发送心跳的时间
long last = next.getValue().getLastUpdateTimestamp();
// 如果上一次发送心跳的时间 + 过期时间 小于 当前时间
if ((last + BROKER_CHANNEL_EXPIRED_TIME) < System.currentTimeMillis()) {
// 关闭Channel
RemotingUtil.closeChannel(next.getValue().getChannel());
// 从brokerLiveTable中移除Broker
it.remove();
log.warn("The broker channel expired, {} {}ms", next.getKey(), BROKER_CHANNEL_EXPIRED_TIME);
// 清除Broker的相关信息
this.onChannelDestroy(next.getKey(), next.getValue().getChannel());
}
}
}
}

路由剔除

onChannelDestroy中主要是做一些清除处理:

  1. 如果channel不为空,从brokerLiveTable中根据channel查找Broker的地址信息,如果查找到,记录Broker的地址信息到brokerAddrFound,如果未查找到,就使用参数中传入的remoteAddr
  2. 根据Broker地址从brokerLiveTable中移除相关信息
  3. 根据Broker地址从filterServerTable中移除相关信息
  4. 遍历Broker地址记录表brokerAddrTable,根据地址查找BrokerData中记录的地址信息集合,如果地址和需要查找的一致,从BrokerData中删除,并记录Broker名称,这一步主要是清理brokerAddrTable中的数据
  5. 遍历clusterAddrTable,根据上一步记录的Broker名称从集群对应的BrokerName集合中查找,并清除相关数据,这一步主要是清理clusterAddrTable中的数据
  6. 遍历topicQueueTable,清除当前Broker的主题信息
   /**
* 销毁处理
* @param remoteAddr Broker地址
* @param channel 通信Channel
*/
public void onChannelDestroy(String remoteAddr, Channel channel) {
String brokerAddrFound = null;
// 如果channel不为空
if (channel != null) {
try {
try {
// 添加读锁,因为这里是查找不做修改所以添加了读锁
this.lock.readLock().lockInterruptibly();
Iterator<Entry<String, BrokerLiveInfo>> itBrokerLiveTable =
this.brokerLiveTable.entrySet().iterator();
// 从brokerLiveTable中查找
while (itBrokerLiveTable.hasNext()) {
Entry<String, BrokerLiveInfo> entry = itBrokerLiveTable.next();
// 根据Channel查找Broker
if (entry.getValue().getChannel() == channel) {
// 如果查找到,记录Broker的地址
brokerAddrFound = entry.getKey();
break;
}
}
} finally {
this.lock.readLock().unlock();
}
} catch (Exception e) {
log.error("onChannelDestroy Exception", e);
}
}
// 如果未查找到,就使用参数中传入的Broker
if (null == brokerAddrFound) {
brokerAddrFound = remoteAddr;
} else {
log.info("the broker's channel destroyed, {}, clean it's data structure at once", brokerAddrFound);
} if (brokerAddrFound != null && brokerAddrFound.length() > 0) { try {
try {
// 添加写锁
this.lock.writeLock().lockInterruptibly();
// 从brokerLiveTable中移除Broker
this.brokerLiveTable.remove(brokerAddrFound);
// 从filterServerTable中移除Broker
this.filterServerTable.remove(brokerAddrFound);
String brokerNameFound = null;
boolean removeBrokerName = false;
Iterator<Entry<String, BrokerData>> itBrokerAddrTable =
this.brokerAddrTable.entrySet().iterator();
// 遍历Broker地址记录表brokerAddrTable
while (itBrokerAddrTable.hasNext() && (null == brokerNameFound)) {
// BrokerData
BrokerData brokerData = itBrokerAddrTable.next().getValue();
// 从BrokerData中获取同一BrokerName的所有地址集合
Iterator<Entry<Long, String>> it = brokerData.getBrokerAddrs().entrySet().iterator();
while (it.hasNext()) {
Entry<Long, String> entry = it.next();
// 获取Broker ID
Long brokerId = entry.getKey();
// 获取Broker地址
String brokerAddr = entry.getValue();
// 如果地址和需要查找的一致
if (brokerAddr.equals(brokerAddrFound)) {
// 记录BrokerName
brokerNameFound = brokerData.getBrokerName();
// 从brokerData中移除
it.remove();
log.info("remove brokerAddr[{}, {}] from brokerAddrTable, because channel destroyed",
brokerId, brokerAddr);
break;
}
}
// 如果brokerData记录的地址集合为空
if (brokerData.getBrokerAddrs().isEmpty()) {
removeBrokerName = true;
// 从brokerAddrTable中移除,因为brokerData中未记录任何Broker的地址,需要清除
itBrokerAddrTable.remove();
log.info("remove brokerName[{}] from brokerAddrTable, because channel destroyed",
brokerData.getBrokerName());
}
}
// 从clusterAddrTable清除对应的Broker信息
if (brokerNameFound != null && removeBrokerName) {
// 遍历clusterAddrTable
Iterator<Entry<String, Set<String>>> it = this.clusterAddrTable.entrySet().iterator();
while (it.hasNext()) {
Entry<String, Set<String>> entry = it.next();
String clusterName = entry.getKey();
Set<String> brokerNames = entry.getValue();
// 从brokerNames集合中移除
boolean removed = brokerNames.remove(brokerNameFound);
if (removed) {
log.info("remove brokerName[{}], clusterName[{}] from clusterAddrTable, because channel destroyed",
brokerNameFound, clusterName);
// 如果移除后集合为空
if (brokerNames.isEmpty()) {
log.info("remove the clusterName[{}] from clusterAddrTable, because channel destroyed and no broker in this cluster",
clusterName);
// 从clusterAddrTable中移除当前集群名称
it.remove();
} break;
}
}
}
// 如果移除的BrokerName不为空
if (removeBrokerName) {
// 遍历主题队列,从topicQueueTable中清除当前Broker的主题信息
Iterator<Entry<String, List<QueueData>>> itTopicQueueTable =
this.topicQueueTable.entrySet().iterator();
while (itTopicQueueTable.hasNext()) {
Entry<String, List<QueueData>> entry = itTopicQueueTable.next();
String topic = entry.getKey();
List<QueueData> queueDataList = entry.getValue();
Iterator<QueueData> itQueueData = queueDataList.iterator();
while (itQueueData.hasNext()) {
QueueData queueData = itQueueData.next();
// 根据BrokerName查找
if (queueData.getBrokerName().equals(brokerNameFound)) {
// 移除
itQueueData.remove();
log.info("remove topic[{} {}], from topicQueueTable, because channel destroyed",
topic, queueData);
}
}
// 如果数据为空
if (queueDataList.isEmpty()) {
// 从itTopicQueueTable中移除
itTopicQueueTable.remove();
log.info("remove topic[{}] all queue, from topicQueueTable, because channel destroyed",
topic);
}
}
}
} finally {
// 释放锁
this.lock.writeLock().unlock();
}
} catch (Exception e) {
log.error("onChannelDestroy Exception", e);
}
}
}

参考

丁威、周继锋《RocketMQ技术内幕》

RocketMQ版本:4.9.3

【RocketMQ】Broker服务注册的更多相关文章

  1. 🏆【Alibaba中间件技术系列】「RocketMQ技术专题」Broker服务端自动创建topic的原理分析和问题要点指南

    前提背景 使用RocketMQ进行发消息时,一般我们是必须要指定topic,此外topic必须要提前建立,但是topic的创建(自动或者手动方式)的设置有一个开关autoCreateTopicEnab ...

  2. Spring Cloud Alibaba系列(一)nacos作为服务注册中心

    Spring Cloud Alibaba各组件版本关系 Spring Cloud Alibaba Version Sentinel Version Nacos Version RocketMQ Ver ...

  3. 记一次 RocketMQ broker 因内存不足导致的启动失败

    原创:西狩 编写日期 / 修订日期:2020-01-12 / 2020-01-12 版权声明:本文为博主原创文章,遵循 CC BY-SA-4.0 版权协议,转载请附上原文出处链接和本声明. 背景 该小 ...

  4. Consul 服务注册与服务发现

    上一篇:Mac OS.Ubuntu 安装及使用 Consul 1. 服务注册 对 Consul 进行服务注册之前,需要先部署一个服务站点,我们可以使用 ASP.NET Core 创建 Web 应用程序 ...

  5. 分布式服务注册和发现consul 简要介绍

    Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其他分布式服务注册与发现的方案,Consul的方案更"一站式",内置了服务注册与发现框 架 ...

  6. Spring-cloud & Netflix 源码解析:Eureka 服务注册发现接口 ****

    http://www.idouba.net/spring-cloud-source-eureka-client-api/?utm_source=tuicool&utm_medium=refer ...

  7. springcloud(第三篇)springcloud eureka 服务注册与发现 *****

    http://blog.csdn.net/liaokailin/article/details/51314001 ******************************************* ...

  8. Dubbo_异常_服务注册运行正常但是Dubbo-Admin看不到服务(亲测可用)

    一.背景: 1.Dubbo服务正常注册到ZooKeeper 2.客户端调用Dubbo服务正常 二.原因: Dubbo-Admin未配置分组信息 三.解决步骤: 1.dubbo.properties添加 ...

  9. SpringCloud+Consul 服务注册与服务发现

    SpringCloud+Consul 服务注册与服务发现 1. 服务注册: 在Spring.factories有一段: # Discovery Client Configuration org.spr ...

随机推荐

  1. 实战-DRF快速写接口(认证权限频率)

    实战-DRF快速写接口 开发环境 Python3.6 Pycharm专业版2021.2.3 Sqlite3 Django 2.2 djangorestframework3.13 测试工具 Postma ...

  2. SpringMVC-注解@RequestParam

    当请求的参数名称与Controller的业务方法不一致时,就需要通过@RequestParam注解进行显示的绑定 1.value:映射参数 @RequestMapping("/report1 ...

  3. 微信小程序--设置和获取剪切板内容

    设置 wx.setClipboardData  // 复制功能 获取 wx.getClipboardData // 粘贴功能     let _this = this     wx.setClipbo ...

  4. 2021.07.26 P1011 车站(斐波那契数列)

    2021.07.26 P1011 车站(斐波那契数列) [P1011 NOIP1998 提高组] 车站 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 重点: 1.改变形式的斐波那契 ...

  5. Go 语言接口及使用接口实现链表插入

    @ 目录 1. 接口定义 1.1 空接口 1.2 实现单一接口 1.3 接口多方法实现 2. 多态 2.1 为不同数据类型的实体提供统一的接口 2.2 多接口的实现 3. 系统接口调用 4. 接口嵌套 ...

  6. Python爬虫__微博某个话题的内容数据

    1 # -*- coding: utf-8 -*- 2 # @Time : 2020/8/18 15:39 3 # @Author : Chunfang 4 # @Email : 3470959534 ...

  7. golang bufio解析

    golang bufio 当进行频繁地对少量数据读写时会占用IO,造成性能问题.golang的bufio库使用缓存来一次性进行大块数据的读写,以此降低IO系统调用,提升性能. 在Transport中可 ...

  8. 清除安装的Rancher、K8s

    #清除主机的所有容器.挂载.镜像(慎用) docker stop $(docker ps -aq) docker system prune -f docker volume rm $(docker v ...

  9. Blazor 组件库 BootstrapBlazor 中Editor组件介绍

    组件介绍 Editor组件是对Summernote 组件的二次封装. 组件分为div模式和editor模式. 默认状态下editor模式的组件样子如下: 其代码如下: <Editor @bind ...

  10. XCTF练习题---WEB---get_post

    XCTF练习题---WEB---get_post flag:cyberpeace{5526ac8044f1c5cfb5c421d34dff7822} 解题步骤: 1.观察题目,打开场景 2.观察页面内 ...