跳到主要内容

MQ 常见问题

消息队列的基本作用?​

消息队列的主要作用是:解耦、异步、削峰。

解耦​

A 系统通过接口调用发送数据到 B、C、D 三个系统。那如果现在 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?现在 A 系统又要发送第二种数据了呢?

这样的话 A 系统的维护成本就非常的高,而且 A 系统要时时刻刻考虑 B、C、D、E 四个系统如果出现故障该怎么办?A 系统是重发还是先把消息保存起来呢?使用消息队列就可以解决这个问题。A 系统只负责生产数据,不需要考虑消息被哪个系统来消费。

异步​

A 系统需要发送个请求给 B 系统处理,由于 B 系统需要查询数据库花费时间较长,以至于 A 系统要等待 B 系统处理完毕后再发送下个请求,造成 A 系统资源浪费。使用消息队列后,A 系统生产完消息后直接丢进消息队列,不用等待 B 系统的结果,直接继续去干自己的事情了。

削峰​

A 系统调用 B 系统处理数据,每天 0 点到 12 点,A 系统风平浪静,每秒并发请求数量就 100 个。结果每次一到 12 点 ~ 13 点,每秒并发请求数量突然会暴增到 1 万条。但是 B 系统最大的处理能力就只能是每秒钟处理 1000 个请求,这样系统很容易就会崩掉。这种情况可以引入消息队列,把请求数据先存入消息队列中,消费系统再根据自己的消费能力拉取消费。

RocketMQ 如何保证高可用的?​

1、多 Master 模式:该模式下必须设置为异步复制,因为没有 Slave,异步刷盘会丢失部分数据,同步刷盘不会丢失数据;如果有一台 Master Broker 宕机,那么其上的消息在服务恢复前不会推送给消费端,会对消息实时性造成影响。

2、多 Master 多 Slave 模式(异步复制):异步复制的模式下在 Master 宕机、磁盘损坏等情况下会造成数据部分丢失;但是消费端仍然可以从 Slave 拉取消息,所以消息的实时性不受影响。

3、多 Master 多 Slave 模式(同步双写):同步复制同步刷盘会导致整体性能比异步模式低一些,发送单个消息的 RT 也会变高,但是服务可用性和数据可用性都很高而且消息实时性不受影响。

如何保证消息不被重复消费?​

或者说如何保证消息消费时的幂等性?

提示

幂等性是什么?

就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品使用约支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条

解决办法:

要保证消息不被重复消费,其实就是要保证消息消费时的幂等性。幂等性:无论你重复请求多少次,得到的结果都是一样的。

例如:一条数据重复出现两次,数据库里就只有一条数据,这就保证了系统的幂等性。

  1. 写数据时,先根据主键查一下这条数据是否存在,如果已经存在则 update;
  2. 数据库的唯一键约束也可以保证不会重复插入多条,因为重复插入多条只会报错,不会导致数据库中出现脏数据;
  3. 如果是写 redis,就没有问题,因为 set 操作是天然幂等性的。

如何保证消息的可靠性传输?​

Producer 端​

生产者端默认采用同步阻塞式发送消息,如果状态返回 OK 表示消息一定发送到了 Broker,如果状态返回失败或者超时,会重试两次,重试次数用完后可能发送成功也可能继续失败。

Broker 端​

发送到 Broker 的消息会通过同步刷盘或者异步刷盘的方式持久化到 CommitLog 里面,即时 Broker 服务宕机,服务恢复后仍然可以找到消息,除非磁盘损坏才会导致消息丢失;

另外在多主多从的 Broker 集群模式下,采用同步复制将 Master Broker 中的消息同步到 Slave Broker,就是 Master Broker 宕机甚至磁盘损坏也可以找到该消息。

一句话:修改刷盘策略为同步刷盘。默认情况下是异步刷盘的。

Consumer 端​

消费端维护了一个 MessageQueue 的队列,不管消息消费成功或者失败都会将当前消息的消费进度 offset 持久化到 MessageQueue 里面;如果消息消费失败会把消息重新发回到 Broker,如果操作失败或者 Broker 挂掉了会定时重试,成功后更新本地的消费进度 offset;

就算 Broker 和 Consumer 都挂掉了由于 Consumer 会定期持久化消费进度,等服务恢复后重新拉取 offset 之前的消息到消费端本地也可以保证消息不丢失;消费端消费失败后会把消息重新发回到 Broker 中的重试队列中,如果消费重试次数超过了最大消费重试次数的话 Broker 会把消息移到死信队列中,然后人工干预处理。

一句话:完全消费正常后在进行手动 ack 确认

如何保证消息的顺序性?​

首先多个 queue 只能保证单个 queue 里的顺序,queue 是典型的 FIFO,天然顺序。多个 queue 同时消费是无法绝对保证消息的有序性的。

可以使用同一 topic,同一个 QUEUE,发消息的时候一个线程去发送消息,消费的时候 一个线程去消费一个 queue 里的消息。

RocketMQ 的消息堆积如何处理?​

1、创建一个 Topic 并且把积压消息的 Topic 下的分区中的消息分发到新建的那个 Topic 中的更多分区中,新建 Topic 的分区数是积压消息的 Topic 的分区的 10 倍(具体多少分区根据具体业务场景决定)。

2、积压的消息分发完以后停掉旧的消费端实例。

3、每一个新建 Topic 的分区都对应启动一个消费端实例。

4、积压的消息都消费完后,再把旧的消费端实例恢复。

MQ 消息发送保存事务一致性​

MQ 消息发送不在 MySQL 事务中如何保证一致性?

如果消息发送不在MySQL事务中,可以采取以下方法来保证消息的一致性:

  1. 引入分布式事务:使用分布式事务管理器(如XA协议)来协调消息发送和数据库事务的一致性。在分布式事务中,可以将消息发送和数据库操作作为一个事务单元,并通过事务协调器来确保它们的一致性。这样,在提交数据库事务之前,可以先提交消息发送事务,如果消息发送失败,则回滚数据库事务,从而保证消息和数据库的一致性。

  2. 两阶段提交(Two-Phase Commit):使用两阶段提交协议来协调消息发送和数据库事务的一致性。在两阶段提交中,有一个协调者(通常是应用程序或中间件),它协调所有参与者(包括消息发送和数据库操作)。在第一阶段,协调者会向所有参与者发送准备消息,并等待它们的回应。如果所有参与者都准备就绪,协调者在第二阶段发送提交消息,参与者将提交消息和数据库操作。如果有任何一个参与者无法准备就绪或者出现故障,协调者发送回滚消息,参与者将回滚消息和数据库操作,从而保持一致性。

  3. 可靠消息确认机制:将消息发送和数据库操作视为两个独立的操作,并使用可靠的消息确认机制来确保消息的可靠投递。在发送消息后,等待MQ系统发送确认消息。只有在收到确认消息后,才提交数据库事务,确保消息和数据库操作的一致性。如果消息发送失败或超时,可以进行重试或其他错误处理。

需要注意的是,以上方法都需要根据具体的应用场景和系统架构进行合理设计和实现。分布式事务和两阶段提交可能会引入一定的复杂性和性能开销,因此需要权衡利弊并根据实际情况选择合适的方案。