与消息发送紧密相关的几行代码:
1. DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
2. producer.start();
3. Message msg = new Message(...)
4. SendResult sendResult = producer.send(msg);
5. producer.shutdown();
 
那这几行代码执行时,背后都做了什么?
 
一. 首先是DefaultMQProducer.start 
@Override
public void start() throws MQClientException {
this.defaultMQProducerImpl.start();
}
调用了默认生成消息的实现类 -- DefaultMQProducerImpl
调用defaultMQProducerImpl.start()方法,DefaultMQProducerImpl.start()会初始化得到MQClientInstance实例对象,MQClientInstance实例对象调用它自己的start方法会 ,启动一些服务,如拉去消息服务PullMessageService.Start()、启动负载平衡服务RebalanceService.Start(),比如网络通信服务MQClientAPIImpl.Start(), 另外,还会执行与生产消息相关的信息,如注册produceGroup、new一个TopicPublishInfo对象并以默认TopicKey为键值,构成键值对存入DefaultMQProducerImpl的topicPublishInfoTable中。efaultMQProducerImpl.start()后,获取的MQClientInstance实例对象会调用sendHeartbeatToAllBroker()方法,不断向broker发送心跳包,yin'b可以使用下面一幅图大致描述DefaultMQProducerImpl.start()过程:
 

上图中的三个部分中涉及的内容:
 
1.1 初始化MQClientInstance
 
一个客户端只能产生一个MQClientInstance实例对象,产生方式使用了工厂模式与单例模式。MQClientInstance.start()方法启动一些服务,源码如下:
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 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;
}
}
}
 
1.2 注册producer
该过程会将这个当前producer对象注册到MQClientInstance实例对象的的producerTable中。一个jvm(一个客户端)中一个producerGroup只能有一个实例,MQClientInstance操作producerTable大概有如下几个方法:
  • -- selectProducer
  • -- updateTopicRouteInfoFromNameServer
  • -- prepareHeartbeatData
  • -- isNeedUpdateTopicRouteInfo
  • -- shutdown
注:
根据不同的clientId,MQClientManager将给出不同的MQClientInstance;
根据不同的group,MQClientInstance将给出不同的MQProducer和MQConsumer
 
