RocketMQ源码阅读之生产者

本文深入剖析RocketMQ生产者创建、启动流程及消息发送机制,涵盖同步与异步发送,帮助理解生产者核心代码逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


目标:本文的目的是梳理RocketMQ生产者从创建到启动及最终发送的源码主体逻辑,掌握生产者代码的主体流程。

生产者创建及启动流程

1. 创建生产者

DefaultMQProducer producer = new DefaultMQProducer("hr_order_group");

在创建生产者的时候,内部创建了生产者的业务流程实现类DefaultMQProducerImpl ,这个类负责对接生产者的发送等行为的实现,创建的时候初始化了发送使用的线程池。

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

2. 启动生产者

入口:producer.start() 使用内部的实现类启动生产者,如果开启了消息轨迹,则同时开启消息轨迹追踪。

public void start() throws MQClientException {
        //设置生产者所属组: 会拼接组所属的名称空间,形式  namespace%producerGroup
        this.setProducerGroup(withNamespace(this.producerGroup));
        //启动默认的生产者实现类
        this.defaultMQProducerImpl.start();
        //判断是否开启了生产者消息轨迹,开启则启动轨迹跟跟踪
        if (null != traceDispatcher) {
            try {
                traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
            } catch (MQClientException e) {
                log.warn("trace dispatcher start failed ", e);
            }
        }
    }

2.1 defaultMQProducerImpl.start() 启动生产者

public void start(final boolean startFactory) throws MQClientException {
   switch (this.serviceState) {
        case CREATE_JUST:
            this.serviceState = ServiceState.START_FAILED;
            //校验配置:主要是对producerGroup的长度,字符等校验
            this.checkConfig();
            //如果不是客户端内置的生产者组则设置生产者的实例名称
            if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
                //设置生产者的实例名称,内部会判断是不是默认的实例名称,如果是,则将实例名称设置为当前进程的pid,所以同一JVM中创建多个生产者要设置不同的实例名称
                this.defaultMQProducer.changeInstanceNameToPID();
            }
                //创建客户端实例
            this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
            //注册生产者,缓存本地:实际上就是 producerGroup映射 Producer的缓存
            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);
            }
            //缓存createTopicKey与发布信息的映射, createTopicKey 默认值是 TBW102
            this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());

            if (startFactory) {
                //TODO 入口:真正启动生产者的方法
                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:
            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();
	//启动定时任务去除过期的请求
    this.timer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            try {
                RequestFutureTable.scanExpiredRequest();
            } catch (Throwable e) {
                log.error("scan RequestFutureTable exception", e);
            }
        }
    }, 1000 * 3, 1000);
}

启动生产者的方法代码如上,最主要的流程代码是

this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);

第一行代码创建了一个客户端实例 MQClientInstance ,在RocketMQ中,无论生产者还是消费者,对于Broker服务端都是客户端,所以通过MQClientInstance抽象进行,通过 MQClientManager 通过factoryTable缓存进行所有实例的管理。
在这里插入图片描述
创建MQClientInstance 实例的时候创建了很多服务,如上图,不管消费者还是生产者,上面的逻辑都是一致的,但是是注册的逻辑不一样。

调用客户端实例的start方法启动所有的服务,方法体如下:

