为什么使用MQ
比较核心的场景:异步、解耦、削峰填谷
- 异步
- 解耦:系统只用把消息扔到MQ,其他系统按需来订阅消息就行。
- 削峰填谷:秒杀场景
使用MQ的优缺点
- 系统可用性降低:如果MQ挂了,后面的系统将受影响
- 系统复杂度提高:加入MQ之后需要考虑消息重复消费、消息丢失、甚至消息顺序性的问题
- 数据一致性问题:
1
2本来A系统调用BC系统接口,如果BC系统出错了,会抛出异常,返回给A系统让A系统知道,这样的话就可以做回滚操作了。
但是使用了MQ之后,A系统发送完消息就完事了,认为成功了。而刚好C系统写数据库的时候失败了,但是A认为C已经成功了?这样一来数据就不一致了。
怎么保证MQ消息不丢失
生产者弄丢了数据
RabbitMQ生产者将数据发送到rabbitmq的时候,可能数据在网络传输中搞丢了,这个时候RabbitMQ收不到消息,消息就丢了。
RabbitMQ提供了两种方式来解决这个问题:
事务方式
1
2
3在生产者发送消息之前,通过`channel.txSelect`开启一个事务,接着发送消息。
如果消息没有成功被RabbitMQ接收到,生产者会收到异常,此时就可以进行事务回滚`channel.txRollback`然后重新发送。假如RabbitMQ收到了这个消息,就可以提交事务`channel.txCommit`。
但是这样一来,生产者的吞吐量和性能都会降低很多,现在一般不这么干。confirm机制
1
2
3
4
5这个confirm模式是在生产者哪里设置的,就是每次写消息的时候会分配一个唯一的id,然后RabbitMQ收到之后会回传一个ack,告诉生产者这个消息ok了。
如果rabbitmq没有处理到这个消息,那么就回调一个nack的接口,这个时候生产者就可以重发。
事务机制和cnofirm机制最大的不同在于事务机制是同步的,提交一个事务之后会阻塞在那儿
但是confirm机制是异步的,发送一个消息之后就可以发送下一个消息,然后那个消息rabbitmq接收了之后会异步回调你一个接口通知你这个消息接收到了。
所以一般在生产者这块避免数据丢失,都是用confirm机制的。
Rabbitmq弄丢了数据
1 | 消息发送到RabbitMQ之后,默认是没有落地磁盘的,万一RabbitMQ宕机了,这个时候消息就丢失了。 |
- 设置持久化有2个步骤
- 第一个是创建queue的时候将其设置为持久化的,这样就可以保证rabbitmq持久化queue的元数据,但是不会持久化queue里的数据。
- 第二个是发送消息的时候将消息的deliveryMode设置为2,就是将消息设置为持久化的,此时rabbitmq就会将消息持久化到磁盘上去。
1
2
3但是这样一来可能会有人说:万一消息发送到RabbitMQ之后,还没来得及持久化到磁盘就挂掉了,数据也丢失了,怎么办?
对于这个问题,其实是配合上面的confirm机制一起来保证的,就是在消息持久化到磁盘之后才会给生产者发送ack消息。
万一真的遇到了那种极端的情况,生产者是可以感知到的,此时生产者可以通过重试发送消息给别的RabbitMQ节点。
消费端弄丢了消息
1 | RabbitMQ消费端弄丢了数据的情况是这样的:在消费消息的时候,刚拿到消息,结果进程挂了,这个时候RabbitMQ就会认为你已经消费成功了,这条数据就丢了。 |
1 | 所以针对这个问题的解决方案就是:关闭RabbitMQ消费者的自动提交ack,在消费者处理完这条消息之后再手动提交ack。 |
怎么保证MQ的高可用性
1 | 这一块基于RabbitMQ这种经典的MQ来说明一下: |
单机模式
1
2单机模式就是demo级别的,就是说只有一台机器部署了一个RabbitMQ程序。
这个会存在单点问题,宕机就玩完了,没什么高可用性可言。一般就是你本地启动了玩玩儿的,没人生产用单机模式。普通集群模式
1
2是在多台机器上启动多个rabbitmq实例。类似的master-slave模式一样。但是创建的queue,只会放在一个master rabbtimq实例上,其他实例都同步那个接收消息的RabbitMQ元数据。
在消费消息的时候,如果你连接到的RabbitMQ实例不是存放Queue数据的实例,这个时候RabbitMQ就会从存放Queue数据的实例上拉去数据,然后返回给客户端。
1 | 总的来说,这种方式有点麻烦,没有做到真正的分布式,每次消费者连接一个实例后拉取数据,如果连接到不是存放queue数据的实例,这个时候会造成额外的性能开销。如果从放Queue的实例拉取,会导致单实例性能瓶颈。 |
- 镜像集群模式
1
2
3镜像集群模式才是真正的rabbitmq的高可用模式,跟普通集群模式不一样的是:创建的queue无论元数据还是queue里的消息都会存在于多个实例上,
每次写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步。
这样的话任何一个机器宕机了别的实例都可以用提供服务,这样就做到了真正的高可用了。
1 | # 但是也存在着不好之处: |