1、类层次结构

监控rocketmq消费线程数 rocketmq消费者线程数设置_消息队列

2、producer的启动

首先设置组,及NameServer,然后调用start启动。启动关键逻辑主要在MQClientInstance中。

(1)启动NettyRemotingClient,创建请求响应处理channel

(2)启动周期性任务

每2分钟获取NameServerAddr

默认每30秒从nameserver中拉取topic信息

默认每30秒清除离线的broker,发送心跳给所有的broker

默认每5秒持久化consumeroffset

默认每1分钟调节核心线程数

(3)开启pullmessage线程服务

(4)开启rebanlance线程服务

public void start() throws MQClientException {

        synchronized (this) {
            switch (this.serviceState) {
                case CREATE_JUST:
                    this.serviceState = ServiceState.START_FAILED;
                    // If not specified,looking address from name server
                    if (null == this.clientConfig.getNamesrvAddr()) {
                        this.mQClientAPIImpl.fetchNameServerAddr();
                    }
                    // Start request-response channel
                    this.mQClientAPIImpl.start();
                    // Start various schedule tasks
                    this.startScheduledTask();
                    // Start pull service
                    this.pullMessageService.start();
                    // Start rebalance service
                    this.rebalanceService.start();
                    // Start push service
                    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;
            }
        }
    }

private void startScheduledTask() {
        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);
        }

        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);

        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);

        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);
    }

其序列图为

监控rocketmq消费线程数 rocketmq消费者线程数设置_java_02

3、producer发送消息

支持同步、异步、单向发送三种类型。主要的发送逻辑在DefaultMqProducerImpl类sendDefaultImpl方法中,首先会调用tryToFindTopicPublishInfo查找得到topic的发布信息,然后调用MQFaultStrategy类选择一个消息队列,如果开启发送消息延时容错,会调用LatencyFaultTolerance选择队列,否则直接调用TopicPublishInfo选择队列,然后调用sendKernalImpl发送消息。

其时序图为

监控rocketmq消费线程数 rocketmq消费者线程数设置_SYS_03

4、事务消息

基于两阶段提交和定时事务状态回查来决定消息最终是提交还是回滚。

producer调用rocketmq消息发送接口,发送状态为preprare的消息。消息发送成功后,会调用本地事件监听程序,记录消息的本地事务状态,该相关标记与本地业务操作同属一个事务,确保消息发送与本地事务的原子性。

broker在收到类型为prepare消息时,会首先备份消息的原主题与原消息消费队列,然后将消息存储在主题 为RMQ_SYS_TRANS_HALF_TOPIC的消息消费队列中。

broker会开启一个线程TransactionalMessageCheckService,来消费RMQ_SYS_TRANS_HALF_TOPIC的消息,向消息发送端发起事务状态回查,producer根据保存的事务状态回馈消息服务器事务的状态。如果是提交或回滚,则broker提交或回滚消息。如果是未知,待下一次回查,broker允许设置一条消息的回查间隔与回查次数,如果在超过回查次数后依赖无法获知消息的事务状态,则默认回滚消息。

其处理交互图为

监控rocketmq消费线程数 rocketmq消费者线程数设置_java_04

4.1 消息发送

事务消息发送是通过TransactionMQProcuder,其类结构为

监控rocketmq消费线程数 rocketmq消费者线程数设置_监控rocketmq消费线程数_05

TransactionListener transactionListener事务监听器,主要定义实现本地事务状态执行、本地事务状态回查

ExecutorService executorService:事务状态回查异步执行线程池。

消息发送步骤为

  1. 添加属性PROPERTY_TRANSCATION_PREPARED和PROPERTY_PRODUCER_GROUP,分别表示消息为prepare消息,消息所属消息生产者组。设置消息生产者组的目的是在查询事务消息本地事务状态时,从该生产者组中随机选择一个消息生产者即可,然后通过同步单向调用方式发送消息。
  2. 根据消息发送结果执行相应操作,如果发送成功,执行TransactionListener#executeLocalTransaction方法,记录事务消息的本地事务状态。如果发送失败,设置事务状态为ROLLBACK_MESSAGE
  3. 结束事务,根据上步的事务状态进行提交、回滚或暂时不处理事务。

broker在收到消息存储请求时,SendMessageProcessor#asyncSendMessage,如果消息为prepare消息,执行TransactionalMessageService#asyncPrepareMessage,否则执行普通消息的存储流程。


