RocketMQ源码解析之消息生产者(同步发送与单向发送)

1. 概述

我们在之前关于RocketMQ生产者文章里面介绍了发送消息分为三种模式,分别是同步发送,异步发送和单向发送,这里简单的介绍下,单向发送,这个就是发送之后不用接收结果的,就是你发出去一个消息,然后就返回了,就算有结果返回也不会接收了,这是站在消息生产者的角度;同步发送的话,就是发出去一个消息,这个线程要等着它返回消息发送结果,然后你这个线程再根据这个消息发送结果再做一些业务操作等等;异步发送,这个就是在你发送消息之前要给一个callback,发送的时候,你这个线程就不用等着,该干什么就干什么,然后发送结果回来的时候,是由其他线程调用你这个callback来处理的,你可以把这个callback看作是一个回调函数,回调方法,这个方法里面的业务逻辑就是你对这个消息发送结果的处理。注意,本文介绍的消息发送只是普通的消息发送,那种事务类型的消息,我们以后会有介绍。

2.源码分析

2.1 同步发送

在翻源码之前,我们先看下这个消息生产者是怎样发送这个同步消息的
在这里插入图片描述
我们可以看到这个代码,你是同步消息你是需要在你自己的业务线程里面接收这个sendResult的,然后在做一些业务处理,比如我这里就是打印了一下这个sendResult。
接下来我们看下它是怎样发送的,这里是调用了这个producer的send方法。
在这里插入图片描述
我们可以看到,这个 DefaultMQProducer 将这个消息给了defaultMQProducerImpl 这个实现的send方法来处理了。
在这里插入图片描述
defaultMQProducerImpl的send方法,加了个超时时间 ,然后有调用它的重载方法send(msg,timeout)
在这里插入图片描述
这个send(msg,timeout)又调用了sendDefaultImpl 方法,然后他这里加了个通信模式是同步,CommunicationMode.SYNC
sendDefaultImpl 方法就比较长了了我们分成几部分来介绍:
在这里插入图片描述
这一小段代码其实就是做了一些准备检查工作,注意第二行的个检查消息合法性,它要检查你topic,消息长度的,你不能发空消息,消息长度也不能太长,默认是不超过4m,接下来这些就是记录一下时间了,再看最后一行,就是根据你这个消息发送的topic,然后获取topic 发送消息的这么一个信息,这里面就有这topic 有几个MessageQueue,然后每个MessageQueue对应在哪个broker上面,broker 的地址又是啥的,它这个方法会先从本地的一个缓存中获取下,没有的话就从nameserv更新下这个本地缓存,再找找,要是再找不到,它就认为你没有这个topic了,然后就去nameserv上面拉取一个默认topic的一些配置信息给你用(这个其实就是在新建一个topic)。 接着这个方法往下看,接着就是判断 这个TopicPublishInfo 是否存在了,如果不存在的话就抛出异常了,没有后续了就,如果存在的话:
在这里插入图片描述
其实下面还有许多处理异常的操作没有放上,不过不影响我们的主流程,这里我把一些重要的点用红框框了出来,先是判断你这个通信模式,如果是同步的话,默认重试次数就是2 ,然后加上本身这次请求,也就是最查请求3次。这个for循环就是失败重试的代码,再看下下一个红框的代码selectOneMessageQueue这个就是选择一个MesssageQueue的方法了,这个是比较重要的,这里我们先不说,你可以把它理解为 我们的负载均衡。接着往下走,就是判断一下时间了,计算一下剩下的时间, 如果这一堆前面的内容耗时很长,然后已经超了之前设置的默认超时时间,这个时候就会超时了,然后将这个calltimeout设置成true了。
接着就是进行发送了调用sendKernelImpl 方法:
在这里插入图片描述
这个sendKernelImpl 也是有点长,然后我们一部分一部分的看下,这就是根据MessageQueue里面的broker name 获取一下broker addr,他这个broker addr 选的是master的,比如说我们 broker使用的是 master/slave 高可用架构,这个时候只会选择那个master,毕竟是往里面写消息,然后只能用master,等到介绍消息消费者的时候,消息消费者是可以向slave node 获取消息消费的,前提是 master 负载比较大,然后消息消费者下次获取消费的消息已经在slave里面了,然后消息消费者获取到消息之后,它里面有个字段是告诉你下次可以去xxx 地址的broker 拉取消息,这个我们介绍到消息消费者的时候再说。接着回来,如果没有获取到这个broker 地址的话,就是去nameserv上更新下本地缓存,然后再获取下。接着再往下就是再次判断一下这个broker addr 了,如果还没有就抛出异常,如果有的话 就执行下面的代码了:
在这里插入图片描述
第一句,这个其实就是进行一个vip通道地址的转换,这个比较有意思,如果你这个支持vip channel的话,它会把broker addr 里面的端口改变一下,这个所谓的vip channel ,其实就是与它的另一个端口建立连接,这个端口就是当前端口-2 ;
接着,如果这个消息不是批量消息的话,我们就给这个消息设置一个唯一的消息id,再往下就是 sysflag的处理了,这个sysflag里面记录了好几个属性值,使用二进制来处理的,比如说消息是否压缩了(这个压缩,就是你消息内容超过了默认的4k之后,就会进行压缩,这个压缩的阈值你是可以配置的),是否是个事务消息等等。
接下来就是执行hook了,这个hook就是forbidenHook ,其实就是对消息进行过滤。
在这里插入图片描述
在往下就是执行一下发送消息之前的hook,再往下就是封装发送消息请求头,然后这个请求头里面就涵盖了很多的参数,比如说topic,MessageQueue 队列Id, 出生日期,flag等等。再往下就是消息发送了
在这里插入图片描述
因为本小节主要是介绍下这个同步发送消息,然后我们就主要介绍下这个sync的代码逻辑:
首先是判断超时,然后交给 MQClientAPI层去处理,然后返回sendResult
我们这里接着看下MQClientAPIImpl里面的sendMessage 实现
在这里插入图片描述
这里先生成一个RemotingCommand 这么个实体对象,然后RequestCode就是SEND_MESSAGE,其实这里判断了一下sendSmartMsg 这个参数,把requestHeader优化了一下,然后换成了requestHeaderV2,其实这个requestHeaderV2 内容跟requestHeader一样,但是他这个曾元变量名是单个字母的,然后序列化,反序列化,传输内容都有所优化,其实他这个序列化使用是json形式的,然后想想就知道有些哪些好处了, 唯一的缺点就是可读性差点,但是这个玩意是对用户透明的,用户不需要关心。
接着就是判断通信类型,然后发送消息了,这里是同步发送,先是判断一下超时时间,接着就是调用sendMessageSync 进行同步发送了,我们接着来看下这个sendMessageSync 方法实现:
在这里插入图片描述
这里就调用到了client 模块(这个client其实就是直接操作netty了)来处理了,然后返回响应,调用processSendResponse 方法来处理响应。
我们再来看下client的 invokeSync 方法:
在这里插入图片描述
这里有两个点需要关注下,首先是根据broker addr 这个地址获取一下对应的channel ,如果不存在的话就创建一下这个连接, 稍微看下这块的代码
在这里插入图片描述
如果你这个addr是空的话,这个就是默认找nameserv的addr ,然后找对应channel就可以了,如果不是null ,然后它会去这个channelTable 这个map中去找,如果没有的话就创建一个对应的channel
接着回到这个invokeSync 方法中,获得channel之后,就是执行一下rpcHook了,这东西就是你在创建MQProducer的时候设置的,在调用前执行一次,调用后执行一次,其实你就可以通过这个hook来实现很多功能,监控的功能比较多些。接着就是调用了invokeSyncImpl 这个实现方法来发送消息了,这个方法是它的一个父类里面的:
在这里插入图片描述
这个方法其实就是最终往 channel里面写内容的方法了,我们来看下,先是为这个次request 创建一个id 吧,这个id主要用来返回响应的时候用的。
接着创建一个ResposeFuture ,这个东西异步,同步都可以用,这个一会介绍一下它的原理,接着就是将这个id 与这个 ResposeFuture 关联起来放到这个 responseTable 里面的, 接着就是往channel里面发送消息了,这里它添加一个listener ,这listener的执行时机就是发送出去的时候,最后就是等待这个响应了。。
我们来解释下这个ResposeFuture 原理, 当执行了responseFuture.waitResponse(timeoutMillis); 这行代码,当前线程就会wait ,然后被阻塞,然后等着响应回来的时候,netty处理响应的线程会从响应里面获取一下这个opaque这个id,就是请求之前在request生成的,broker 在响应的时候会会把这个id 放回到response 中, 然后会根据这个opaque 从responseTable中找到这个 ResposeFuture ,然后把响应设置到这个里面,最后唤醒一下wait在这个对象里面的线程就可以了,这样你这个业务线程就得到了这个RemotingResponse 了。 好了,到这我们就解释清楚了,然后我们看下他这个代码是怎样实现的
在这里插入图片描述
不过它这个ResposeFuture 是使用CountDownLatch 来实现这个wait与唤醒的。我们来具体看下这个 waitResponse方法与这个putResponse方法在这里插入图片描述
在这里插入图片描述
还是很清楚的。
好了到这里我们这个同步发送的源码解析就ok了,我们用一个图来总结下它的调用
在这里插入图片描述
(原图的话可以访问下我的process on :链接 支持免费克隆)

