RabbitMQ---应用问题
(一)幂等性介绍
幂等性是本身是数学中的运算性质,他们可以被多次应用,但是不会改变初始应用的结果
1.应用程序的幂等性介绍
包括很多,有数据库幂等性,接口幂等性以及网络通信幂等性等
就比如数据库的select操作,这个操作就是符合幂等性的,虽然不同时间查询的结果会不同,但是幂等性指的是对资源的影响,而不是返回结果,查询数据本质上是不会对资源产生影响的,所以即使两次查询结果不同,那也是因为查询间有一些其他的操作对资源进行了修改
我们再来看接口的幂等性,就是说同一个接口多次调用,对系统的影响是相同的,就比如多次调用支付接口(同一个订单),对系统的影响也是一样的(也就是只生效一次)
2.MQ的幂等性
对于MQ而言,幂等性是指,同一条消息,被多次消费,对系统的影响也是相同的
一般我们消息中间件的消息传输保障分为三个级别:
1)At most once:最多⼀次. 消息可能会丢失,但绝不会重复传输
2)At least once:最少⼀次. 消息绝不会丢失,但可能会重复传输.
3. Exactly once:恰好⼀次. 每条消息肯定会被传输⼀次且仅传输⼀次.
RabbitMQ只支持最多一次和最少一次,对于恰好一次,我们目前还做不到这么精准
在业务使用中,对于一些可靠性高的场景,建议使用最少一次,以防止我们消息的丢失,在一些不需要高可靠性的场景,我们可以使用最多一次,就比如我们日志的记录
那我们就来看一下什么情况下会导致同一条消息被多次消费
首先就是发送时消息重复:
当一条消息已经成功发送到交换机并成功到达队列完成持久化,此时出现了网络问题导致MQ没有给生产者发送ack,这样就会导致生产者重新发送一条消息到交换机,此时就会导致发送时消息重复(这两条内容是一样的)
还有消费时消息重复:
当消息已经从队列给消费者并且完成业务处理应该返回ack的时候,网络出现问题,导致ack丢失了,这样MQ就会认为消息没有被成功消费,此时就会触发重试机制,MQ重新给消费者传递消息
最少一次会有一个问题,消费端会手动重复的消息,就会对同一条消息进行多次处理,就有可能出现一些问题,如果此时我们消息不是幂等性的,比如扣款业务,就会出现多次扣款的情况
3.解决方案
MQ消费者幂等性的解决方案一般有以下几种:
1)全局唯一id
1.为每一条消息都分配一个标识符,可以是UUID也可以是自增ID但是一定要保证唯一性
2.消费者收到消息后,先去判断该id是否消费过如果消费过就放弃处理,如果没消费,消费者就开始消费消息,并且把ID保存到数据库中
这里我们可以使用redis原子性操作setnx来保证幂等性,把唯一ID作为key放到redis中,返回1就说明之前没有消费过,返回0就说明之前存在,就放弃处理
2)业务逻辑判断
在业务逻辑层面实现消息处理的幂等性,就比如我们先判断数据库中是否有相关数据记录,或者使用乐观锁机制避免已被其他事务更改的数据,或者检查相关业务的状态,如果是未处理,我们再进行处理
(二)顺序性保证
消息的顺序性是指消费者消费的消息和生产者发送消息的顺序一致
在很多业务场景下,消息的消费是不用保证顺序的,只需要执行就可以了,就比如我们订单的处理,先处理哪一个都可以,但是还有一些就不可以,比如对用户信息的修改,一个用户在很短时间内对同一个资料进行修改,就需要我们保证消息的顺序
我们RabbitMQ本质上是不能够保障顺序性的,在不考虑消息丢失,网络问题,并且只有一个消费者和一个生产者,点对点的情况下,是可以保障消息的顺序性,如果有多个生产者同时发送消息,我们就无法确定到达队列的顺序,就更无法确定到达消费者的顺序,也就无法保证顺序性
那除了这种还有什么情况会打破顺序性?
1)有多个消费者:当队列有多个消费者的时候,消息会被不同消费者处理,有的消费者处理的快有的处理的慢,所以我们消息处理的顺序性是无法保证的
2)网络波动或异常:在消息传递时如果ack丢失,就会使得消息重新入队,重新消费,造成顺序性问题
3)消息重试:这个在多个消费者会出问题。如果消费者处理消息未完全确认,那么就会触发重试机制,会影响消息处理的顺序性问题
4)消息路由问题:消息会根据routingkey被映射到不同的队列,从而无法保证全局的顺序性
5)死信队列:如果消息被拒绝放到死信队列,那么就会导致消息被消费的顺序打乱
顺序性保障方案
那如何来保障我们消息的顺序性?
我们分为局部顺序性保证和全局顺序性保证
局部顺序性通常指在一个队列内保证消息顺序,全局顺序性是指在全部队列内保证消息顺序
在实际应用中,全局顺序很难实现,相对来说局部顺序更加常见和容易实现
接下来说一下
消息的顺序保障的常见方法
1.单队列单消费者
最简单方法就是使用单个队列,队列由一个消费者进行处理
2.分区消费
单个消费者吞吐量太低,我们需要多个消费者并且还要保障顺序的时候,就可以使用分区消费,把一个队列分成多个队列,每个队列分配一个消费者,我们可以根据id进行哈希然后分成多个队列,这样我们只需要保障每个队列的顺序性即可
但是RabbitMQ本身不支持分区消费,所以需要业务逻辑实现,我们可以使用Spring-cloud-stream来实现
Partitioning with the RabbitMQ Binder :: Spring Cloud Stream
3.消息确认机制
消息确认就是消费者在处理完一条消息后,显示发送确认,这样RabbitMQ才会继续发送其他消息
4.业务逻辑控制
在一些情况下,即使消息的顺序不对,也可以通过我们设置序列号,然后在消费信息时进行判断来处理,保证顺序性
注:我们保证顺序性不是说上面一个就可以,要把上面的方法进行结合
RabbitMQ本⾝并不保证全局的严格顺序性,特别是在分布式系统中.在实际应⽤开发中根据具体的业 务需求,可能需要结合多种策略来实现所需要的顺序保证
(三)消息积压问题
消息积压是指在消息队列中,待处理的消息超过了消费者的处理能力,导致消息在队列中不断积压
产生消息积压有以下原因:
1.消息生产过快:在高流量情况下,生产者用高速率发送消息,超过了消费者的处理能力
2.消费者处理能力慢:消费者消费消息的速度低于生产者生产的速度,导致队列积压
这里可能有以下原因:
消费者的业务逻辑复杂,消费端代码性能低,系统资源限制,异常处理不当
3.网络问题:网络延迟或者不稳定,消费者无法立刻接收或者确认消息,导致消息积压
4.RabbitMQ服务配置低,消息积压会导致系统性能下降,影响用户体验,导致系统崩溃
解决方案:
1.提高消费者效率
增加消费者实例数量,比如新增机器
提高业务逻辑,使用多线程进行处理
设置消息分发,当一个消费者阻塞,把消息分发给其他消费者
消息发生异常,设置重试策略,或者转入死信队列
2.限制生产者速度
流量控制:在消息生产者设置流量控制逻辑
限流:使用限流工具,给消息发送速率设置上限
设置过期时间:消息到过期时间后,可以配置死信队列,等消费者空闲后,再进行消费
3.资源和配置优化:
升级RabbitMQ服务器的硬件,调整MQ参数