amqp协议_RabbitMQ学习(一):AMQP协议与RabbitMQ的核心组件分析

概念

  • RabbitMQ是基于erlang语言开发的一个消息队列系统,是对AMQP协议的实现,其中AMQP的全称为Advanced Message Queuing Protocl,即高级消息队列协议,该协议主要用于制定基于队列进行消息传递的一个开放标准。
  • AMQP的核心概念包括:虚拟主机vhost,连接Connection,信道Channel,数据交换器Exchanger,队列Queue,交换器与队列之间的绑定Binding,统一负责消息接收和分发的服务端Broker。由于RabbitMQ是基于AMQP协议实现的,故在RabbitMQ的实现当中也是围绕对这些概念进行内部功能组件的设计,并将这些组件整合起来提供一个完整的消息队列服务。
  • 在应用方面,RabbitMQ起源于金融系统,主要用于分布式系统的内部各子系统之间的数据存储转发,这是系统解耦方面的一种运用。
  1. 即如果是单体应用则通常可以使用内存队列,如Java的BlockingQueue即可,而将单体应用拆分为分布式系统之后,则通过RabbitMQ这种进程队列来在各子系统之间进行消息传递,从而达到解耦的作用。
  2. 除此之外,RabbitMQ还可以运用在高并发系统当中的流量削峰,即将请求流量数据临时存放到RabbitMQ当中,从而避免大量的请求流量直接达到后台服务,把后台服务冲垮。通过使用RabbitMQ来存放这些请求流量,后台服务从RabbitMQ中消费数据,从而达到流量削峰的目的。
  3. 处理系统解耦和流量削峰外,RabbitMQ最常用于消息通讯,即可以用于实现IM聊天系统。

核心设计

由以上分析可知,RabbitMQ是基于AMQP协议实现的一个消息队列中间件,主要用于分布式系统当中不同系统之间的消息传递。所以在核心设计层面也是围绕AMQP协议来展开的。如下为RabbitMQ的核心架构示意图:(图片引自《RabbitMQ实战指南》)

2a7c266a450c42d689421f89d9449dd6

1. 虚拟主机vhost与权限

虚拟主机

  • 多租户或者称为虚拟主机vhost,主要用于实现不同业务系统之间的消息队列的隔离,即可以部署一个RabbitMQ服务端,但是可以设置多个虚拟主机给多个不同的业务系统使用,这些虚拟主机对应的消息队列内部的数据是相互隔离的。所以多个虚拟主机也类似于同一套房子里面的多个租户,每个租户都自己做饭吃饭,而不会去其他租户家里做饭吃饭。
  • 虚拟主机的概念相当于Java应用程序的命名空间namespace,不同虚拟主机内部可以包含相同名字的队列。
  • RabbitMQ服务器默认包含一个虚拟主机,即“/”,如果需要创建其他的虚拟主机,可以在RabbitMQ控制台执行如下命令:通过rabbitmqctl add_vhost命令添加一个新的“test_host”虚拟主机。
xyzdeMacBook-Pro:plugins xyz$ rabbitmqctl list_vhostsListing vhosts/xyzdeMacBook-Pro:plugins xyz$ rabbitmqctl add_vhost test_hostCreating vhost "test_host"xyzdeMacBook-Pro:plugins xyz$ rabbitmqctl list_vhostsListing vhosts/test_host

用户与权限

  • 一个RabbitMQ服务端可以包含多个虚拟主机,而这多个虚拟主机通常是对应多个不同的业务。所以为了保证不同业务不相互影响,则RabbitMQ中定义了用户和权限的概念。
  • 在RabbitMQ中,权限控制是以虚拟主机vhost为单位的,即当创建一个用户时,该用户需要被授予对一个或者多个虚拟主机进行操作的权限,而操作的对象主要包括交换器,队列和绑定关系等,如添加,删除交换器、队列等操作。
  • 创建用户和设置权限的相关命令主要在rabbitmqctl定义,RabbitMQ默认包含一个guest用户,密码也是guest,该用户的角色为管理员:
xyzdeMacBook-Pro:plugins xyz$ rabbitmqctl list_usersListing usersguest[administrator]xyzdeMacBook-Pro:plugins xyz$ rabbitmqctl list_permissions -p /Listing permissions in vhost "/"guest.*.*.*xyzdeMacBook-Pro:plugins xyz$ rabbitmqctl list_permissions -p test_hostListing permissions in vhost "test_host"

