RocketMq系列之Producer启动源码分析(一)

247 阅读4分钟

欢迎关注公众号【sharedCode】致力于主流中间件的源码分析, 个人网站:www.shared-code.com/

前言

笔者写的rokcetMq的源码分析,都是基于4.5.1版本。

源码入口

下面是一个很简单的服务端启动的两行源码

MQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
producer.start();

启动源码的逻辑主要集中在start方法里面

@Override
    public void start() throws MQClientException {
        // 1. 设置生产组信息
        this.setProducerGroup(withNamespace(this.producerGroup));
        // 2. 重点在这个方法,生产者进行启动
        this.defaultMQProducerImpl.start();
        if (null != traceDispatcher) {
            try {
                // 3. RocketMQ在4.4.0 Release版本中支持了消息轨迹特性。
                traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
            } catch (MQClientException e) {
                log.warn("trace dispatcher start failed ", e);
            }
        }
    }

步骤说明 , 按照我在代码里面标的序号:

  1. 设置生产组信息
  2. 启动生产者,这里面做了一系列初始化操作
  3. RocketMQ在4.4.0 Release版本中支持了消息轨迹特性,这一特性的增加能够让我们对消息从生产到存储到消费这一整个流程有一个清晰的掌握,配合上console1.0.1以上版本的图形化界面,对于错误排查及日常运维是一个很有用处的feature。(该特性后期单独开篇讲解)

DefaultMQProducerImpl

start方法

public void start() throws MQClientException {
    // 调用真正的初始化方法
    this.start(true);
}

真正的start方法上拥有这个startFactory参数 , 一个boolean参数,用来标识是否启动mQClientFactory

下面看一下这个方法,

/**
     * 生产者初始化
     *
     * @param startFactory 用来表示,是否初始化mQClientFactory
     * @throws MQClientException
     */
public void start(final boolean startFactory) throws MQClientException {
  // 首次调用该方法时serviceState = CREATE_JUST , 等待创建
  switch (this.serviceState) {
    case CREATE_JUST:
      // 1. 一进来,就默认状态为失败,主要是为了防止重复执行
      this.serviceState = ServiceState.START_FAILED;
      // 2. 检查生产组名称配置
      this.checkConfig();
      // 3. 如果生产组名称不等于 CLIENT_INNER_PRODUCER , 则需要吧instanceName修改一下
      if (!this.defaultMQProducer.getProducerGroup().
          equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
        this.defaultMQProducer.changeInstanceNameToPID();
      }
      // 4. 创建一个和broker交互的MQ实例
      this.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientInstance(
        this.defaultMQProducer, rpcHook);
      // 5. 注册生产组信息,到本地的MAP里面去
      boolean registerOK = mQClientFactory.registerProducer(
        this.defaultMQProducer.getProducerGroup(), this);
      if (!registerOK) {
        // 如果注册不成功,那么需要把服务的启动状态设置为CREATE_JUST,
        this.serviceState = ServiceState.CREATE_JUST;
        throw new MQClientException("The producer group[" + this.defaultMQProducer.
        getProducerGroup()+ "] has been created before, specify another name please." + 		   FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),null);
      }
      // 生产者启动是,默认的 topic = TBW102 , 放入到本地的topic的ConcurrentHashMap中
      this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
      // 启动一个生产组时,只有一次startFactory = true, 因为在mQClientFactory.start();方法中也会调用start方法,只不过startFactory = false
      if (startFactory) {
        // 6. 启动MQ实例
        mQClientFactory.start();
      }

      log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(),
               this.defaultMQProducer.isSendMessageWithVIPChannel());
      // 启动成功
      this.serviceState = ServiceState.RUNNING;
      break;
    case RUNNING:
    case START_FAILED:
    case SHUTDOWN_ALREADY:
      // 正在运行, 启动失败,准备关闭,这3个状态下,如果还有人调用start方法,那么就会直接报错了
      throw new MQClientException("The producer service state not OK, maybe started once, "
                                  + this.serviceState
                                  + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),
                                  null);
    default:
      break;
  }
  // 7. 设置发送心跳给broker
  this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
}

上面的方法是整个生产者初始化入口核心流程,需要看的仔细一点,已我在代码标的序号为准。

步骤说明:

  1. 在一开始的状态量为CREAT_JUST,可以顺利进入具体的开始实现,同时立马将状态修改为失败,主要是为了防止重复执行

  2. 对生产者的group名称做校验

