RocketMq源码分析(五):生产者启动和发送消息到broker

108 阅读4分钟

生产者发送消息的代码

public static void main(String[] args) throws MQClientException, InterruptedException {

    DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP);

    producer.setNamesrvAddr(DEFAULT_NAMESRVADDR);

    producer.start();
 
    Message msg = new Message(TOPIC ,TAG, ("Hello RocketMQ 同步 " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
   SendResult sendResult = producer.send(msg);
   }

一.生产者启动

1.构建生产者

入口:DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP); 继续进入

public DefaultMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook) {
    this.namespace = namespace;
    this.producerGroup = producerGroup;
    //todo 只是创建一些对象
    defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
}

创建这个对象,比较简单,只是创建一些对象,阻塞队列/线程池等

2.启动生产者

入口: producer.start()

@Override
public void start() throws MQClientException {
    this.setProducerGroup(withNamespace(this.producerGroup));
    // 调用 defaultMQProducerImpl 的 start() 方法
    this.defaultMQProducerImpl.start();
    // 消息轨迹相关,我们不关注
    if (null != traceDispatcher) {
        try {
            traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
        } catch (MQClientException e) {
            log.warn("trace dispatcher start failed ", e);
        }
    }
}

接着进入defaultMQProducerImpl.start()

public void start(final boolean startFactory) throws MQClientException {
    switch (this.serviceState) {
        case CREATE_JUST:
            this.serviceState = ServiceState.START_FAILED;
            // 检查一些配置信息
            this.checkConfig();
            // 修改当前的 instanceName 为当前进程id
            if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
                this.defaultMQProducer.changeInstanceNameToPID();
            }
            // 获取mq实例
            this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
            // 注册 mqClient 实例
            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);
            }

            this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
            // TODO 启动实例
            if (startFactory) {
                //TODO producer 和 consumer 都是调用这个方法
                mQClientFactory.start();
            }

            log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(),
                this.defaultMQProducer.isSendMessageWithVIPChannel());
            this.serviceState = ServiceState.RUNNING;
            break;
...
        default:
            break;
    }
    // TODO 发送心跳到所有的broker
    this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
    // 定时扫描异步请求的返回结果
    RequestFutureHolder.getInstance().startScheduledTask(this);

}

核心步骤总结如下

2.1 启动mQClientFactory实例

入口:mQClientFactory.start()

public void start() throws MQClientException {

    synchronized (this) {
        switch (this.serviceState) {
            case CREATE_JUST:
                this.serviceState = ServiceState.START_FAILED;
                // 1.获取 nameServer 的地址
                if (null == this.clientConfig.getNamesrvAddr()) {
                    this.mQClientAPIImpl.fetchNameServerAddr();
                }
                // 2.启动客户端的远程服务,这个方法会配置netty客户端,并没有连接
                this.mQClientAPIImpl.start();
                // Start various schedule tasks
                //3. 启动定时任务
                this.startScheduledTask();
                // Start pull service
                // 4.todo pull服务,仅对consumer启作用,这是consumer拉取消息的处理类
                this.pullMessageService.start();
                // 5.Start rebalance service
                // 启动负载均衡服务,仅对consumer启作用
                this.rebalanceService.start();
                // Start push service
                // TODO 启用内部的 producer
                this.defaultMQProducer.getDefaultMQProducerImpl().start(false);
                log.info("the client factory [{}] start OK", this.clientId);
                this.serviceState = ServiceState.RUNNING;
                break;
            case START_FAILED:
                throw new MQClientException("The Factory object[" + this.getClientId() + "] has been created before, and failed.", null);
            default:
                break;
        }
    }
}

比较重要的一个方法是 this.startScheduledTask();,会启动一些定时任务 ,比如

  • todo 定时更新topic的路由信息 ★★★重要★★★ MQClientInstance.this.updateTopicRouteInfoFromNameServer();

  • 定时发送心跳信息 MQClientInstance.this.sendHeartbeatToAllBrokerWithLock();

  • 持久化消费者的消费偏移量 MQClientInstance.this.persistAllConsumerOffset();

我们举例进入MQClientInstance.this.updateTopicRouteInfoFromNameServer()方法中看一看,最后会进入到 MQClientAPIImpl#getTopicRouteInfoFromNameServer(String, long, boolean)方法中

