学习资源:狂神 https://www.bilibili.com/video/BV1dX4y1V73G?p=28&spm_id_from=pageDriver

RabbitMQ

RabbitMQ支持的消息发送的模式

Simple简单模式、Work工作模式、fanout发布订阅模式、direct路由模式、主题Topic模式

1、Simple简单模式
image.png
2、工作模式
image.png
工作模式中公平分发和轮询分发都是我们经常使用的模式。
公平分发需要消费者开启手动应答,关闭自动应答
3、发布订阅模式
image.png
4、路由模式
image.png
5、主题Topic模式
image.png

为什么使用消息队列?

消息队列使用的场景有 解耦、异步、削峰
解耦:
如果有个场景,A系统需要发送数据到BCD三个系统,通过接口调用发送,但是如果碰到业务修改的问题,如果E系统也需要调用数据,如果B系统突然又不需要了,那么对于A系统的业务逻辑的修改无疑是比较复杂的。
而且A系统与各种系统耦合在一起,要时刻考虑发送给他们的消息,如果消息发送失败该怎么办;如果其他系统挂了该怎么办

如果使用MQ解耦,那么A系统产生一条数据,发送到MQ里面去,如果某个系统需要数据,则去MQ中消费即可。如果新系统不需要消费了,就取消对MQ的消费;A系统不需要考虑给谁发送数据。

异步:
如果不使用MQ,如果这个请求不仅需要在A系统写入数据,还要在BCD系统写入数据,那么接口请求的延时就会很慢
如果使用MQ,那么A系统只需要在自己地方写入数据,并把消息发送到MQ中,就可以直接返回了,请求的延时会很快

削峰:

消息队列的优缺点

缺点:

  • 系统可用性降低:系统结构复杂,如果MQ挂掉,则会导致整个系统都崩溃。所以需要保证消息队列的高可用性
    如何保证消息队列的高可用性?
    比如RabbitMQ,有单机模式、集群模式、镜像集群模式(高可用)
    其中镜像集群是高可用的
    镜像集群中,创建的queue队列,无论是元数据还是queue队列里面的消息,都会存在于多个实例上,每个RabbitMQ节点都有这个queue的一个完整的镜像,包括queue的全部数据。每次写消息的时候,都会自动把消息同步到多个实例的queue上。
    这样一个机器宕机后,消费者还可以去其他机器消费。缺点是性能开销大,使得网络带宽压力和消耗很重
    再拿kafka举例:

  • 系统复杂度提高
    消费者有可能出现重复消费的情况,需要处理消息丢失,保证消息传递的顺序性等等
    如何保证消息的重复消费(如何保证幂等性),和消息的顺序性?
    重复消费可能出现的场景:如kafka
    kafka有个offset的概念,就是每个消息写进去都有一个消息序号,然后定期把自己消费过的消息的offset提交一下,表明已经消费过。
    但是如果碰到消费者系统重启,消费者还没来得及把消费的序号提交给zookeeper时,那么就会造成启动后,碰到重复的消息过来。
    遇到重复消费不可怕,但是最重要的是考虑怎么保证幂等性
    幂等性就是一个数据或一个请求,重复多次,我们最终的结果是不变的。
    这个解决的思路可以结合具体的业务逻辑:

    1. 如果数据库存在这个主键,那么把插入操作变为更新操作
    2. 如果写redis,那么就没问题,每次set数据都一样,就是幂等的
    3. 如果场景复杂一点,可以将已经处理过的消息的id存入redis,每次处理时就可以判断消息是否已经被处理过
    4. 基于数据库唯一键保证重复数据只会插入一条

如何处理消息的丢失?
数据丢失的问题,可能出现在生产者、MQ、消费者中
如果针对生产者可能丢失消息的情况,有两种解决办法:
1.开启RabbitMQ的事务,如果在发送消息中生产者捕获到异常那么,就回滚事务,然后尝试重发消息
2.设置RabbitMQ的confirm机制,就是说,如果每次写入的消息会有一个唯一的id,然后如果消息成功写入MQ,则MQ会回传一个ACK消息,如果失败,则会回调一个nack接口。
两者的区别是,事务机制是同步的,confirm机制是异步的
如果针对MQ丢失消息的情况
可以设置MQ持久化磁盘上,RabbitMQ哪怕挂了,也会恢复之后自动读取之前存储的数据。
针对消费者弄丢了数据
关闭RabbitMQ的自动ACK,使用手动ACK的方式确认

  • 一致性问题

如果自己设计一个消息中间件,你会怎么设计?

  1. 生产者、消费者模型
  2. 支持分布式架构
  3. 数据的高可用
  4. 消息数据不丢失