RocketMQ源码解析之Producer

723 阅读6分钟

我们从RocketMQ的官网示例可以看到,使用producer发消息可以分为三个步骤:

  1. 创建一个producer实例
  2. 调用start方法启动producer
  3. 调用producer的send方法发送消息
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
        producer.setNamesrvAddr("34.87.32.119:9876");
        producer.start();

        for (int i = 0; i < 1000; i++)
            try {
                {
                    Message msg = new Message("TopicTest",
                        "TagA",
                        "OrderID188",
                        "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
                    SendResult sendResult = producer.send(msg);
                    System.out.printf("%s%n", sendResult);
                }

            } catch (Exception e) {
                e.printStackTrace();
            }

        producer.shutdown();

实例化

public DefaultMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook) {
        this.namespace = namespace;
        this.producerGroup = producerGroup;
        defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
    }

初始化groupname,nameserver,
初始化线程池defaultAsyncSenderExecutor,该线程池是给异步发消息用的
rpchook是远程调用的钩子,有两个方法为doBeforeRequest和doAfterResponse,在netty发送请求和拿到响应之后调用,这里主要用来做acl

start过程

  1. 在mQClientFactory注册producer和group信息
  2. 主要启动流程在MQClientInstance.start里,MQClientInstance是producer和consumer公用的对象,所以start方法里也包含了producer和consumer的启动过程;
  3. 代码写的很清楚了,pullMessageService和rebalanceService都是消费者的服务,startScheduledTask是启动定时任务,包括:
  • 定时更新nameSrvList,间隔2min
  • 定时从nameServer拉取路由信息,并更新到本地缓存,间隔30s,
  • 定时更新broker信息,清理下线的broker,并向所有在线的broker发送心跳,间隔30s
// Start request-response channel
this.mQClientAPIImpl.start();
// 启动定时任务
this.startScheduledTask();
//启动一个单线程用于阻塞的等待pullRequest,消费者拉取消息service
this.pullMessageService.start();
/* Start rebalance service 每20s一次,消费者专用
    第一次启动时,会初始化所有的<mq,pq>,并开始拉取消息
    具体实现看 RebalanceImpl.rebalanceByTopic
 */
this.rebalanceService.start();
//定时任务线程池只有一个后台线程
//定时更新nameSrvList
if (null == this.clientConfig.getNamesrvAddr()) {
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

        @Override
        public void run() {
            try {
                MQClientInstance.this.mQClientAPIImpl.fetchNameServerAddr();
            } catch (Exception e) {
                log.error("ScheduledTask fetchNameServerAddr exception", e);
            }
        }
    }, 1000 * 10, 1000 * 60 * 2, TimeUnit.MILLISECONDS);
}
//定时从nameServer拉取路由信息,并更新到本地缓存
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

    @Override
    public void run() {
        try {
            MQClientInstance.this.updateTopicRouteInfoFromNameServer();
        } catch (Exception e) {
            log.error("ScheduledTask updateTopicRouteInfoFromNameServer exception", e);
        }
    }
}, 10, this.clientConfig.getPollNameServerInterval(), TimeUnit.MILLISECONDS);
//定时更新broker信息,清理下线的broker,并向所有在线的broker发送心跳
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

    @Override
    public void run() {
        try {
            MQClientInstance.this.cleanOfflineBroker();
            MQClientInstance.this.sendHeartbeatToAllBrokerWithLock();
        } catch (Exception e) {
            log.error("ScheduledTask sendHeartbeatToAllBroker exception", e);
        }
    }
}, 1000, this.clientConfig.getHeartbeatBrokerInterval(), TimeUnit.MILLISECONDS);
//消费者定时将本地缓存的offset持久化(并发消费则交给broker持久化,顺序消费就本地文件存储)
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

    @Override
    public void run() {
        try {
            MQClientInstance.this.persistAllConsumerOffset();
        } catch (Exception e) {
            log.error("ScheduledTask persistAllConsumerOffset exception", e);
        }
    }
}, 1000 * 10, this.clientConfig.getPersistConsumerOffsetInterval(), TimeUnit.MILLISECONDS);
//定时调整消费线程池的线程数量(根据消息积累情况),目前代码被注释了,没用可以略过
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

    @Override
    public void run() {
        try {
            MQClientInstance.this.adjustThreadPool();
        } catch (Exception e) {
            log.error("ScheduledTask adjustThreadPool exception", e);
        }
    }
}, 1, 1, TimeUnit.MINUTES);

