RocketMQ源码解析之消息生产者(启动流程)

1.消息生产者一些原理

在上篇《RocketMQ源码解析(搭建环境)》我们主要介绍了读源码的一些知识储备与源码环境搭建,在读源码的一些知识储备中介绍了一下RocketMQ的架构,我们知道RocketMQ 分为nameserv 与broker ,再就是客户端 也就是我们说的消息生产者与消息消费者。接下来我们分别来介绍下:

  • nameserv :玩过微服务的同学可以将nameserv看作注册中心,broker 启动要注册到nameserv中,同时要定时向nameserv发送心跳来告诉nameserv它还活着,然后nameserv除了维护broker信息,还要维护topic的信息,比如一个topic 消息发送到哪几个broker上,然后topic 分为几个message queue等等,现在我们只需要简单知道这个nameserv 维护这broker信息与topic 信息。
  • broker:broker就是接收消息生产者发送的消息的,然后将消息存到一个commitlog文件中,同时有consumqueue 维护着这条消息的偏移量,broker同时还向消息消费者提供消息进行消费,当然这里面还有很多细节的东西,这里只是说了一点点,还有高可用,消息存储,分片等等,我们后面解析到broker的时候再来详细叨叨。
  • 消息生产者:这个就是需要我们写业务的时候关心的了,封装消息,然后管nameserv 要到消息属于那个topic的broker信息,然后使用什么样的发送方式,根据一定的策略发送到对应的broker上面。
  • 消息消费者:这个也是我们写业务需要关心的,就是管broker 要某个topic的消息,但是不是盲目的要,需要去nameserv上面获取某个topic在哪些broker上面,这个里面也有很多细节问题,等着解析到了,我们会详细叨叨的。
    接下来简单介绍下消息生产者一些工作流程:
    我们先从RocketMQ的example项目中找出一个使用消息生产者的代码:
    在这里插入图片描述

代码前三行是创建并启动一个消息生产者,启动的时候会创建一个MQClientInstance,这个MQClientInstance启动的时候会创建netty客户端,注意这个时候这个netty客户端是不会连接的,因为它还不知道与谁连接,同时还启动一堆任务调度,比如说有获取nameserv地址,有去nameserv获取topic 信息的,有管理broker的,发送心跳的等等,那它什么时候创建连接呢?是使用的时候,比如说定时任务每30秒去nameserv上拉取topic信息的时候,它会根据你配置的那个nameserv地址,也就是上面第二行代码设置的地址,去一个维护channel与地址对应关系的channel表中获取这个channel,如果没有的话,就创建连接,缓存到channel中。

接下来就是发送消息了,发送消息,需要告诉你这个消息属于哪个topic,这个topic 中文解释成主题,你可以理解成同一个业务类型的消息,怎样算是同一个业务类型的消息呢,比如说要做注册功能,需要发送验证码短信,发送验证码短信这个业务topic就可以是sendMessageCode,有需要发送验证码的请求,就可以生成一个发送验证码的msg,投递到mq中,然后消息消费者拉到这个消息就会处理对应的业务,当然你可以把发送短信验证码分的更细些,有发送注册验证码,有发送登录验证码,这个要看你特殊需求没,这个topic可以是用RocktMQ的可视化管理平台创建,修改,也可以在消息生产者使用api创建,也可以不用管,发送消息的时候直接指定就行,默认就会创建了。

接着就是向broker发送消息了,发送消息有三种模式,同步发送,异步发送,单向发送,同步发送就是发出去消息等着broker把响应返回后,这个线程再继续下面的工作。异步发送是提供一个回调方法,等着响应过来的时候会调用你提供的那个回调方法,这个线程将消息投递出去后就会继续干下面的事情了,执行你那个回调线程的方法也不是发送的那个线程,是MQ客户端里面的线程做的。单向发送,就是消息生产者将消息发出去就不需要管了,也不用等响应,反正就是broker丢不丢消息都不管。

其实发送消息这里面还有一个MessageQueue的概念,我们在创建一个topic的时候默认是创建4个MessageQueue,当你有一个broker的时候,那4个MessageQueue 都会分到这个broker上面,注意一个broker指的是一个broker name,那种master/slave高可用架构broker虽然是多个broker实例,但是它们用的同一个broker name ,就算是一个broker,因为参与写的那个就master一个实例,其他slave只提供备份与部分读的工作,这个我们后面再说,如果有2个broker 的话,第二个broker也是会分为4个MessageQueue,如下图:这是MessageQueue的简单介绍。
在这里插入图片描述
有了这么多MessageQueue,接着问题又来了,发送消息的时候到底会发送到哪个MessageQueue上面呢?其实它是有一个策略的,如果不开启发送延迟故障容错的话,也就是sendLatencyFaultEnable这个参数,默认是不开启的,是false的,如果不开启的话,就会在单个线程内轮询,就是单个线程内有一个计数器,然后%MessageQueue的数量,就能实现某个线程内的轮询效果,就是下面这段代码:
在这里插入图片描述
接下来再介绍下关于消息生产者失败重试与延迟容错的处理,不管是同步发送,异步发送,还是单向发送,都是有失败重试的,这个broker 失败后,下次重试尽量不会再发送到这个失败broker上面,默认重试次数是2,也就是最多发送3次,这是重试的内容,还有,重试的时候会再次选一个MessageQueue,然后选的这个MessageQueue 对应的broker不能与上次失败的那个broker是一个。接着就是延迟容错了,就是我们上面说的sendLatencyFaultEnable这个参数对应的,当它为true的时候开启,这里先说下原理,就是它会对每次的请求做延迟记录,如果某个broker在这次请求延迟超过了设定的一个值,下次发送消息的时候就会尽量不找这个broker,如果是请求失败,异常的情况,也会触发隔离,我们来看下它默认的一个阈值。
在这里插入图片描述
第一个数组是延迟值,第二个数组是对应的多长时间不能提供服务或者是多长时间尽量不选它。下面我们会详细介绍它的。

