第二章-RocketMQ源码解析-生产者-普通消息发送

2.1 同步发送

同步发送是最常用的方式,是指消息发送方发出一条消息后,会在收到服务端同步响应之后才发下一条消息的通讯方式,可靠的同步传输被广泛应用于各种场景,如重要的通知消息、短消息通知等。

第二章-RocketMQ源码解析-生产者-普通消息发送_第1张图片

先从一段官方示例代码开始:

public class SyncProducer {
   
    public static void main(String[] args) throws Exception {
   
        // 初始化一个producer并设置Producer group name
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); //(1)
        // 设置NameServer地址
        producer.setNamesrvAddr("localhost:9876");  //(2)
        // 启动producer
        producer.start();
        for (int i = 0; i < 100; i++) {
   
            // 创建一条消息,并指定topic、tag、body等信息,tag可以理解成标签,对消息进行再归类,RocketMQ可以在消费端对tag进行过滤
            Message msg = new Message("TopicTest" /* Topic */,
                                      "TagA" /* Tag */,
                                      ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
                                     );   //(3)
            // 利用producer进行发送,并同步等待发送结果
            SendResult sendResult = producer.send(msg);   //(4)
            System.out.printf("%s%n", sendResult);
        }
        // 一旦producer不再使用,关闭producer
        producer.shutdown();
    }
}

同步发送的整个代码流程如下:

  1. 首先会创建一个producer。普通消息可以创建 DefaultMQProducer,创建时需要填写生产组的名称,生产者组是指同一类Producer的集合,这类Producer发送同一类消息且发送逻辑一致。
  2. 设置 NameServer 的地址。Apache RocketMQ很多方式设置NameServer地址(客户端配置中有介绍),这里是在代码中调用producer的API setNamesrvAddr进行设置,如果有多个NameServer,中间以分号隔开,比如"127.0.0.2:9876;127.0.0.3:9876"。
  3. 第三步是构建消息。指定topic、tag、body等信息,tag可以理解成标签,对消息进行再归类,RocketMQ可以在消费端对tag进行过滤。
  4. 最后调用send接口将消息发送出去。同步发送等待结果最后返回SendResult,SendResut包含实际发送状态还包括SEND_OK(发送成功), FLUSH_DISK_TIMEOUT(刷盘超时), FLUSH_SLAVE_TIMEOUT(同步到备超时), SLAVE_NOT_AVAILABLE(备不可用),如果发送失败会抛出异常。

消息生产的入口类是DefaultMQProducer,首先要调用start()启动生产者实例,然后调用send()发送消息,这就算结束了,消息发送完成。是不是非常简单,接下来,我们来窥探一下它的内部实现原理。

2.1.1 DefaultMQProducer

2.1.1.1 类的继承关系

继承ClientConfig,实现MQProducer接口(定义了,生产者所有的功能实现),这个类是对外给客户使用的,实际的内部实现都在DefaultMQProducerImpl这个类中,稍后会讲到。下面是继承关系图(偷个懒,网上找的)

第二章-RocketMQ源码解析-生产者-普通消息发送_第2张图片

2.1.1.2 构造函数
// 默认构造函数
public DefaultMQProducer() {
   
    // 指定默认的生产者组为:DEFAULT_PRODUCER
    this(null, MixAll.DEFAULT_PRODUCER_GROUP, null);
}

/**
     * 指定 RPC hook 的构造函数
     *
     * @param rpcHook RPC hook 用于每次执行远程处理命令.
     */
public DefaultMQProducer(RPCHook rpcHook) {
   
    this(null, MixAll.DEFAULT_PRODUCER_GROUP, rpcHook);
}

// 指定生产者组的构造函数
public DefaultMQProducer(final String producerGroup) {
   
    this(null, producerGroup, null);
}

public DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace,
                         final String customizedTraceTopic) {
   
    this.producerGroup = producerGroup; // 指定生产者组
    // 内部消息生产者实现类,详情看章节`2.1.2`
    defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
    //是否客户端开启消息追踪
    if (enableMsgTrace) {
   
        try {
   
            // 下面逻辑都是涉及消息追踪的,这个留待后续章节讲
            AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(customizedTraceTopic, rpcHook);
            dispatcher.setHostProducer(this.defaultMQProducerImpl);
            traceDispatcher = dispatcher;
            this.defaultMQProducerImpl.registerSendMessageHook(
                new SendMessageTraceHookImpl(traceDispatcher));
        } catch (Throwable e) {
   
            log.error("system mqtrace hook init failed ,maybe can't send msg trace data");
        }
    }
}

// 指定命名空间和生者组的构造函数
public DefaultMQProducer(final String namespace, final String producerGroup) {
   
    this(namespace, producerGroup, null);
}

