4、RocketMQ中Producer的启动(四)

在NameServer与Broker启动之后,我们就可以来创建Producer进行生产消息,客户端常用的生产类是DefaultMQProducer,我们启动消费者其实就是调用该类的start方法。
在这里插入图片描述
初始化逻辑

通过构建一起DefaultMQProducer类来实现初始化,查看源码我们可以得到DefaultMQProducer类的构造方法存在很多类型,但是最终会去执行最后一个构造方法。


public DefaultMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook) {
    // 命名空间
    this.namespace = namespace;
    // 生产者组
    this.producerGroup = producerGroup;
    // 创建DefaultMQProducerImpl实例,负责发送消息
    defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
}

该构造函数主要就是用来指定命名空间,生产者组以及用来处理消息的发送的DefaultMQProducerImpl类,该类里面包含消息处理的所有方法。


public DefaultMQProducerImpl(final DefaultMQProducer defaultMQProducer, RPCHook rpcHook) {
    this.defaultMQProducer = defaultMQProducer;
    this.rpcHook = rpcHook;
  // 异步发送消息的线程池队列
    this.asyncSenderThreadPoolQueue = new LinkedBlockingQueue<Runnable>(50000);
    // 初始化线程
    this.defaultAsyncSenderExecutor = new ThreadPoolExecutor(
        Runtime.getRuntime().availableProcessors(),
        Runtime.getRuntime().availableProcessors(),
        1000 * 60,
        TimeUnit.MILLISECONDS,
        this.asyncSenderThreadPoolQueue,
        new ThreadFactory() {
            private AtomicInteger threadIndex = new AtomicInteger(0);

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "AsyncSenderExecutor_" + this.threadIndex.incrementAndGet());
            }
        });
}

主要就是初始化一个异步发送消息的线程池。

启动逻辑

生产者的启动逻辑调用start方法,其实就是去调用DefaultMQProducerImpl的start方法。


public void start(final boolean startFactory) throws MQClientException {
    // 根据serviceState的状态来判断是否重复启动
    switch (this.serviceState) {
        case CREATE_JUST:
            this.serviceState = ServiceState.START_FAILED;
            // 检查配置信息
            this.checkConfig();

            if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
                this.defaultMQProducer.changeInstanceNameToPID();
            }
            // 客户端核心管理组件的初始化mQClientFactory
            this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
            // 往核心组件中注册一个Producer,往hashMap插入数据
            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);
            }
            // 注册topicPublishInfoTable
            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;
    }

    this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();

    RequestFutureHolder.getInstance().startScheduledTask(this);
}

首先使用checkConfig去检查配置信息,比如生产者组等是否符合规范,并通过getOrCreateMQClientInstance方法根据clientId获取mQClientFactory的实例。

将生产者注册到MQClientInstance实例的ProducerTable当中,同时注册注册topicPublishInfoTable对象。

启动MQClientInstance实例,调用其start方法用于初始化netty服务,定时任务,拉取消息服务等。

最后主动调用sendHeartbeatToAllBrokerWithLock方法发送心跳信息给所有Broker并且移除超时的request请求,执行异常回调。

MQClientInstance的启动

MQClientInstance类封装了RockerMQ底层通讯处理的API,Producer与Consumer类都会使用到这个类,它是Producer,Consumer与NameServer,Broker打交道的网络通道,这个类可以理解成一个工厂,是对消费者与生产者以及控制台三者的一个合集,内部封装了netty客户端,消息的生成,消费和负载均衡的实现类等。

MQClientInstance类的初始化

public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String clientId, RPCHook rpcHook) {
    this.clientConfig = clientConfig;
    this.instanceIndex = instanceIndex;
    // 创建netty客户端配置类
    this.nettyClientConfig = new NettyClientConfig();
    this.nettyClientConfig.setClientCallbackExecutorThreads(clientConfig.getClientCallbackExecutorThreads());
    this.nettyClientConfig.setUseTLS(clientConfig.isUseTLS());
    // 客户端请求处理器
    this.clientRemotingProcessor = new ClientRemotingProcessor(this);
    // 创建客户端远程通信API实现类的实例
    this.mQClientAPIImpl = new MQClientAPIImpl(this.nettyClientConfig, this.clientRemotingProcessor, rpcHook, clientConfig);

    // 更新nameServer的地址
    if (this.clientConfig.getNamesrvAddr() != null) {
        this.mQClientAPIImpl.updateNameServerAddressList(this.clientConfig.getNamesrvAddr());
        log.info("user specified name server address: {}", this.clientConfig.getNamesrvAddr());
    }
  // 客户端ID
    this.clientId = clientId;
  // mq的admin控制台操作实现
    this.mQAdminImpl = new MQAdminImpl(this);
  // push模式下拉取消息服务
    this.pullMessageService = new PullMessageService(this);
  // 负载均衡服务器
    this.rebalanceService = new RebalanceService(this);

    this.defaultMQProducer = new DefaultMQProducer(MixAll.CLIENT_INNER_PRODUCER_GROUP);
    this.defaultMQProducer.resetClientConfig(clientConfig);
  // 消费者统计管理器
    this.consumerStatsManager = new ConsumerStatsManager(this.scheduledExecutorService);

    log.info("Created a new client Instance, InstanceIndex:{}, ClientID:{}, ClientConfig:{}, ClientVersion:{}, SerializerType:{}",
        this.instanceIndex,
        this.clientId,
        this.clientConfig,
        MQVersion.getVersionDesc(MQVersion.CURRENT_VERSION), RemotingCommand.getSerializeTypeConfigInThisServer());
}