2.启动流程

从本小节开始我们就从源码的角度进行分析了,首先来分析下消息生产者的启动流程,我们就以上面那个消息生产者代码开始分析下,看看RocketMQ都干了什么,注意:消息生产者代码入口是在client子项目中。

DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");

进入构造中:
在这里插入图片描述
这里它有调用了自己的另外一个构造,第一参数是 group ,也就是生产者组,第二个参数rpchook,这个其实就是一个钩子,发送消息前,响应回来都会回调这个钩子
在这里插入图片描述
在它的这个构造中,我们看到了它创建了个defaultMQProducerImpl 对象,这个可以看作是默认的消息生产者实现,DefaultMQProducer 的send方法其实都是都是调用的这个Impl类。
讲到这里我们看下DefaultMQProducer 这个类有哪些功能,功能一:给消息生产者配置参数,调整参数就是调用这个类的api,比如上面我们设置nameserv地址producer.setNamesrvAddr("127.0.0.1:9876");,可以把它看作一个配置类, 功能二:发送消息的功能,这里它发送消息都是调用defaultMQProducerImpl 这个类,功能三:它实现MQAdmin 接口里面关于topic与MessageQueue的操作。
我们来看下它里面有哪些重要的参数:

字段默认值解释
producerGroupnull组信息,需要你自己指定
defaultTopicQueueNums4就是新建一个topic 默认设置4个MessageQueue
sendMsgTimeout3000发送消息的超时时间 单位ms
compressMsgBodyOverHowmuch1024 * 4这个就是当发送的消息内容大于这个数的时候 进行压缩,压缩阈值,默认是4k
maxMessageSize1024 * 1024 * 4允许发送消息最大大小 默认是4m
retryTimesWhenSendFailed2发送失败的时候重试次数 默认是2次
retryTimesWhenSendAsyncFailed2异步发送失败的时候重试次数 默认是2次
retryAnotherBrokerWhenNotStoreOKfalse指示是否在内部发送失败时重试另一个broker

DefaultMQProducer这个类还继承了一个ClientConfig ,这ClientConfig 不用看就知道是个客户端配置类,我们看看它里面重要字段的解释:

字段默认值解释
namesrvAddr默认去jvm启动参数 rocketmq.namesrv.addr ,系统环境变量 NAMESRV_ADDR 中找nameserv的地址,这个最好是自己设置进去,你在DefaultMQProducer 类set 的就是赋值给了它
clientIP自己去找本客户端地址,他自己就会去找的
instanceName默认是去jvm启动参数rocketmq.client.name 中找,没有设置DEFAULT,这个它会自己重新设置的实例名称
clientCallbackExecutorThreadscpu核心数执行callback 线程池的线程核心数
pollNameServerInterval1000 * 30多久去nameserv 获取topic 信息,默认是30s
heartbeatBrokerInterval1000 * 30与broker心跳间隔时间,默认是30s,就是每隔30向broker发送心跳