// 指定 rpcHook 和生者组的构造函数
public DefaultMQProducer(final String producerGroup, RPCHook rpcHook) {
   
    this(null, producerGroup, rpcHook);
}
// 指定 rpcHook、生者组和命名空间的构造函数
public DefaultMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook) {
   
    this.namespace = namespace; // 命名空间
    this.producerGroup = producerGroup; // 生产者组
    //  内部消息生产者实现类,详情看章节`2.1.2`
    defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
}

// 指定生产者组和消息追踪的构造函数
public DefaultMQProducer(final String producerGroup, boolean enableMsgTrace) {
   
    this(null, producerGroup, null, enableMsgTrace, null);
}

// 指定生产者组、消息追踪和追踪topic的构造函数
public DefaultMQProducer(final String producerGroup, boolean enableMsgTrace, final String customizedTraceTopic) {
   
    this(null, producerGroup, null, enableMsgTrace, customizedTraceTopic);
}

public DefaultMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook,
                         boolean enableMsgTrace, final String customizedTraceTopic) {
   
    this.namespace = namespace; // 命名空间
    this.producerGroup = producerGroup; // 生产者组
    //  内部消息生产者实现类,详情看章节`2.1.2`
    defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
    //是否客户端开启消息追踪
    if (enableMsgTrace) {
   
        try {
   
            // 下面逻辑都是涉及消息追踪的,这个留待后续章节讲
            AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(customizedTraceTopic, rpcHook);
            dispatcher.setHostProducer(this.getDefaultMQProducerImpl());
            traceDispatcher = dispatcher;
            this.getDefaultMQProducerImpl().registerSendMessageHook(
                new SendMessageTraceHookImpl(traceDispatcher));
        } catch (Throwable e) {
   
            log.error("system mqtrace hook init failed ,maybe can't send msg trace data");
        }
    }
}
2.1.1.3 start启动
public void start() throws MQClientException {
   
    // 将生产者组改成:namespace + '%' + producerGroup,当然里面还有一些针对重试和死信消息的处理,有兴趣的读者可以进去看看
    this.setProducerGroup(withNamespace(this.producerGroup));
    this.defaultMQProducerImpl.start(); //DefaultMQProducerImpl具体实现启动逻辑,详情看章节`2.1.2`
    if (null != traceDispatcher) {
   
        try {
   
            // 启动消息追踪
            traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
        } catch (MQClientException e) {
   
            log.warn("trace dispatcher start failed ", e);
        }
    }
}
2.1.1.4 send发送消息
public SendResult send( Message msg) 
    throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
   
    /*消费的校验,主要校验以下信息:
    1.topic判空、格式、长度(255)、是否为TBW102,只要1条不合格,校验失败
    2.body消息体判空,如果为空,抛出异常
    3.body消息体长度判断:默认4M,这个值可以修改,但是建议不要改,超过抛出异常
    */
    Validators.checkMessage(msg, this);
    //如果有命名空间的,那么要在原topic前增加命名空间的前缀:namespace + '%' + topic
    msg.setTopic(withNamespace(msg.getTopic()));
    //DefaultMQProducerImpl具体实现启动逻辑,详情看章节`2.1.2`
    return this.defaultMQProducerImpl.send(msg);
}

2.1.2 DefaultMQProducerImpl

2.1.2.1 start实现逻辑
public void start(final boolean startFactory) throws MQClientException {
   
    // 服务状态:CREATE_JUST--刚创建、RUNNING--运行中、SHUTDOWN_ALREADY--服务关闭、START_FAILED--启动失败
    switch (this.serviceState) {
   
        case CREATE_JUST:  //创建还未启动
            this.serviceState = ServiceState.START_FAILED;
            //group命名检查,主要检查空、格式、长度,并且不能是默认生产者组名
            this.checkConfig();
            //判断group名字与CLIENT_INNER_PRODUCER不等,证明不是内部group,而是用户自定义的group
            if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
   
                this.defaultMQProducer.changeInstanceNameToPID();//将实例进程id设置成实例名
            }
            //获取或创建(详情见`3.2.1`)MQClientInstance实例,该实例通过单例模式获取,这个类很重要,同时涵盖了生产者和消费者的操作,详情见3.3
            this.mQClientFactory = MQClientManager.getInstance()
                .getAndCreateMQClientInstance(this.defaultMQProducer, rpcHook);
            //将当前生产者实例对象与group名绑定,并注册到MQClientInstance对象,方便后续使用时通过group取出,见`3.3.1`
            boolean registerOK = mQClientFactory
                .registerProducer(this.defaultMQProducer.getProducerGroup(), this);
            if (!registerOK) {
   
                //重复注册失败,恢复状态,并抛出异常
                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发布消息对象TopicPublishInfo,并存入topicPublishInfoTable map容器中
            this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo

你可能感兴趣的:(RocketMQ源码,rocketmq,java)