RocketMQ笔记

RocketMQ架构

在这里插入图片描述

Broker

  • RocketMQ的服务,或者说一个进程,叫做 Broker,Broker的作用是存储和转发消息。RocketMQ 单机大约能承受 10 万 QPS 的请求。

  • 为了提升 Broker 的可用性(防止单点故障),以及提升服务器的性能(实现负载),通常会做集群的部署。

  • 跟kafka 或者 Redis Cluster一样,RocketMQ集群的每个Broker节点保存总数据的一部分,因此可以实现横向扩展。

  • 为了提升可靠性(防止数据丢失),每个 Broker 可以有自己的副本(slave)。

  • 默认情况下,读写都发生在 master上。在slaveReadEnable=true 的情况下,slave 也可以参与读负载。但是默认只有 Brokerld=1的 slave 才会参与读负载,而且是在master 消费慢的情况下,由 whichBrokerWhenConsumeSlowly 这个参数决定。


TOPIC

  • Topic用于将消息按主题做划分,跟kafka不同的是,在 RocketMQ中,Topic是一个逻辑概念,消息不是按 Topic 划分存储的。

  • Producer 将消息发往指定的 Topic,Consumer 订阅这个Topic就可以收到相应的消息。

  • 跟kafka 一样,如果 Topic不存在,会自动创建(可以配置取消)。

  • Topic 跟生产者和消费者都是多对多的关系,一个生产者可以发送消息到多个Topic,一个消费者也可以订阅多个 Topic。


NameServer

当不同的消息存储在不同的 Broker上,生产者和消费者对于Broker的选取,或者说路由选择是一个非常关键的问题。

我们可以把NameServer 理解为是RocketMQ的路由中心,每一个NameServer 节点都保存着全量的路由信息,为了保证高可用,NameServer自身也可以做集群的部署。它的作用有点像 Eureka 或者 Redis Sentinel。
也就是说,Broker会在 NameServer 上注册自己,Porducer和Consumer用NameServer 来发现 Broker。
在这里插入图片描述

NameServer 作为路由中心到底怎么工作的呢?

  • 每个Broker节点在启动时,都会根据配置遍历NameServer列表。
  • 与每个NameServer 建立TCP长连接,注册自己的信息,之后每隔30s发送心跳信息(服务主动注册)。
  • 主从注册,还有定时探活。每个NameServer每隔10s检查一下各个Broker 的最近一次心跳时间,如果发现某个Broker超过120s都没发送心跳,就认为这个Broker 已经挂掉了,会将其从路由信息里移除。
    在这里插入图片描述

一致性问题如何解决?

一个天大的问题来了,NameServer 之间是互相不通信的,也没有主从之分,它们是怎么保持一致性的?

  1. 服务注册
    因为没有master,Broker每隔30秒会向所有的NameServer发送心跳信息,所以还是能保持一致的。

  2. 服务剔除
    如果一个 Broker 挂了,怎么从所有的 NameServer 中移除它的信息?
    (1)如果 Broker正常关闭∶连接就断开了,Netty的通道关闭监听器会监听到连接断开事件,然后会将这个 Broker 信息剔除掉。
    (2)如果Broker异常关闭∶ NameServer 的定时任务每10秒扫描Broker列表,如果某个 Broker 的心跳包的最新时间戳超过当前时间 120秒,就会被移除。

  3. 路由发现
    如果 Broker 的信息更新了(增加或者减少节点),客户端怎么获取最新的 Broker 列表?
    生产者:发送第一条消息的时候,根据Topic从NameServer 获取路由信息。
    消费者:消费者一般是订阅固定的Topic,在启动的时候就要获取 Broker 信息。
    这之后呢? 如果说 Broker 信息动态变化了怎么办?
    因为 NameServer 不会主动推送服务信息给客户端,客户端也不会发送心跳到Nameserver,所以在建立连接之后,需要生产者和消费者定期更新

总结一下,各个NameServer 的数据是能够保持一致的。而且生产者和消费者会定期更新路由信息,所以可以获取最新的信息。

