RocketMQ发送消息原理分析
1. 路由注册
RocketMQ路由注册是通过Broker与NameServer的心跳功能实现的。Broker启动后每隔30s向集群中所有NameServer发送心跳包,然后Name Server每隔10s扫描Broker信息,如果连续120s没有收到心跳包,NameServer将移除该Broker的路由信息。
2. 消息发送
- 同步发送:发送者向MQ发送消息时,同步等待,直到消息服务器返回发送结果。
- 异步发送:发送者向MQ发送消息时,指定回调函数后便返回,消息发送者线程不阻塞。
- 单向发送:发送者向MQ发送消息时,直接返回,不在乎消息是否成功存储在消息服务器上。
2.1 DefaultMQProducer的创建流程
从start()方法作为入口,简单分析源码
- 创建MQClientInstance实例。整个JVM实例中只存在一个MQClientManager实例,MQClientManager维护一个MQClientInstance缓存表。MQClientInstance对应唯一的clientId,clientId由客户端IP + instanceName(一般会是进程ID)组成。(且同一个JVM中的不同消费者和不同生产者在启动时获取到的MQClientInstane实例都是同一个)
- 将当前生产者加入到MQClientInstance管理中,方便后续调用网络请求、进行心跳检测等。
- 启动MQClientInstance。
2.2 消息发送基本流程
消息发送流程主要的步骤:验证消息、查找路由、消息发送(包含异常处理机制)。
默认消息发送以同步方式发送,默认超时时间为3s。
以sendMessage作为入口,简单分析源码
- 消息发送之前,首先确保生产者处于运行状态,然后验证消息是否符合相应的规范,具体的规范要求是主题名称、消息体不能为空、消息长度不能等于0且默认不能超过允许发送消息的最大长度4M。
- 其次获取主题的路由信息,只有获取了这些信息我们才知道消息要发送到具体的Broker节点。
a) 如果获取不到主题路由信息(未创建主题)时,则使用默认的主题去查询,并将路由信息中的读写队列个数设置为produce默认的队列个数。
b) 将查询到的主题路由信息和本地缓存进行比较,如果发生改变则更新本地的broker,然后会更新该MQClientInstance所管辖的所有消息发送关于topic的路由信息。
3. 消息路由查找完之后,选择具体的消息队列。
a) 本地对于每个topic会记录一个sendWhichQueue,选择消息队列后自增。
b) 发送消息会采用重试机制,在收到发送失败结果执行回调之前会进行循环执行。当对某个broker的一个队列发送失败时,会跳过该broker。
DefaultMQProducerImpl#sendDefaultImpl
MQClientAPIImpl#onExceptionImpl
注意:从代码可以看出同步模式下只有在成功收到响应,且结果不是SEND_OK后才会触发重试机制;异步模式则是在通信异常后触发重试机制,响应结果只要不为空都会正常返回!也就是说同步模式下我们需要自己决定出现通信异常时如何处理,异步模式我们则需要自己决定响应结果不是SEND_OK时如何处理。
这里和书上说的刚好相反!
c) 在多次消息发送过程中利用故障检测机制(即在一定的时间内规避不可用的broker)暂时排除故障broker。
4. 发送消息核心入口,DefaultMQProducerImpl#sendKernelImpl
- 根据MessageQueue获取Broker的网络地址。如果MQClientInstance的brokerAddrTable未缓存该Broker的信息,则从NameServer主动更新一下topic的路由信息。
- 为消息分配全局唯一ID,如果消息体默认超过4K,会对消息体采用zip压缩,并设置消息的系统标记为MessageSysFlag.COMPRESSED_FLAG。如果是事务Prepared消息,则设置消息的系统标记为MessageSysFlag.TRANSACTION_PREPARED_TYPE。
- 处理before钩子函数,即可以在消息发送前增加一些额外处理。
- 构建消息发送请求包。
- 根据消息发送方式,同步、异步、单向方式进行网络传输。
- 处理after钩子函数,即使发送异常也会处理。
参考《RocketMQ技术内幕》