RocketMQ(二):揭秘发送消息核心原理(源码与设计思想解析)

RocketMQ(二):揭秘发送消息核心原理(源码与设计思想解析)

上篇文章主要介绍消息中间件并以RocketMQ架构展开描述其核心组件以及MQ运行流程

本篇文章以Product的视角来看看发送消息的核心原理与设计思想,最后以图文并茂的方式描述出发送消息的核心流程

消息发送方式

RocketMQ中普通消息提供三种发送方式:同步、异步、单向

上篇文章中我们已经使用封装好的API延时过同步发送

在使用三种方式前,我们先来理解它们的理论知识

同步发送:发送完消息后,需要阻塞直到收到Broker的响应,通常用于数据一致性较高的操作,需要确保消息到达Broker并持久化

同步发送收到响应并不一定就是成功,还需要根据响应状态进行判断

SendResult响应状态包括:

  1. SEND_OK:发送成功
  2. FLUSH_DISK_TIMEOUT:刷盘超时
  3. FLUSH_SLAVE_TIMEOUT:同步到备超时
  4. SLAVE_NOT_AVAILABLE:备不可用

(这些状态与设置的刷盘策略有关,后续保证消息可靠的文章再进行详细展开说明,本篇文章还是回归主线探究发送消息)

异步发送:发送完消息后立即响应,不需要阻塞等待,但需要设置监听器,当消息成功或失败时进行业务处理,可以在失败时进行重试等其他逻辑保,通常用于追求响应时间的场景

异步发送相当于同步发送,需要新增SendCallback回调来进行后续成功/失败的处理,并且异步发送没有返回值

@GetMapping("/asyncSend")
public String asyncSend() {
   
    producer.sendAsyncMsg(topic, "tag", "async hello world!", new SendCallback() {
   
        @Override
        public void onSuccess(SendResult sendResult) {
   
            log.info("消息发送成功{}", sendResult);
        }

        @Override
        public void onException(Throwable throwable) {
   
            log.info("消息发送失败", throwable);
            //记录后续重试
        }
    });
    return "asyncSend ok";
}

原生API封装:

public void sendAsyncMsg(String topic, String tag, String jsonBody, SendCallback sendCallback) {
   
        Message message = new Message(topic, tag, jsonBody.getBytes(StandardCharsets.UTF_8));
        try {
   
            producer.send(message, sendCallback);
        } catch (MQClientException | RemotingException | InterruptedException e) {
   
            throw new RuntimeException(e);
        }
}

单向发送:只要发出消息就响应,性能最好,通常用于追求性能,不追求可靠的场景,如:异步日志收集

由于单向发送的特性,即不需要回调也没有返回结果

@GetMapping("/sendOnewayMsg")
public String onewaySend() {
   
    producer.sendOnewayMsg(topic, "tag", "oneway hello world!");
    return "sendOnewayMsg ok";
}

原生API封装:

public void sendOnewayMsg(String topic, String tag, String jsonBody) {
   
    Message message = new Message(topic, tag, jsonBody.getBytes(StandardCharsets.UTF_8));
    try {
   
        producer.sendOneway(message);
    } catch (MQClientException | RemotingException | InterruptedException e) {
   
        throw new RuntimeException(e);
    }
}

发送消息原理

在研究发送消息的原理前,不妨来思考下,如果让我们实现,我们要思考下需要哪些步骤?

像我们平时进行业务代码编写前的第一步就是进行参数校验,因为要防止参数“乱填”的意外情况

然后由于需要找到对应的Broker,那肯定要获取Topic路由相关信息

这个路由信息前文说过是从NameServer集群定时获取即时更新的,那么客户端的内存里肯定会进行存储

像这样的数据肯定是类似于多级缓存的,先在本地缓存,如果本地没有或者本地是旧数据,那么就网络通信再去远程(NameServer)获取一份后再更新本地缓存

获取完路由信息后,可以通过设置的Topic获取对应的MessageQueue队列信息,因为Topic下可能有很多队列,因此需要负载均衡算法决定要发送的队列

rocketmq发送消息还提供超时、重试等机制,因此在这个过程中需要计算时间、重试次数

最后发送消息会进行网络通信,我们要选择合适的工具进行RPC

总结一下,如果让我们设计起码要有这些流程:参数校验、获取路由信息、根据负载均衡算法选择队列、计算超时,重试次数、选择网络通信RPC工具…

在设计完流程后,如果我们是一位”成熟的设计师“,那么一定会将这些步骤中通用的步骤抽象成模板,模板可以作为三种发送消息通用方式,而那些变动的就是策略,解耦互不影响,并在重要的流程前后留下”钩子“,方便让使用者进行扩展

rocketmq流程与我们设计、思考的流程类似,先准备一张最终的流程图,方便跟着流程图一起阅读源码:
image.png

sendDefaultImpl 通用发送消息模板

通过三种发送方式,都会来到DefaultMQProducerImpl.sendDefaultImpl这个就是通用方法的模板

代码块中只展示部分关键代码,流程如下:

  1. 参数校验 Validators.checkMessage
  2. 获取路由信息 tryToFindTopicPublishInfo
  3. 选择一个要发送的MessageQueue selectOneMessageQueue
  4. 发送消息 sendKernelImpl

在3、4步骤中还会进行重试、超时判断

private SendResult sendDefaultImpl(
    //消息
    Message msg,
    //方式
    final CommunicationMode communicationMode,
    //异步的回调
    final SendCallback sendCallback,
    //超时时间
    final long timeout
) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
   
    //参数校验
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值