RocketMQ 是如何发送消息

创建Topic的时候为何要指定MessageQueue数量?

  • 简单来说,就是你要指定你的这个Topic对应了多少个队列,也就是多少个MessageQueue。
  • MessageQueue就是RocketMQ中非常关键的一个数据分片机制,他通过MessageQueue将一个Topic的数据拆分为了很多个数据分片,然后在每个Broker机器上都存储一些MessageQueue。
  • Topic是一个逻辑上的概念,实际上在每个broker上以queue的形式保存,也就是说每个topic在broker上会划分成几个逻辑队列,每个逻辑队列保存一部分消息数据,但是保存的消息数据实际上不是真正的消息数据,而是指向commit log的消息索引。

生产者发送消息的时候写入哪个MessageQueue?

  • 生产者会跟NameServer进行通信获取Topic的路由数据, 以生产者从NameServer中就会知道,一个Topic有几个MessageQueue,哪些MessageQueue在哪台Broker机器上
  • 让一个Topic中的数据分散在多个MessageQueue中,进而分散在多个Broker机器上,实现RocketMQ集群分布式存储海量的消息数据了

如果某个Broker出现故障该怎么办?

  • 如果某个Broker临时出现故障了,比如Master Broker挂了,此时正在等待的其他Slave Broker自动热切换为Master Broker,那么这个时候对这一组Broker就没有Master Broker可以写入了
  • 如果你还是按照之前的策略来均匀把数据写入各个Broker上的MessageQueue,那么会导致你在一段时间内,每次访问到这个挂掉的Master Broker都会访问失败,这个似乎不是我们想要的样子。
  • 对于这个问题,通常来说建议大家在Producer中开启一个开关,就是sendLatencyFaultEnable
  • 一旦打开了这个开关,那么他会有一个自动容错机制,比如如果某次访问一个Broker发现网络延迟有500ms,然后还无法访问,那么就会自动回避访问这个Broker一段时间,比如接下来3000ms内,就不会访问这个Broker了

RocketMQ 是如何持久化消息的

1、为什么Broker数据存储是最重要的一个环节

  • roker数据存储实际上才是一个MQ最核心的环节,他决定了生产者消息写入的吞吐量,决定了消息不能丢失,决定了消费者获取消息的吞吐量,这些都是由他决定的

2、CommitLog消息顺序写入机制

  • 当生产者的消息发送到一个Broker上的时候,他接收到了一条消息,接着他会对这个消息做什么事情?首先第一步,他会把这个消息直接写入磁盘上的一个日志文件,叫做CommitLog,直接顺序写入这个文件
  • 这个CommitLog是很多磁盘文件,每个文件限定最多1GB,Broker收到消息之后就直接追加写入这个文件的末尾,就跟上面的图里一样。如果一个CommitLog写满了1GB,就会创建一个新的CommitLog文件。

RocketMq是如何写入数据的

