public RemotingCommand unregisterClient(ChannelHandlerContext ctx, RemotingCommand request)
      throws RemotingCommandException {
    final RemotingCommand response =
        RemotingCommand.createResponseCommand(UnregisterClientResponseHeader.class);
    final UnregisterClientRequestHeader requestHeader =
        (UnregisterClientRequestHeader)
            request.decodeCommandCustomHeader(UnregisterClientRequestHeader.class);

    ClientChannelInfo clientChannelInfo =
        new ClientChannelInfo( //
            ctx.channel(), //
            requestHeader.getClientID(), //
            request.getLanguage(), //
            request.getVersion() //
            );

    // 注销Producer
    {
      final String group = requestHeader.getProducerGroup();
      if (group != null) {
        this.brokerController.getProducerManager().unregisterProducer(group, clientChannelInfo);
      }
    }

    // 注销Consumer
    {
      final String group = requestHeader.getConsumerGroup();
      if (group != null) {
        this.brokerController.getConsumerManager().unregisterConsumer(group, clientChannelInfo);
      }
    }

    response.setCode(ResponseCode.SUCCESS);
    response.setRemark(null);
    return response;
  }
  public RemotingCommand heartBeat(ChannelHandlerContext ctx, RemotingCommand request) {
    RemotingCommand response = RemotingCommand.createResponseCommand(null);

    HeartbeatData heartbeatData = HeartbeatData.decode(request.getBody(), HeartbeatData.class);

    ClientChannelInfo clientChannelInfo =
        new ClientChannelInfo( //
            ctx.channel(), //
            heartbeatData.getClientID(), //
            request.getLanguage(), //
            request.getVersion() //
            );

    // 注册Consumer
    for (ConsumerData data : heartbeatData.getConsumerDataSet()) {
      SubscriptionGroupConfig subscriptionGroupConfig =
          this.brokerController
              .getSubscriptionGroupManager()
              .findSubscriptionGroupConfig(data.getGroupName());
      if (null != subscriptionGroupConfig) {
        String newTopic = MixAll.getRetryTopic(data.getGroupName());
        this.brokerController
            .getTopicConfigManager()
            .createTopicInSendMessageBackMethod( //
                newTopic, //
                subscriptionGroupConfig.getRetryQueueNums(), //
                PermName.PERM_WRITE | PermName.PERM_READ);
      }

      boolean changed =
          this.brokerController
              .getConsumerManager()
              .registerConsumer( //
                  data.getGroupName(), //
                  clientChannelInfo, //
                  data.getConsumeType(), //
                  data.getMessageModel(), //
                  data.getConsumeFromWhere(), //
                  data.getSubscriptionDataSet() //
                  );

      if (changed) {
        log.info(
            "registerConsumer info changed {} {}", //
            data.toString(), //
            RemotingHelper.parseChannelRemoteAddr(ctx.channel()) //
            );
      }
    }

    // 注册Producer
    for (ProducerData data : heartbeatData.getProducerDataSet()) {
      this.brokerController
          .getProducerManager()
          .registerProducer(data.getGroupName(), clientChannelInfo);
    }

    response.setCode(ResponseCode.SUCCESS);
    response.setRemark(null);
    return response;
  }
  private RemotingCommand queryConsumerOffset(ChannelHandlerContext ctx, RemotingCommand request)
      throws RemotingCommandException {
    final RemotingCommand response =
        RemotingCommand.createResponseCommand(QueryConsumerOffsetResponseHeader.class);
    final QueryConsumerOffsetResponseHeader responseHeader =
        (QueryConsumerOffsetResponseHeader) response.getCustomHeader();
    final QueryConsumerOffsetRequestHeader requestHeader =
        (QueryConsumerOffsetRequestHeader)
            request.decodeCommandCustomHeader(QueryConsumerOffsetRequestHeader.class);

    long offset =
        this.brokerController
            .getConsumerOffsetManager()
            .queryOffset(
                requestHeader.getConsumerGroup(),
                requestHeader.getTopic(),
                requestHeader.getQueueId());

    // 订阅组存在
    if (offset >= 0) {
      responseHeader.setOffset(offset);
      response.setCode(ResponseCode.SUCCESS_VALUE);
      response.setRemark(null);
    }
    // 订阅组不存在
    else {
      // 新版本服务器不做消费进度纠正
      if (request.getVersion() >= MQVersion.Version.V3_0_6_SNAPSHOT.ordinal()) {
        response.setCode(MQResponseCode.QUERY_NOT_FOUND_VALUE);
        response.setRemark("Not found, V3_0_6_SNAPSHOT maybe this group consumer boot first");
      }

      // TODO:以下流程待所有客户端都升级后,可以删除此段代码
      else {
        long minOffset =
            this.brokerController
                .getMessageStore()
                .getMinOffsetInQuque(requestHeader.getTopic(), requestHeader.getQueueId());
        long maxOffset =
            this.brokerController
                .getMessageStore()
                .getMaxOffsetInQuque(requestHeader.getTopic(), requestHeader.getQueueId());

        boolean consumeFromMinEnable = false;
        if (0 == minOffset && maxOffset > 0) {
          long minCommitLogOffset =
              this.brokerController
                  .getMessageStore()
                  .getCommitLogOffsetInQueue(
                      requestHeader.getTopic(), requestHeader.getQueueId(), minOffset);
          long maxCommitLogOffset =
              this.brokerController
                  .getMessageStore()
                  .getCommitLogOffsetInQueue(
                      requestHeader.getTopic(), requestHeader.getQueueId(), maxOffset - 1);

          long memorySpan =
              (long)
                  (StoreUtil.TotalPhysicalMemorySize
                      * (this.brokerController
                              .getMessageStoreConfig()
                              .getAccessMessageInMemoryMaxRatio()
                          / 100.0));

          long diff = maxCommitLogOffset - minCommitLogOffset;
          if (diff < memorySpan) {
            consumeFromMinEnable = true;
            log.info(
                "the consumer group[{}] first subscribed, minOffset: {} maxOffset: {}, from min.", //
                requestHeader.getConsumerGroup(), //
                minOffset, //
                maxOffset);
          }
        } else if (minOffset > 0 && maxOffset > 0) {
          consumeFromMinEnable = false;
        } else {
          consumeFromMinEnable = true;
          log.info(
              "the consumer group[{}] first subscribed, minOffset: {} maxOffset: {}, from min, and unknow offset.", //
              requestHeader.getConsumerGroup(), //
              minOffset, //
              maxOffset);
        }

        // 说明这个队列在服务器存储的消息比较少或者没有消息
        // 订阅组消费进度不存在情况下,从0开始消费
        if (consumeFromMinEnable) {
          responseHeader.setOffset(0L);
          response.setCode(ResponseCode.SUCCESS_VALUE);
          response.setRemark(null);
        } else {
          response.setCode(MQResponseCode.QUERY_NOT_FOUND_VALUE);
          response.setRemark("Not found, maybe this group consumer boot first");
        }
      }
    }

    return response;
  }