RocketMQ

一、简述

RocketMQ 一个纯 Java、分布式、队列模型的开源消息中间件,前身是 MetaQ,是阿里研发的一个队列模型的消息中间件,后开源给 apache 基金会成为了 apache 的顶级开源项目,具有高性能、高可靠、高实时、分布式特点。RocketMQ 的逻辑存储结构是物理队列+逻辑队列的结构。
RocketMQ_第1张图片

物理队列只有一个,采用固定大小的文件顺序存储消息。逻辑队列有多个,每个逻辑队列有多个分区,每个分区有多个索引。
a.消息顺序写入物理文件里面,每个文件达到一定的大小,新建一个文件继续顺序写数据(消息的写入是串行的,避免了磁盘竞争)。
b.消息的索引则顺序的写入逻辑文件中,并不存放真正的消息,只是存放指向消息的索引。RocketMQ 对于客户端展现的是逻辑队列就是消费队列,consumer 从消费队列里顺序取消息进行消费。

优点:

  1. 这种设计是把物理和逻辑分离,消费队列更加轻量化。单个队列数据量很小,可以支撑更多的消费队列数,提升消息的吞吐量,并且有一定的消息堆积能力。
  2. 对磁盘的访问串行化,避免磁盘竞争,不会因为队列增加导致 IO WAIT 增高

缺点:

  1. 随机读。写虽然是顺序写,但是读却是随机读的。
  2. 读一条消息,需要先读逻辑队列,再读物理队列,增加了开销。
  3. 要保证物理队列与逻辑队列完全一致,增加了编程的复杂度。

解决办法 :

  1. 随机读,尽可能让读命中 PAGECACHE(页高速缓存),减少 IO 读操作,所以内存越大越好。
    • 系统中堆积的消息过多,读数据要访问磁盘并不会由于随机读导致系统性能急剧下降。
    • 访问 PAGECACHE 时,即使只访问 1k 的消息,系统也会提前预读出更多数据,在下次读时,就可能命中内存。
    • 随机访问物理队列磁盘数据,系统 IO 调度算法设置为 NOOP 方式,会在一定程度上将完全的随机读变成顺序跳跃方式,而顺序跳跃方式读较完全的随机读性能会高 5 倍以上。
  2. 由于逻辑队列存储数据量极少,而且是顺序读,在 PAGECACHE 预读作用下,逻辑队列的读性能几乎与内存一致,即使堆积情况下。所以可认为逻辑队列完全不会阻碍读性能。
  3. RocketMQ 的所有消息都是持久化的,先写入系统 PAGECACHE,然后刷盘,可以保证内存与磁盘都有一份数据,访问时,直接从内存读取。
  4. 物理队列中存储了所有的元信息,类似于 MySQL 的 binlog,Oracle 的 redolog。所以只要有物理队列在,逻辑队列即使数据丢失,仍然可以恢复出来。

二、RocketMQ 的演进

RocketMQ 前后共经历了三代演进:

1️⃣第一代,推模式

数据存储采用关系型数据库,典型代表包括 Notify、Napoli。

2️⃣第二代,拉模式

自研的专有消息存储,在日志处理方面参考 Kafka,典型代表 MetaQ。

3️⃣第三代,以拉模式为主,兼有推模式

低延迟消息引擎 RocketMQ,在二代功能特性的基础上,为电商金融领域添加了可靠重试、基于文件存储的分布式事务等特性。使用在了阿里大量的应用上,典型如双十一场景,具有万亿级消息流转。

三、RocketMQ 的架构设计

RocketMQ 的核心组件主要由 NameServer、Broker、Producer 以及 Consumer 四部分构成。RocketMQ_第2张图片

1️⃣【NameServer】主要负责对于源数据的管理,包括了对于 Topic 和路由信息的管理。它是一个功能齐全的服务器,其角色类似 Dubbo 中的 Zookeeper,但 NameServer 与 Zookeeper 相比更轻量。主要是因为每个 NameServer 节点互相之间是独立的,没有任何信息交互。

2️⃣【Producer】消息生产者,负责产生消息,一般由业务系统负责产生消息。由用户进行分布式部署,消息由 Producer 通过多种负载均衡模式发送到 Broker 集群,发送低延时,支持快速失败。