2.2 单向发送

单向发送其实这块跟同步发送的流程差不多,我们来看下它的消费者代码是怎样写的
在这里插入图片描述
可以看到我们最后发送的时候调用的是sendOneway方法,这个方法是没有返回值的。
在这里插入图片描述
这里就是调用了defaultMQProducerImpl的 sendOneway方法
在这里插入图片描述
这里需要注意的是它也是调用了sendDefaultImpl 方法,然后通信方式是oneway 。这里我们就不细说了,可以看下同步方法解析这个方法的说明,这里唯一要提一点是单向发送是没有这个重试的,然后就发送一次。下面的流程都是一样的,然后就到了这个MQClientAPIImpl 的 sendMessage 方法
在这里插入图片描述
然后他这个是又调用了NettyRemotingClient 的 invokeOneway 方法
在这里插入图片描述
这里也是根据broker addr 获取channel, 如果没有的话,也是创建一个,接着就是执行这个rpc调用前的hook ,注意这里没有调用后的一个hook,因为我们并不知道它是什么情况。
接着又调用了invokeOnewayImpl 方法
在这里插入图片描述
这里使用了semaphore进行限流,然后默认的话是同时支持65535 个请求发送的,这个semaphore 限流只有单向发送与这个异步发送会有,接着就会将这个request写入channel中,然后add了一个listener ,这个listener执行时机就是消息发送出去了,这个时候就会释放 信号量。
到这我们这个单向发送就解析完成了。

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