暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

RocketMQ源码分析之Broker注册(1)

徘徊笔记 2019-04-08
423

来源:https://github.com/apache/rocketmq


在broker启动时会开始第一次注册,以后会定时向注册中心注册本机的信息

        this.registerBrokerAll(true, false, true);


    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {


    @Override
    public void run() {
    try {
    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);


    包装topic的配置信息以及对应的版本号TopicConfigSerializeWrapper,然后根据本机的读写配置重写topic配置。

      public synchronized void registerBrokerAll(final boolean checkOrderConfig, boolean oneway, boolean forceRegister) {
      TopicConfigSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildTopicConfigSerializeWrapper();


      if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission())
      || !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) {
      ConcurrentHashMap<String, TopicConfig> topicConfigTable = new ConcurrentHashMap<String, TopicConfig>();
      for (TopicConfig topicConfig : topicConfigWrapper.getTopicConfigTable().values()) {
      TopicConfig tmp =
      new TopicConfig(topicConfig.getTopicName(), topicConfig.getReadQueueNums(), topicConfig.getWriteQueueNums(),
      this.brokerConfig.getBrokerPermission());
      topicConfigTable.put(topicConfig.getTopicName(), tmp);
      }
      topicConfigWrapper.setTopicConfigTable(topicConfigTable);
      }


      if (forceRegister || needRegister(this.brokerConfig.getBrokerClusterName(),
      this.getBrokerAddr(),
      this.brokerConfig.getBrokerName(),
      this.brokerConfig.getBrokerId(),
      this.brokerConfig.getRegisterBrokerTimeoutMills())) {
      doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper);
      }
      }


      当直接注册标识打开时forceRegister直接进行注册,这里我们看一下另一个是否需要注册的条件,超时时间为6000ms,直接开始执行是否注册请求处理

        private boolean needRegister(final String clusterName,
        final String brokerAddr,
        final String brokerName,
        final long brokerId,
        final int timeoutMills) {


        TopicConfigSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildTopicConfigSerializeWrapper();
        List<Boolean> changeList = brokerOuterAPI.needRegister(clusterName, brokerAddr, brokerName, brokerId, topicConfigWrapper, timeoutMills);
        boolean needRegister = false;
        for (Boolean changed : changeList) {
        if (changed) {
        needRegister = true;
        break;
        }
        }
        return needRegister;
        }


        先获取配置的所有注册中心的地址,每个地址都需要访问一下,保持一致,当任何一个注册中心返回需要注册的话,那么上面那个请求就会返回true,也就是要给每个注册中心重新注册。这里用CountDownLatch来达到等待所有请求都返回才会执行返回操作,当然对每个注册中心的注册请求都是异步线程进行操作,

          publicList<Boolean> needRegister(
          final String clusterName,
          final String brokerAddr,
          final String brokerName,
          final long brokerId,
          final TopicConfigSerializeWrapper topicConfigWrapper,
          final int timeoutMills) {
          final List<Boolean> changedList = new CopyOnWriteArrayList<>();
          List<String> nameServerAddressList = this.remotingClient.getNameServerAddressList();
          if (nameServerAddressList != null && nameServerAddressList.size() > 0) {
          final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size());
          for (final String namesrvAddr : nameServerAddressList) {
          brokerOuterExecutor.execute(new Runnable() {
          @Override
          public void run() {
          try {
          QueryDataVersionRequestHeader requestHeader = new QueryDataVersionRequestHeader();
          requestHeader.setBrokerAddr(brokerAddr);
          requestHeader.setBrokerId(brokerId);
          requestHeader.setBrokerName(brokerName);
          requestHeader.setClusterName(clusterName);
          RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_DATA_VERSION, requestHeader);
          request.setBody(topicConfigWrapper.getDataVersion().encode());
          RemotingCommand response = remotingClient.invokeSync(namesrvAddr, request, timeoutMills);
          DataVersion nameServerDataVersion = null;
          Boolean changed = false;
          switch (response.getCode()) {
          case ResponseCode.SUCCESS: {
          QueryDataVersionResponseHeader queryDataVersionResponseHeader =
          (QueryDataVersionResponseHeader) response.decodeCommandCustomHeader(QueryDataVersionResponseHeader.class);
          changed = queryDataVersionResponseHeader.getChanged();
          byte[] body = response.getBody();
          if (body != null) {
          nameServerDataVersion = DataVersion.decode(body, DataVersion.class);
          if (!topicConfigWrapper.getDataVersion().equals(nameServerDataVersion)) {
          changed = true;
          }
          }
          if (changed == null || changed) {
          changedList.add(Boolean.TRUE);
          }
          }
          default:
          break;
          }
          log.warn("Query data version from name server {} OK,changed {}, broker {},name server {}", namesrvAddr, changed, topicConfigWrapper.getDataVersion(), nameServerDataVersion == null ? "" : nameServerDataVersion);
          } catch (Exception e) {
          changedList.add(Boolean.TRUE);
          log.error("Query data version from name server {} Exception, {}", namesrvAddr, e);
          } finally {
          countDownLatch.countDown();
          }
          }
          });


          }
          try {
          countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS);
          } catch (InterruptedException e) {
          log.error("query dataversion from nameserver countDownLatch await Exception", e);
          }
          }
          return changedList;
          }


          执行线程为

            private BrokerFixedThreadPoolExecutor brokerOuterExecutor = new BrokerFixedThreadPoolExecutor(4, 10, 1, TimeUnit.MINUTES,
            new ArrayBlockingQueue<Runnable>(32), new ThreadFactoryImpl("brokerOutApi_thread_", true));


            构造请求头QueryDataVersionRequestHeader,code为QUERY_DATA_VERSION = 322;每一种请求都对应一个请求头类以及一个返回类,当然也和请求码code相对应。通过通信客户端执行同步请求操作

              public RemotingCommand invokeSync(String addr, final RemotingCommand request, long timeoutMillis)
              throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException {
              long beginStartTime = System.currentTimeMillis();
              final Channel channel = this.getAndCreateChannel(addr);
              if (channel != null && channel.isActive()) {
              try {
              doBeforeRpcHooks(addr, request);
              long costTime = System.currentTimeMillis() - beginStartTime;
              if (timeoutMillis < costTime) {
              throw new RemotingTimeoutException("invokeSync call timeout");
              }
              RemotingCommand response = this.invokeSyncImpl(channel, request, timeoutMillis - costTime);
              doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(channel), request, response);
              return response;
              } catch (RemotingSendRequestException e) {
              log.warn("invokeSync: send request exception, so close the channel[{}]", addr);
              this.closeChannel(addr, channel);
              throw e;
              } catch (RemotingTimeoutException e) {
              if (nettyClientConfig.isClientCloseSocketIfTimeout()) {
              this.closeChannel(addr, channel);
              log.warn("invokeSync: close socket because of timeout, {}ms, {}", timeoutMillis, addr);
              }
              log.warn("invokeSync: wait response timeout exception, the channel[{}]", addr);
              throw e;
              }
              } else {
              this.closeChannel(addr, channel);
              throw new RemotingConnectException(addr);
              }
              }


              获取通信通道,如果没有的话就直接创建,最后保存起来

                 private Channel getAndCreateChannel(final String addr) throws InterruptedException {
                if (null == addr) {
                return getAndCreateNameserverChannel();
                }


                ChannelWrapper cw = this.channelTables.get(addr);
                if (cw != null && cw.isOK()) {
                return cw.getChannel();
                }


                return this.createChannel(addr);
                }


                当不传递对应的配置中心地址时,就会选择一个地址创建通信通道

                  private Channel getAndCreateNameserverChannel() throws InterruptedException {
                  String addr = this.namesrvAddrChoosed.get();
                  if (addr != null) {
                  ChannelWrapper cw = this.channelTables.get(addr);
                  if (cw != null && cw.isOK()) {
                  return cw.getChannel();
                  }
                  }


                  final List<String> addrList = this.namesrvAddrList.get();
                  if (this.lockNamesrvChannel.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
                  try {
                  addr = this.namesrvAddrChoosed.get();
                  if (addr != null) {
                  ChannelWrapper cw = this.channelTables.get(addr);
                  if (cw != null && cw.isOK()) {
                  return cw.getChannel();
                  }
                  }


                  if (addrList != null && !addrList.isEmpty()) {
                  for (int i = 0; i < addrList.size(); i++) {
                  int index = this.namesrvIndex.incrementAndGet();
                  index = Math.abs(index);
                  index = index % addrList.size();
                  String newAddr = addrList.get(index);


                  this.namesrvAddrChoosed.set(newAddr);
                  log.info("new name server is chosen. OLD: {} , NEW: {}. namesrvIndex = {}", addr, newAddr, namesrvIndex);
                  Channel channelNew = this.createChannel(newAddr);
                  if (channelNew != null) {
                  return channelNew;
                  }
                  }
                  }
                  } catch (Exception e) {
                  log.error("getAndCreateNameserverChannel: create name server channel exception", e);
                  } finally {
                  this.lockNamesrvChannel.unlock();
                  }
                  } else {
                  log.warn("getAndCreateNameserverChannel: try to lock name server, but timeout, {}ms", LOCK_TIMEOUT_MILLIS);
                  }


                  return null;
                  }


                  创建一个新的通讯通道,然后和配置中心进行连接返回ChannelFuture包装成ChannelWrapper保存起来后等待连接成功,超时时间为3000ms。成功后返回

                    private Channel createChannel(final String addr) throws InterruptedException {
                    ChannelWrapper cw = this.channelTables.get(addr);
                    if (cw != null && cw.isOK()) {
                    cw.getChannel().close();
                    channelTables.remove(addr);
                    }


                    if (this.lockChannelTables.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
                    try {
                    boolean createNewConnection;
                    cw = this.channelTables.get(addr);
                    if (cw != null) {


                    if (cw.isOK()) {
                    cw.getChannel().close();
                    this.channelTables.remove(addr);
                    createNewConnection = true;
                    } else if (!cw.getChannelFuture().isDone()) {
                    createNewConnection = false;
                    } else {
                    this.channelTables.remove(addr);
                    createNewConnection = true;
                    }
                    } else {
                    createNewConnection = true;
                    }


                    if (createNewConnection) {
                    ChannelFuture channelFuture = this.bootstrap.connect(RemotingHelper.string2SocketAddress(addr));
                    log.info("createChannel: begin to connect remote host[{}] asynchronously", addr);
                    cw = new ChannelWrapper(channelFuture);
                    this.channelTables.put(addr, cw);
                    }
                    } catch (Exception e) {
                    log.error("createChannel: create channel exception", e);
                    } finally {
                    this.lockChannelTables.unlock();
                    }
                    } else {
                    log.warn("createChannel: try to lock channel table, but timeout, {}ms", LOCK_TIMEOUT_MILLIS);
                    }


                    if (cw != null) {
                    ChannelFuture channelFuture = cw.getChannelFuture();
                    if (channelFuture.awaitUninterruptibly(this.nettyClientConfig.getConnectTimeoutMillis())) {
                    if (cw.isOK()) {
                    log.info("createChannel: connect remote host[{}] success, {}", addr, channelFuture.toString());
                    return cw.getChannel();
                    } else {
                    log.warn("createChannel: connect remote host[" + addr + "] failed, " + channelFuture.toString(), channelFuture.cause());
                    }
                    } else {
                    log.warn("createChannel: connect remote host[{}] timeout {}ms, {}", addr, this.nettyClientConfig.getConnectTimeoutMillis(),
                    channelFuture.toString());
                    }
                    }


                    return null;
                    }


                    判断通信通道是否有效后执行rpc请求前置钩子,完事后判断是否已经超时,

                      protected void doBeforeRpcHooks(String addr, RemotingCommand request) {
                      if (rpcHooks.size() > 0) {
                      for (RPCHook rpcHook: rpcHooks) {
                      rpcHook.doBeforeRequest(addr, request);
                      }
                      }
                      }


                      执行同步请求,请求命令在创建时会创建该次请求id即opaque,把对应的信息包装成ResponseFuture与对应的请求id存储起来,等待后续失败或者成功后移除。通道监听器主要是看请求有没有发送成功,当成功后就会设置对应的状态后返回,不成功则设置状态后清除掉前面保存的请求设置返回为null,

                        public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
                        final long timeoutMillis)
                        throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
                        final int opaque = request.getOpaque();


                        try {
                        final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
                        this.responseTable.put(opaque, responseFuture);
                        final SocketAddress addr = channel.remoteAddress();
                        channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture f) throws Exception {
                        if (f.isSuccess()) {
                        responseFuture.setSendRequestOK(true);
                        return;
                        } else {
                        responseFuture.setSendRequestOK(false);
                        }


                        responseTable.remove(opaque);
                        responseFuture.setCause(f.cause());
                        responseFuture.putResponse(null);
                        log.warn("send a request command to channel <" + addr + "> failed.");
                        }
                        });


                        RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);
                        if (null == responseCommand) {
                        if (responseFuture.isSendRequestOK()) {
                        throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis,
                        responseFuture.getCause());
                        } else {
                        throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
                        }
                        }


                        return responseCommand;
                        } finally {
                        this.responseTable.remove(opaque);
                        }
                        }


                        后面的程序一直在等待对应的相应,当写入失败的时候会唤醒,或者是超时时间到达,再就是正确的回应到达时会唤醒该操作,最后判断回应后返回。

                          public RemotingCommand waitResponse(final long timeoutMillis) throws InterruptedException {
                          this.countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
                          return this.responseCommand;
                          }
                          public void putResponse(final RemotingCommand responseCommand) {
                          this.responseCommand = responseCommand;
                          this.countDownLatch.countDown();
                          }



                          文章转载自徘徊笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                          评论