侧边栏壁纸
  • 累计撰写 16 篇文章
  • 累计创建 17 个标签
  • 累计收到 1 条评论

RabbitMQ总结

xiuxiubiu
2021-04-13 / 0 评论 / 0 点赞 / 1,299 阅读 / 4,581 字 / 正在检测是否收录...

RabbitMQ基本概念

RabbitMQ是一个开源的AMQP实现,服务端使用Erlang语言编写,支持多种客户端,用户在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
6969B0AE57B5412FA5F99E4FF26F316E.png

  1. Broker:简单来说就是消息队列服务器实体。
  2. Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
  3. Queue:消息队列,每个消息都会被投入到一个或多个消息队列。
  4. Binding:绑定规则,把exchange和queue按照路由规则绑定起来。
  5. Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
  6. VHost:vhost可以理解为虚拟broker,即mini-RabbitMQ server。其内部均含有独立的queue、exchange和binding等,但最重要的是,其拥有独立的权限系统,可以做到vhost范围的用户控制。当然,从RabbitMQ的全局角度,vhost可以作为不同权限隔离的手段,典型的离职就是不同的应用可以泡在不同的vhost中。
  7. Producer:消息生产者,就是投递消息的程序。
  8. Consumer:消息消费者,就是接受消息的程序。
  9. Channel:消息通道,在客户端的每个链接里,可建立多个channel,每个channel代表一个会话任务。
    由Exchange、Queue、RoutingKey三个才能决定一个从Exchange到Queue的唯一路线。

RabbitMQ工作模式

simple模式

pythononeoverall.png
生产者产生消息,将消息放入队列,消费者监听消息队列,如果队列中有消息就消费掉,消息被拿走后会自动从队列中删除,存在隐患需要消费者设置ACK确认,消费者处理完后要及时发送ack给队列,否则会造成内存溢出。
耦合性过高,生产者一一对应消费者,如果有多个消费者想消费队列中信息就无法实现了。

work工作模式(资源的竞争)

pythontwo.png
生产者将消息放入队列,消费者可以有多个。一般有两种模式:

  1. 轮询分发:MQ不管两个消费者谁忙,数据总是你一个我一个,MQ给两个消费发送数据的时候是不知道消费者的性能的,默认就是雨露均沾。此时autoAck = true。
  2. 公平分发:要让消费者消费完毕一条数据后就告知MQ,再让MQ发数据即可。自动应答要关闭,实现按照消费者性能消费。消费成功后需要手动ack,否则会一直阻塞。

fanout publish/subscribe 发布订阅模式

exchanges.png
类似公众号的订阅和发布,属于fanout模式,不处理路由键,不需要指定routing-key,只需要把队列绑定到交换机上,消息就会自动发送到所有的队列:

  1. 一个生产者多个消费者
  2. 每个消费者都有一个自己的队列
  3. 生产者没有把消费发送给队列,而是发送到了交换机上
  4. 每一个队列都需要绑定到交换机上
  5. 生产者发送的消息经过交换机到达队列,从而实现一个消息被多个消费者消费

direct routing 路由模式

3EF228136B1845D48C448C36C80EDDA6.png
direct模式处理路由键,需要制定routing-key,此时生产者发送数据到MQ的时候会指定key,任务队列也会指定key,只有key一样的消息才会被传传送到队列中。

topic主题模式

pythonfive.png
将路由键跟某个模式匹配,生产者会带routing-key,但是消费者的MQ会带模糊的routing-key

  1. # 表示匹配>=1个字符
  2. * 表示匹配一个
  3. 交换机根据key的规则模糊匹配到对应的队列

常见考题

消息怎么路由的

消费队列通过routing-key绑定到交换机上,消息到达交换机后根据消息的routing-key匹配对应队列,常见交换器如下:

  1. fanout:交换机收到消息后,广播到所有绑定的队列
  2. direct:消息被投递到key完全匹配的消息队列
  3. topic:消息被投递到符合key模糊匹配规则的队列