初始化MQClientInstance类的时候会初始化netty客户端以及各种服务实例等。

MQClientInstance类的启动
在调用MQClientInstance的start方法时,会启动很多相关的类比如初始化netty客户端等,接下来我们就一起来分析。

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()) {
                    // 获取nameServer的地址
                    this.mQClientAPIImpl.fetchNameServerAddr();
                }
                // Start request-response channel+
                // 启动mQClientAPIImpl,建立客户端与服务端的channel
                this.mQClientAPIImpl.start();
                // Start various schedule tasks
                // 启动定时任务
                this.startScheduledTask();
                // Start pull service
                // 启动拉消息服务
                // 发送消息
                this.pullMessageService.start();
                // Start rebalance service
                // 负载均衡 rebalanceService为线程类,start方法执行run方法
                // 负载均衡服务,消费者与生产者建立关系
                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;
        }
    }
}

首先会去启动一个netty的客户端,在前面分析的NettyRemotingServer类里面有过类似的启动逻辑,这里就不再重复,感兴趣的客户去看一下。

调用this.startScheduledTask()启动很多的定时任务,获取nameServer地址的,更新topic路由的,清除取消Broker以及发送心跳信息到所有Broker,持久化消费者进度,广播消息持久化到本地,集群消息推送到Broker端,尝试调整消费线程池的线程数量(目前还未实现该方法)。

注意:该类为生产者和消费者公用的类,所以作用于每一个Producer与Consumer。


private void startScheduledTask() {
    if (null == this.clientConfig.getNamesrvAddr()) {
        // 定时获取nameServer的地址
        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路由
    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
    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);
    // 尝试调整push模式的消费线程池的线程数量(没有实现逻辑)
    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);
}

更新topic路由信息

每隔30s从nameServer内部拉取并更新topic路由消息。

public void updateTopicRouteInfoFromNameServer() {
    // 使用set集合就是为了有效的去重
    Set<String> topicList = new HashSet<String>();

    // 从ConsumerTable中获取
    {
        Iterator<Entry<String, MQConsumerInner>> it = this.consumerTable.entrySet().iterator();
        while (it.hasNext()) {
            Entry<String, MQConsumerInner> entry = it.next();
            MQConsumerInner impl = entry.getValue();
            if (impl != null) {
                Set<SubscriptionData> subList = impl.subscriptions();
                if (subList != null) {
                    for (SubscriptionData subData : subList) {
                        topicList.add(subData.getTopic());
                    }
                }
            }
        }
    }

    // 从producerTable中获取
    {
        Iterator<Entry<String, MQProducerInner>> it = this.producerTable.entrySet().iterator();
        while (it.hasNext()) {
            Entry<String, MQProducerInner> entry = it.next();
            MQProducerInner impl = entry.getValue();
            if (impl != null) {
                Set<String> lst = impl.getPublishTopicList();
                topicList.addAll(lst);
            }
        }
    }
  // 比较更新本地的路由信息
    for (String topic : topicList) {
     /**
     * 从nameSerer拉取到topic路由信息之后,调用topicRouteDataIsChange方法与本地
     * 的旧topic路由信息比较看是否更改,比较的数据包括:topic的队列信息queueDatas,
     * topic的broker信息brokerDatas,顺序topic配置orderTopicConf,
     * 消费过滤信息filterServerTable。
     * 当判断需要更新的时候,会更新本地的topic缓存,包括:
     *      1. 更新brokerName到brokerAddr的地址的映射关系,即brokerAddrTable;
     *      2. 更新生产者producerTable集合,更新MQProducerInner的topicPublishInfoTable属性;
     *      3. 更新消费者的consumerTable集合,更新MQConsumerInner的rebalanceImpl.topicSubscribeInfoTable属性;
     *      4. 更新topicRouteTable集合,更新本地topic路由信息。
   */
        this.updateTopicRouteInfoFromNameServer(topic);
    }
}

主要逻辑就是从consumerTable以及producerTable中获取配置的所有topic集合,包括consumer订阅的topic集合以及Producer中topicPublishTable集合中的数据。

从nameSerer拉取到topic路由信息之后,调用topicRouteDataIsChange方法与本地的旧topic路由信息比较看是否更改,比较的数据包括:topic的队列信息queueDatas,topic的broker信息brokerDatas,顺序topic配置orderTopicConf,消费过滤信息filterServerTable。

清理无效的Broker

调用cleanOfflineBroker()方法区清除下线的Broker。

