RabbitMQ快速入门之面试宝典

132 阅读9分钟

简介

AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。

AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。

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

核心概念

  • Broker:简单来说就是消息队列服务器实体。
  • Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
  • Queue:消息队列载体,每个消息都会被投入到一个或多个队列。
  • Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。
  • Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
  • vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。
  • producer:消息生产者,就是投递消息的程序。
  • consumer:消息消费者,就是接受消息的程序。
  • channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。

Queue

消息队列提供了FIFO的处理机制,具有缓存消息的能力。队列消息可以设置为持久化,临时或者自动删除。

  • 持久化的队列:queue中的消息会在服务器本地硬盘存储一份,防止系统崩溃后数据丢失
  • 临时队列:queue中的数据在系统重启之后就会丢失
  • 自动删除的队列:当不存在用户连接到服务器,队列中的数据会被自动删除

Exchange

Exchange类似于数据通信网络中的交换机,提供消息路由策略。Producer不是通过信道直接将消息发送给queue,而是先发送给Exchange。一个Exchange可以和多个Queue进行绑定,producer在传递消息的时候,会传递一个ROUTING_KEY,Exchange会根据这个ROUTING_KEY按照特定的路由算法,将消息路由给指定的queue。和Queue一样,Exchange也可设置为持久化,临时或者自动删除。

Exchange有4种类型:direct(默认),fanout, topic, 和headers,不同类型的Exchange转发消息的策略不同:

  • direct:直接交换器,工作方式类似于单播,Exchange会将消息发送完全匹配ROUTING_KEY的Queue
  • fanout:广播是式交换器,不管消息的ROUTING_KEY设置为什么,Exchange都会将消息转发给所有绑定的Queue。
  • topic:主题交换器,工作方式类似于组播,Exchange会将消息转发和ROUTING_KEY匹配模式相同的所有队列,比如,ROUTING_KEY为 a.b 的Message会转发给绑定匹配模式为 *.a, a.b*.*#.a.b.# 的队列。(* 匹配一个任意词组,# 匹配0个或多个词组)
  • headers:消息体的header匹配

重点:常用的路由方式:单播、广播、组播

Virtual Host

在RabbitMQ server上可以创建多个虚拟的broker,又叫做Virtual Hosts (vhosts)。每一个vhost本质上是一个mini-RabbitMQ server,分别管理各自的exchange和bindings。各个vhosts之间互相隔离,互不干扰,用于多租户隔离。Producer和Consumer连接RabbitMQ server需要指定一个vhost。

通信过程

假设P1和C1注册了相同的Broker,Exchange和Queue。P1发送的消息最终会被C1消费。基本的通信流程大概如下所示:

  1. P1生产消息,发送给服务器端的Exchange
  2. Exchange收到消息,根据ROUTING_KEY,将消息转发给匹配的Queue1
  3. Queue1收到消息,将消息发送给订阅者C1
  4. C1收到消息,发送ACK给队列确认收到消息
  5. Queue1收到ACK,删除队列中缓存的此条消息

消息接收确认

Consumer收到消息时需要显式的向RabbitMQ broker发送basic.ack消息或者consumer订阅消息时设置auto_ack参数为true。在通信过程中,队列对ACK的处理有以下几种情况:

  • 如果consumer接收了消息,发送ack,RabbitMQ会删除队列中这个消息,接着发送另一条消息给consumer。
  • 如果cosumer接收了消息, 但在发送ack之前断开连接,RabbitMQ会认为这条消息没有被deliver,在consumer再次连接的时候,这条消息会被redeliver。
  • 如果consumer接收了消息,但是程序中有bug,忘记了ack,RabbitMQ不会重复发送消息,可能导致内存占用过多。
  • 支持consumer reject某条(类)消息,可以通过设置requeue参数中的reject为true达到目的,那么RabbitMQ将会把消息发送给下一个注册的consumer。

重点:实际使用时常见的问题,如果手动ack,但是忘记ack,消息不会重复发送,但可能导致内存占用高。

消息可靠性保障

消息发送可靠性保障方式主要有事务模式和Confirm模式两种。

事务模式