发送消息

同步发送

this.makeSureStateOK();
Validators.checkMessage(msg, this.defaultMQProducer);

final long invokeID = random.nextLong();
long beginTimestampFirst = System.currentTimeMillis();
long beginTimestampPrev = beginTimestampFirst;
long endTimestamp = beginTimestampFirst;
/*获取topic的路由信息,先从本地缓存获取,没有则从nameserver获取,
 *路由信息里存有topic对应的broker,每个broker有多个mq(默认4),每次从中选择一个发送消息
 */
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();
        /*
         * 选择一个消息队列发送消息,
         * 1.没有开启容错策略时(sendLatencyFaultEnable=false),轮训mqlist,采用递增取模选择一个mq
         * 2.开启容错策略时,会根据上次发消息的时候确定一个退避时间,在退避时间内该broker不能再发消息,必须换一个broker
         */
        MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
        if (mqSelected != null) {
            mq = mqSelected;
            //将当前发送的brokername记录到数组中,备后面异常信息用
            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;
                }
                //发送消息的核心实现,网络请求由netty实现,这部分解析在后面
                sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
                endTimestamp = System.currentTimeMillis();
                //容错策略,退避算法,将本次发送花费时间和退避时间存入本地缓存
                this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
                switch (communicationMode) {
                    case ASYNC:
                        return null;
                    case ONEWAY:
                        return null;
                    case SYNC:
                        if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
                            //当返回失败时,根据容错策略,换一个broker
                            if (this.defaultMQProducer.isRetryAnotherBrokerWhenNotStoreOK()) {
                                continue;
                            }
                        }

                        return sendResult;
                    default:
                        break;
                }
            }

异步发送

//构造方法中初始化了
ExecutorService executor = this.getAsyncSenderExecutor();
try {
    executor.submit(new Runnable() {
        @Override
        public void run() {
            long costTime = System.currentTimeMillis() - beginStartTime;
            if (timeout > costTime) {
                try {
                    //与其他方式调用同样的方法
                    sendDefaultImpl(msg, CommunicationMode.ASYNC, sendCallback, timeout - costTime);
                } catch (Exception e) {
                    sendCallback.onException(e);
                }
            } else {
                sendCallback.onException(
                        new RemotingTooMuchRequestException("DEFAULT ASYNC send call timeout"));
            }
        }

    });
} catch (RejectedExecutionException e) {
    throw new MQClientException("executor rejected ", e);
}

单向发送

try {
    this.sendDefaultImpl(msg, CommunicationMode.ONEWAY, null, this.defaultMQProducer.getSendMsgTimeout());
} catch (MQBrokerException e) {
    throw new MQClientException("unknown exception", e);
}

rocketmq支持顺序发送、批量发送、事务消息,下面具体解析这些功能都是怎么实现的

顺序发送

RocketMQ实现顺序发送,其原理就是发送至一个指定的mq,并且使用同步发送,必须等待前一个消息发送成功了才继续发送,RocketMQ的topic内的队列机制,可以保证存储满足FIFO,这样就保证了发送有序。消息消费有序靠业务系统自己保证,rocketMq其实提供了顺序消费的实现,在消费端可以自己选择。

public SendResult send(Message msg, MessageQueueSelector selector, Object arg)
        throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        msg.setTopic(withNamespace(msg.getTopic()));
        return this.defaultMQProducerImpl.send(msg, selector, arg);
    }

MessageQueueSelector就是自定义的选择器,系统提供了三个SelectMessageQueueByHash,SelectMessageQueueByMachineRoom,SelectMessageQueueByRandom。假如我们发送的是订单消息,那就可以用订单号hash取模选择mq,这样就能保证同一个订单号发送到同一个mq。

批量发送

将消息列表构造成MessageBatch对象交给同步发送接口,内部核心实现方法构造的网络请求不一样。

private MessageBatch batch(Collection<Message> msgs) throws MQClientException {
        MessageBatch msgBatch;
        try {
            msgBatch = MessageBatch.generateFromList(msgs);
            for (Message message : msgBatch) {
                Validators.checkMessage(message, this);
                MessageClientIDSetter.setUniqID(message);
                message.setTopic(withNamespace(message.getTopic()));
            }
            msgBatch.setBody(msgBatch.encode());
        } catch (Exception e) {
            throw new MQClientException("Failed to initiate the MessageBatch", e);
        }
        msgBatch.setTopic(withNamespace(msgBatch.getTopic()));
        return msgBatch;
    }

