RabbitMQ基本概念以及常见问题总结

243 阅读8分钟

RabbitMQ基本概念以及常见问题总结

使用场景

  • 消息队列是一种先进先出的数据结构,消息由生产者发送到MQ中进行排队,然后由消费者进行处理,常见的比如QQ、微信就是典型的MQ场景

  • MQ的作用主要有三个方面

    • 异步,可以提高系统的响应速度和吞吐量
    • 解耦,可以服务间进行解耦,减少服务之间的影响,提高系统的稳定性和可扩展性
    • 削峰,可以用稳定的系统资源应对突发的流量冲击
  • MQ的缺点

    • 系统可用性降低,一旦MQ宕机整个业务就会产生影响,所以需要高可用
    • 数据一致性问题,多个系统之间如果同时处理就会有这个问题
    • 系统复杂性提高,引入MQ之后数据链路会变得很复杂,会产生很多问题,比如如何保证消息可靠性传输(消息幂等性&消息不丢失)消息有序

工作队列模式

  • 简单(Simple)模式

    • 一个生产者对应一个消费者

    • 场景

      • 发送短信给指定的人
  • 工作队列(Work Queue)模式

    • 一个生产者多个消费者

    • 场景

      • 多个订单让多个消费者并行处理
  • 发布订阅(Publish/Subscribe)模式

    • 生产者发送消息到fanout交换机,转发到多个消费者,消费者都会收到消息

    • 场景

      • 订阅天气预报的人都会收到消息
  • 路由(Routing)模式

    • 生产者发送消息到direct交换机,交换机根据指定的路由key发到对应的消费者

    • 场景

      • iphone促销活动可以接受主题为iphone的消息
  • 主题(Topic)模式

    • 生产者发送消息到topic交换机,交换机根据匹配的路由key发到对应的消费者

    • 场景

      • iphone促销活动可以接受主题为iphone的消息
  • RPC模式

    • 在远程计算机上运行功能并等待结果就可以使用RPC

死信队列和延迟队列

  • 消息成为死信的三种情况

    • 队列消息长度达到限制

    • 消费者拒收消息,并且不重回队列

    • 消息在队列的存活时间超过设置的TTL时间

      • 问题: 什么是TTL

        • TTL就是存活时间也就是过期时间,当消息达到过期时间后还没被消费就会被清除,RabbitMQ可以对消息设置TTL或者整个队列设置TTL
  • 死信消息会被RabbitMQ进行特殊处理,如果配置了死信队列,那么死信会被丢进死信队列中,如果没有配置就直接丢弃

  • 为每个需要使用死信队列的业务配置一个死信交换机,然后给每个队列单独分配一个路由key,对于死信交换机跟普通的交换机一样,可以是fanout、direct、topic

  • 对于延迟队列就是消息进入队列后需要等待一段时间才会被消费,对于RabbitMQ并没有提供延迟队列的功能,但是可以使用TTL + 死信队列组合的方式实现延迟队列的效果

