RocketMq消费发送源码解析

RocketMq消费发送

流程图

RocketMq消费发送

1.生产者启动流程
public void start(final boolean startFactory) throws MQClientException {
     				//1.检查 productGroup 是否符合要求;
            this.checkConfig();
						//2. 并改变生产者 instanceName 为进程 ID
            if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
                this.defaultMQProducer.changeInstanceNameToPID();
            }
						//3.创建MQClientInstance.整个jvm实例中只存在MQClientManager实例,维护一个缓存表
            this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
          //4.向MQClientInstance注册,将当前生产者加入到MQClientInstance管理中,方便后续调用网络请求,进行心跳检测
            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());
  //5.启动MQClientInstance,如果MQClientInstance已经启动,则本次启动不会真正执行
            if (startFactory) {
                mQClientFactory.start();
            }

            log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(),
                this.defaultMQProducer.isSendMessageWithVIPChannel());
            this.serviceState = ServiceState.RUNNING;
            break;
}
  1. 检查 productGroup 是否符合要求;并改变生产者 instanceName 为进程 ID
public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) {
    String clientId = clientConfig.buildMQClientId();
    MQClientInstance instance = this.factoryTable.get(clientId);
    if (null == instance) {
        instance =
            new MQClientInstance(clientConfig.cloneClientConfig(),
                this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook);
        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;
}
  1. 创建MQClientInstance 实例,整个jvm实例中只存在一个MQClientManager实例,维护一个MQClientInstance缓存表ConcurrentMap<String/* clientId */, MQClientInstance> factoryTable = new ConcurrentHashMap<String, MQClientInstance>();也就是同一个clientId只会创建一个MQClientInstance

clientld 为客户端 IP+ instance+ (unitname 可选),如果在 一台物理服务器部署两个应用用程序,应用程序 clientld 相同,会混乱吗?

为了避免这个问题 如果 instance 为默认值 DEFAULT 的话, RocketMQ 会自动将instance 设置为进程 ID ,这样避免了不同进程的相互影响,但同 一个JVM 的不同消费者和生产者在启动的时候获取都是同一个clientId.

MQClientlnstance 封装了 RocketMQ 网络处理 API ,是消息生产者( Producer )、消息消费者(Consumer )与 NameServer、Broker 打交道的网络通道

public boolean registerProducer(final String group, final DefaultMQProducerImpl producer) {
    if (null == group || null == producer) {
        return false;
    }

    MQProducerInner prev = this.producerTable.putIfAbsent(group, producer);
    if (prev != null) {
        log.warn("the producer group[{}] exist already.", group);
        return false;
    }

    return true;
}

if (startFactory) {
                    mQClientFactory.start();
                }
  1. 向MQClientInstance注册,将当前生产者加入到MQClientInstance管理中,方便后续调用网络请求,进行心跳检测
  2. 启动MQClientInstance,如果MQClientInstance已经启动,则本次启动不会真正执行
2.消费发送

第一次发送消息时,本地没有缓存 topic 的路由信息,查询 NameServer 尝试获取,如果路由信息未找到,再次尝试用默认主题 DefaultMQProducerlmpl#createTopicKey 去查询,如果 BrokerConfig#autoCreateTopicEnable true 时, NameServer 将返回路由信息,如果autoCreateTopicEnab为false 将抛出无法找到 topic 路由异常.

public SendResult send(
    Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    return send(msg, this.defaultMQProducer.getSendMsgTimeout());
}
1.消息长度验证
  1. 消费发送默认是以同步方式发送,默认超时时间为3秒
private SendResult sendDefaultImpl(
    Message msg,
    final CommunicationMode communicationMode,
    final SendCallback sendCallback,
    final long timeout
) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    this.makeSureStateOK();
  //消息长度验证
    Validators.checkMessage(msg, this.defaultMQProducer);
  //查找主题路由信息
    TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
    if (topicPublishInfo != null && topicPublishInfo.ok()) {
      //如果有路由信息,选择一个队列进行发送
       MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
    }
  //消息发送
   sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);

	//最终未找到路由信息,抛出No route info of this topi
    throw new MQClientException("No route info of this topic: " + msg.getTopic() + FAQUrl.suggestTodo(FAQUrl.NO_TOPIC_ROUTE_INFO),
        null).setResponseCode(ClientErrorCode.NOT_FOUND_TOPIC_EXCEPTION);
}
2.查找主题路由信息
private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
  //1.看看是否缓存了topic的路由信息
    TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
  //如果缓存不存在且消息队列为0
    if (null == topicPublishInfo || !topicPublishInfo.ok()) {
      //重新放到缓存里面
        this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
      //修改路由信息
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
        topicPublishInfo = this.topicPublishInfoTable.get(topic);
    }
			//如果该路由信息中包含了消息队列,则直接返回该路由信息
    if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
        return topicPublishInfo;
    } else {
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
        topicPublishInfo = this.topicPublishInfoTable.get(topic);
        return topicPublishInfo;
    }
}
  1. tryToFindTopicPublishlnfo 是查找主题的路由信息的方法,如果生产者中缓存了 topic的路由信息,如果该路由信息中包含了消息队列,则直接返回该路由信息,如果没有缓存或没有包含消息队列, 则向nameServer 查询该 topic 路由信息 ,如果最终未找到路由信息,则抛出异常 无法找到主题相关路由信息异常
    MQClientlnstance#updateTopicRouteInfoFromNameServer这个方法的功能是消息生产者更新和维护路由缓存