事务消息

public TransactionSendResult sendMessageInTransaction(final Message msg,
                                                          final LocalTransactionExecuter localTransactionExecuter, final Object arg)
            throws MQClientException {
        TransactionListener transactionListener = getCheckListener();
        if (null == localTransactionExecuter && null == transactionListener) {
            throw new MQClientException("tranExecutor is null", null);
        }
        Validators.checkMessage(msg, this.defaultMQProducer);

        SendResult sendResult = null;
        //事务消息属性设置,标识为prepare msg
        MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
        MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
        try {
            //调用同步发送接口,将消息发送至broker,该消息被标记为prepare,当该消息没有对应的确认消息时,消费者pull时会被过滤掉
            sendResult = this.send(msg);
        } catch (Exception e) {
            throw new MQClientException("send message Exception", e);
        }

        LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
        Throwable localException = null;
        switch (sendResult.getSendStatus()) {
            case SEND_OK: {
                try {
                    if (sendResult.getTransactionId() != null) {
                        msg.putUserProperty("__transactionId__", sendResult.getTransactionId());
                    }
                    //
                    String transactionId = msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);
                    if (null != transactionId && !"".equals(transactionId)) {
                        msg.setTransactionId(transactionId);
                    }
                    //老接口已废除
                    if (null != localTransactionExecuter) {
                        localTransactionState = localTransactionExecuter.executeLocalTransactionBranch(msg, arg);
                    }
                    //实际走这个分支,如果prepare消息发送成功,则开始调用transactionListener的本地事务
                    else if (transactionListener != null) {
                        log.debug("Used new transaction API");
                        localTransactionState = transactionListener.executeLocalTransaction(msg, arg);
                    }
                    if (null == localTransactionState) {
                        localTransactionState = LocalTransactionState.UNKNOW;
                    }

                    if (localTransactionState != LocalTransactionState.COMMIT_MESSAGE) {
                        log.info("executeLocalTransactionBranch return {}", localTransactionState);
                        log.info(msg.toString());
                    }
                } catch (Throwable e) {
                    log.info("executeLocalTransactionBranch exception", e);
                    log.info(msg.toString());
                    localException = e;
                }
            }
            break;
            case FLUSH_DISK_TIMEOUT:
            case FLUSH_SLAVE_TIMEOUT:
            case SLAVE_NOT_AVAILABLE:
                localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE;
                break;
            default:
                break;
        }

        try {
            //发送confirm消息(调用单向发送接口),将本地事务的执行状态发送给broker,具体逻辑由broker内部处理
            this.endTransaction(sendResult, localTransactionState, localException);
        } catch (Exception e) {
            log.warn("local transaction execute " + localTransactionState + ", but end broker transaction failed", e);
        }

        TransactionSendResult transactionSendResult = new TransactionSendResult();
        transactionSendResult.setSendStatus(sendResult.getSendStatus());
        transactionSendResult.setMessageQueue(sendResult.getMessageQueue());
        transactionSendResult.setMsgId(sendResult.getMsgId());
        transactionSendResult.setQueueOffset(sendResult.getQueueOffset());
        transactionSendResult.setTransactionId(sendResult.getTransactionId());
        transactionSendResult.setLocalTransactionState(localTransactionState);
        //返回的SendStatus是prepare的状态,不是最终消息的状态,可以根据localTransactionState判定
        return transactionSendResult;
    }

延时发送

现在RocketMq并不支持任意时间的延时,需要设置几个固定的延时等级,从1s到2h分别对应着等级1到18,也就是给message设置DelayTimeLevel。

DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup");
         // Launch producer
         producer.start();
         int totalMessagesToSend = 100;
         for (int i = 0; i < totalMessagesToSend; i++) {
             Message message = new Message("TestTopic", ("Hello scheduled message " + i).getBytes());
             // This message will be delivered to consumer 10 seconds later.
             message.setDelayTimeLevel(3);
             // Send the message
             producer.send(message);
         }
    
         // Shutdown producer after use.
         producer.shutdown();

sql过滤消息

通过给message设置自定义属性,消费端根据这些自定义属性去匹配

Message msg = new Message("TopicTest",
    tag,
    ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)
);
// Set some properties.
msg.putUserProperty("a", String.valueOf(i));