1.3 向路由信息表中添加路由
topicPublishInfoTable定义:
public class DefaultMQProducerImpl implements MQProducerInner {
private final Logger log = ClientLogger.getLog();
private final Random random = new Random();
private final DefaultMQProducer defaultMQProducer;
private final ConcurrentMap<String/* topic */, TopicPublishInfo> topicPublishInfoTable = new ConcurrentHashMap<String, TopicPublishInfo>();
它是一个以topic为key的Map型数据结构,DefaultMQProducerImpl.start()时会默认创建一个key=MixAll.DEFAULT_TOPIC的TopicPublishInfo存放到topicPublishInfoTable中。
 
1.4 发送心跳包
MQClientInstance向broker发送心跳包时,调用sendHeartbeatToAllBroker( ),以及从MQClientInstance实例对象的brokerAddrTable中拿到所有broker地址,向这些broker发送心跳包。
sendHeartbeatToAllBroker会涉及到prepareHeartbeatData()方法,该方法会生成heartbeatData数据,发送心跳包时,heartbeatData作为心跳包的body。与producer相关的部分代码如下:
// Producer
for (Map.Entry<String/* group */, MQProducerInner> entry : this.producerTable.entrySet()) {
MQProducerInner impl = entry.getValue();
if (impl != null) {
ProducerData producerData = new ProducerData();
producerData.setGroupName(entry.getKey()); heartbeatData.getProducerDataSet().add(producerData);
}
 
 
二、. SendResult sendResult = producer.send(msg)
 
首先会调用DefaultMQProducer.send(msg) ,继而调用sendDefaultImpl:
 public SendResult send(Message msg,
long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return this.sendDefaultImpl(msg, CommunicationMode.SYNC, null, timeout);
}
 
 
sendDefaultImpl做了啥?
2.1. 获取topicPublishInfo
根据msg的topic从topicPublishInfoTable获取对应的topicPublishInfo,如果没有则更新路由信息,从nameserver端拉取最新路由信息。从nameserver端拉取最新路由信息大致为:首先getTopicRouteInfoFromNameServer,然后topicRouteData2TopicPublishInfo。


 
 
2.2 选择消息发送的队列
普通消息:默认方式下,selectOneMessageQueue从topicPublishInfo中的messageQueueList中选择一个队列(MessageQueue)进行发送消息,默认采用长轮询的方式选择队列 。它的机制如下:正常情况下,顺序选择queue进行发送;如果某一个节点发生了超时,则下次选择queue时,跳过相同的broker。不同的队列选择策略形成了生产消息的几种模式,如顺序消息,事务消息。
 
顺序消息:将一组需要有序消费的消息发往同一个broker的同一个队列上即可实现顺序消息,假设相同订单号的支付,退款需要放到同一个队列,那么就可以在send的时候,自己实现MessageQueueSelector,根据参数arg字段来选择queue。
    private SendResult sendSelectImpl(
Message msg,
MessageQueueSelector selector,
Object arg,
final CommunicationMode communicationMode,
final SendCallback sendCallback, final long timeout
) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { 。。。}
 
 事务消息:只有在消息发送成功,并且本地操作执行成功时,才发送提交事务消息,做事务提交,消息发送失败,直接发送回滚消息,进行回滚,具体如何实现后面会单独成文分析。
 
 
2.3 封装消息体通信包,发送数据包
首先,根据获取的MessageQueue中的getBrokerName,调用findBrokerAddressInPublish得到该消息存放对应的broker地址,如果没有找到则跟新路由信息,重新获取地址 :
brokerAddrTable.get(brokerName).get(MixAll.MASTER_ID)
可知获取的broker均为master(id=0)
 
然后, 将与该消息相关信息打包成RemotingCommand数据包,其RequestCode.SEND_MESSAGE
根据获取的broke地址,将数据包到对应的broker,默认是发送超时时间为3s。
 
封装消息请求包的包头:
 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);

发送消息包(普通消息默认为同步方式):

SendResult sendResult = null;
switch (communicationMode) {
   case SYNC:
  sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
  brokerAddr,
  mq.getBrokerName(),
   msg,
  requestHeader,
   timeout,
  communicationMode,
  context,
  this);
break;

处理来自broker端的响应数据包:

 private SendResult sendMessageSync(
final String addr,
final String brokerName,
final Message msg,
final long timeoutMillis,
final RemotingCommand request
) throws RemotingException, MQBrokerException, InterruptedException {
RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis);
assert response != null;
return this.processSendResponse(brokerName, msg, response);
}

broker端处理request数据包后会将消息存储到commitLog,具体过程后续分析。

 (完)
 
 