使用事务模式,即通过txSelect()开启一个事务,然后发送消息给服务器,然后通过txCommit()提交该事务。如果txCommit()提交了,则该消息一定会持久化,如果txCommit()还未提交即崩溃,则该消息不会被服务器接收。当然RabbitMQ也提供了txRollback()命令用于回滚某一个事务。

重点:同步重量,可确认可回滚,但性能较差。

Confirm模式

通过该channel发送的消息都会被分配一个唯一的ID,然后一旦该消息被正确的路由到匹配的队列中后,服务器会返回给生产者一个Confirm,该Confirm包含该消息的ID,这样生产者就会知道该消息已被正确分发。对于持久化消息,只有该消息被持久化后,才会返回Confirm。Confirm机制的最大优点在于异步,生产者在发送消息以后,即可继续执行其他任务。而服务器返回Confirm后,会触发生产者的回调函数,生产者在回调函数中处理Confirm信息。如果消息服务器发生异常,导致该消息丢失,会返回给生产者一个nack,表示消息已经丢失,这样生产者就可以通过重发消息,保证消息不丢失。

Confirm机制在性能上要比事务优越很多。但是Confirm机制,无法进行回滚,就是一旦服务器崩溃,生产者无法得到Confirm信息,生产者其实本身也不知道该消息吃否已经被持久化,只有继续重发来保证消息不丢失,但是如果原先已经持久化的消息,并不会被回滚,这样队列中就会存在两条相同的消息,系统需要支持去重。

重点:异步轻量,但不可回滚,生产者要做到至少发送成功一次,所以要注意重复消费问题,consumer最好能做到幂等或去重。

消息序列化

RabbitMQ使用ProtoBuf序列化消息,它可作为RabbitMQ的Message的数据格式进行传输,由于是结构化的数据,这样就极大的方便了Consumer的数据高效处理,当然也可以使用XML。

RPC

MQ本身是基于异步的消息处理,前面的示例中所有的生产者(P)将消息发送到RabbitMQ后不会知道消费者(C)处理成功或者失败(甚至连有没有消费者来处理这条消息都不知道)。

但实际的应用场景中,我们很可能需要一些同步处理,需要同步等待服务端将我的消息处理完成后再进行下一步处理,这相当于RPC(Remote Procedure Call,远程过程调用)。在RabbitMQ中也支持RPC。

  1. 客户端发送请求(消息)时,在消息的属性(MessageProperties,在AMQP协议中定义了14中properties,这些属性会随着消息一起发送)中设置两个值replyTo(一个Queue名称,用于告诉服务器处理完成后将通知我的消息发送到这个Queue中)和correlationId(此次请求的标识号,服务器处理完成后需要将此属性返还,客户端将根据这个id了解哪条请求被成功执行了或执行失败)
  2. 服务器端收到消息并处理
  3. 服务器端处理完消息后,将生成一条应答消息到replyTo指定的Queue,同时带上correlationId属性
  4. 客户端之前已订阅replyTo指定的Queue,从中收到服务器的应答消息后,根据其中的correlationId属性分析哪条请求被执行了,根据执行结果进行后续业务处理

部署模式

RabbitMQ主要有三种部署模式。

单一模式

就是单点,主要用于开发测试。

普通模式

对于Queue来说,消息实体只存在于其中一个节点,A、B两个节点仅有相同的元数据,即队列结构。当消息进入A节点的Queue中后,consumer从B节点拉取时,RabbitMQ会临时在A、B间进行消息传输,把A中的消息实体取出并经过B发送给consumer。所以consumer应尽量连接每一个节点,从中取消息。即对于同一个逻辑队列,要在多个节点建立物理Queue。否则无论consumer连A或B,出口总在A,会产生瓶颈。该模式存在一个问题就是当A节点故障后,B节点无法取到A节点中还未消费的消息实体。如果做了消息持久化,那么得等A节点恢复,然后才可被消费;如果没有持久化的话,消息就丢失了。

重点:性能较高但可靠性低

镜像模式

该模式解决了上述问题,其实质和普通模式不同之处在于,消息实体会主动在镜像节点间同步,而不是在consumer取数据时临时拉取。该模式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉。所以在对可靠性要求较高的场合中适用。

重点:可靠性高但性能较低,可能导致网络堵塞

关注我,分享更多技术干货。

原文地址