DefaultMQProducer还实现一个MQProducer 接口,不用看,这个MQProducer 接口就是一堆send方法与start,shutdown方法,然后让DefaultMQProducer去实现。
这里介绍完了DefaultMQProducer ,我们接着往下走,看看它的这个实现DefaultMQProducerImpl类
在这里插入图片描述
我们看到这个DefaultMQProducerImpl 类也没干啥,就是创建了一个异步发送的线程池,这个线程池是在异步发送的时候,你把这个消息给DefaultMQProducer然后它又把这个消息给了DefaultMQProducerImpl ,然后他就交给这个线程池。
好了到这就走不动了,我们再回到我们自己写的消息消费者 发送消息的那段代码(其实是RocketMQ提供的例子),我们分析完了new DefaultMQProducer 这句,接着就是设置nameserv了,这就不看了,上面说了,接着就是执行producer.start();来启动client了。
在这里插入图片描述
下面的那个判断不用管他,咱们也没设置,看他调用了DefaultMQProducerImpl 的start方法,我们在来看看:
在这里插入图片描述
它又调用了一个带参数的start方法,先来说下这个参数,就是要不要启动MQClientInstance。
在这里插入图片描述
他这里其实有一个有个switch判断启动状态的,这里我们第一次启动就是CREATE_JUST 状态,刚创建还没有启动,this.checkConfig();就是检查group组的合法性,接着你这个group组只要不是它内部的一个,就要重新设置一下instanceName,你之前没指定,就用你的pid,pid就是进程号,之前指定了,它就不会改了,接着就是使用MQClientManager 类创建一个MQClientInstance对象了,然后注册producer,没注册成功就抛出已经存在的异常,接着往存放topic信息表中放个topic信息,最后是启动这个MQClientInstance对象。
先来看下创建MQClientInstance 对象
在这里插入图片描述
需要注意的是MQClientManager 是个单例类, 先是创建一个clientId,其实就是 ip@instanceName,这个instanceName 没指定就是进程号。接着就是根据clientId去factory表中获取对应的instance,第一次肯定是没有的,然后就创建塞到这个factory表中,塞的时候,如果之前已经有了的话,就返回之前的instance,关于instance的创建我们后面再说,先来看下注册producer 的
【图】
注册producer也是非常简单,先是检查group与producer,然后将group与producer,如果以前有了的话就是注册失败,注册失败就会抛出异常,其实这个producerTable 是在MQClientInstance里面的,也就是一个客户端维护了多个producer,而且还是不同组的。
现在我们来看下这个MQClientInstance的构造方法了
【图】
主要是看红框里面的内容,可以看到创建了一个MQClientAPIImpl对象,其实这个MQClientAPIImpl对象里面就是一些api的实现,比如说发送消息,拉取消息等等,它里面封装了一个远程客户端remotingClient,这个远程客户端使用netty作为通信框架,然后实现一系列发送请求的操作等等,这里还有一个点,就是它在后面又创建了个消息生产者对象,不过它的组是CLIENT_INNER_PRODUCER,也会注册到那个生产者表上面(这个大家注意点就可以了)。
接下来我们再来看下MQClientInstance的启动代码,我们只需要关心红框画的就可以了
在这里插入图片描述this.mQClientAPIImpl.start();这行其实是启动netty客户端的,this.startScheduledTask();这行就是启动一堆定时任务,比如说定时去nameserv拉去topic信息,向broker发送心跳等等。
在看MQClientAPIImpl 的启动方法之前,我们需要看下它的构造
在这里插入图片描述
我们还是主要把关注点放到红框圈起来的,clientConfig这个是关于client配置的一些信息,然后往后创建了一个NettyRemotingClient对象,这个就是netty client,封装了一些同步异步的发送,接着clientRemotingProcessor,这个就是netty收到消息时候处理类,然后下面这一堆注册processor这个就不看了,其实就是当收到这些code的消息时候让哪个processor来处理。
先来看下clientConfig 有哪些配置字段需要我们关注:

字段默认值解释调优
clientWorkerThreads4这个其实就是处理netty 那堆自定义handler的线程
clientCallbackExecutorThreadscpu核心线程数callback线程数,这个用来执行你注册的那些processor
clientOnewaySemaphoreValue默认是65535当发送单向消息的时候,信号量限流
clientAsyncSemaphoreValue65535当发送异步消息的时候,信号量限流
connectTimeoutMillis3000连接超时时间没啥现实意义
clientSocketSndBufSize65535netty 客户端 发送buffer
clientSocketRcvBufSize65535netty 客户端 接收buffer
clientChannelMaxIdleTimeSeconds120s这个就是netty channel 最大空闲时间也能调优

来看下NettyRemotingClient 的构造方法:
在这里插入图片描述
首先是调用父类的构造,创建2个信号量Semaphore,这两个分别用来对单向发送,异步发送进行限流,默认是65535。之后就是创建public线程池,这个线程池主要是用来处理你注册那堆processor,默认线程数是cpu核心数,再往后就是创建netty niogroup组就1个线程,这个主要是用来处理连接的,最后就是判断是否使用ssl,使用的话创建ssl context。
好了,这里我们把remotingClient的创建看完了,再来看下它的启动start
在这里插入图片描述
这里其实就是netty client 创建过程,有些参数需要注意下,比如它默认使用4个线程的线程池来处理 你自定义的那堆handler,再就是netty的一些参数,tcp的参数,往后看启动了一个定时任务,主要是用来扫秒响应表的,其实就是把超时的响应进行回调,或者是返回,这个任务是1s执行一次。
好了到这我们remotingClient启动流程也OK了,再回到MQClientInstance,我们还有个启动任务startScheduledTask方法没有说。
在这里插入图片描述
这个任务是获取nameserv地址的,每隔2分钟执行一次
在这里插入图片描述
这个就是从nameserv获取topic信息的任务,默认是30s拉取一次
在这里插入图片描述
这就是清除那种下线的broker,然后往所有的broker发送心跳消息,默认也是30s。
到这,我们的启动流程就算完成了。

3.总结

本篇主要是讲解了一下关于RocketMQ消息生产者的一些原理性的东西,包括topic的理解,MessageQueue的概念,发送消息的三种模式等等,还介绍了RocketMQ 消息生产者的启动流程,这里需要注意的就是mq 客户端的配置与netty client的配置。

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页