RocketMQ源码分析(第四篇)- 生产者启动流程

772 阅读3分钟

本文章主要参考《RocketMQ技术内幕:RocketMQ架构设计与实现原理》一书,主要是将自己学习的过程进行记录和梳理,方便以后翻阅。

1. 生产者介绍

如图,生产者的默认实现是DefaultMQProducer,在DefaultMQProducer中有一个成员变量DefaultMQProducerImpl,里面实现了生产者的很多方法,可以说是生产者的具体实现类。

在DefaultMQProducer中定义了一些发送消息的核心属性,如下:

/**
 * 生产者具体实现
 */
protected final transient DefaultMQProducerImpl defaultMQProducerImpl;

/**
 * 生产者组
 */
private String producerGroup;

/**
 * 默认的topickey
 */
private String createTopicKey = MixAll.AUTO_CREATE_TOPIC_KEY_TOPIC;

/**
 * 每个主题在一个broker中默认的队列数量
 */
private volatile int defaultTopicQueueNums = 4;

/**
 * 消息发送超时时间
 */
private int sendMsgTimeout = 3000;

/**
 * 当消息体超过该值的时候会使用压缩
 */
private int compressMsgBodyOverHowmuch = 1024 * 4;

/**
 * 同步发送失败重试次数
 */
private int retryTimesWhenSendFailed = 2;

/**
 * 异步发送失败重试次数
 */
private int retryTimesWhenSendAsyncFailed = 2;

/**
 * 消息重试时选择另一个broker时,是否不等待存储结果就返回
 */
private boolean retryAnotherBrokerWhenNotStoreOK = false;

/**
 * 允许发送的消息的最大长度
 */
private int maxMessageSize = 1024 * 1024 * 4; // 4M

2. 生产者启动流程

当发送消息的时候,调用DefaultMQProducer的start()方法就开始启动生产者。

#org.apache.rocketmq.client.producer.DefaultMQProducer#start

this.setProducerGroup(withNamespace(this.producerGroup));
this.defaultMQProducerImpl.start();

可以看到,真的的启动其实是在DefaultMQProducerImpl中实现的。

switch (this.serviceState) {
    case CREATE_JUST:
        this.serviceState = ServiceState.START_FAILED;

        this.checkConfig();//1

        if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
            //修改instanceName为进程ID
            this.defaultMQProducer.changeInstanceNameToPID();//2
        }

        //获取MQClientInstance
        this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);//3

        boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);//4
        if (!registerOK) {//已经存在注册的Producer
            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);
        }

        this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());//5

        if (startFactory) {//启动MQClientInstance
            mQClientFactory.start();//6
        }

        log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(),
                this.defaultMQProducer.isSendMessageWithVIPChannel());
        this.serviceState = ServiceState.RUNNING;//7
        break;
    case RUNNING:
    case START_FAILED:
    case SHUTDOWN_ALREADY:
        throw new MQClientException("The producer service state not OK, maybe started once, "
                + this.serviceState
                + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),
                null);
    default:
        break;
}

//向所有的broker发送心跳检测
this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();//8

1、首先,会对ProducerGroup进行检查。

2、并且如果不是默认的ProducerGroup,将生产者的InstanceName设置为进程ID。

3、接着创建一个MQClientInstance实例:

#org.apache.rocketmq.client.impl.MQClientManager

public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) {

//IP+instanceName+unitname,
String clientId = clientConfig.buildMQClientId();
MQClientInstance instance = this.factoryTable.get(clientId);
if (null == instance) {
    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;
}

首先看一下MQClientManage,MQClientManager是一个单例的,整个JVM中只有一个,其中维护了一个缓存表,负责缓存每个ClientID和MQClientInstance的对应关系。

private static MQClientManager instance = new MQClientManager();

private ConcurrentMap<String/* clientId */, MQClientInstance> factoryTable =
new ConcurrentHashMap<String, MQClientInstance>();

接着看getOrCreateMQClientInstance()方法,先去生成clientId,ClientId主要是由进程IP@InstanceName@unitName组成,其中uniteName是可选的。

# org.apache.rocketmq.client.ClientConfig

public String buildMQClientId() {
    StringBuilder sb = new StringBuilder();
    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实例,并放入缓存中。

4、向MQClientInstance注册当前的生产者,这个注册只是将生产者放入一个缓存列表中,方便后续使用。

# org.apache.rocketmq.client.impl.factory.MQClientInstance

 public boolean registerProducer(final String group, final DefaultMQProducerImpl producer) {
    if (null == group || null == producer) {
        return false;
    }

    //如果已经存在,则不需要进行注册
    MQProducerInner prev = this.producerTable.putIfAbsent(group, producer);
    if (prev != null) {
        log.warn("the producer group[{}] exist already.", group);
        return false;
    }

    return true;
}

5、维护topic信息列表。 6、启动MQClientInstance。 7、将生产者状态修改为运行中。 8、向所有的Broker发送心跳检测。

到此,生产者启动流程完成。生产者启动最重要的就是创建MQClientInstance实例,并且启动它。

参考资料

  • RocketMQ技术内幕:RocketMQ架构设计与实现原理