问题∶如果 Broker刚挂,客户端 30 秒以后才更新路由信息,那是不是会出现最多30 秒钟的数据延迟?比如说一个Broker 刚挂了,客户端缓存的还是旧的路由信息,发消息和接收消息都会失败。
这个问题有几个解决思路∶
1)重试;
2)把无法连接的 Broker 隔离掉,不再连接;
3)或者优先选择延迟小的节点,就能避免连接到容易挂的 Broker 了。
问题∶如果作为路由中心的 NameServer全部挂掉了,而且暂时没有恢复呢?也没有关系,客户端肯定要缓存 Broker 的信息,不能完全依赖于NameServer。


Producer

生产者,用于生产消息,会定时从NameServer拉取路由信息 (不用配置 RocketMQ 的服务地址),然后根据路由信息与指定的 Broker 建立 TCP长连接,从而将消息发送到 Broker 中。

  • 发送逻辑一致的Producer 可以组成一个 Group
  • RocketMQ的生产者同样支持批量发送,不过 List要自己传进去。
  • Producer写数据只能操作 master 节点

Consumer

消息的消费者,通过NameServer集群获得Topic的路由信息,连接到对应的Broker 上消费消息。

  • 消费逻辑一致的 Consumer可以组成一个Group,这时候消息会在Consumer 之间负载。

  • 由于 Master和 Slave 都可以读取消息,因此Consumer 会与Master和Slave都建立连接
    注意∶同一个consumer group 内的消费者应该订阅同一个topic。或者反过来,消费不同 topic的消费者不应该采用相同的consumer group名字。如果不一样,后面的消费者的订阅,会覆盖前面的订阅。

  • 消费者有两种消费方式∶一种是集群消费(消息轮询),一种是广播消费(全部收到相同副本)。

  • 从消费模型来说,RocketMQ支持 pull 和 push 两种模式。

pull

Pull模式是consumer轮询从broker拉取消息。
pull 模式有两种实现方式:

  1. 一种是普通的轮询(Polling)。不管服务端数据有无更新,客户端每隔定长时间请求拉取一次数据,可能有更新数据返回,也可能什么都没有。
    缺点∶因为大部分时候没有数据,这些无效的请求会大大地浪费服务器的资源。而且定时请求的间隔过长的时候,会导致消息延迟。

  2. RocketMQ的 pull 用长轮询来实现。
    客户端发起Long Polling,如果此时服务端没有相关数据,会hold 住请求,直到服务端有相关数据,或者等待一定时间超时才会返回。返回后,客户端又会立即再次发起下一次 Long Polling(所谓的 hold 住请求指的服务端暂时不回复结果,保存相关请求,不关闭请求连接,等相关数据准备好,写回客户端)。
    长轮询解决了轮询的问题,唯一的缺点是服务器在挂起的时候比较耗内存

push

Push模式是 Broker 推送消息给 Consumer。

RocketMQ 的 push 模式实际上是基于 pull 模式实现的,只不过是在 pull 模式上封装了一层,所以RocketMQ push 模式并不是真正意义上的"推模式"。

Message Queue

我们知道 Broker 有master节点和slave节点,当我们有多个master的时候,发往一个Topic的多条信息会在多个master Broker 上存储。那么最关键的问题来了,怎么把发到一个 Topic 里面的消息分布到不同的 master上呢?

在kafka 里面设计了一个 partition,一个 Topic可以拆分成多个 partition,这些partition可以分布在不同的 Broker上,这样就实现了数据的分片,也决定了kafka可以实现横向扩展。

RocketMQ 有没有这样的设计呢?
在一个 Broker上,RocketMQ只有一个存储文件,并没有像kafka一样按照不同的Topic 分开存储。
也就是说,如果有 3个 Broker,也就是只有3个用来存储不同数据的 commitlog。
那问题就来了,如果不按照分区去分布,数据应该根据什么分布呢?

RocketMQ里面设计了一个叫做 Message Queue 的逻辑概念,作用跟 partition 类似。

  • 首先,我们创建Topic 的时候会指定队列的数量,一个叫 writeQueueNums(写队列数量),一个 readQueueNums(读队列数量)。
  • 写队列的数量决定了有几个 Message Queue,读队列的数量决定了有几个线程来消费这些 Message Queue (只是用来负载的)。
  • 不指定 MQ 的时候,服务端创建一个 Topic 默认 8个队列(BrokerConfig)。
  • topic不存在,生产者发送消息时创建默认4个队列(DefaultMQProducer)。