RabbitMQ消息基于什么传输

信道(channel)是生产、消费者与RabbitMQ通信的渠道。信道是建立在TCP链接上的虚拟链接,也就是说RabbitMQ在一条TCP上建立成百上千个信道来达到多线程处理,这个TCP被多个线程共享,每个线程对应一个信道,信道在RabbitMQ有唯一的ID来保证信道私有性。用信道不用TCP链接的原因是,TCP链接的创建和销毁开销大,且并发数受资环限制,会造成性能瓶颈。

如何保证RabbitMQ消息不丢失

消息丢失分为生产者丢失消息、消息列表丢失消息、消费者丢失消息。

生产者丢失消息

RabbitMQ提供transaction和confirm模式来确保生产者不丢消息。

  • transaction事务模式就是,发送消息前,开启事务(channel.txSelect),然后发送消息,如果发送过程中出现异常,事务回滚(channel.txRollback),如果发送成功则事务提交(channel.txCommit)。事务阻塞会导致后面的消息无法发送,官方指出加入事务机制MQ会降速250倍。
  • confirm发送方确认模式,此模式使用居多。一旦channel进入confirm模式,所有在该信道上的发布消息会指派一个从1开始的唯一ID,消息被投递到匹配的队列后,RabbitMQ会发送一个包含消息的唯一ID的ACK给生产者,使生产者知道消息已到达目的队列,若RabbitMQ没处理此消息,则会发送一个Nack消息,可以进行重试操作。此模式为异步操作,生产者在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用时,生产者的回掉方法会被触发。

消息列表丢失消息

处理消息队列丢失数据,一般是开启持久化磁盘的配置。持久化磁盘配置可以和confirm机制配合使用,可以在消息持久化磁盘后,给生产者发送一个ack信号,如果持久化前,RabbitMQ挂了生产者收不到信号,则自动重发。

  1. durable = true,将queue的持久化设置为true
  2. 发送消息时将消息设置为持久化deliveryMode = 2
    关于持久化其实是个权衡问题,持久化可能会导致系统QPS下降,所以一般仅对关键消息做持久化处理,且应该保证持久化不会导致系统性能瓶颈。

消费者丢失消息

消费者丢失数据一般是因为采用了自动确认模式,此时,消费者在收到消息后,处理消息前,会自动回复RabbitMQ已收到消息,如果此时消息处理失败,则消息丢失。所以将消息确认改为手动即可解决,处理消息成功后,手动回复确认。

  1. 消费者接收到消息没有确认,连接也未断开,则RabbitMQ任务该消费者繁忙,不会再给此消费者分发更多消息。
  2. 如果消费者接收到消息,确认之前断开了连接或取消了订阅,则RabbitMQ会重新分发给下一个订阅者,可能存在重复消费的隐患,消费端需要保证幂等性。

如何避免重复消费

消息重复消费可以分为生产者端重复消费和消费者端重复消费,通过幂等性来保证重复消费的消息不对结果产生影响即可。
消息生成时,根据同一条消息有一个全局唯一的消息ID,可以将订单ID、支付ID等做为生成依据,消费端收到消息后,根据消息ID去重即可避免重复消费。

如何保证消息顺序执行

顺序执行的必要性:生产者的消息顺序是插入、更新、删除,如果消费者的执行顺序是更新、删除、插入,则跟预期结果不一致。
出现乱序一般由两种情况导致

  1. 一个Queue,有多个Consumer去消费,无法保证先读到消息的consumer一定先完成操作。
  2. 一个Queue对应一个Consumer,但是Consumer内是多线程消费,导致消费顺序错误。
    解决办法:
  3. 拆分成多个Queue,每个Queue对应一个Consumer,将三个有先后顺序的操作根据用户ID等信息投递到同一Queue,来保证消息的先后性,但是这样会导致吞吐量下降。
  4. 应用层消息同步触发,执行完插入后触发更新、更新后触发删除。

