在微服务项目对接口响应速度有要求,就会用到MQ做无关重要业务逻辑的异步处理。本章以RabbitMQ举例,列举出使用目的、使用原则、可靠性保证(本章重点)、顺序消费场景。
使用目的
解耦、异步、削峰。这些名词早已经被玩烂,我们使用MQ最重要的无非就是异步处理,提高响应速度,削峰控制推送消息的速度,不要使消费者服务器过于繁忙导致宕机。
虽然网上一堆的例子,但为了文章流畅性,找了张图 :
使用MQ之前
使用MQ之后
可以看到直接发送一个消息,消息里面带上所需信息,发送过后直接返回结果,减少了等待RPC调用服务执行过程。
如果面试问到MQ的使用场景,我们应该重点放在异步、其次削峰,解耦的话可能我目前参与的项目不是很好,感知不明显,因为微服务的远程调用其实已经把业务逻辑职责单一了,但MQ作为一个中介等于把调用方和被调用方在代码层面变得没有关系了,这估计就是解耦了吧,但逻辑上还是关联的。
使用原则
使用一个新技术前要思考他解决什么问题,我上个标题已经解答了,项目用户体系大,某些接口需要请求外部公网可以预料到执行时间比较长时,就可以引入MQ服务,来优化给用户响应速度。但技术选型显然不是本章讨论的内容(因为我还没到做架构的层次),下面列举使用MQ做异步的场景。
1. 上游(消息发送方)不关心执行结果(就像大部分线程不需要返回值),属于告诉你了,剩下的不关我事了,我不会跟据你的返回结果做逻辑处理。
2. 消息发送方关心执行结果,但消息接收方执行时间很长(线程需要返回值)。需要他的返回值做逻辑处理,但可以预料到执行时间长。执行流程 消息发送方 -> 消息接收方 (处理完成之后发送) -> 原来的消息发送方。
不适合的场景:
1. 消息发送方关心执行结果,但服务之间强依赖,如支付等涉及到钱的场景。
可靠性保证
MQ服务器宕机、消费者网络波动宕机没有处理完等造成的消息丢失,或者重复消费。我以面试题的形式说明解决方案。
消息丢失问题
生产者:
- 事务(同步不推荐),使用该消息中间件提供的API来开启 提交 回滚
- ack消息确认(异步推荐)
MQ服务器:
- 开启 交换机、队列、持久化
消费者:
- 消息确认,手动ack(自动ack,服务器在发送给消费者消息后为成功,直接删除消息,
设置手动ack可以在逻辑执行完后再删除,不成功重试
)
以上方式针对于单机,但还是有丢失的可能性,如果想要进一步降低丢失概率和提升性能,还可以为mq搭建镜像集群(不同节点有某个节点的备份数据)。如果想要消息完全不丢失是不可能的,所以我把使用原则写在了前面。
消息重复消费
场景一:注册服务消费者设置了手动ack,在执行完业务逻辑后确认,在注册成功后插入数据完成,向MQ发送处理邮件消息,最后一步没有执行完由于网络波动宕机(没来得及确认),服务器认为这个消息没有被消费,在消费者重启后再发送,导致插入了两条相同的用户信息,导致了数据的幂等性问题。
场景二: 生产者可能会重复推送一条数据到 MQ 中,为什么会出现这种情况呢?也许是一个 Controller 接口被重复调用了 2 次,没有做接口幂等性导致的;也可能是推送消息到 MQ 时响应比较慢,生产者的重试机制导致再次推送了一次消息。
所以消息重复消费问题在于数据的幂等性(数据库中存入了除ID外完全相同的数据)。
为了保证消息不被重复消费,首先要保证每个消息是唯一的,所以可以给每一个消息携带一 个全局唯一的id,流程如下:
1、消费者监听到消息后获取id,先去查询这个id是否存中
2、如果不存在,则正常消费消息,并把消息的id存入 数据库或者redis中(下面的编码示例使用redis)
3、如果存在则丢弃此消息
实现方法
1. 将id存入redis的string中(单消费者场景):这样一个队列,redis数据只有一条,每次消息过来都覆盖之前的消息,但是消费者多的情况不适用,可能会存在问题--一个消息被多个消费者消费。
2. 将id存入list中(多消费者场景):这个方案可以解决多消费者的问题,但是随着mq的消息增加,redis数据越来越多,需要去清除redis数据。
3. 将id以key值增量存入string中并设置过期时间:以消息id为key,消息内容为value存入string中,设置过期时间(可承受的redis服务器异常时间,比如设置过期时间为10分钟,如果redis服务器断了20分钟,那么未消费的数据都会丢了)。
顺序消费
大家可以参考这篇文章 ,这位大牛写的非常好(18条消息) MQ中怎样去实现消息的顺序消费_WannaRunning的博客-CSDN博客_mq顺序消费