public TopicRouteData getTopicRouteInfoFromNameServer(final String topic, final long timeoutMillis,
   boolean allowTopicNotExist) throws MQClientException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException {
   GetRouteInfoRequestHeader requestHeader = new GetRouteInfoRequestHeader();
   requestHeader.setTopic(topic);
   // 构建获取topic的请求
   RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC, requestHeader);

   RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis);
   assert response != null;
   switch (response.getCode()) {
       case ResponseCode.TOPIC_NOT_EXIST: {
           if (allowTopicNotExist) {
               log.warn("get Topic [{}] RouteInfoFromNameServer is not exist value", topic);
           }

           break;
       }
       case ResponseCode.SUCCESS: {
           byte[] body = response.getBody();
           if (body != null) {
               return TopicRouteData.decode(body, TopicRouteData.class);
           }
       }
       default:
           break;
   }

   throw new MQClientException(response.getCode(), response.getRemark());
}

就是使用netty发送"获取topic"的请求,返回topic的路由信息

2.2 发送心跳到所有broker

入口: this.mQClientFactory.sendHeartbeatToAllBrokerWithLock(); 最终会调用

private void sendHeartbeatToAllBroker() {
    final HeartbeatData heartbeatData = this.prepareHeartbeatData();
    final boolean producerEmpty = heartbeatData.getProducerDataSet().isEmpty();
    final boolean consumerEmpty = heartbeatData.getConsumerDataSet().isEmpty();
    if (producerEmpty && consumerEmpty) {
        log.warn("sending heartbeat, but no consumer and no producer. [{}]", this.clientId);
        return;
    }

    if (this.brokerAddrTable.isEmpty()) {
        return;
    }
    long times = this.sendHeartbeatTimesTotal.getAndIncrement();
    for (Entry<String, HashMap<Long, String>> brokerClusterInfo : this.brokerAddrTable.entrySet()) {
        String brokerName = brokerClusterInfo.getKey();
        HashMap<Long, String> oneTable = brokerClusterInfo.getValue();
        if (oneTable == null) {
            continue;
        }
        for (Entry<Long, String> singleBrokerInstance : oneTable.entrySet()) {
            Long id = singleBrokerInstance.getKey();
            String addr = singleBrokerInstance.getValue();//TODO 获取broker的地址
            if (addr == null) {
                continue;
            }
            if (consumerEmpty && MixAll.MASTER_ID != id) {
                continue;
            }

            try {
                //TODO 发送心跳
                int version = this.mQClientAPIImpl.sendHeartbeat(addr, heartbeatData, clientConfig.getMqClientApiTimeout());

2.3 定时扫描异步请求的返回结果

入口:RequestFutureHolder.getInstance().startScheduledTask(this); 不是很重要

二.生产者发送数据到broker

第一章写了生产者的启动,启动后,会通过一下定时任务,从nameserver拉取一些topic路由信息,也会将自己注册给broker,通过这些步骤, 生产者,nameserver和broker,都能感知对方的存在,接下来,就是生产者向broker发送消息

入口是 SendResult sendResult = producer.send(msg) 最后会进入DefaultMQProducerImpl#sendDefaultImpl

private SendResult sendDefaultImpl(
    Message msg,
    final CommunicationMode communicationMode,
    final SendCallback sendCallback,
    final long timeout
) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    this.makeSureStateOK();
    Validators.checkMessage(msg, this.defaultMQProducer);
    final long invokeID = random.nextLong();
    long beginTimestampFirst = System.currentTimeMillis();
    long beginTimestampPrev = beginTimestampFirst;
    long endTimestamp = beginTimestampFirst;
    // 通过topic获取之前同步的topic信息
    TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
    if (topicPublishInfo != null && topicPublishInfo.ok()) {
        boolean callTimeout = false;
        MessageQueue mq = null;
        Exception exception = null;
        SendResult sendResult = null;
        // 获取发送失败后的,总的发送次数
        int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
        int times = 0;
        String[] brokersSent = new String[timesTotal];
        for (; times < timesTotal; times++) {
            String lastBrokerName = null == mq ? null : mq.getBrokerName();
            //TODO 找到一个messageQueue,其实真 queueid每次会变化 ,从0-->3
            MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
            if (mqSelected != null) {
                mq = mqSelected;
                brokersSent[times] = mq.getBrokerName();
                try {
                    beginTimestampPrev = System.currentTimeMillis();
                    if (times > 0) {
                        //Reset topic with namespace during resend.
                        msg.setTopic(this.defaultMQProducer.withNamespace(msg.getTopic()));
                    }
                    long costTime = beginTimestampPrev - beginTimestampFirst;
                    if (timeout < costTime) {
                        callTimeout = true;
                        break;
                    }
                    // TODO 发送
                    sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);

上面代码会获取topic的信息, 计算总的发送的次数,封装MessageQueue,调用 sendKernelImpl()方法发送,继续跟进

private SendResult sendKernelImpl(final Message msg,
    final MessageQueue mq,
    final CommunicationMode communicationMode,
    final SendCallback sendCallback,
    final TopicPublishInfo topicPublishInfo,
    final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    long beginStartTime = System.currentTimeMillis();
    // 获取broker的名字
    String brokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(mq);
    // 获取broker的地址
    String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(brokerName);
    if (null == brokerAddr) {
        // 如果找不到 broker 的地址,就再一次从 nameServer 获取主题发布信息
        tryToFindTopicPublishInfo(mq.getTopic());
        brokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(mq);
        brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(brokerName);
    }
    ...

        // TODO 构架请求参数
        SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();
        requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
        requestHeader.setTopic(msg.getTopic());
        requestHeader.setDefaultTopic(this.defaultMQProducer.getCreateTopicKey());
        requestHeader.setDefaultTopicQueueNums(this.defaultMQProducer.getDefaultTopicQueueNums());
        requestHeader.setQueueId(mq.getQueueId());
        requestHeader.setSysFlag(sysFlag);
        requestHeader.setBornTimestamp(System.currentTimeMillis());
        requestHeader.setFlag(msg.getFlag());
        requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties()));
        requestHeader.setReconsumeTimes(0);
        requestHeader.setUnitMode(this.isUnitMode());
        requestHeader.setBatch(msg instanceof MessageBatch);
        
        ...
        // 判断发送模式
      switch (communicationMode) {
                    case SYNC:
                        long costTimeSync = System.currentTimeMillis() - beginStartTime;
                        if (timeout < costTimeSync) {
                            throw new RemotingTooMuchRequestException("sendKernelImpl call timeout");
                        }
                        // TODO 同步方式发送
                        sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
                            brokerAddr,
                            brokerName,
                            msg,
                            requestHeader,
                            timeout - costTimeSync,
                            communicationMode,
                            context,
                            this);

上面代码会查找broker的name和地址,封装请求头,最终都会调用接口 mQClientFactory.getMQClientAPIImpl().sendMessage(xxx) 继续跟进

public SendResult sendMessage(
    final String addr,
    final String brokerName,
    final Message msg,
    final SendMessageRequestHeader requestHeader,
    final long timeoutMillis,
    final CommunicationMode communicationMode,
    final SendCallback sendCallback,
    final TopicPublishInfo topicPublishInfo,
    final MQClientInstance instance,
    final int retryTimesWhenSendFailed,
    final SendMessageContext context,
    final DefaultMQProducerImpl producer
) throws RemotingException, MQBrokerException, InterruptedException {
    long beginStartTime = System.currentTimeMillis();
    RemotingCommand request = null;
    String msgType = msg.getProperty(MessageConst.PROPERTY_MESSAGE_TYPE);
    boolean isReply = msgType != null && msgType.equals(MixAll.REPLY_MESSAGE_FLAG);
    // 构造request命令
    if (isReply) {
        if (sendSmartMsg) {
            SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader);
            request = RemotingCommand.createRequestCommand(RequestCode.SEND_REPLY_MESSAGE_V2, requestHeaderV2);
        } else {
            request = RemotingCommand.createRequestCommand(RequestCode.SEND_REPLY_MESSAGE, requestHeader);
        }
    } else {
        if (sendSmartMsg || msg instanceof MessageBatch) {
            SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader);
            request = RemotingCommand.createRequestCommand(msg instanceof MessageBatch ? RequestCode.SEND_BATCH_MESSAGE : RequestCode.SEND_MESSAGE_V2, requestHeaderV2);
        } else {
            request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, requestHeader);
        }
    }
    request.setBody(msg.getBody());
    // 调用发送方法
    switch (communicationMode) {
        case ONEWAY:
            this.remotingClient.invokeOneway(addr, request, timeoutMillis);
            return null;
        case ASYNC:
            final AtomicInteger times = new AtomicInteger();
            long costTimeAsync = System.currentTimeMillis() - beginStartTime;
            if (timeoutMillis < costTimeAsync) {
                throw new RemotingTooMuchRequestException("sendMessage call timeout");
            }
            this.sendMessageAsync(addr, brokerName, msg, timeoutMillis - costTimeAsync, request, sendCallback, topicPublishInfo, instance,
                retryTimesWhenSendFailed, times, context, producer);
            return null;
        case SYNC:
            long costTimeSync = System.currentTimeMillis() - beginStartTime;
            if (timeoutMillis < costTimeSync) {
              //  throw new RemotingTooMuchRequestException("sendMessage call timeout");
            }
            return this.sendMessageSync(addr, brokerName, msg, timeoutMillis - costTimeSync, request);
        default:
            assert false;
            break;
    }

    return null;
}

上面会构建请求的request,调用接口sendMessageSync(addr, brokerName, msg, timeoutMillis - costTimeSync, request) 会使用内部的remotingClient对象,使用netty客户端,调用broker的netty服务端,代码入口在remotingClient.invokeSync(addr, request, timeoutMillis); 继续进入代码

public RemotingCommand invokeSync(String addr, final RemotingCommand request, long timeoutMillis)
    throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException {
    long beginStartTime = System.currentTimeMillis();
    //todo 获取和服务端addr之前的channel,如果channel为空,就建立连接,并把连接放入map中,供下一次使用
    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 the addr[" + addr + "] timeout");
            }
            // todo 调用
            RemotingCommand response = this.invokeSyncImpl(channel, request, timeoutMillis - costTime);

继续进入invokeSyncImpl(channel, request, timeoutMillis - costTime)方法

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

    try {
        /**
         * 如果判断发送超时
         * 1.new 出来 responseFuture对象中responseCommand为null
         * 2.发送成功,broker会返回结果,存储到 responseFuture中
         * 3.如果发送成功,sendRequestOK = true
         * 4.如果发送失败,sendRequestOK = false
         */
        final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
        this.responseTable.put(opaque, responseFuture);
        final SocketAddress addr = channel.remoteAddress();
        //TODO 调用netty的writeAndFLush发送数据
        channel.writeAndFlush(request).addListener((ChannelFutureListener) f -> {
            if (f.isSuccess()) {
                //根据listener,设置发送成功
                responseFuture.setSendRequestOK(true);
                return;
            }

            responseFuture.setSendRequestOK(false);
            responseTable.remove(opaque);
            responseFuture.setCause(f.cause());
            responseFuture.putResponse(null);
            log.warn("Failed to write a request command to {}, caused by underlying I/O operation failure", addr);
        });
        /**
         * 这里使用 responseFuture 等待 timeoutMillis秒后,获取responseCommand
         * 如果 responseCommand 为空,说明在时间超时内,broker没有返回结果
         * 如果
         */
        RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);
        // 如果 responseCommand 为空,说明在时间超时内,broker没有返回结果
        if (null == responseCommand) {
            // 显示发送成功,但是在超时时间内没有收到broker的返回信息,说明超时
            if (responseFuture.isSendRequestOK()) {
                throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis,
                    responseFuture.getCause());
            } else {
                //SendRequestOK=false,说明直接发送失败了
                throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
            }
        }

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

这里会使用netty底层是writeAndFlush方法,写入数据,同时等待设置好的超时时间后,根据responseCommandsendRequestOK字段,判断是超时还是发送失败

三.总结

produer启动后,会启动一些定时任务,创建一些定时任务,与nameserver和broker保持通信,通过topic的路由信息,获取broker的地址,在发送消息的时候,会拿到broker的地址,调用netty底层来发送数据,同时判断发送是否失败,如果失败的话,根据失败重试的次数,进行重发消息