RabbitMQ的集群

RabbitMQ是基于主从(非分布式)做高可用性的,有三种模式:单机模式、普通集群模式、镜像集群模式。

单机模式

生产环境一般没人用单机模式

普通集群模式

在N台机器上启动N个RabbitMQ实例。创建的Queue只会放到一个实例上,但是每个实例都同步Queue的元数据。消费时如果连接了另外一个实例,那个实例会从Queue所在的实例上拉取数据过来,让集群的多个节点来服务Queue的读写操作,以此提高吞吐量。

镜像集群模式

RabbitMQ的高可用模式,在镜像集群模式下,无论是Queue的元数据还是消息都会存在多个实例上,每个节点都有这个Queue的全部数据。写消息时会自动同步消息到多个实例上。

  • 优点:任何一台机器宕机,其他节点还存在Queue的完整数据。
  • 缺点:消息需要同步所有数据,网络带宽消耗和压力很重。

死信队列和延迟队列

死信

死信(Dead Letter)是RabbitMQ中的一种消息机制,当消费队列中出现以下情况,该消息成为死信:

  1. 消息被否定确认,使用channel.backNick或channel.basicReject,并且default-requeue-rejected(由于监听器抛出异常而拒绝的消息是否被重新放回队列)设置为false。
  2. 消息在队列的存活时间超过设置TTL时间。
  3. 消息队列的数量超过最大队列长度。

死信队列

死信队列并不是什么特殊的队列,只不过是绑定在死信交换机上的队列。死信交换机只是用来接收死信的普通交换机,所以可以是任何类型的交换机,比如:Dirct、Fanout、Topic。
死信队列是RabbitMQ做的一层保障,在较为重要的业务中,可以确保未被正常消费的消息不被丢弃。其实我们也可以不使用死信队列,在消费异常时,将消息主动投递到另一个交换机。

TTL

TTL(Time To Live)是消息或者队列的属性,如果一条消息设置了TTL或者投递到有TTL属性的队列,若消息在TTL时间内没有被消费,则会成为死信。如果同时设置了队列和消息的TTL,则较小的值生效。

  1. 队列设置TTL,一旦消息过期就会被队列抛弃。
  2. 消息设置TTL,是否过期是在投递到消费者时判断,如果队列有严重的消息积压,则已过期的消息可能还会存活较长时间。

延迟队列

  1. 使用死信队列+TTL实现延迟队列,给消息设置过期时间,消息过期后成为死信,消费者一直消费死信队列。
  2. 使用rabbitmq_delayed_message_exchange插件

消息积压

  1. 若Consumer有问题先修复问题,保证消费速度。
  2. 临时扩容:创建原来N倍的队列,将积压队列的消息分发到新创建的队列。
  3. 永久扩容:创建需要扩容数量的队列,将旧消息和新消息分发到扩容队列。

RabbitMQ中的推和拉

  1. 推模式:消息中间件主动将消息推送给消费者
  2. 拉模式:消费者主动从消息中间件拉去消息

Push推模式

  1. 推模式时最有时效性的一种消息处理方式,channel.basicConsume方法会将信道设置成投递模式,直到取消队列订阅位置。当消息到达RabbitMQ时,RabbitMQ会自动地、不间断地将详细投递给匹配的消费者,投递消息个数受channel.basicQos限制。
  2. 消费者必须设置一个缓冲区缓存消息,优点是消费者总是有一堆待处理的消息,效率很高。

Pull拉模式

  1. 如果只想从队列获取单条消息,而不是持续订阅,可以使用channel.basicGet方法获取消息
  2. 拉模式在消费者需要时才去拉取消息,会增加网络开销和延迟,降低系统吞吐量。
  3. 拉模式需要消费者主动去拉取消息,实时性较差。

参考文章
RabbitMQ 高频考点

0

评论