消息发送的三种方式
1.可靠同步发送
2.可靠异步发送
3.单向发送
消息的组成
RocketMQ 中消息的基础属性包括:主题 topic、消息 flag、扩展属性 properties和消息体 body。其中拓展属性包含以下几个:tag(消息 tag,用于消息过滤)、keys(消息索引建,用多个空格隔开,RocketMQ 可以根据这些 key 快速检索到消息)、waitStoreMsgOK(消息发送时是否等消息存储完成后再返回)、delayTimeLevel(消息延迟级别,用于定时消息或消息重试)
生产者的启动
生产者的主要功能
生产者负责按不同的方式发送消息、根据特定的条件查询消息(key、msgId、timestamp)、查询某个消息队列的物理偏移、批量发送等功能。
生产者的启动流程
1.检查 producer group 是否符合要求;改变生产者的 instanceName 为进程 ID
2.创建一个 MQClientInstance 实例。同一个 clientId(clinetId 为客户端 IP+instance+(unitname 可选)) 只会创建一个 MQClientInstance。由于 clientId 根据 instanceName 创建,所以多生产者的情况下会共用同一个 MQClientInstance。
单例模式的应用 MQClientManager
整个JVM 实例中只存在一个 MQClientManager 实例,维护一个 MQClientInstance 缓存表。
3.向MQClientInstance 注册,将当前生产者加入到 MQClientInstance 管理中,方便后续调用网络请求、进行心跳检测等。
4.启动MQClientInstance。
消息发送基本流程
消息发送的基本流程包括:验证消息、查找路由、消息发送 (包含异常处理机制)
函数调用栈
DefaultMQProducerImpl.sendDefaultImpl -> sendKernelImpl -> MQClientAPIImpl.sendMessage
异步发送消息流程
当用户创建DefaultMQProducer
调用send()
发送消息时,具体流程如下:
1.在DefaultMQProducer
中的send()
方法调用Validators 检测 message 的合法性,包括:是否有主题名称、消息体不能为空、消息长度不能等于 0且默认不能超过消息最大长度限制。之后设置带有命名空间的 topic。
2.调用DefaultMQProducerImpl
中的sendDefaultImpl()
,查找主题路由信息,并选择了消息队列后,调用sendKernelImpl()
。
3.在sendKernelImpl()
中,首先根据消息队列获取对应 broker 的地址,然后为消息分配全局 id,判断是否可以压缩以及是否是事务消息。如果注册了钩子函数,则在发送消息前调用执行。
4.构建requestHeader
,包含生产者组、主题名称、默认创建主题 key、该主题在单个broker 中的默认队列数、队列 id、消息系统标记、消息发送时间、消息标记 flag、消息扩展属性、消息重试次数、是否是批量消息等。
5.选择异步方式进行发送,调用MQClientAPIImpl
中的sendMessage()
,构建RemotingCommand
,将 body 和 header 放入 request 中。根据communicationMode
调用sendMessageAsync()
异步发送消息。该方法直接调用了remotingClient
中的invokeAsync
方法,并传入回调。回调函数会调用注册的钩子函数对返回的response
进行处理,并更新故障延迟项。在出现异常时,会调用onexceptionImpl
进行异常处理并重发消息。
获取主题路由信息
第一次发送消息时,本地没有换成 topic 的路由信息,查询 NameServer 尝试获取,如果路由消息未找到,再次尝试使用默认主题去查询。如果 BrokerConfig#autoCreateTopicEnable 为 true 时,NameServer 将返回路由信息,如果为 false 将抛出无法找到 topic 路由异常。
路由换成由 MQClientInstance 负责维护。
选择消息队列
选择消息队列有两种方式,一种是默认的机制,一种是故障延迟机制。
默认的机制在每次重试的时候,会自动选择下一个非lastBroker
的消息队列。
故障延迟机制则是每次发送失败时,会根据响应的时延来决定该 broker 不可用的时间,每次选择消息队列时,除了要排除lastBroker
外,还会排除当前不可用的 broker。这样就能避免默认机制下选择故障的 broker。
因为路由消息是 30s从 NameServer 处更新一次,所以在这段时间内自行将不可用的 broker 隔离以保证消息发送的高可用
批量消息发送
批量消息发送是指把多条同一主题的消息一起打包发送到服务端,减少网络调用次数,提高网络传输效率。RocketMQ 将消息同一封装在RemotingCommand
这个数据结构中。
RemotingCommand
RemotingCommand
的属性如下:
1.code:请求命令编码,请求命令类型
2.version:版本号
3.opaque:客户端请求序号
4.flag:标记。倒数第一位表示请求类型,0:请求;1:返回。倒数第二位,1:表示 oneway。
5.remark:描述
6.extFields:扩展属性
7customeHeader:每个请求对应的请求头信息
8.body:消息体内容
批量发送流程
RocketMQ 将多条消息体的内容存储在 body 中并使用固定格式进行存储,方便服务端解析。
首先在消息发送端,调用 batch 方法,将一批消息封装成 MessageBatch 对象。MessageBatch 继承自 Message 对象,持有一个List<Message> messages
。然后将每条消息编码成一个byte[]
,作为聚合消息的消息体,在服务端就能正确解析了。在对每条消息进行编码时,只编码flag、body 和 properties。
Q & A
1.路由信息如何加载到本地
消息生产者在发送信息时,如果本地路由表中未缓存 topic 的路由信息,向 NameServer 发送获取路由信息请求,更新本地路由表,并且消息生产者每隔 30s 从 NameServer 更新路由表
2.消息发送如何实现高可用
(1)重试
(2)在一次消息发送过程中发现错误,在某一段时间内不会选择该 Broker 上的消息队列,提高发送消息的成功率
3.批量消息发送如何实现一致性
使用规定的编码方式对单条消息进行编码,编码成一个byte[]
,统一放在聚合消息的body
里。在服务端再用相同的协议解码即可。