public void start() throws MQClientException {
    synchronized (this) {
        switch (this.serviceState) {
            case CREATE_JUST:
                this.serviceState = ServiceState.START_FAILED;
                //TODO 获取远程设置的namserver地址
                //没有设置nameservere地址,则默认请求: http://{jmenv.tbsite.net}:8080/rocketmq/{nsaddr}{-unitName}?nofix=1 获取,
                // 可以设置系统属性修改domain和url后缀,unitName可以在procducer中设置属性值
                //String wsDomainName = System.getProperty("rocketmq.namesrv.domain", "jmenv.tbsite.net");
                //String wsDomainSubgroup = System.getProperty("rocketmq.namesrv.domain.subgroup", "nsaddr");
                if (null == this.clientConfig.getNamesrvAddr()) {
                    this.mQClientAPIImpl.fetchNameServerAddr();
                }
                //启动客户端负责api调用的线程: 初始化netty相关组件,启动定时任务守护线程间隔1去响应缓存的请求:包括异步拉取消息,异步发送消息 ,查询消息的请求 responseTable
                //定时任务的目的是将失效的消息移除缓存,然后执行回调
                this.mQClientAPIImpl.start();
                //启动定时任务线程,负责定时任务调度
                this.startScheduledTask();
                //启动消息拉取服务线程,负责消息的拉取,调用PullMessageService的run方法 pullRequestQueue pullMessage(pullRequest)
                this.pullMessageService.start();
                //启动负载均衡线程:守护线程,每执行一次等待20秒后再执行,调本类中的doRebalance方法,同时触发一个拉取消息的请求  consumerTable缓存操作
                //也就是说
                this.rebalanceService.start();
                //启动(内部生产者)
                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;
        }
    }
}

上面启动的四个服务中 pullMessageService 和 pullMessageService 这个两个服务是跟消费者相关的,生产者启动线程后会一直阻塞,因为没有数据可以消费。这个地方的设计个人没有完全明白,为什么不剥离出来消费者启动的时候才启动这两个服务。

定时任务线程startScheduledTask() 启动的任务如下:
在这里插入图片描述
重点查看 客户端负责api调用的线程 mQClientAPIImpl.start() ,这个线程负责处理发送完成后的异步消息。同步消息是阻塞的,所以不需要这个线程处理。方法代码如下:
在这里插入图片描述
这里先是创建了一个长连接的Netty客户端,然后启动了一个守护线程作定时任务,去扫描缓存的超时的响应信息,然后执行回调的方法。
在这里插入图片描述
结合图片,综合说明流程: 当用户调用生产者的异步发送消息接口时候,发送的消息会交给线程池处理,处理后会调用Netty的API接口发送给Broker ,由于调用时异步的,所以处理完成后会回调一个方法将响应的消息放到 responseTable这个HashMap中,这个定时任务就是除了响应后的消息,回调用户的 SendCallback方法。具体结合下面的发送消息流程

生产者发送消息流程

发送消息整个过程中最复杂的就是发送异步消息,涉及很多异步调用后回调。发送同步消息由于是阻塞的,所以流程相对简单。发送顺序消息,则只是发送请调用自定义的获取发送队列的方法,后面逻辑与前两种保持一致。

发送异步消息

producer.send(msg, new SendCallback() {
    @Override
    public void onSuccess(SendResult sendResult) {
        System.out.println("onSuccess");
    }
    @Override
    public void onException(Throwable e) {
    }
});

这个异步发送的方法,跟踪后发现最终是将任务交给线程池处理,同时将回调的实现类传递了,如下:
在这里插入图片描述
继续跟踪代码调用,在DefaultMQProducerImpl类中有 private SendResult sendKernelImpl 这个方法,通过方法名可知,这是发送消息调用的核心方法,发送的消息都会调用这个方法。
在这里插入图片描述
如上图,这个方法中根据发送的模式,匹配不同的发送方式,异步消息调用的了异步的发送方式。
在这里插入图片描述
这里最后调用了netty客户端异步调用的方法,异步调用设置了回调函数,同时将我们自定的回调函数设置到回调函数中进行调用。
查看netty接口定义类如下:
在这里插入图片描述
最后跟踪代码发现最后调用如下:
在这里插入图片描述
发送成功后回调会修改消息状态,然后等到设置的超时时间过了,上面启动的时候提到的定时任务会将这个缓存剔除,然后回调我们的方法。
发送失败则是立即调用剔除缓存,进行回调,到这里整个发送流程形成闭环。

发送同步消息

发送同步消息在发送消息的核心方法后都是一样的,只是根据发送模式匹配不同的发送方式而已。调用的方式则是同步阻塞,不适用线程池的方式,所以逻辑也相对简单,就不赘述了。

由于源码中涉及很多细节,方法调用链上比较复杂,有什么疑问可以留言一起探讨。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值