3️⃣【Broker】消息中转角色,负责存储消息,转发消息。

  1. Broker 是具体提供业务的服务器,单个 Broker 节点与所有的 NameServer 节点保持长连接及心跳,并会定时将 Topic 信息注册到 NameServer,顺带一提底层的通信和连接都是基于 Netty 实现的。
  2. Broker 负责消息存储,以 Topic 为纬度支持轻量级的队列,单机可以支撑上万队列规模,支持消息推拉模型。
  3. 官网上有数据显示:具有上亿级消息堆积能力,同时可严格保证消息的有序性。

4️⃣【Consumer】消息消费者,负责消费消息,一般是后台系统负责异步消费。它也由用户部署,支持 PUSH 和 PULL 两种消费模式,支持集群消费和广播消息,提供实时的消息订阅机制。RocketMQ_第3张图片

• Push Consumer:向 Consumer 对象注册一个 Listener 接口,收到消息后回调 Listener 接口方法,采用 long-polling 长轮询实现 push。

long-polling 长轮询:1.服务端会阻塞请求直到数据传递或超时才返回。2.客户端收到请求返回后,再次发出请求,重新建立连接。3.当客户端处理接受的数据、重新建立连接时,服务器保存新到达的数据直到客户端重新建立连接,客户端会一次把当前服务器所有信息取回。

• Pull Consumer:Consumer 主动拉取信息

5️⃣大致流程

Broker 在启动的时候会去向 NameServer 注册并且定时发送心跳,Producer 在启动的时候会到 NameServer 上去拉取 Topic 所属的 Broker 具体地址,然后向具体的 Broker 发送消息。具体如图:RocketMQ_第4张图片

四、RocketMQ 的消息领域模型

主要分为 Message、Topic、Queue、Offset 以及 Group。RocketMQ_第5张图片

1️⃣【Message】一条消息

2️⃣【Topic】软分区,表示消息的第一级类型,比如一个电商系统的消息可以分为:交易消息、物流消息等。一条消息必须有一个 Topic。它是最细粒度的订阅单位,一个 Group 可以订阅多个 Topic 的消息。

3️⃣【Tag】表示消息的第二级类型,比如交易消息又可以分为:交易创建消息,交易完成消息等。RocketMQ 提供 2 级消息分类,方便灵活控制。

4️⃣【Group】组,一个组可以订阅多个 Topic。

  1. Consumer Group:一类 Consumer 的集合名称,这类 Consumer 通常消费一类消息,且消费逻辑一致。
  2. Producer Group:一类 Producer 的集合名称,这类 Producer 通常发送一类消息,且发送逻辑一致。

5️⃣【Message Queue】硬分区,消息的物理管理单位。一个 Topic 下可以有多个 Queue,Queue 的引入使得消息的存储可以分布式集群化,具有了水平扩展能力。

  1. Message Queue:不存储信息,只存储 CommitLog 中对应的偏移地址信息,根据该信息找到存储在 CommitLog 中的信息
  2. CommitLog:真正存储消息的文件,不同的 Topic 的信息都存在 CommitLog 中,保证了顺序写。

6️⃣【Offset】绝对偏移值,Message Queue 中有两类 offset:

  1. CommitOffset:存储在 OffsetStore 中表示消费到的位置。
  2. offset:在 PullRequest 中为拉取消息位置。

在 RocketMQ 中,所有消息队列都是持久化,长度无限的数据结构,所谓长度无限是指队列中的每个存储单元都是定长,访问其中的存储单元使用 Offset 来访问,offset 为 Java long 类型,64 位,理论上在 100年内不会溢出,所以认为是长度无限。也可以认为 Message Queue 是一个长度无限的数组,Offset 就是下标。

五、RocketMQ 的关键特性

1️⃣支持严格的消息顺序

消息的顺序指的是消息消费时,能按照发送的顺序来消费。例如:一个订单产生了 3 条消息,分别是订单创建、订单付款、订单完成。消费时,要按照这个顺序消费才有意义。但同时订单之间又是可以并行消费的。

RocketMQ 是通过将相同 ID 的消息发送到同一个队列,而一个队列的消息只由一个消费者处理来实现顺序消费。如图:RocketMQ_第6张图片

这样对于同一个订单的创建、付款和完成消息,确保按照这一顺序被发送和消费。