MessageQueue 在磁盘上是可以看到的,但是数量只跟写队列相关
比如 TopicTest 有4个写队列,consumequeue 目录下面就会出现四个目录

/usr/local/soft/rocketmq/store/broker-a/consumequeue/TopicTest/

在这里插入图片描述
客户端封装了一个 MessageQueue 对象,里面其实就是三块内容∶
在这里插入图片描述

topic 表示它是哪个topic的队列。
brokerName 代表它在哪个Broker上,比如有两个master,一个叫 broker-a,一个叫 broker-b。
queueld 代表它是第几个分片。

举例
一个Topic有3个Message Queue,编号是1、2、3。刚好有三个Broker,第一个MQ指向Broker1,第二个MQ指向 Broker2,第三个MQ指向 Broker3。
发送消息的时候,生产者会根据一定的规则,获得 MessageQueue,只要拿到了queueld,就知道要发往哪个 Broker,然后在 commitlog 写入消息。


磁盘上看到的队列数量,是由写队列的数量决定的,而且在所有 master 上个数是一样的(但是数据存储不一样)

举例
集群有两个master。如果创建一个topic,有2个写队列、1个读队列。
那么两台机器的 consumequeue 目录会出现2个队列,一共4个队列。
也就是总队列数量是∶ 写队列数*节点数
在这里插入图片描述
如果读队列的数量比写队列的数量少,会出现消费不了的情况。

思考
Queue 的数量到底会产生什么影响?
Queue的数量要比 Broker 的数量多(倍数),才能实现尽量平均的负载,或者应对未来的扩容。
队列数量也要比消费者数量多,否则有部分消费者无法消费消息。

RocketMQ原理

生产者

前面我们说 Message Queue 是用来实现横向扩展的,生产者利用队列可以实现消息的负载和平均分布。那什么时候消息会发到哪个队列呢?

消息发送规则

MessageQueueSelector 有三个实现类∶
(a)SelectMessageQueueByHash(默认)∶它是一种不断自增、轮询的方式。
(b)SelectMessageQueueByRandom∶ 随机选择一个队列。
(c)SelectMessageQueueByMachineRoom∶ 返回空,没有实现。
除了上面自带的策略,也可以自定义 MessageQueueSelector。

顺序消息

顺序消息的场景∶
一个客户先提交了一笔订单,订单号1688,然后支付,后面又发起了退款,产生了三条消息∶一条提交订单的消息,一条支付的消息,一条退款的消息。
这三笔消息到达消费者的顺序,肯定要跟生产者产生消息的顺序一致。不然,没有订单,不可能付款;没有付款,是不可能退款的。

在 RPC 调用的场景中我们不用考虑有序性的问题,本来代码中调用就是有序的。而消息中间件经过了Broker的转发,而且可能出现多个消费者并发消费,就会导致乱序的问题。

这里我们先区分一个概念,全局有序和局部有序。
全局有序就是不管有几个生产者,在服务端怎么写入,有几个消费者,消费的顺序跟生产的顺序都是一致的。
全局有序(或者叫严格有序)的实现比较麻烦,而且即使实现了,也会对MQ的性能产生很大的影响。所以我们这里说的顺序消息其实是局部有序

比如不同的颜色表示不同的订单相关的消息,只要同一个订单相关的消费的时候是有序的时候就 OK 了。
在这里插入图片描述

要保证消息有序,要分成几个环节分析∶

  1. 生产者发送消息的时候,到达 Broker 应该是有序的。所以对于生产者,不能
    使用多线程异步发送
    ,而是顺序发送。
  2. 写入 Broker的时候,应该是顺序写入的。也就是相同主题的消息应该集中写
    入,选择同一个 queue
    ,而不是分散写入。
  3. 消费者消费的时候只能有一个线程。否则由于消费的速率不同,有可能出现记
    录到数据库的时候无序。

RocketMQ的顺序消息怎么实现呢?

  1. 首先是生产者发送消息,是单线程的。消息是同步发送的, 生产者需要等待 Broker 的响应。生产者收到 Broker 的成功的 Response 才算是消息发送成功。
  2. 其次是消息要路由到相同的 queue(关键是大家要在相同的通道排队,不能各排各的队,才能实现先进先出)。(传入一个相同的key, 大家使用相同的hashKey,就会选择同一个队列)
  3. 最后在消费者,需要保证一个队列只有一个线程消费