事务消息备份消息的原主题与原消息消费队列,然后将主题修改为RMQ_SYS_TRANS_HALF_TOPIC,消费队列修改为0,然后消息按照普通消息存储在commitlog文件进而转发到RMQ_SYS_TRANS_HALF_TOPIC主题对应的消息消费队列。通过TransactionalMessageCheckService线程定时去消费该主题,然后将该消息在满足特定条件下恢复消息主题 ,进而被消费者消费。

4.2 事务提交或者回滚

事务提交或者回滚是通过DefaultMQProducerImpl#endTransaction,根据消息的消息队列获取broker的ip和端口号,然后发送结束事务命令,通过本地执行事务的状态分别提交、回滚或者不处理。

brokere的结束事务处理器为EndTranscationPRocessor。如果是提交事务

  • 首先从结束事务请求命令中获取消息的物理像偏移量(commitLogOffset),其实现是由TransactionalMessageService#commitMessage完成
  • 恢复消息主题、消费队列,构造新的消息对象,由EndTransactionProcessor#endMessageTransaction完成。
  • 将消息再次存储在commitlog文件中,此时的消息主题则为业务方发送的消息,将被转发到对应的消息消费队列,供消息消费者消费。由EndTransactionProcessor#sendFinalMessage完成。
  • 消息存储后,删除prepare消息,并不是真正的删除,将prepare消息存放到RMQ_SYS_TRANS_OP_HALF_TOPIC主题中,表示该事务已经处理过(提交或者回滚),为未处理的事务进行事务回查提供查找依据。

4.3 事务消息回查

其相关类图为

监控rocketmq消费线程数 rocketmq消费者线程数设置_消息队列_06

事务消息存储在消息服务器时主题被替换为RMQ_SYS_TRANS_HALF_TOPIC,执行完本地事务返回本地事务状态为UN_KNOW时,结束事务时不做任何处理,而是通过事务状态定时回查以得到发送端明确的事务操作(提交事务或者回滚事务)。通过TransactionalMessageCheckService线程定时检测RMQ_SYS_TRANS_HALF_TOPIC主题中的消息,回查消息的事务状态,默认检测频率为1分钟。

transactionTimeOut:事务过期时间,只有当消息的存储 时间加上过期时间大于系统当前时间,才对消息执行事务状态回查,否则在下一次周期中执行事务回查操作。

transactionCheckMax:事务回查最大检测次数,如果超过最大检测次数还是无法获知消息的事务状态,broker将不会继续对消息进行事务回查,而是直接丢弃,即回滚事务。

其实现类为TransactionalMessageServiceImpl#check,获取RMQ_SYS_TRANS_HALF_TOPIC主题下的所有消息队列,根据事务消息队列获取与之对应的消息队列,其实就是获取已处理消息的消息消息队列,其主题为RMQ_SYS_TRANS_OP_HALF_TOPIC。fillOpRemoveMap根据当前的处理进度依次从已处理队列拉取32条消息,方便判断当前处理的消息是否已经处理过,如果处理过㞱则无须再次发送事务状态回查请求,避免重复发送事务回查请求。

在执行事务回查之前,把该消息存储在commitlog文件,新的消息设置最新的物理偏移量。因为下文 的发送事务消息是异步处理的,无法立刻知道其处理结果,为了简化prepare消息队列和处理队列的消息消费进度处理,先存储,然后消费进度向前推进,重复发送的消息在事务回查之前会判断是否处理过。另外一个目的就是需要修改消息的检查次数,rocketmq的存储设计采用顺序写,支修改已存储的消息,其性能无法高性能。

发送具体的事务回查命令,使用线程池来异步发送回查消息,为了回查消息进度保存的简化,只要发送了回查消息,当前回查进度会向前推进,如果回查失败,上一步新增的消息将可以再次发送回查消息。首先构造事务状态回查请求消息,核心参数消息offsetId,消息id(索引),消息事务id,事务消息队列中的偏移量,消息主题,消息队列,然后根据消息的生产组,从中随机选择一个消息发送者。最后向消息发送者发送事务回查命令。

事务回查命令的处理者为ClientRemotingProcessor#processRequest方法,将任务提交到TransactionMQProducer的线程池中执行,最终交给TransactinListener#checkLocalTransaction方法,返回事务状态,根据事务状态调用 processTransactionState

5、消息id

在发送消息时,会设置属性PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX,如果没有设置,通过MessageClientIDSetter.setUniqID来设置。

监控rocketmq消费线程数 rocketmq消费者线程数设置_SYS_07