RabbitMQ 问题收集
RabbitMQ 问题收集
RabbitMQ可靠性
发送方确认模式
- 将信道设置成
confirm
模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID
。 - 一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一 ID)。
- 如果
RabbitMQ
发生内部错误从而导致消息丢失,会发送一条nack
(notacknowledged
,未确认)消息。 - 发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。
接收方确认机制
- 消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,
RabbitMQ
才能安全地把消息从队列中删除。 - 这里并没有用到超时机制,
RabbitMQ
仅通过Consumer
的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ
给了Consumer
足够长的时间来处理消息。保证数据的最终一致性;
下面罗列几种特殊情况
- 如果消费者接收到消息,在确认之前断开了连接或取消订阅,
RabbitMQ
会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要去重) - 如果消费者接收到消息却没有确认消息,连接也未断开,则
RabbitMQ
认为该消费者繁忙,将不会给该消费者分发更多的消息。
如何保证RabbitMQ
消息的可靠传输?
- 消息不可靠的情况可能是消息丢失,劫持等原因;
- 丢失又分为:生产者丢失消息、消息列表丢失消息、消费者丢失消息;
-
生产者丢失消息:从生产者弄丢数据这个角度来看,
RabbitMQ
提供transaction
和confirm
模式来确保生产者不丢消息;transaction
机制就是说:发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。然而,这种方式有个缺点:吞吐量下降;confirm
模式用的居多:一旦channel
进入confirm
模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后;rabbitMQ
就会发送一个ACK
给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了;如果
rabbitMQ
没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。 -
消息队列丢数据:消息持久化。
处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。
这个持久化配置可以和
confirm
机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack
信号。这样,如果消息持久化磁盘之前,
rabbitMQ
阵亡了,那么生产者收不到Ack
信号,生产者会自动重发。那么如何持久化呢?
这里顺便说一下吧,其实也很容易,就下面两步
1. 将
queue
的持久化标识durable
设置为true
,则代表是一个持久的队列 2. 发送消息的时候将
deliveryMode=2
这样设置以后,即使
rabbitMQ
挂了,重启后也能恢复数据 -
消费者丢失消息:消费者丢数据一般是因为采用了自动确认消息模式,改为手动确认消息即可!
消费者在收到消息之后,处理消息之前,会自动回复
RabbitMQ
已收到消息;如果这时处理消息失败,就会丢失该消息;
解决方案:处理消息成功后,手动回复确认消息。
为什么不应该对所有的 message 都使用持久化机制?
- 首先,必然导致性能的下降,因为写磁盘比写
RAM
慢的多,message
的吞吐量可能有 10 倍的差距。 - 其次,
message
的持久化机制用在RabbitMQ
的内置cluster
方案时会出现“坑爹”问题。矛盾点在于,若message
设置了persistent
属性,但queue
未设置durable
属性,那么当该queue
的owner node
出现异常后,在未重建该queue
前,发往该queue
的message
将被blackholed
;若message
设置了persistent
属性,同时queue
也设置了durable
属性,那么当queue
的owner node
异常且无法重启的情况下,则该queue
无法在其他node
上重建,只能等待其owner node
重启后,才能恢复该queue
的使用,而在这段时间内发送给该queue
的message
将被blackholed
。 - 所以,是否要对 message 进行持久化,需要综合考虑性能需要,以及可能遇到的问题。若想达到
100,000
条/秒以上的消息吞吐量(单RabbitMQ
服务器),则要么使用其他的方式来确保message
的可靠delivery
,要么使用非常快速的存储系统以支持全持久化(例如使用 SSD)。另外一种处理原则是:仅对关键消息作持久化处理(根据业务重要程度),且应该保证关键消息的量不会导致性能瓶颈。
如何保证高可用的?RabbitMQ 的集群
RabbitMQ
是比较有代表性的,因为是基于主从(非分布式)做高可用性的,我们就以RabbitMQ
为例子讲解第一种 MQ 的高可用性怎么实现。RabbitMQ
有三种模式:单机模式、普通集群模式、镜像集群模式。
-
单机模式,就是
Demo
级别的,一般就是你本地启动了玩玩儿的?,没人生产用单机模式 -
普通集群模式:
- 意思就是在多台机器上启动多个
RabbitMQ
实例,每个机器启动一个。 - 你创建的
queue
,只会放在一个RabbitMQ
实例上,但是每个实例都同步queue
的元数据(元数据可以认为是queue
的一些配置信息,通过元数据,可以找到queue
所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个queue
的读写操作。
- 意思就是在多台机器上启动多个
-
镜像集群模式:
- 这种模式,才是所谓的
RabbitMQ
的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的queue
,无论元数据还是queue
里的消息都会存在于多个实例上,就是说,每个RabbitMQ
节点都有这个queue
的一个完整镜像,包含queue
的全部数据的意思。然后每次你写消息到queue
的时候,都会自动把消息同步到多个实例的queue
上。RabbitMQ
有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建queue
的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。 - 这样的好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个
queue
的完整数据,别的consumer
都可以到其它节点上去消费数据。坏处在于,第一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!RabbitMQ
一个queue
的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个queue
的完整数据。
- 这种模式,才是所谓的
如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,怎么办?
- 消息积压处理办法:临时紧急扩容:
- 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 cnosumer 都停掉。
新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。
然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。
等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息。
MQ中消息失效:假设你用的是 RabbitMQ,RabbtiMQ 是可以设置过期时间的,也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在 mq 里,而是大量的数据会直接搞丢。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。 - mq消息队列块满了:如果消息积压在 mq 里,你很长时间都没有处理掉,此时导致 mq 都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,消费一个丢弃一个,都不要了,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。
设计MQ思路
- 比如说这个消息队列系统,我们从以下几个角度来考虑一下:
- 首先这个 mq 得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下 kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?
- 其次你得考虑一下这个 mq 的数据要不要落地磁盘吧?那肯定要了,落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是 kafka 的思路。
- 其次你考虑一下你的 mq 的可用性啊?这个事儿,具体参考之前可用性那个环节讲解的 kafka 的高可用保障机制。多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。
- 能不能支持数据 0 丢失啊?可以呀,有点复杂的。