org.apache.rocketmq.client.impl.factory.MQClientInstance#updateTopicRouteInfoFromNameServer
public boolean updateTopicRouteInfoFromNameServer(final String topic, boolean isDefault,
      TopicRouteData topicRouteData;
                    if (isDefault && defaultMQProducer != null) {
                        //:如果isDefault为true,则使用默认主题去查询,如果查询到路由信息,则替换路由信息中读写队列个数为
                        // 消息生产者默认的队列个数(defaultTopicQueueNums );如isDefault为false ,则使用参数topic去查询;
                        // 如果未查询到路由信息,则返回 false ,表示路由信息未变化
                        topicRouteData = this.mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(defaultMQProducer.getCreateTopicKey(),
                            1000 * 3);
                        //如果默认的topic存在,遍历里面的队列重新设置队列数量4
                        if (topicRouteData != null) {
                            for (QueueData data : topicRouteData.getQueueDatas()) {
                                int queueNums = Math.min(defaultMQProducer.getDefaultTopicQueueNums(), data.getReadQueueNums());
                                data.setReadQueueNums(queueNums);
                                data.setWriteQueueNums(queueNums);
                            }
                        }
                    } else {
                        topicRouteData = this.mQClientAPIImpl.getTopicRouteInfoFromNameServer(topic, 1000 * 3);
                    }
}
  1. 第一次发送消息时,本地没有缓存 topic 的路由信息,查询 NameServer 尝试获取,如果路由信息未找到,再次尝试用默认主题 DefaultMQProducerlmpl#createTopicKey 去查询,如果 BrokerConfig#autoCreateTopicEnable true 时, NameServer 将返回路由信息,如果autoCreateTopicEnab为false 将抛出无法找到 topic 路由异常.
  2. 如果isDefault为true,则使用默认主题去查询,如果查询到路由信息,则替换路由信息中读写队列个数为消息生产者默认的队列个数(defaultTopicQueueNums );如isDefault为false ,则使用参数topic去查询;如果未查询到路由信息,则返回 false ,表示路由信息未变化
TopicRouteData old = this.topicRouteTable.get(topic);
//路由信息找到,与本地缓存中的路由信息进行对比,判断路由信息是否发了改变
boolean changed = topicRouteDataIsChange(old, topicRouteData);
if (!changed) {
  //更新 MQClientlnstance Broker 地址缓存表
    changed = this.isNeedUpdateTopicRouteInfo(topic);
} else {
    log.info("the topic[{}] route info changed, old[{}] ,new[{}]", topic, old, topicRouteData);
}
  1. 如果路由信息找到,与本地缓存中的路由信息进行对比,判断路由信息是否发了改变, 如果未发生变化,则直接返回 false

  2. 更新 MQClientlnstance Broker 地址缓存表

TopicRouteData cloneTopicRouteData = topicRouteData.cloneTopicRouteData();
for (BrokerData bd : topicRouteData.getBrokerDatas()) {
    this.brokerAddrTable.put(bd.getBrokerName(), bd.getBrokerAddrs());
}
// Update Pub info
{
    TopicPublishInfo publishInfo = topicRouteData2TopicPublishInfo(topic, topicRouteData);
    publishInfo.setHaveTopicRouterInfo(true);
    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) {
            impl.updateTopicPublishInfo(topic, publishInfo);
        }
    }
}

  1. 根据 topicRouteData 中的 List 转换成问 List列表 ,然后更新该 MQClientInstance所管辖的所

    有消息发送关于 topic 的路由信息