2. 连接Connection与信道Channel

  • 在高并发系统设计当中,需要尽量减少服务器的连接数,因为每个连接都需要占用服务器的一个文件句柄,而服务器的文件句柄数量是有限的,具体可以通过ulimit命令查看。
  • 所以为了减少连接的数量,AMQP协议抽象了信道Channel的概念,一个客户端与RabbitMQ服务器建立一个TCP连接,在客户端可以使用多个Channel,这多个Channel公用这条TCP连接来进行与服务端之间的数据传输。即Channel是建立在这个TCP连接之上的虚拟连接,就相当于每个channel都是一个独立的TCP连接一样。而在数据安全性方面,Rabbitmq的设计是每个不同Channel实例都对应一个唯一的ID,故这个真实的TCP连接发送和接收到数据时,则可以根据这个唯一的ID来确定这个数据属于哪个channel。
  • 使用Channel的场景通常为在客户端每个线程使用一个独立的Channel实例来进行数据传输,这样就实现了不同线程之间的隔离。不过由于所有线程都共用一个TCP连接进行数据传输,如果传输的数据量小则问题不大,如果需要进行大数据量传输,则该TCP连接的带宽就会成为性能瓶颈,所以此时需要考虑使用多个TCP连接。

3. RabbitMQ服务器Broker

在AMQP协议中,消息队列服务器称为Broker,在Broker中接收生产者的消息,将该消息放入对应的队列中,然后将消息分发给消息这个队列消息的消费者。所以Broker内部通常包含数据交换器Exchanger,队列Queue两大组件和需要实现这两大组件之间的绑定。

1. 交换器Exchanger

  • 在RabbitMQ的设计当中,交换器主要用于分析生产者传递过来的消息,根据消息的路由信息,即路由键route key,和自身维护的和队列Queue的绑定信息来将将消息放到对应的队列中,或者如果没有匹配的队列,则丢弃该消息或者扔回给生产者。

交换器类型

在RabbitMQ的交换器设计当中,交换器主要包含四种类型,分别为fanout,direct,topic和headers。

  1. fanout:相当于广播类型,忽略生产者传递过来的消息的路由信息,将所有消息都广播到所有与这个交换器绑定的队列中。
  2. direct:完全匹配,根据消息的路由键route key去完全匹配该交换器与队列的绑定键binding key,如果存在完全匹配的,则将该消息投递到该队列中;
  3. topic:模糊匹配或者正则匹配,交换器与队列之间使用正则表达式类型的绑定键,具体规则如下:
  4. 绑定键binding key和消息的路由键route key都是使用点号“.”分隔的字符串,如trade.alibaba.com为路由键,*.alibaba.com为绑定键;
  5. 在绑定键中,可以包含星号“*”和井号“#”,其中星号 “ * ”表示匹配0个或者多个单词,井号“ # ”表示匹配一个单词。
  6. 所以topic相对于direct能够匹配更多的消息,即topic类型的交换器可以成功投递更多的消息到其绑定的队列中。
  7. headers:headers类型不是基于消息的路由键来进行匹配的,而是基于消息的headers属性的键值对的,即首先交换器和队列之间基于一个键值对来建立绑定映射关系,当交换器接收到消息时,分析该消息headers属性的键值对是否与这个建立交换器和队列绑定关系的键值对完全匹配,是则投递到该队列。由于这种方式性能较低,故基本不会使用。