事务消息

这个里面出现了两个新的概念∶

  1. 半消息(half message)∶暂不能投递消费者的消息,发送方已经将消息成功发送到了 MQ 服务端,但是服务端未收到生产者对该消息的二次确认,此时该消息被标记成"暂不能投递"状态,处于该种状态下的消息即半消息。
  2. 消息回查(Message Status Check)∶由于网络闪断、生产者应用重启等原因,导致某条事务消息的二次确认丢失,MQ 服务端通过扫描发现某条消息长期处于"半消息"时,需要主动向消息生产者询问该消息的最终状态(Commit 或是 Rollback),该过程即消息回查。

看一下事务消息的整体流程∶

在这里插入图片描述

流程描述∶

  1. 生产者向 MQ 服务端发送消息。
  2. MQ 服务端将消息持久化成功之后,向发送方 ACK 确认消息已经发送成功,此时消息为半消息。
  3. 发送方开始执行本地数据库事务逻辑。
  4. 发送方根据本地数据库事务执行结果向 MQ Server 提交二次确认(Commit 或是 Rollback),MQ Server 收到 Commit 状态则将半消息标记为可投递,订阅方最终将收到该消息;MQ Server 收到 Rollback 状态则删除半消息,订阅方将不会接受该消息。
  5. 在断网或者是应用重启的特殊情况下,上述步骤 4 提交的二次确认最终未到达MQ Server,经过固定时间后 MQ Server 将对该消息发起消息回查。
  6. 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
  7. 发送方根据检查得到的本地事务的最终状态再次提交二次确认,MQ Server 仍按照步骤 4 对半消息进行操作(commit/rollback)。

在代码中怎么实现呢?

RocketMQ提供了一个TransactionListener接口,这个里面可以实现执行本地事务。
example.transaction.TransactionListenerlmpl

public RocketMQLocalTransactionState executeLocalTransaction(Message msg. Object arg){
	// local transaction process,return bollback, commit or unknown 
	log.info("executeLocalTransaction:"+JSON.toJSONString(msg));
	return 	RocketMQLocalTransactionState.UNKNOWN;
}

executeLocalTransaction 方法中执行本地事务逻辑

这个方法会决定 Broker是 commit消息还是丢弃消息,所以必须 return一个状态。这个状态可以有三种∶

  1. COMMIT 状态,表示事务消息被提交,会被正确分发给消费者。
  2. ROLLBACK 状态,该状态表示该事务消息被回滚,因为本地事务逻辑执行失败导致。
    如果既没有收到 COMMIT,也没有收到 ROLLBACK,可能是事务执行时间太长,或者报告 Broker 的时候网络出了问题呢? 那就是第三种状态。
  3. UNKNOW 状态∶表示事务消息未确定。
    返回 UNKNOW之后,因为不确定到底事务有没有成功,Broker 会主动发起对事务执行结果的查询。

延迟消息

在 RabbitMQ里面需要通过死信队列或者插件来实现。
RocketMQ可以直接支持延迟消息。但是开源版本功能被阉割了,只能支持特定等级的消息。商业版本可以任意指定时间。

实现原理∶
Broker端内置延迟消息处理能力,核心实现思路都是一样∶将延迟消息通过一个临时存储进行暂存,到期后才投递到目标 Topic 中。

在这里插入图片描述

步骤说明如下∶

  1. producer 要将一个延迟消息发送到某个 Topic 中;
  2. Broker 判断这是一个延迟消息后,将其通过临时存储进行暂存;
  3. Broker 内部通过一个延迟服务(delay service)检查消息是否到期,将到期的消息投递到目标 Topic 中;
  4. 消费者消费目标 topic 中的延迟投递的消息。

临时存储和延迟服务都是在 Broker 内部实现,对业务透明。
事实上,RocketMQ的消息重试也是基于延迟消息来完成的。在消息消费失败的情况下,将其重新当做延迟消息投递回 Broker。


Broker

消息存储

1、消息存储设计理念

