MQ有什么作用/为什么使用MQ
应用解耦
- A通过接口调用的形式发送数据给BCD, 此时如果E也需要这个数 据呢? 如果C不要这个数据呢? 这时候A和其他系统严重耦合
- 如果A直接发送数据给MQ, 新系统需要数据, 直接监听MQ即可, 旧系统不需要数据了, 就不需要监听MQ了.
- 这样就是谁需要谁去拿, A只用和MQ交互. A不需要考虑给谁发送 数据, 也不需要考虑别人调用失败, 或者超时了怎么办.
异步提速
- A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三 个系统写库
- 自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、 450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s
- 如果使用 MQ,那么 A 系统自己本地写库要 3ms, 连续发送 3 条 消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一 个请求到 返回响应给用户,总时长是 3 + 5 = 8ms; 就非常快
削峰填谷
- 假设我们的系统每秒只能承载1000请求,如果请求瞬间增多到每秒 5000,则会造成系统崩溃。此时引入mq即可解决该问题
- 使用了MQ之后,限制消费消息的速度为1000,这样一来,高峰期产生的数据势必会被积压在MQ中,高峰就被"削"掉了,但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000,直到消费完积压的消息,这就叫做"填谷"。
MQ优缺点
优点就是: 异步 削峰 解耦
缺点就是:
- 系统的可用性降低了: 新引入了MQ, 那么如果MQ挂了, 和MQ相 关的服务就崩溃了
- 系统复杂度提高了: 硬生生加个 MQ 进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况? 怎么保证消息传递的顺序性?问题一大堆
- 数据一致性问题: A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致 了
延时队列
延迟队列指的是消息发送后到mq不会立即被消费,mq会存储对应的延迟消息,而是等待特定时间后,消费者才能拿到这个消息进行消费
比如订单的超时取消, 订单信息被放到mq中, 30分钟未支付订单就取 消. 如果使用延时队列, 那监听mq的消费者从mq中直接拿到的就是 30分钟未支付订单的信息, 然后直接取消订单. 避免轮询数据库查找超时订单
什么是死信队列? 如何导致死信
死信, 就是无法被正常消费的消息. 当一个队列中的消息满足下列情 况之一时,可以成为死信(dead letter):
- 消息被消费者拒绝且不重新入队
- 消息TTL到期,超时无人消费
- 要投递的队列消息堆积满了
死信队列是放置死信的队列, 即把无法被消费的消息放到一个队列中
- 死信队列可以作为消息消费失败的兜底方案
- 在死信队列里面可以对我们的异常消息进行MySQL/Redis持 久化,然后人工处理等操作
- 另一方面可以处理消息超时无人消费以及队列满了的问题
如何保证消息可靠性
所谓保证消息可靠性就是要保证消息不丢失
你要保证消息不丢,是不是得先知道消息啥时候会丢?那消息啥时候会丢呢?
- 第1,从生产者发送消息给mq这个过程会丢。
- 第2,mq自己也可能会把消息弄丢,比如mq宕机重启了。
- 第3,mq把消息发送给消费者也可能会丢,或者消费者拿到消息 还没来得及消费就出现了异常或者消费者重启了或者线程崩溃了 等等
那应该怎么解决呢?
- 首先从生产者发送到mq这里,可以使用ack机制。当mq收到消 息,给生产者回复一个ack即可。
- 其次, mq自己把消息弄丢了怎么办?开启mq的持久化机制,把 消息持久化到磁盘中,这样即便mq宕机重启,也还是能从磁盘 中恢复消息。然后就是做mq集群,保证高可用,避免mq宕机呀
- 最后,消费者还没消费,消息就丢了。那就使用消费者的ack机 制,当消费者消费完消息再给mq发送ack。mq收到ack后再删除 消息。否则mq就重发消息。
消息的幂等性
消息幂等性是指:无论消息被重复消费多少次,业务效果与只消费一次完全相同
什么情况下会产生重复消息
- 消息生产者
- 生产者发完消息, mq由于网络抖动, 网络延迟, 没有及时返回 ack确认.
- 那生产者为了确保消息发送成功, 就会重发消息, 就导致mq会 收到了重复消息
- 或者在业务层, 也会有一些重试操作, 也有可能重发消息
- 消息消费者
- 如果消费者处理完消息之后, 发送ack之前就宕机重启了, 或者 说因为网络问题, ack延迟了
- 那mq一段时间没收到ack就认为消息没有被成功处理, 然后重 发消息. 这就会导致消息的重复消费问题
解决方案:唯一id
- 最通用的方案就是给发消息时给消息分配一个唯一id
- 消费者消费时, 把消息id存到redis, 使用setnx去存, 如果能保存说明是第一次消费, 如果失败, 就说明是重复消息
- 当然了还要给这个key设置一个过期时间, 避免一直占内存
消息积压如何处理
当生产者发送消息的速度超过了消费者处理消息的速度, 或者如果消费者因为某些原因持续阻塞,就会导致队列中的消息堆积,直到队列存储消息达到上限。最早接收到的消息,可能就会成为死信,会被丢弃,这就是消息堆积问题
解决消息堆积
- 增加更多消费者,提高消费速度
- 提高单个消息者的处理能力, 在消费者内开启线程池加快消息处理速度
- 缺点: 消息太多就会开启太多新线程, cpu压力大