3.选择消息队列

首先在一次消息发送过程中,可能会多次执行选择消息队列这个方法, lastBrokerName就是上一次选择的执行发送消息失败的 Broker 第一次执行消息队列选择时,lastBrokerName为null ,此时直接 sendWhichQueue 自增再获取值 与当前路由 表中消息队列个数取模, 返回该位置 MessageQueue(selectOneMessageQueue() 方法如果消息发送再失败的话,下次进行消息队列选择时规避上次 MesageQueue 在的 Broker 则还很有可能再次失败.

该算法在一次消息发送过程中能成功规避故障的 Broker,但如果 Broker 若机,由于路由算法中的消息队 是按 Broker 的,如果上一次根据路由算法选择的是若机的 Broker排序的第一个队列 ,那么随后的下次选择的是若 Broker 第二个队列,消息发送很有可能会失败,再再次引发重试,带来不必要的性能损耗,那么有什么方法在一次消息发送失败后,暂时将该 Broker 排除在消息队列选择范围外呢?或许有朋友会问, Broker不可用后,路由信息中为什么还会包含该 Broker的路由信息呢?

其实这不难解释:首先, NameServer检查Broker 是否可用是有延迟的,最短为一次心跳检测间隔( 10s ); 其次, NameServer

不会检测到 Broker 岩机后马上推送消息给消息生产者,而是消息生产者每隔 30s 更新一次路由信息,所以消息生产者 快感知 Broker 最新的路由信息也需要 30s ,如果能引人一种机制,可以将Broker 宕机期间,如果一次消息发送失败后,可以将该 Broker 暂时排除在消息队列的选择范围中

public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
  //sendLatencyFaultEnable=true ,启用 Broker 障延迟机制 
  if (this.sendLatencyFaultEnable) {
        try {
            int index = tpInfo.getSendWhichQueue().getAndIncrement();
            for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
                int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
                if (pos < 0)
                    pos = 0;
                MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
                if (latencyFaultTolerance.isAvailable(mq.getBrokerName())) {
                    if (null == lastBrokerName || mq.getBrokerName().equals(lastBrokerName))
                        return mq;
                }
            }

            final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
            int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
            if (writeQueueNums > 0) {
                final MessageQueue mq = tpInfo.selectOneMessageQueue();
                if (notBestBroker != null) {
                    mq.setBrokerName(notBestBroker);
                    mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums);
                }
                return mq;
            } else {
                latencyFaultTolerance.remove(notBestBroker);
            }
        } catch (Exception e) {
            log.error("Error occurred when selecting message queue", e);
        }

        return tpInfo.selectOneMessageQueue();
    }
// sendLatencyFaultEnable=false ,默认不启用 Broker 故障延迟机制
    return tpInfo.selectOneMessageQueue(lastBrokerName);
}
4.消息发送
String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
if (null == brokerAddr) {
    tryToFindTopicPublishInfo(mq.getTopic());
    brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
}
  1. 根据 MessageQueue 获取 Broker 的网络地址 如果 MQClientlnstance的brokeraddrTable 没有缓存该 Broke 的信息,则从 NameServer 主动更新一下 topic 的路由信息,如果路由更新后还 找不到 Broker 信息,则抛出 MQClientExcetion ,提示 Broker不

    存在

if (!(msg instanceof MessageBatch)) {
    MessageClientIDSetter.setUniqID(msg);
}

boolean topicWithNamespace = false;
if (null != this.mQClientFactory.getClientConfig().getNamespace()) {
    msg.setInstanceId(this.mQClientFactory.getClientConfig().getNamespace());
    topicWithNamespace = true;
}

int sysFlag = 0;
boolean msgBodyCompressed = false;
if (this.tryToCompressMessage(msg)) {
    sysFlag |= MessageSysFlag.COMPRESSED_FLAG;
    msgBodyCompressed = true;
}

final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
if (tranMsg != null && Boolean.parseBoolean(tranMsg)) {
    sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
}
  1. 为消息分配全局唯一Id ,如果消息 默认超 4K(compressMsgBodyOverHowmuch), 会对消息体采用 zip 压缩,并设置消息的系统标记为 MessageSysFlag.COMPRESSED_FLAG,如果是事务 Prepared 消息,则设 消息的系统标记为 MessageSysF ag .TRANSACTION_

    PREPARED_TYPE

SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();
requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
requestHeader.setTopic(msg.getTopic());
requestHeader.setDefaultTopic(this.defaultMQProducer.getCreateTopicKey());
requestHeader.setDefaultTopicQueueNums(this.defaultMQProducer.getDefaultTopicQueueNums());
requestHeader.setQueueId(mq.getQueueId());
requestHeader.setSysFlag(sysFlag);
requestHeader.setBornTimestamp(System.currentTimeMillis());
requestHeader.setFlag(msg.getFlag());
requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties()));
requestHeader.setReconsumeTimes(0);
requestHeader.setUnitMode(this.isUnitMode());
requestHeader.setBatch(msg instanceof MessageBatch);
if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
    String reconsumeTimes = MessageAccessor.getReconsumeTime(msg);
    if (reconsumeTimes != null) {
        requestHeader.setReconsumeTimes(Integer.valueOf(reconsumeTimes));
        MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_RECONSUME_TIME);
    }

    String maxReconsumeTimes = MessageAccessor.getMaxReconsumeTimes(msg);
    if (maxReconsumeTimes != null) {
        requestHeader.setMaxReconsumeTimes(Integer.valueOf(maxReconsumeTimes));
        MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_MAX_RECONSUME_TIMES);
    }
}

3.:构建消息发送请求 主要包含如下重要信息:生产者组、主题名称、默认创建主题 Key 、该主题在单个 Broker 默认 队列数、队 ID (队列序号)、消息系统标( MessageSysFlag )消息发送时间 、消息标记( RocketMQ 对消息中的 flag 不做任何处理供应用程序使用) 消息扩展属性 、消息重试次数、是否是批量消息等等

SendResult sendResult = null;
switch (communicationMode) {
    case ASYNC:
        sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
            brokerAddr,
            mq.getBrokerName(),
            tmpMessage,
            requestHeader,
            timeout - costTimeAsync,
            communicationMode,
            sendCallback,
            topicPublishInfo,
            this.mQClientFactory,
            this.defaultMQProducer.getRetryTimesWhenSendAsyncFailed(),
            context,
            this);
        break;
    case ONEWAY:
    case SYNC:

        sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
            brokerAddr,
            mq.getBrokerName(),
            msg,
            requestHeader,
            timeout - costTimeSync,
            communicationMode,
            context,
            this);
        break;
    default:
        assert false;
        break;
}
  1. 根据发送方式,同步、异步、单向方式进行网络传输
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RocketMQ NameServer 是 RocketMQ 的一个核心组件,主要负责管理 RocketMQ 集群中的各个 Broker 节点的信息,包括 Broker 的名称、IP 地址、状态等信息。在 RocketMQ 集群中,所有的 Broker 都需要向 NameServer 注册,以便 NameServer 能够掌握整个集群的状态信息。 RocketMQ NameServer 的源码位于 `rocketmq-namesrv` 模块中,其主要实现了以下功能: 1. 启动时加载配置文件,包括监听端口、存储路径、集群名称等信息; 2. 处理 Broker 节点的注册、注销请求,维护 Broker 节点的状态信息; 3. 处理 Consumer 节点的心跳请求,维护 Consumer 节点的状态信息; 4. 处理 Topic 的创建、删除请求,维护 Topic 的状态信息; 5. 提供查询 Broker 节点、Topic 等信息的接口。 RocketMQ NameServer 的核心类是 `NamesrvController`,它继承了 Netty 的 `NettyRemotingServer` 类,并实现了 `RequestProcessor` 接口,用于处理来自 Broker 和 Consumer 节点的请求。在 `NamesrvController` 中,还包含了 `RouteInfoManager`、`BrokerHousekeepingService`、`KVConfigManager` 等组件,用于维护集群状态信息和管理配置文件。 RocketMQ NameServer 的启动入口是 `main` 方法,它会加载配置文件并启动 `NamesrvController`。启动后,NameServer 会监听指定端口,等待来自 Broker 和 Consumer 节点的请求,并根据请求类型调用相应的处理方法进行处理。 总之,RocketMQ NameServer 的主要作用是管理整个 RocketMQ 集群的状态信息,确保集群中各个节点的状态始终保持同步。其源码实现比较复杂,需要深入理解 RocketMQ 的设计思想和架构原理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值