RocketMQ的消息存储与Kafka 有所不同。既没有分区的概念,也没有按分区存储消息。
RocketMQ 官方对这种设计进行了解释∶
http://rocketmq.apache.org/rocketmq/how-to-support-more-queues-in-rocketmq/

Kafka分区存储的问题:

  • 每个分区存储整个消息数据。尽管每个分区都是有序写入磁盘的,但随着并发写入分区的数量增加,从操作系统的角度来看,写入变成了随机的。
  • 由于数据文件分散,很难使用 Linux IO Group Commit 机制(指的是一次把多个数据文件刷入磁盘。例如∶批量发送3条数据,分散在3个文件里面,做不到一次刷盘)。

所以RocketMQ干脆另辟蹊径,设计了一种新的文件存储方式,就是所有的Topic 的所有的消息全部写在同一个文件中(这种存储方式叫集中型存储或者混合型存储),这样就能够保证绝对的顺序写。

这样做的优势
1)队列轻量化,单个队列数据量非常少。
2)对磁盘的访问串行化,完全顺序写,避免磁盘竞争,不会因为队列增加导致IOWAIT 增高。

当然消费的时候就有点复杂了。

在kafka中是一个topic下面的 partition有独立的文件,只要在一个topic 里面找肖息就 OK了,kafka 把这个consumer group跟topic的 offset的关系保存在一个特殊的 topic 中。
现在变成了∶要到一个统一的巨大的commitLog 中去找消息,需要遍历全部的消息,效率太低了。

怎么办呢?

如果想要每个 consumer group只查找自己的 topic的 offset 信息,可以为每一个consumer group把他们消费的topic的最后消费到的 offset单独存储在一个地方。

这个存储消息的偏移量的对象就叫做 consume queue。

在这里插入图片描述
也就是说,消息在 Broker存储的时候,不仅写入commitlog,同时也把在commit log 中的最新的 offset(异步)写入对应的 consume queue。

消费者在消费消息的时候,先从consume queue 读取持久化消息的起始物理位置偏移量 offset、大小size和消息Tag的HashCode值,随后再从commit log中进行读取待拉取消费消息的真正实体内容部分。

consume queue 可以理解为消息的索引,它里面没有存消息。

总结∶

  1. 写虽然完全是顺序写,但是读却变成了完全的随机读(对于commit log)。
  2. 读一条消息,会先读consume queue,再读commit log,增加了开销。

2、物理存储文件分析
源码方式安装,默认的存储路径在/root/store。存储路径也可以自定义,例如:

/usr/local/soft/rocketmg/store/broker-a

在这里插入图片描述
在这里插入图片描述

  • commit log
    commit log,一个文件集合,每个默认文件1G大小。当第一个文件写满了,第二个文件会以初始偏移量命名。比如起始偏移量 1080802673,第二个文件名为00000000001080802673,以此类推。
    跟 kafka 一样,commit log 的内容是在消费之后是不会删除的。

  • consume queue
    consume queue∶ 一个Topic可以有多个,每一个文件代表一个逻辑队列,这里存消息在 commit log 的偏移值以及大小和 Tag 属性。

  • index file
    在Java的 API方法中,Message 有一个keys 参数,它是用来检索消息的。所以,如果出现了keys,服务端就会创建索引文件,以空格分割的每个关键字都会产生一个索引。
    单个 IndexFile 可以保存 2000W个索引,文件固定大小约为 400M。
    索引的目的是根据关键字快速定位消息。根据关键字定位消息?那这样的索引用什么样的数据结构合适?
    HashMap,没错,RocketMQ的索引是一种哈希索引。由于是哈希索引,key尽量设置为唯一不重复。


3、RocketMQ存储关键技术(持久化 /刷盘)

RocketMQ消息存储在磁盘上,但是还是能做到这么低的延迟和这么高的吞吐,到底是怎么实现的呢?

