1. 消息队列
缺点:系统可用性降低;系统复杂度提高;数据一致性问题
1.1. 为什么用消息队列?
- 解耦
- 通过MQ,Pub/Sub发布订阅消息模型,可以做到系统解耦
- A系统产生数据,发送到MQ,哪个系统需要,自己去MQ消费即可
- 新增一个系统,如果需要,直接可以对接消费
- 如果哪个系统不需要数据,直接取消消费即可
- 异步
- 用户反问每个解耦请求必须在200ms以内完成,对用户无感知
- 异步处理,发送到队列效率快,不影响当前系统业务
- 削峰
- 在某个时间点,访问量突然暴增,全部打到数据库可能导致数据库不可用,系统挂掉
1.2. 如何保证消息队列的高可用
RabbitMq是基于主从做高可用的
- 单机模式
- 普通集群模式(无高可用,只是提高了吞吐量)
- 多台机器上启动多个RabbitMQ实例,每个机器启动一个
- 创建的queue,只会放在一个RabbitMq实例,但每个实例都同步queue的元数据(queue的配置信息),消费时,如果连接到一个实例,那实例会从queue所在实例上拉取数据
- 如果放queue的实例宕机,会导致其它实例无法拉取,如果开启了消息持久化,让RabbitMQ落地存储消息,不一定会丢,等实例恢复,可以继续从这个queue拉取数据
- 镜像集群模式(高可用性)
- 在镜像集群模式下,创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,每个节点都有queue的一个完整镜像,每次写消息到queue的时候,会自动把消息同步到多个实例的queue上
- 好处:任何一个机器宕机,别的consumer都可以去其它节点上消费数据
- 缺点:性能开销大,消息需要同步到所有机器,导致网络带宽压力和消耗很重;没有扩展性,加机器,新增的机器也包含了queue的所有数据
1.3. 如何保证幂等性
一个数据,或者一个请求,重复多少次,确保对应的数据是不会改变的,不能出错就是幂等性
解决:比如写库操作,现根据主键查询,如果数据有了就不再插入;或者写入redis,去检验是否操作过;或者每条消息增加全剧唯一的id,先去查询再操作
1.4. 保证消息的可靠性传输
rabbitmq消息丢失的3种情况
消息在传入过程中丢失 解决:开启confirm模式,生产者那里设置开启confirm,每次写的消息都会分配一个唯一的id,如果写入了RabbitMQ,会回传一个ack消息。如果没能处理,会回调一个nack接口,通知消息接收失败。
RabbitMQ收到消息,暂存在内存中,还没消费,自己挂掉,内存中的数据搞丢 解决:
- 开启RabbitMQ的持久化,消息写入后持久化到磁盘,RabbitMq挂了恢复后会自动读取之前存储的数据,一般数据不会丢失
- 设置持久化步骤:(需要同时设置这两个持久化才行,RabbitMQ哪怕挂了再重启,也会从磁盘上重启恢复queue和数据)
- 创建queue的时候将其设置为持久化:可以保证持久化queue的元数据,但是不会持久化queue里的数据; 发送消息的时候将消息的deliveryMode设置为2:将消息设置持久化
- 结合confirm机制配合使用,只有消息被持久化到磁盘后,才会通知生产者ack
- 开启RabbitMQ的持久化,消息写入后持久化到磁盘,RabbitMq挂了恢复后会自动读取之前存储的数据,一般数据不会丢失
消费者收到消息,但还没来得及处理,就挂了 解决:RabbitMQ提供的ack机制,关闭自动ack,在业务逻辑处理完后调用一个手动ack的api
1.5. 保证消息的顺序性
拆分多个queue,一个queue一个consumer,一个queue对应一个consumer,然后这个consumer内部用内存队列做排队;mq本身是无序的,需要手动将多个queue排序或者在内存内排队处理
1.6. 解决消息队列的延时以及过期失效问题
1.6.1. 大量消息在mq里积压了几个小时还没解决
- 先修复consumer问题,确保恢复消费速度,然后停掉现有的consumer
- 写一个临时分发数据的consumer程序,这个程序部署去消费积压的数据,消费后不做耗时处理,直接均匀轮询写入临时建立好的10倍数量的queue
- 临时征用10倍机器来部署consumer,每一批consumer消费一个临时queue的数据
- 快速消费完积压数据后,恢复原先部署的架构,重新用原先的consumer机器来消费
1.6.2. mq消息过期失效了
批量重导,将丢失的数据,写临时程序,查询出来,重新灌入mq里
1.7. 如何设计一个消息队列
- 支持可伸缩,需要时快速扩容,增加吞吐量和容量
- mq数据落磁盘,顺序写
- 可用性,挂了重新选举
- 支持数据0丢失