2. 队列Queue与绑定Binding

  • 在RabbitMQ的设计当中,队列Queue是进行数据存放的地方,即交换器Exchanger其实只是一个映射关系而已,不会实际占用RabbitMQ服务器的资源,而队列Queue由于在消费者消费消息之前,需要临时存放生产者传递过来的消息,故需要占用服务器的内存和磁盘资源。
  • 默认情况下,RabbitMQ的数据是存放在内存中的,当消费者消费队列的数据并发回了ACK确认时,RabbitMQ服务器才会将内存中的数据,即队列Queue中的数据,标记为删除,并在之后某个时刻进行实际删除。
  • 不过RabbitMQ也会使用磁盘来存放消息:
  1. 第一种场景是内存不够用时,RabbitMQ服务器会将内存中的数据临时换出到磁盘中存放,之后当内存充足或者消费者需要消费时,再换回内存;
  2. 第二种场景是队列Queue和生产者发送过来的消息都是持久化类型的,其中队列Queue持久化需要在创建该队列时指定,而消息的持久化为通过设置消息的deliveryMode属性为2来提示RabbitMQ服务器持久化这条消息到磁盘。
  • 如果RabbitMQ服务器是采用集群部署,如果没有开启镜像队列,则消息也是只存放在一个队列中的,这种情况下集群的目的主要是在不同的机器节点部署不同的队列Queue,从而来解决单机性能瓶颈,而不是解决数据的高可靠性。如果开启了镜像队列,则是基于Master-Slave的模式,将队列的数据复制到集群其他节点的队列中存放,从而实现数据高可用和高可靠。

4. 生产者

生产者主要负责投递消息到RabbitMQ服务器broker,具体为首先建立与broker的一个TCP连接,然后创建一个或者多个虚拟连接channel,每个channel就是一个生产者,在channel指定需要投递的交换器,消息的路由键和消息内容,最后调用publish方法发布到这个交换器。

1. 路由键Route key

  • 生产者需要指定消息的路由键route key,路由键通常与broker的交换器和队列之间的绑定键binding key对应,然后结合交换器的类型,路由键和绑定键来决定投递给哪个队列,或者如果没有可以投递的队列,则丢失消息或者返回消息给生产者。

2. 消息确认机制

  • 消息确认机制主要用于保证生产者投递的消息成功到达RabbitMQ服务器,具体为成功到达RabbitMQ服务器的交换器,如果此交换器没有匹配的队列,则也会丢失该消息。
  • 如果要保证数据成功到达队列,则可以结合Java API的mandatory参数,即没有匹配的队列可投递,则返回该消息给生产者,有生产者设置回调来处理;或者转发给备份队列来处理。

5. 消费者

  • 消费者用于消费队列中的数据,与生产者类似,消费者也是作为RabbitMQ服务器的一个客户端,即需要首先建立一个TCP连接,然后建立channel作为消费者,从而实现不同channel对应不同队列消费者。
  • 在数据消费层面,RabbitMQ服务器会将同一个队列数据以轮询的负载均衡方式分发给消费这个队列的多个消费者,每个消息默认只会给到其中一个消费者。

1. 推模式和拉模式

  • 消费者消费队列中的数据可以基于推、拉两种模式,其中推模式为RabbitMQ服务器当对应的队列有数据时,主动推送给消费者channel;而拉模式是消费者channel主动发起获取数据请求,每发起一次则获取一次数据,不发起则不会获取数据。如果在一个while死循环中轮询,则相当于推模式,不过这种方式很耗费资源,通常使用推模式代替。

2. 消息确认ACK与队列的消息删除

  • 在RabbitMQ的设计当中,RabbitMQ服务器是不会主动删除队列中的消息的,而是需要等到消费这条消息的消费者发送ACK确认时才会将队列的这条消息删除。注意,RabbitMQ服务器在等待消费者的ACK确认过程中,是没有超时的概念的,如果该消费者连接还存在且没有回传ACK,则这条消息一直保留在该队列中。如果该消费者连接断了且没有回传ACK,则RabbitMQ服务器将该消费发送给另外一个消费者。
  • 消费者确认在使用Java API时,可以使用自动确认和手动确认,其中自动确认会存在消费者还没处理就崩溃的情况,此时出现数据丢失,是“至多一次”的场景;如果手动提交,存在处理完还没提交ACK,则消费者崩溃,此时RabbitMQ会重复投递给其他消费者,故是“至少一次”的场景,存在消费重复。
  • 所以RabbitMQ在数据重复性和数据丢失方面,提供的是“至少一次”和“至多一次”的保证,不提供“恰好一次”的保证。即会存在重复消息和丢失消息。

2. 消息拒绝与重入队

  • 当消费者接收到RabbitMQ服务器发送过来的消息时,可以选择拒绝这条消息。消费者拒绝的时候,可以告诉RabbitMQ服务器是否将该消息重新入队,如果是,则RabbitMQ服务器会将该消息重新投递给其他消费者,否则丢弃这条消息。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值