private void checkConfig() throws MQClientException {
        Validators.checkGroup(this.defaultMQProducer.getProducerGroup());
        // 生产组名称不能为空
        if (null == this.defaultMQProducer.getProducerGroup()) {
            throw new MQClientException("producerGroup is null", null);
        }
        // 生产组名称不能 等于 DEFAULT_PRODUCER
        if (this.defaultMQProducer.getProducerGroup().equals(MixAll.
         DEFAULT_PRODUCER_GROUP)) {
            throw new MQClientException("producerGroup can not equal " + MixAll.DEFAULT_PRODUCER_GROUP + ", please specify another one.",
                    null);
        }
    }
  1. 如果生产组名称不等于 CLIENT_INNER_PRODUCER (mQClientFactory作为和broker交互的实例里面初始化的名称就是这个) , 则需要把instanceName修改一下 ,因为, 执行changeInstanceNameToPID方法
private String instanceName = System.getProperty("rocketmq.client.name", "DEFAULT");

instanceName如果不设置的话就是DEFAULT

public void changeInstanceNameToPID() {
		//这代码差评, 竟然写魔法值。
   if (this.instanceName.equals("DEFAULT")) {
     // 更新DEFAULT = 当前JVM的进程PID
    this.instanceName = String.valueOf(UtilAll.getPid());
   }
}
  1. 创建一个和broker交互的MQ实例 , 通过MQClientManager这个类创建MQClient ,
public MQClientInstance getAndCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) {
  		  // 构建MQClientId, 这个ID很重要,在MQ多个集群的时候,这个如果不注意会出现问题。
        String clientId = clientConfig.buildMQClientId();
        // 通过clientId从factoryTable中获取是否已经被创建过,如果已经被创建过,则直接复用
        MQClientInstance instance = this.factoryTable.get(clientId);
        if (null == instance) {
          	// 为空,则继续创建MQClientInstance , 
            instance =new MQClientInstance(clientConfig.cloneClientConfig(),
            this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook);
            MQClientInstance prev = this.factoryTable.putIfAbsent(clientId, instance);
            if (prev != null) {
                instance = prev;
                log.warn("Returned Previous MQClientInstance for clientId:[{}]", clientId);
            } else {
                log.info("Created new MQClientInstance for clientId:[{}]", clientId);
            }
        }

        return instance;
    }

// org.apache.rocketmq.client.ClientConfig
public String buildMQClientId() {
        StringBuilder sb = new StringBuilder();
  			// 本机IP
        sb.append(this.getClientIP());

        sb.append("@");
  		  // 实例名称
        sb.append(this.getInstanceName());
        if (!UtilAll.isBlank(this.unitName)) {
            sb.append("@");
            sb.append(this.unitName);
        }

        return sb.toString();
    }

MQClientInstance后面单独开篇幅讲解,这个属于很重要的一个概念, ClientId的组成,是通过 ip@pid这种方式组成的, pid其实是通过上面的代码中通过改写InstanceName来达到这个目的的。

blog.csdn.net/a417930422/…

  1. 注册生产组信息,到本地的MAP里面去
  2. 启动MQ实例 , MQClientInstance.start方法
public void start() throws MQClientException {
        synchronized (this) {
            switch (this.serviceState) {
                case CREATE_JUST:
                    this.serviceState = ServiceState.START_FAILED;
                    // If not specified,looking address from name server
                    if (null == this.clientConfig.getNamesrvAddr()) {
                        this.mQClientAPIImpl.fetchNameServerAddr();
                    }
                    // 负责网络通信的接口调用启动,
                    this.mQClientAPIImpl.start();
                    // 初始化各类定时任务
                    this.startScheduledTask();
                    // 开启pullService , 针对consumer,到讲解consumer时再做详细说明
                    this.pullMessageService.start();
                    // 启动RebalanceService服务,针对consumer,到讲解consumer时再做详细说明
                    this.rebalanceService.start();
                    // 启动一个groupName为CLIENT_INNER_PRODUCER的DefaultMQProducer,
                    // 用于将消费失败的消息发回broker,消息的topic格式为%RETRY%ConsumerGroupName。
                    this.defaultMQProducer.getDefaultMQProducerImpl().start(false);
                    log.info("the client factory [{}] start OK", this.clientId);
                    this.serviceState = ServiceState.RUNNING;
                    break;
                case RUNNING:
                    break;
                case SHUTDOWN_ALREADY:
                    break;
                case START_FAILED:
                    throw new MQClientException("The Factory object[" + this.getClientId() + "] has been created before, and failed.", null);
                default:
                    break;
            }
        }
    }