首先要介绍 Page Cache 的概念。

  • CPU 如果要读取或者操作磁盘上的数据,必须要把磁盘的数据加载到内存,这个是由硬件结构和访问速度的差异决定的。

  • 这个加载的大小有一个固定的单位,叫做 Page。x86 的linux 中一个标准页面大小是4KB。如果要提升磁盘访问速度,或者说尽量减少磁盘I/O,可以把访问过的Page在内存中缓存起来。这个内存的区域就叫做 Page Cache。

  • 下次处理I/O请求的时候,先到Page Cache 查找,找到了就直接操作。没找到就到磁盘查找。

  • Page Cache 本身也会对数据文件进行预读取,对于每个文件的第一个读请求操作,系统在读入所请求页面的同时会读入紧随其后的少数几个页面。

但这里还是有一个问题。我们知道,虚拟内存分为内核空间和用户空间。PageCache 属于内核空间,用户空间访问不了,因此读取数据还需要从内核空间拷贝到用户空间缓冲区。
在这里插入图片描述
可以看到数据需要从Page Cache再经过一次拷贝程序才能访问得到。这个copy的过程会降低数据访问的速度。有什么办法可以避免从内核空间到用户空间的 copy 呢?

这里用到了一种零拷贝的技术。

干脆把 Page Cache 的数据在用户空间中做一个地址映射,这样用户进程就可以通过指针操作直接读写Page Cache,不再需要系统调用(例如 read())和内存拷贝。
在这里插入图片描述
RocketMQ中具体的实现是使用 mmap(memory map,内存映射),不论是CommitLog 还是 CosumerQueue 都采用了 mmap。Kafka 用的是sendfile。


4、文件清理策略

跟kafka一样,RocketMQ中被消费过的消息是不会删除的,所以保证了文件的顺序写入。如果不清理文件的话,文件数量不断地增加,最终会导致磁盘可用空间越来越少。

首先,哪些文件需要清理?

  • 主要清除 CommitLog、ConsumeQueue 的过期文件。

其次,什么情况下这些文件变成过期文件?

  • 默认是超过72个小时的文件。

再者,过期的文件什么时候删除呢?

  • 通过定时任务,每天凌晨 4 点,删除这些过期的文件。
  • 磁盘使用空间超过了 75%,开始删除过期文件。
  • 如果磁盘空间使用率超过 85%,会开始批量清理文件,不管有没有过期,直到空间充足。
  • 如果磁盘空间使用率超过 90%,会拒绝消息写入。

消费者

消费端的负载均衡与 rebalance

消费者启动的时候,或者有消费者挂掉的时候,默认最多20秒,就会做一次 rebalance,让所有的消费者可以尽量均匀地消费队列的消息。

AllocateMessageQueueStrategy有6种实现的策略,可以指定,也可以自定义实现:

  1. AllocateMessageQueueAveragely∶ 连续分配(默认)
    在这里插入图片描述

  2. AllocateMessageQueueAveragelyByCircle∶ 每人轮流一个
    在这里插入图片描述

  3. AllocateMessageQueueByConfig∶通过配置

  4. AllocateMessageQueueConsistentHash∶ 一致性哈希

  5. AllocateMessageQueueByMachineRoom∶指定一个broker的 topic中的queue 消费

  6. AllocatelMachineRoomNearby∶ 按 Broker 的机房就近分配

队列的数量尽量要大于消费者的数量。


消费端重试与死信队列

先看一个业务流程中 RocketMQ的使用场景。订单系统是消息的生产者,物流系统是消息的消费者。物流系统收到消费消息后需要登记数据库,生成物流记录。

在这里插入图片描述
然后反馈给 Broker,通知消费成功,更新 offset。

如果物流系统处理消息的过程发生异常,比如数据库不可用,或者网络出现问题,这时候返回给 Broker 的是 RECONSUME_LATER,表示稍后重试。
这个时候消息会发回给 Broker,进入到 RocketMQ 的重试队列中。服务端会为consumer group 创建一个名字为%RETRY%开头的重试队列。

重试队列的消息过一段时间会再次发送给消费者,如果还是异常,会再次进入重试队列。重试的时间间隔会不断衰减,从10秒开始直到2个小时,最多重试16次。

重试消费 16 次都没有成功怎么处理呢?这个时候消息就会丢到死信队列了。Broker 会 创 建 一 个 死 信 队 列,死 信 队 列的 名 字 是 %DLQ% +ConsumerGroupName。

死信队列的消息最后需要人工处理,可以写一个线程,订阅%DLQ% +ConsumerGroupName,消费消息。

高可用架构