private void cleanOfflineBroker() {
    try {
        // 加锁
        if (this.lockNamesrv.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS))
            try {
                ConcurrentHashMap<String, HashMap<Long, String>> updatedTable = new ConcurrentHashMap<String, HashMap<Long, String>>(this.brokerAddrTable.size(), 1);

                Iterator<Entry<String, HashMap<Long, String>>> itBrokerTable = this.brokerAddrTable.entrySet().iterator();
                // 遍历brokerAddrTable
                while (itBrokerTable.hasNext()) {
                    Entry<String, HashMap<Long, String>> entry = itBrokerTable.next();
                    String brokerName = entry.getKey();
                    HashMap<Long, String> oneTable = entry.getValue();

                    HashMap<Long, String> cloneAddrTable = new HashMap<Long, String>(oneTable.size(), 1);
                    cloneAddrTable.putAll(oneTable);

                    Iterator<Entry<Long, String>> it = cloneAddrTable.entrySet().iterator();
                    while (it.hasNext()) {
                        Entry<Long, String> ee = it.next();
                        String addr = ee.getValue();
                         // 判断broker地址是否存在于topicRouteTable的任意一个topic的路由信息中,
                        // 如果不存在则直接移除该broker地址
                        if (!this.isBrokerAddrExistInTopicRouteTable(addr)) {
                            it.remove();
                            log.info("the broker addr[{} {}] is offline, remove it", brokerName, addr);
                        }
                    }
                  // 集合为空就移除
                    if (cloneAddrTable.isEmpty()) {
                        itBrokerTable.remove();
                        log.info("the broker[{}] name's host is offline, remove it", brokerName);
                    } else {
                        // 否则更新剩下的
                        updatedTable.put(brokerName, cloneAddrTable);
                    }
                }
              // 不为空直接更新
                if (!updatedTable.isEmpty()) {
                    this.brokerAddrTable.putAll(updatedTable);
                }
            } finally {
                this.lockNamesrv.unlock();
            }
    } catch (InterruptedException e) {
        log.warn("cleanOfflineBroker Exception", e);
    }
}

该方法间隔30s就会被执行,遍历并更新brokerAddrTable集合,其主要的步骤就是获取每一个人对象地址,首先去本地topicRouteTable是否存在,存在即保留不存在就表名Broker以下线需要被清除,如果brokerAddrTable中的value集合也是空则会直接删除键值对。

发送心跳信息给Broker

该方法每隔30s向所有的Broker发送心跳包的定时任务方法,客户的consumer和producer都是通过该定时任务发送心跳数据包的。在其他地方也会主动调用一次该方法,例如DefaultMQProducerImpl、DefaultMQPushConsumerImpl等类的start方法的结尾都会主动调用一次该方法。

public void sendHeartbeatToAllBrokerWithLock() {
     // 加锁
    if (this.lockHeartbeat.tryLock()) {
        try {
            // 发送心跳包给所有broker
            this.sendHeartbeatToAllBroker();
            // 上传过滤类到Broker对应的所有Filtersrv,push模式消费使用
            this.uploadFilterClassSource();
        } catch (final Exception e) {
            log.error("sendHeartbeatToAllBroker exception", e);
        } finally {
            this.lockHeartbeat.unlock();
        }
    } else {
        log.warn("lock heartBeat, but failed. [{}]", this.clientId);
    }
}

首先会通过prepareHeartbeatData方法准备心跳包的数据进行发送,然后会遍历brokerAddrTable的broker地址进行循环发送心跳包。
注意:如果单单启动了生产者,这时候我们要去判断是否为Master节点,由于生产者只能与Master发送消息数据,而当我们启动消费者此时我们就需要向所有的broker发送心跳包。

持久化消费偏移量

@Override
public void persistConsumerOffset() {
    try {
        this.makeSureStateOK();
        Set<MessageQueue> mqs = new HashSet<MessageQueue>();
        Set<MessageQueue> allocateMq = this.rebalanceImpl.getProcessQueueTable().keySet();
        mqs.addAll(allocateMq);
      // 持久化的方法
        this.offsetStore.persistAll(mqs);
    } catch (Exception e) {
        log.error("group: " + this.defaultMQPushConsumer.getConsumerGroup() + " persistConsumerOffset exception", e);
    }
}

调用persistAllConsumerOffset()进行持久化,而持久化的功能时消费端所有的。通过调用DefaultMQPushConsumer的persistAll()方法,如果你是广播就会执行LocalFileOffsetStore的对应方法,如果你是集群就会执行RemoteBrokerOffsetStore的对应方法。

发送消息的逻辑在Producer的消息发送逻辑里面,下一章分析。

负载均衡也是相对于消费端,消费端在处理消息的时候使用的消费模式,后期去消费端里面详解。

@Override
public void run() {
    log.info(this.getServiceName() + " service started");

    while (!this.isStopped()) {
        this.waitForRunning(waitInterval);
        // 执行该方法进行负载均衡
        this.mqClientFactory.doRebalance();
    }

    log.info(this.getServiceName() + " service end");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

princeAladdin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值