设定一个topic -> 根据设定的MessageQueue个数 -> 分不在不同的master Broker里边 -> 每个MessageQueue是由多个 CommitLog组成 -> Commit是采用顺序读写。加上OS PageCache来保证写入性能 -> 首先。OS PageCache是基于内存的缓冲池。采用异步刷盘或者同步刷盘顺序写入磁盘 (异步刷盘宕机是会有可能导致数据丢失的

  • DLedger 相当于替换了 CommitLog
  • DLedger CommitLog 来构建出机器上的MessageQueue
  • Broker机器刚刚启动的时候,就是靠这个DLedger基于Raft协议实现的leader选举机制,互相投票选举出来一个Leader,其他人就是Follower,然后只有Leader可以接收数据写入,Follower只能接收Leader同步过来的数据
  • DLedger收到一条数据之后,会标记为uncommitted状态,然后他会通过自己的DLedgerServer组件把这个uncommitted数据发送给Follower Broker的DLedgerServer。
  • 接着Follower Broker的DLedgerServer收到uncommitted消息之后,必须返回一个ack给Leader Broker的DLedgerServer,然后如果Leader Broker收到超过半数的Follower Broker返回ack之后,就会将消息标记为committed状态。
  • 也就是说。当leaderBroker收到消息之后会同步给 FollowerBroker 节点。当节点响应ack之后主节点才会返回给生产者ack

源码索引

  • 消息发送
  • producer.send(msg);
  • -> defaultMQProducerImpl.sendDefaultImpl
    • -> this.tryToFindTopicPublishInfo 从 NameService 获取 Topic路由信息(本地有缓存就从缓存中获取)
    • -> this.selectOneMessageQueue 选择一个消息队列 queue
    • -> this.sendKernelImpl 调用发送核心方法
    • -> mQClientFactory.getMQClientAPIImpl().sendMessage 进行发送
  • -> MQClientAPIImpl#sendMessageSync
  • -> remotingClient.invokeSync 调用netty方法发送 RequestCode.SEND_MESSAGE 消息
  • broker接受到消息的处理
  • -> NettyServerHandler#channelRead0
  • -> NettyRemotingAbstract#processMessageReceived
  • -> NettyRemotingAbstract#processRequestCommand 处理客户端的请求消息
  • -> processor.asyncProcessRequest 客户端发送的是异步消息,不需要同步返回成功
  • -> SendMessageProcessor#asyncProcessRequest 进入消息处理
  • -> AbstractSendMessageProcessor#parseRequestHeader 解析请求
  • -> SendMessageProcessor#asyncSendMessage 异步保存发送的消息
  • -> this.brokerController.getMessageStore().asyncPutMessage(msgInner) MessageStore存储消息
  • -> MessageStore#asyncPutMessage 异步保存发送的消息
  • -> MessageStore#putMessage 保存发送的消息
  • -> DefaultMessageStore#asyncPutMessages DefaultMessageStore保存消息默认实现
  • -> this.commitLog.asyncPutMessages(messageExtBatch) 保存发送的消息
  • -> CommitLog#asyncPutMessages 保存发送的消息
  • -> mappedFile.appendMessages(messageExtBatch, this.appendMessageCallback, putMessageContext) mappedFile
  • -> CommitLog#submitFlushRequest 提交刷盘 (异步 / 同步)
  • -> CommitLog#submitReplicaRequest 将消息同步到从节点。可配置备份多少个
  • -> 消息保存完毕

rocketMq 同步消息的原理

  • netty调用方会触发的动作

  • RemotingClient#invokeSync

  • RemotingClient#invokeSyncImpl 发送同步方法的实现

  • this.responseTable.put(opaque, responseFuture) 这个很关键,将opaque 存到响应的 responseTable里边

  • 然后下方 responseFuture.waitResponse(timeoutMillis) 会阻塞当前请求

  • netty被调用方会触发的动作

  • NettyRemotingAbstract 然后我们看此处的方法。

  • RemotingResponseCallback callback = new RemotingResponseCallback() 构建了一个远程

  • 如果请求是同步请求的话,一定会触发 callback.callback(response);

final RemotingResponseCallback callback = new RemotingResponseCallback() {
    @Override
    public void callback(RemotingCommand response) {
        doAfterRpcHooks(remoteAddr, cmd, response);
        if (!cmd.isOnewayRPC()) {
            if (response != null) {
                response.setOpaque(opaque);
                response.markResponseType();
                try {
                    ctx.writeAndFlush(response);
                } catch (Throwable e) {
                    log.error("process request over, but response failed", e);
                    log.error(cmd.toString());
                    log.error(response.toString());
                }
            } else {
            }
        }
    }
};
  • 请观察一下这个的实现。 response.setOpaque(opaque); 想当于将请求的 opaque 塞入到了response里边。

  • 然后将 ctx.writeAndFlush(response); 到调用方

  • 然后回到调用方

  • NettyRemotingAbstract#processMessageReceived 检查到是 RESPONSE_COMMAND 响应的请求

  • responseFuture.putResponse 会设置 responseCommand 并且 countDownLatch.countDown 释放之前阻塞的请求

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值