如何保证消息可靠性传输

  • 消息可靠传输代表了两层意思,既不能多也不能少

    • 要保证消息不能多,也就是消息不重复消费,也就是生产者不能重复的生产消息或者说消费者不能重复消费消息

      • 首先要确保消息不多发,这个不常出现也比较难控制,因为如果出现了多发,很大的原因就是生产者的原因,如果要避免出现问题,就需要在消费端做控制
      • 要避免不重复消费,最保险的机制就是消费者实现幂等性,就算重复消费也不会出问题,通过幂等性也能解决生产者重复发送消息的问题
    • 要保证消息不能少,也就是消息不丢失

      • 问题: 如何保证消息不丢失

        • 对于MQ消息丢失主要发生在跨网络的情况,比如生产者发送消息到MQ、主从节点数据同步、MQ发送消息到消费者

          • 问题: 如何保证生产者发送消息到MQ不丢失

            • 可以通过生产者的消息确认机制,通过多次确认的方式来保证生产者的发送消息到MQ不丢失
            • 可以通过手动事务的方式,通过channel开启事务、提交事务、回滚事务来保证,但是这种方式会产生阻塞导致吞吐量下降
          • 问题: 如何保证主从节点数据同步不丢失

            • 对于RabbitMQ的普通集群,消息是分散存储的,节点之间不会主动进行消息同步,最有可能丢失消息

            • 镜像集群,数据会主动在节点之间进行数据同步,这样丢失数据的概率不会太高

            • 问题: 如何保证消息存盘不丢失

              • RabbitMQ将队列配置成持久化队列,新增的Quorum类型的队列,会采用Raft协议来进行消息同步

                • 问题: Raft协议是什么

                  • Raft协议是一种分布式算法,主要是用来解决分布式一致性问题
                  • 问题: 简述Raft协议
                  • 问题: 简述ZAB协议
          • 问题: 如何保证MQ发送消息到消费者不丢失

            • RabbitMQ消费消息的时候可以指定手动应答或者自动应答

              • 如果是自动应答,消费者会在完成业务的时候自动应答,而如果消费者的业务逻辑有异常,MQ会将消息进行重试,这样是不会丢失消息的,但是有可能会造成消息重复消费的问题
              • 如果是手动应答,可以提高消息消费的可靠性

如何保证消息幂等性

  • 当消费者的业务逻辑出现异常时,MQ会将消息进行重试,所以就出现了幂等性的问题

  • 问题: 什么是幂等性问题

    • 幂等性就是一个数据或者一个请求,重复多次确保对应的数据是不会改变的,不能出错,比如新增、减库存就需要幂等性处理
    • 可以在生产者发送消息的时候带上一个全局id,消费者拿到消息后,先根据这个全局id去Redis中查询一下看有没有消费过,如果没消费就进行处理并且将这个id写入Redis,后续就不处理这个id的消息了
    • 可以基于数据库的唯一键

如何保证消息有序

  • 在一些场景下需要保证消息消费有序,比如下单、减库存、扣款,三条消息需要有序,对于这里RabbitMQ比较好的策略就是采用单队列+单消息推送也就是只保证目标exchange只对应一个队列并且一个队列只有一个消费者
  • 多队列的场景下,RabbitMQ并没有很好的解决方案,如果非要用多队列的话,比如这种场景下单、减库存、扣款、发短信,我们可以把发短信单独放到一个队列里,然后下单完在消费者那进行回调执行减库存以及扣款,这里要注意的是要保证后续两个操作的原子性

如何保证分布式事务的最终一致性

  • 分布式事务就是业务相关的多个操作,保证它们同时成功或者失败

  • 最终一致性与之对应的就是强一致性

  • MQ要实现最终一致性,就需要做到两点

    • 生产者要保证100%的消息投递,比如事务消息机制
    • 消费者这一端需要保证幂等消费,比如全局id
  • 对于RabbitMQ是基于erlang,所以天生就成为一种屏障

如何解决消息堆积问题

  • 对于RabbitMQ一直以来都有消息堆积处理不好的问题,当RabbitMQ有大量消息堆积的时候,整体性能就会严重下降,虽然最新推出的Quorum以及Stream旨在解决这个问题,但是还未完善,所以尽量要保障消息的消费的速度和生产速度一致

  • 如果还是出现了消息堆积的问题

    • 对于生产者端,可以降低生产消息的速度,但是这一端和业务挂钩的所以不好直接优化,但是可以尽量多采用批处理消息的方式,降低网络IO开销
    • 对于服务端,对于消息堆积严重的队列可以预先添加懒加载机制或者创建分片队列,这些有助于优化服务端消息堆积,或者尝试使用Stream队列
    • 对于消费者端,提升消费最直接的方式,就是增加消费者的数量,争取在最短的时间内消费堆积的消息,对于单个消费者端,可以通过提升机器配置来提升消费者的吞吐量