RabbitMQ系列--消息异常的处理

消息丢失

其他网址:

官方文档:https://www.rabbitmq.com/confirms.html

【消息队列】RabbitMQ如何处理消息丢失 - 个人文章 - SegmentFault 思否

RabbitMQ防止消息丢失_大数据_杨航的专栏-CSDN博客

首先明确一点 一条消息的传送流程:生产者->MQ->消费者

所以有三个地方都会丢失数据:

  1. Producer端:发送消息过程中出现网络问题:producer以为发送成功,但RabbitMQ server没有收到;
  2. RabbitMQ server 端:接收到消息后由于服务器宕机或重启等原因(消息默认存在内存中)导致消息丢失;
  3. Consumer端:Consumer端接收到消息后处理消息出错,没有完成消息的处理;

一、生产者弄丢了数据

生产者将数据发送到RabbitMQ的时候,可能因为网络问题导致数据就在半路给搞丢了。

方案1:.使用事务(性能差)

        可以选择用RabbitMQ提供的事务功能,在生产者发送数据之前开启RabbitMQ事务(channel.txSelect),然后发送消息,如果消息没有成功被RabbitMQ接收到,那么生产者会收到异常报错,此时就可以回滚事务(channel.txRollback),然后重试发送消息;如果收到了消息,那么可以提交事务(channel.txCommit)。但是问题是,开始RabbitMQ事务机制,基本上吞吐量会下来,因为太耗性能。

方案2.发送回执确认(推荐)

        可以开启confirm模式,在生产者那里设置开启confirm模式之后,你每次写的消息都会分配一个唯一的id,然后如果写入了RabbitMQ中,RabbitMQ会给你回传一个ack消息,告诉你说这个消息ok了。如果RabbitMQ没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。
  事务机制和cnofirm机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是confirm机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息RabbitMQ接收了之后会异步回调你一个接口通知你这个消息接收到了。

        所以一般在生产者这块避免数据丢失,都是用confirm机制的。

方案3:本地消息表+定时任务(推荐)

见:分布式事务系列--消息表+MQ_feiying0canglang的博客-CSDN博客
               =>本地消息表+MQ(无事务)=> 实例流程

二、RabbitMQ弄丢了数据(开启RabbitMQ的数据持久化)

  为了防止RabbitMQ自己弄丢了数据,这个你必须开启RabbitMQ的持久化,就是消息写入之后会持久化到磁盘,哪怕是RabbitMQ自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。除非极其罕见的是,RabbitMQ还没持久化,自己就挂了,可能导致少量数据会丢失的,但是这个概率较小。

  设置持久化有两个步骤,第一个是创建queue的时候将其设置为持久化的,这样就可以保证RabbitMQ持久化queue的元数据,但是不会持久化queue里的数据;第二个是发送消息的时候将消息的deliveryMode设置为2,就是将消息设置为持久化的,此时RabbitMQ就会将消息持久化到磁盘上去。必须要同时设置这两个持久化才行,RabbitMQ哪怕是挂了,再次重启,也会从磁盘上重启恢复queue,恢复这个queue里的数据。

  而且持久化可以跟生产者那边的confirm机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者ack了,所以哪怕是在持久化到磁盘之前,RabbitMQ挂了,数据丢了,生产者收不到ack,你也是可以自己重发的。

  若生产者那边的confirm机制未开启的情况下,哪怕是你给RabbitMQ开启了持久化机制,也有一种可能,就是这个消息写到了RabbitMQ中,但是还没来得及持久化到磁盘上,结果不巧,此时RabbitMQ挂了,就会导致内存里的一点点数据会丢失。

三、消费端弄丢了数据

  主要是因为你消费的时候,刚消费到,还没处理,结果进程挂了比如重启了,那么就尴尬了,RabbitMQ认为你都消费了,这数据就丢了。或者消费者拿到数据之后挂了,这时候需要MQ重新指派另一个消费者去执行任务(一块肉,刚用筷子夹起来,发地震抖了一下,肉掉了)

  这个时候得用RabbitMQ提供的ack机制,也是一种处理完成发送回执确认的机制。如果MQ等待一段时间后你没有发送过来处理完成的回执,那么RabbitMQ就认为你还没处理完,这个时候RabbitMQ会把这个消费分配给别的consumer去处理,消息是不会丢的。

