☞ 顺序消费
因为 RocketMQ 的主题可以分布在多个 Broker 上,所以 RocketMQ 在主题层面是无序的,只在 队列层面 才保证有序
顺序方式
普通顺序:消费者通过 同一个消费队列 收到的消息是有顺序的 ,不同消息队列收到的消息则可能是无序的
严格顺序:消费者收到的 所有消息 均是有顺序的,在异常情况下也保证消息的顺序性
但在实际业务中要求严格顺序的场景并不多,并且如果使用了严格顺序模式,Broker 集群中只要有一台机器不可用,则整个集群都不可用
常用的场景比如短信系统或者邮件系统稍微晚几秒发送短信或邮件是完全可以接受的,所以如果能容忍短暂的乱序则推荐使用普通顺序模式
普通顺序下的实现
普通顺序只能保证同一个消费队列里的消息是顺序的,但 Producer 生产消息的时候会根据 负载均衡策略 进行轮询来向同一主题的不同消息队列发送消息,假设有三个消息分别是同一个业务流程的步骤 1、步骤 2、步骤 3,在轮询的策略下这三个消息会被发送到 不同队列,不在一个队列里就无法通过队列有序特性来保证消息有序性了
所有消息根据 Sharding Key 进行区块分区,我们可以根据同一个业务的业务流水号进行 HASH取模 使 Sharding Key 一致,来保证同一个业务在同一个队列中,从而实现顺序消费
☞ 重复消费
如果 Consumer 消费完并返回给消息队列处理成功回应的时候出现了网络波动或 Broker 意外重启等意外情况,回应没有发送成功,导致队列的 offset 没有 +1,那么这个消息如果再次被消费就会出现问题
可以分两种情况考虑:
-
可以重复消费的消息
比如发短信通知这种消息,发两个一模一样的短信对业务没有太大的影响,所以可以选择弱校验或者不校验
-
不可重复消费的消息
比如用户的账户余额这种,必须要求
Consumer实现强校验 ,可以选择不执行重复的消息,或者对同一个消息执行多少次也不会影响结果
☞ 分布式事务
事务就是要么都执行要么都不执行,同一个系统中可以比较简单的实现事务,在分布式架构中不同服务需要进行调用,实现事务就会比较困难
常见的分布式事务实现有 2PC(Two Phase Commitment)、TCC(Try Confirm Cancel) 和 事务消息(half 半消息机制),每一种实现都有其特定的使用场景,但是也有各自的问题,都 不是完美 的解决方案
RocketMQ 使用的是 事务消息加上事务反查机制 来解决分布式事务问题
在第一步发送的 half 消息 ,在事务提交之前,对于消费者来说,这个消息是不可见的
如果消息是 half 消息,将备份原消息的主题与消息队列,然后改变主题为 RMQ_SYS_TRANS_HALF_TOPIC,由于消费组未订阅该主题,所以无法消费 half 类型的消息,RocketMQ 会开启一个定时任务,从主题为 RMQ_SYS_TRANS_HALF_TOPIC 中拉取消息进行消费,根据生产者组获取一个服务提供者发送回查事务的状态请求,根据事务状态来决定是提交或回滚消息
如果第 4 步的消息没有发送成功,则 RocketMQ 必须要通过第 5 步来进行 事务反查,否则 RocketMQ 无法得知是否需要给消费者消费
需要注意的是,在 MQ Server 指向系统 B 的操作已经和系统 A 不相关了,也就是说在消息队列中的分布式事务是 本地事务和存储消息到消息队列才是同一个事务,这样也就产生了事务的最终一致性,因为整个过程是异步的,每个系统只要保证它自己那一部分的事务就行了
☞ 消息堆积
消息堆积的原因就只有两个:生产者生产太快 或 消费者消费太慢
生产者生产太快
可以考虑 限流降级,降低进入队列的消息数量
消费者消费太慢
可以考虑增加多个消费者实例,主要注意的是,在 RocketMQ 中,一个队列只会被一个消费者消费,所以在增加消费者实例的同时 还需要增加每个主题的队列数量,如果不同时增加队列就会出现下面的情况
增加了 Consumer3 但因为没有增加队列导致 Consumer3 处于闲置状态,不会产生任何效果
其他方式
后台定时任务每隔固定时间,删除旧的没有使用过的消息信息
消息定时转移
☞ 回溯消费
来自官方文档的解释
回溯消费是指 Consumer 已经消费成功的消息,由于业务上需求需要重新消费,在 RocketMQ 中, Broker 在向 Consumer 投递成功消息后,消息仍然需要保留,并且重新消费一般是按照时间维度,例如由于 Consumer 系统故障,恢复后需要重新消费1小时前的数据,那么 Broker 要提供一种机制,可以按照时间维度来回退消费进度。RocketMQ 支持按照时间回溯消费,时间维度精确到毫秒
☞ 刷盘机制
同步刷盘和异步刷盘
队列中的消息是需要进行存储持久化,在 RocketMQ 中有 同步刷盘 和 异步刷盘 两种方式
主要的区别就是同步刷盘中需要等待一个刷盘成功的 ACK,同步刷盘则是开启线程进行刷盘
-
同步刷盘
消息可靠性高,但 性能上会有较大影响 ,一般地适用于金融等特定业务场景
-
异步刷盘
降低了读写延迟 ,提高了 MQ 的性能和吞吐量,但在
Broker意外宕机的时候会丢失部分数据,一般适用于如发验证码等对于消息保证要求不太高的场景
同步复制和异步复制
同步刷盘和异步刷盘是在单个结点层面的,而同步复制和异步复制主要是指的 Borker 主从模式下,主节点返回消息给客户端的时候是否需要同步从节点
- 同步复制: 只有消息同步双写到主从结点上时才返回写入成功
- 异步复制: 消息写入主节点之后就直接返回写入成功
一般来说,消息写入的节点越多就更能保证消息的可靠性,但是随之的性能也会下降,需要根据特定业务场景去选择适应的方案
需要注意的是,不同的刷盘策略保证的是 消息可靠性,不同的复制策略仅仅是影响到了 可用性,RocketMQ 是不支持自动主从切换的,当主节点挂掉之后,生产者就不能再给这个主节点生产消息了
如果采用异步复制的方式,在主节点还未发送完需要同步的消息的时候主节点挂掉了,这个时候从节点就少了一部分消息。但是此时生产者无法再给主节点生产消息了,消费者可以自动切换到从节点进行消费(仅仅是消费),所以在主节点挂掉的时间只会产生主从结点短暂的消息不一致的情况,降低了可用性,而当主节点重启之后,从节点那部分未来得及复制的消息还会继续复制
在单主从架构中,如果一个主节点挂掉了,那么也就意味着整个系统不能再生产了;多主从架构中每个 Topic 是分布在不同 Broker 中的,提高了可用性,如下图所示
但是这种复制方式无法保证 严格顺序。之前提到了如何保证的消息顺序性是通过将一个语义的消息发送在同一个队列中,使用 Topic 下的队列来保证顺序性的。如果此时我们主节点 A 负责的是订单 A 的一系列语义消息,然后它挂了,这样其他节点是无法代替主节点 A 的,如果我们任意节点都可以存入任何消息,那就没有顺序性可言了
而在 RocketMQ 中采用了 Dledger 解决这个问题,要求在写入消息的时候,要求至少消息复制到半数以上的节点之后,才给客⼾端返回写⼊成功,并且它是⽀持通过选举来动态切换主节点的
也不是说
Dledger是个完美的方案,至少在Dledger选举过程中是无法提供服务的,而且他必须要使用三个节点或以上,如果多数节点同时挂掉他也是无法保证可用性的,而且要求消息复制半数以上节点的效率和直接异步复制还是有一定的差距的
☞ 消息丢失
每发送一个消息,同步落盘后才返回生产者消息发送成功,这样只要生产者得到了消息发送生成的返回,除了硬盘损坏的情况,都可以保证不会消息丢失
💐 鸣谢
消息队列扫盲 👍