rocketmq--消息的产生(普通消息)的更多相关文章

  1. 转 Kafka、RabbitMQ、RocketMQ等消息中间件的对比 —— 消息发送性能和优势

    Kafka.RabbitMQ.RocketMQ等消息中间件的对比 —— 消息发送性能和优势 引言 分布式系统中,我们广泛运用消息中间件进行系统间的数据交换,便于异步解耦.现在开源的消息中间件有很多,前 ...

  2. Apache RocketMQ 正式开源分布式事务消息

    近日,Apache RocketMQ 社区正式发布4.3版本.此次发布不仅包括提升性能,减少内存使用等原有特性增强,还修复了部分社区提出的若干问题,更重要的是该版本开源了社区最为关心的分布式事务消息, ...

  3. rocketMq消息的发送和消息消费

    rocketMq消息的发送和消息消费 一.消息推送 public void pushMessage() { String message = "推送消息内容!"; try { De ...

  4. RocketMQ系列(三)消息的生产与消费

    前面的章节,我们已经把RocketMQ的环境搭建起来了,是一个两主两从的异步集群.接下来,我们就看看怎么去使用RocketMQ,在使用之前,先要在NameServer中创建Topic,我们知道Rock ...

  5. 【源码】RocketMQ如何实现获取指定消息

    概要 消息查询是什么? 消息查询就是根据用户提供的msgId从MQ中取出该消息 RocketMQ如果有多个节点如何查询? 问题:RocketMQ分布式结构中,数据分散在各个节点,即便是同一Topic的 ...

  6. 消息队列之事务消息,RocketMQ 和 Kafka 是如何做的?

    每个时代,都不会亏待会学习的人. 大家好,我是 yes. 今天我们来谈一谈消息队列的事务消息,一说起事务相信大家都不陌生,脑海里蹦出来的就是 ACID. 通常我们理解的事务就是为了一些更新操作要么都成 ...

  7. 深入研究RocketMQ消费者是如何获取消息的

    前言 小伙伴们,国庆都过的开心吗?国庆后的第一个工作日是不是很多小伙伴还沉浸在假期的心情中,没有工作状态呢? 那王子今天和大家聊一聊RocketMQ的消费者是如何获取消息的,通过学习知识来找回状态吧. ...

  8. RabbitMQ,RocketMQ,Kafka 几种消息队列的对比

    常用的几款消息队列的对比 前言 RabbitMQ 优点 缺点 RocketMQ 优点 缺点 Kafka 优点 缺点 如何选择合适的消息队列 参考 常用的几款消息队列的对比 前言 消息队列的作用: 1. ...

  9. RabbitMQ,RocketMQ,Kafka 事务性,消息丢失和消息重复发送的处理策略

    消息队列常见问题处理 分布式事务 什么是分布式事务 常见的分布式事务解决方案 基于 MQ 实现的分布式事务 本地消息表-最终一致性 MQ事务-最终一致性 RocketMQ中如何处理事务 Kafka中如 ...

  10. 消息服务MNS和消息队列ONS产品对比

    消息服务MNS和消息队列ONS产品对比 MNS已经进过严格测试,已达到商业化的稳定性要求,其主要特点和适用场景 1.数据高可靠(10个9),对于数据可靠性敏感(要求消息数据不丢)的应用场景建议选择. ...

随机推荐

  1. mysql 安装(压缩包安装和exe安装)

    1:mysql官网:https://dev.mysql.com/downloads/file/?id=482487 2:压缩包安装:https://www.cnblogs.com/jamespan23 ...

  2. L2TP/IPSec一键安装脚本

    本脚本适用环境:系统支持:CentOS6+,Debian7+,Ubuntu12+内存要求:≥128M更新日期:2017 年 05 月 28 日 关于本脚本:名词解释如下L2TP(Layer 2 Tun ...

  3. angular的指令独立作用域(以及$watch的使用)

    在编写指令的时候,会有一个独立作用域的问题(scope),他默认的是 scope:false 不创建自己的作用域,直接使用的就是父级的作用域, 问题:容易出现全局的污染,是的指令的重复性使用回出现一些 ...

  4. elastic search6.2.2 实现用户搜索记录查询(去重、排序)

    elastic search6.2.2 实现搜索记录查询 ,类似新浪微博这种,同样的搜索记录后面时间点的会覆盖前面的(主要思路:关键词去重,然后按时间排序) 先创建索引 //我的搜索 PUT my_s ...

  5. DBUtils 增删改查例子

    sql CREATE TABLE [dbo].[Person] ( , ) NOT NULL , ) COLLATE Chinese_PRC_CI_AS NULL , [age] [int] NULL ...

  6. 访问进程环境变量environ时的一个坑

    在unistd.h中定义了变量char **environ;来表示当前所有环境变量,一般来说访问特定环境变量可以用getenv,但是想遍历所有环境变量就得使用environ. 即在程序内全局声明ext ...

  7. Java-API-POI:POI 笔记

    ylbtech-Java-API-POI:POI 笔记 1. 笔记一返回顶部 1. 1,POI对JDK版本支持及XLSX (2017-04-01 13:51:39)对JDK6的支持,最后版本是POI- ...

  8. 022:SQL优化--JOIN算法

    目录 一. SQL优化--JOIN算法 1.1. JOIN 写法对比 2. JOIN的成本 3. JOIN算法 3.1. simple nested loop join 3.2. index nest ...

  9. nfs之脚气

    nfs工作模式参考 一.NFs是什么 NFS是Network File System的缩写,即网络文件系统.客户端通过挂载的方式将NFS服务器端共享的数据目录挂载到本地目录下. nfs为什么需要RPC ...

  10. 设计模式——单例模式(C++)

    一: 饿汉式单例: 静态区初始化instance,然后通过getInstance返回.这种方式没有多线程的问题,是一种以空间换时间的方式,不管程序用不用,都会构造唯一的实例. #pragma once ...