重复消费

        AMQP 定义了消费者确认机制(message ack),如果一个消费者应用崩溃掉(此时连接会断掉,broker 会得知),但是 broker 尚未获得 ack,那么消息会被重新放入队列。所以 AMQP 提供的是“至少一次交付”(at-least-once delivery),异常情况下,消息会被重复消费。

        造成消息重复的根本原因是:网络不可达。只要通过网络交换数据,就无法避免这个问题。所以解决这个问题的办法就是绕过这个问题。那么问题就变成了:如果消费端收到两条一样的消息,应该怎样处理?有以下两个方法

  1. 消费端处理消息的业务逻辑保持幂等性。
  2. 保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现。
第一种 第二种
说明 只要保持幂等性,不管来多少条重复消息,最后处理的结果都一样 利用一张日志表来记录已经处理成功的消息的 ID,如果新到的消息 ID 已经在日志表中,那么就不再处理这条消息。
实现者 应该在消费端实现,不属于消息系统要实现的功能。

可以消息系统实现,也可以消费端实现。正常情况下出现重复消息的概率其实很小,如果由消息系统来实现的话,肯定会对消息系统的吞吐量和高可用有影响,所以最好还是由消费端自己处理消息重复的问题,这也是 RabbitMQ 不解决消息重复的问题的原因。

第一种方法

  • 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。
  • 比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。
  • 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。

实例网址:RocketMQ重复消息终极解决方案 - 简书

第二种方法

【RabbitMQ】保证消息的不重复消费_大数据_千千-CSDN博客

顺序性

关于MQ的几件小事(五)如何保证消息按顺序执行 - 简书
聊一聊顺序消息(RocketMQ顺序消息的实现机制) - 杭州.Mark - 博客园

消息堆积

消息堆积产生原因

消息堆积即消息没及时被消费,是生产者生产消息速度快于消费者消费的速度导致的,消费者消费慢可能是因为:本身逻辑耗费时间较长、阻塞了

预防措施

生产者

  1. 给消息设置年龄,超时就丢弃
  2. 考虑使用队列最大长度限制

消费者

  1. 增加消费者的处理能力(例如优化代码),或减少发布频率
  2. 建立新的queue,消费者同时订阅新旧queue,采用订阅模式
  3. 默认情况下,rabbitmq消费者为单线程串行消费(org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer类的concurrentConsumers与txSize(对应prefetchCount)都是1),设置并发消费两个关键属性concurrentConsumers和prefetchCount。concurrentConsumers:设置的是对每个listener在初始化的时候设置的并发消费者的个数;prefetchCount:每次从broker里面取的待消费的消息的个数。
    配置方法:修改application.properties:
    spring.rabbitmq.listener.concurrency=m
    spring.rabbitmq.listener.prefetch=n

    Spring Amqp的解释:

    prefetchCount(prefetch)
        The number of messages to accept from the broker in one socket frame. The higher this is the faster the messages can be delivered, but the higher the risk of non-sequential processing. Ignored if the acknowledgeMode
        is NONE. This will be increased, if necessary, to match the txSize
    
    concurrentConsumers(concurrency)
        The number of concurrent consumers to initially start for each listener.

    其他网址:RabbitMQ消费者的几个参数 - 简书

综合(使用缓存):

  • 生产者端缓存数据,在mq被消费完后再发送到mq,打破发送循环条件。设置合适的qos值(channel.BasicQos()方法:每次从队列拉取的消息数量),当qos值被用光,而新的ack没有被mq接收时,就可以跳出发送循环,去接收新的消息。
  • 消费者主动block接收进程,消费者感受到接收消息过快时主动block,利用block和unblock方法调节接收速率,当接收线程被block时,跳出发送循环。

已出事故的解决措施

其他网址:完了!生产事故!几百万消息在消息队列里积压了几个小时!_一诺-CSDN博客_消息队列消费者来不及消耗

情况1:堆积的消息还需要使用

方案1:简单修复

修复consumer的问题,让他恢复消费速度,然后等待几个小时消费完毕

方案2:复杂修复

临时紧急扩容了,具体操作步骤和思路如下:

1)先修复consumer的问题,确保其恢复消费速度,然后将现有consumer都停掉
2)新建一个topic,partition是原来的10倍,临时建立好原先10倍或者20倍的queue数量
3)然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue
4)接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据
5)这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据
6)等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息

情况2:堆积的消息不需要使用

删除消息即可。另见:RabbitMQ系列--使用_feiying0canglang的博客-CSDN博客

你可能感兴趣的:(MQ)