RocketMQ源碼解析之Producer啓動

閱讀須知

文章中使用/* */註釋的方法會做深入分析

正文

使用RocketMQ發送普通消息時,一般我們會進行如下配置:

<bean id="rocketmqProducer" class="org.apache.rocketmq.client.producer.DefaultMQProducer" init-method="start"
          destroy-method="shutdown">
    <property name="producerGroup" value="${rocketmq.producer.group}"/>
    <property name="namesrvAddr" value="${rocketmq.nameserver.address}"/>
</bean>

我們看到配置中 init-method 配置了 start 方法,所以在 bean 的初始化過程中會調用 DefaultMQProducer.start 方法:
DefaultMQProducer:

public void start() throws MQClientException {
    this.setProducerGroup(withNamespace(this.producerGroup));
    /* producer啓動 */
    this.defaultMQProducerImpl.start();
    // TraceDispatcher是異步傳輸數據接口
    if (null != traceDispatcher) {
        try {
            traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
        } catch (MQClientException e) {
            log.warn("trace dispatcher start failed ", e);
        }
    }
}

DefaultMQProducerImpl:

public void start() throws MQClientException {
    this.start(true);
}

DefaultMQProducerImpl:

public void start(final boolean startFactory) throws MQClientException {
    switch (this.serviceState) {
        case CREATE_JUST:
            this.serviceState = ServiceState.START_FAILED;
            // 檢查配置,主要檢查配置的 producerGroup 的格式、長度、是否與默認 producerGroup 衝突等
            this.checkConfig();
            if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
            	// 將實例名稱轉換爲進程ID
                this.defaultMQProducer.changeInstanceNameToPID();
            }
            /* 獲取並創建 client */
            this.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQProducer, rpcHook, this.eventLoopGroup, this.eventExecutorGroup);
            // 註冊 producer,將 group 和 producer 的映射放入 producerTable 中
            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());
            if (startFactory) {
            	/* 啓動 */
                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();
}

MQClientManager:

public MQClientInstance getAndCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) {
	// 拼接 clientId
    String clientId = clientConfig.buildMQClientId();
    // 初始化 MQClientInstance 內部的成員屬性
    MQClientInstance instance = this.factoryTable.get(clientId);
    if (null == instance) {
        instance =
            new MQClientInstance(clientConfig.cloneClientConfig(),
                this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook);
        // 保存 clientId 和 client 對象的映射
        MQClientInstance prev = this.factoryTable.putIfAbsent(clientId, instance);
        if (prev != null) {
            instance = prev;
            log.warn("Returned Previous MQClientInstance for clientId:[{}]", clientId);
        } else {
            log.info("Created new MQClientInstance for clientId:[{}]", clientId);
        }
    }
    return instance;
}

關於MQ Client的啓動流程,Client端的Producer和Consumer都是複用的同樣的流程,所以後面的流程對於Producer和Consumer是通用的。
MQClientInstance:

public void start() throws MQClientException {
    synchronized (this) {
        switch (this.serviceState) {
            case CREATE_JUST:
                this.serviceState = ServiceState.START_FAILED;
                // 如果未指定具體的NameServer地址,則從NameServer服務器查找地址
                if (null == this.clientConfig.getNamesrvAddr()) {
                    this.mQClientAPIImpl.fetchNameServerAddr();
                }
                /* 啓動netty */
                this.mQClientAPIImpl.start();
                /* 啓動一些定時任務 */
                this.startScheduledTask();
                // 啓動拉取消息服務
                this.pullMessageService.start();
                // 啓動重新平衡服務
                this.rebalanceService.start();
                // 啓動推送消息服務
                this.defaultMQProducer.getDefaultMQProducerImpl().start(false);
                log.info("the client factory [{}] start OK", this.clientId);
                this.serviceState = ServiceState.RUNNING;
                break;
            case RUNNING:
                break;
            case SHUTDOWN_ALREADY:
                break;
            case START_FAILED:
                throw new MQClientException("The Factory object[" + this.getClientId() + "] has been created before, and failed.", null);
            default:
                break;
        }
    }
}

MQClientAPIImpl:

public void start() {
    this.remotingClient.start();
}

NettyRemotingClient:

public void start() {
    this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(
        nettyClientConfig.getClientWorkerThreads(),
        new ThreadFactory() {
            private AtomicInteger threadIndex = new AtomicInteger(0);
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "NettyClientWorkerThread_" + this.threadIndex.incrementAndGet());
            }
        });
    Bootstrap handler = this.bootstrap.group(this.eventLoopGroupWorker).channel(NioSocketChannel.class)
        .option(ChannelOption.TCP_NODELAY, true)
        .option(ChannelOption.SO_KEEPALIVE, false)
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyClientConfig.getConnectTimeoutMillis())
        .option(ChannelOption.SO_SNDBUF, nettyClientConfig.getClientSocketSndBufSize())
        .option(ChannelOption.SO_RCVBUF, nettyClientConfig.getClientSocketRcvBufSize())
        .handler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                if (nettyClientConfig.isUseTLS()) {
                    if (null != sslContext) {
                        pipeline.addFirst(defaultEventExecutorGroup, "sslHandler", sslContext.newHandler(ch.alloc()));
                        log.info("Prepend SSL handler");
                    } else {
                        log.warn("Connections are insecure as SSLContext is null!");
                    }
                }
                pipeline.addLast(
                    defaultEventExecutorGroup,
                    new NettyEncoder(),
                    new NettyDecoder(),
                    new IdleStateHandler(0, 0, nettyClientConfig.getClientChannelMaxIdleTimeSeconds()),
                    new NettyConnectManageHandler(),
                    // 註冊請求處理器
                    new NettyClientHandler());
            }
        });
    this.timer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            try {
            	// 定期調用此方法以掃描和終止已棄用的請求
                NettyRemotingClient.this.scanResponseTable();
            } catch (Throwable e) {
                log.error("scanResponseTable exception", e);
            }
        }
    }, 1000 * 3, 1000);
    if (this.channelEventListener != null) {
        this.nettyEventExecutor.start();
    }
}

MQClientInstance:

private void startScheduledTask() {
	// 定時獲取NameServer地址
    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);
    }
    // 定時更新topic路由信息,主要通過向 NameServer 發送 GET_ROUTEINTO_BY_TOPIC 請求來獲取最新的 topic 路由信息,然後更新內部消費者的訂閱信息和生產者的發佈信息
    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發送心跳
    // 同樣的,這裏客戶端的生產者和消費者數據都會封裝到心跳數據中通過 HEART_BEAT 命令統一發送給Broker,Broker負責處理心跳清楚的的處理器爲 ClientManageProcessor
    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);
    // 定時持久化消費偏移量,這裏的持久化分兩種,一種是寫入到本地文件,另一種是更新到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);
}

到這裏,Producer的啓動流程就分析完成了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章