2️⃣消息重复

  1. 消息重复的原因

消息领域有一个对消息投递的 QoSQuality of Service 服务质量定义,分为:

①最多一次(At most once)
②至少一次(At least once)
③仅一次(Exactly once)

几乎所有的 MQ 产品都声称自己做到了 At least once。既然是至少一次,那避免不了消息重复,尤其是在分布式网络环境下。比如:网络原因闪断,ACK 返回失败等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将该消息分发给其他的消费者。

不同的消息队列发送的确认信息形式不同。例如 RabbitMQ 是发送一个 ACK 确认消息,RocketMQ 是返回一个 CONSUME_SUCCESS 成功标志,kafka 实际上有个 offset 的概念。

RocketMQ 没有内置消息去重的解决方案,最新版本是否支持还需确认。

  1. 消息去重

①去重原则:使用业务端逻辑保持幂等性。幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用,数据库的结果都是唯一的,不可变的。只要保持幂等性,不管来多少条重复消息,最后处理的结果都一样,需要业务端来实现。

消费幂等(消费端去重):

  • 消息的唯一键,在消费前是否在DB或缓存中存在
  • 使用业务状态机校验

②去重策略:保证每条消息都有唯一编号(比如唯一流水号),且保证消息处理成功与去重表的日志同时出现。

建立一个消息表,拿到这个消息做数据库的 insert 操作。给这个消息做一个主键(primary key)或者唯一约束,那么就算出现重复消费的情况,就会导致主键冲突,那么就不再处理这条消息。

3️⃣支持 Topic 与 Queue 两种模式

4️⃣亿级消息堆积能力

5️⃣友好的分布式特性

6️⃣支持 Push 与 Pull 方式消费信息

7️⃣CommitLog 的顺序读写保证了 RocketMQ 的高吞吐量

六、消息类型

1️⃣普通消息

  1. 无序消息
  2. 消息随机放入指定Topic的queue中,消费者随机选取queue消费消息

2️⃣顺序消息

  1. 全局顺序消息:同一 Topic 的消息存储指定的 Queue 中,保证该 Topic 的所有消息都是有序的。
  2. 分区顺序消息:同一 Topic 的消息根据 Shaeding key 进行分区,每个分区都是一个 Queue,同一个 Queue 的消费顺序有序。

3️⃣事务消息

RocketMQ_第7张图片

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

七、RocketMQ 的应用场景

1️⃣削峰填谷

比如秒杀等大型活动时会带来较高的流量脉冲,如果没做相应的保护,将导致系统超负荷甚至崩溃。如果因限制太多导致请求大量失败而影响用户体验,可以利用 MQ 超高性能的消息处理能力来解决。

2️⃣异步解耦

通过上、下游业务系统的松耦合设计,比如:交易系统的下游子系统(如积分等)出现不可用甚至宕机,都不会影响到核心交易系统的正常运转。

3️⃣顺序消息

与 FIFO 原理类似,MQ 提供的顺序消息即保证消息的先进先出,可以应用于交易系统中的订单创建、支付、退款等流程。

4️⃣分布式事务消息

比如阿里的交易系统、支付红包等场景需要确保数据的最终一致性,需要引入 MQ 的分布式事务,既实现了系统之间的解耦,又可以保证最终的数据一致性。

将大事务拆分成小事务,减少系统间的交互,既高效又可靠。再利用 MQ 的可靠传输与多副本技术确保消息不丢,At-Least-Once 特性来最终确保数据的最终一致性。

八、消息发送失败后的重试机制

消息客户端集成如下功能:重试 n 次,n 通常为 3-5。更换 Broker 重试,避免 Broker 宕机导致 send 失败。send 总耗时不超过 sendTimeout。
经过以上重试机制,仍然可能存在发送失败。此时需要应用端进行处理:将消息保存到 DB,并由后台定时任务去定期重试,保证消息到达 Broker。

九、提升消息处理速度的方式

  1. 提高消费的并发度来提升消息处理速度。大部分消息是 IO 密集型(数据库操作,RPC 操作)。
  2. 批量处理消息。通过设置批量参数提升消息的处理速度。
  3. 跳过非重要消息。
  4. 优化每条消息的处理过程,合并数据库的访问等。

你可能感兴趣的:(MessageQueue,java)