MQ问答

为什么使用MQ

比较核心的场景:异步、解耦、削峰填谷

  • 异步

mark

  • 解耦:系统只用把消息扔到MQ,其他系统按需来订阅消息就行。

mark

  • 削峰填谷:秒杀场景

mark

使用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
2
3
消息发送到RabbitMQ之后,默认是没有落地磁盘的,万一RabbitMQ宕机了,这个时候消息就丢失了。
所以为了解决这个问题,RabbitMQ提供了一个持久化的机制,消息写入之后会持久化到磁盘.
这样哪怕是宕机了,恢复之后也会自动恢复之前存储的数据,这样的机制可以确保消息不会丢失。
  • 设置持久化有2个步骤
    • 第一个是创建queue的时候将其设置为持久化的,这样就可以保证rabbitmq持久化queue的元数据,但是不会持久化queue里的数据。
    • 第二个是发送消息的时候将消息的deliveryMode设置为2,就是将消息设置为持久化的,此时rabbitmq就会将消息持久化到磁盘上去。
      1
      2
      3
      但是这样一来可能会有人说:万一消息发送到RabbitMQ之后,还没来得及持久化到磁盘就挂掉了,数据也丢失了,怎么办?
      对于这个问题,其实是配合上面的confirm机制一起来保证的,就是在消息持久化到磁盘之后才会给生产者发送ack消息。
      万一真的遇到了那种极端的情况,生产者是可以感知到的,此时生产者可以通过重试发送消息给别的RabbitMQ节点。
消费端弄丢了消息
1
2
3
4
RabbitMQ消费端弄丢了数据的情况是这样的:在消费消息的时候,刚拿到消息,结果进程挂了,这个时候RabbitMQ就会认为你已经消费成功了,这条数据就丢了。

对于这个问题,要先说明一下RabbitMQ消费消息的机制:在消费者收到消息的时候,会发送一个ack给RabbitMQ,告诉RabbitMQ这条消息被消费到了,这样RabbitMQ就会把消息删除。
但是默认情况下这个发送ack的操作是自动提交的,也就是说消费者一收到这个消息就会自动返回ack给RabbitMQ,所以会出现丢消息的问题。
1
2
所以针对这个问题的解决方案就是:关闭RabbitMQ消费者的自动提交ack,在消费者处理完这条消息之后再手动提交ack。
这样即使遇到了上面的情况,RabbitMQ也不会把这条消息删除,会在你程序重启之后,重新下发这条消息过来。

怎么保证MQ的高可用性

1
2
3
这一块基于RabbitMQ这种经典的MQ来说明一下:
RabbitMQ是比较有代表性的,因为是基于主从做高可用性的,我们就以他为例子讲解第一种MQ的高可用性怎么实现。
rabbitmq有三种模式:单机模式,普通集群模式,镜像集群模式
  • 单机模式

    1
    2
    单机模式就是demo级别的,就是说只有一台机器部署了一个RabbitMQ程序。
    这个会存在单点问题,宕机就玩完了,没什么高可用性可言。一般就是你本地启动了玩玩儿的,没人生产用单机模式。
  • 普通集群模式

    1
    2
    是在多台机器上启动多个rabbitmq实例。类似的master-slave模式一样。但是创建的queue,只会放在一个master rabbtimq实例上,其他实例都同步那个接收消息的RabbitMQ元数据。
    在消费消息的时候,如果你连接到的RabbitMQ实例不是存放Queue数据的实例,这个时候RabbitMQ就会从存放Queue数据的实例上拉去数据,然后返回给客户端。
1
2
总的来说,这种方式有点麻烦,没有做到真正的分布式,每次消费者连接一个实例后拉取数据,如果连接到不是存放queue数据的实例,这个时候会造成额外的性能开销。如果从放Queue的实例拉取,会导致单实例性能瓶颈。
如果放queue的实例宕机了,会导致其他实例无法拉取数据,这个集群都无法消费消息了,没有做到真正的高可用。
  • 镜像集群模式
    1
    2
    3
    镜像集群模式才是真正的rabbitmq的高可用模式,跟普通集群模式不一样的是:创建的queue无论元数据还是queue里的消息都会存在于多个实例上,
    每次写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步。
    这样的话任何一个机器宕机了别的实例都可以用提供服务,这样就做到了真正的高可用了。
1
2
3
4
# 但是也存在着不好之处:
性能开销过高:消息需要同步所有机器,会导致网络带宽压力和消耗很重
扩展性低:无法解决某个queue数据量特别大的情况,导致queue无法线性拓展。
就算加了机器,那个机器也会包含queue的所有数据,queue的数据没有做到分布式存储。
-------------本文结束感谢您的阅读-------------
原创技术分享,感谢您的支持。