在 RocketMQ的高可用架构中,我们主要关注两块∶主从同步故障转移
在这里插入图片描述

主从同步的意义

  1. 数据备份∶ 保证了两/多台机器上的数据冗余,特别是在主从同步复制的情况下,一定程度上保证了 master 出现不可恢复的故障以后,数据不丢失。
  2. 高可用性∶即使 master 掉线,consumer 会自动重连到对应的slave机器,不会出现消费停滞的情况。
  3. 提高性能∶主要表现为可分担 master 读的压力,当从 master 拉取消息,拉取消息的最大物理偏移与本地存储的最大物理偏移的差值超过一定值,会转向 slave(默认brokerld=1)进行读取,减轻了master 压力。
  4. 消费实时∶master节点挂掉之后,依然可以从slave节点读取消息,而且会选择一个副本作为新的 master,保证正常消费。

数据同步

主从的关联

主从服务器怎么联系在一起?比如 A机器上的 broker-a-master和B机器上的broker-a-slave。

  1. 首先,集群的名字相同。
  2. 其次,连接到相同的 NameServer。
  3. 第三,在配置文件中∶brokerld =0代表是master,brokerld=1代表是slave。

主从同步和刷盘类型

在部署节点的时候,配置文件中设置了 Broker 角色和刷盘方式∶
在这里插入图片描述

https://github.com/apache/rocketmg/blob/master/docs/cn/design.md

通常情况下,会把 Master和 Slave 的 Broker均配置成ASYNC_FLUSH异步刷盘方式,主从之间配置成 SYNC MASTER同步复制方式,即∶异步刷盘+同步复制

主从同步流程

  1. 从服务器主动建立 TCP 连接主服务器,然后每隔 5s 向主服务器发送commitLog 文件最大偏移量拉取还未同步的消息;
  2. 主服务器开启监听端口,监听从服务器发送过来的信息,主服务器收到从服务器发过来的偏移量进行解析,并返回查找出未同步的消息给从服务器;
  3. 从服务器收到主服务器的消息后,将这批消息写入 commitLog 文件中,然后更新 commitLog 拉取偏移量,接着继续向主服务拉取未同步的消息。

HA与故障转移

Dledger快速搭建

在之前的版本中,RocketMQ 只有 master/slave一种部署方式,一组 Broker 中有一个master,有零到多个slave,slave 通过同步复制或异步复制方式去同步master的数据。master/slave 部署模式,提供了一定的高可用性。
但这样的部署模式有一定缺陷。比如故障转移方面,如果主节点挂了还需要人为手动的进行重启或者切换,无法自动将一个从节点转换为主节点。

RocketMQ2019年3月发布的4.5.0版本中,利用 Dledger技术解决了自动选主的问题。DLedger就是一个基于raft 协议的 commitlog 存储库,也是 RocketMQ实现新的高可用多副本架构的关键。它的优点是不需要引入外部组件,自动选主逻辑集成到各个节点的进程中,节点之间通过通信就可以完成选主。

基于 Dledger 的架构图∶
在这里插入图片描述
在这种情况下,commitlog 是 Dledger 管理的,具有选主的功能。

RocketMQ特性

现在市面上有这么多流行的消息中间件,RocketMQ又有什么不同之处?
一般我们会从使用、功能、性能、可用性和可靠性几个方面来衡量。其中有一些是基础特性,这里重点说一下 RocketMQ 比较突出的∶

  1. 单机可以支撑上万个队列的管理——可以满足众多项目创建大量队列的需求;
  2. 上亿级消息堆积能力——在存储海量消息的情况下,不影响收发性能;
  3. 具有多副本容错机制——消息可靠性高,数据安全;
  4. 可以快速扩容缩容,有状态管理服务器——那就意味着具备了横向扩展的能力;
  5. 可严格保证消息的有序性——满足特定业务场景需求;
  6. Consumer 支持Push和Pull两种消费模式——更灵活(主要是Pull);
  7. 支持集群消费和广播消息——适合不同业务场景;
  8. 低延迟∶客户端消息延迟控制在毫秒级别(从双十一的复盘情况来看延迟在 1ms 以内的消息比例99.6%;延迟在10ms以内的消息占据99.996%)——效率高。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值