RabbitMQ消息通信中间件中的那些概念

本章主要内容

  • 了解rabbitmq的诞生
  • 环境设置与安装
  • AMQP协议
  • 消息通信概念-----生产者与消费者
  • 消息持久化

了解rabbitmq的诞生

        20世纪80年代,IBM、微软等公司研发了商业级的MQ组件,但大多停留在金融行业等大型组织内部使用,因其价格昂贵,且不同供应商之间的MQ协议不能,不能直接相互通信,很多中小型公司无法使用这项技术。2004年,JPMorgan Chase需要一个更好的消息通信解决方案,并开始和iMatix公司一起合作开发Advanced Message Queuing Protocol(AMQP,高级消息队列协议),它被设计成开放标准,以解决众多的消息队列需求和拓扑结构问题。随后,RabbitMq实现了AMPQ的特性,使其成为构建分布式应用的最完美的通信总线不二之选。

       今天,RabbitMQ并不是开放消息通信的唯一选择,其它还有像ActiveMQ\ZeroMQ\apache Qpid\kafka等都提供了不同的开源消息队列方案。相比之下RabbitMQ有什么特点呢:

  • 除Qpid外,RabbitMQ是唯一实现了AMQP标准的代理服务器
  • 正是使用了Erlang语言编写的RabbitMQ,使得它配置集群不可思议的简单。
  • 更可靠,更能防止崩溃

环境设置与安装

以window为例,先下载安装Erlang

http://www.erlang.org下载最新的发行版,并配置环境变量

http://www.rabbitmq.com上下载最新的MQ安装包(注意对应的系统),也同样配置环境变量

扫描二维码关注公众号,回复: 4492882 查看本文章

下载安装到对应的目录下,执行:

rabbitmq-server启动MQ,可以在浏览器中打开对应的管理界面,默认用户与密码都是guest

至此,一个按默认配置的MQ就运行起来了。接下来,我们先了解一下rabbitmq的内部的构成元素有哪些,各起到什么样的作用。

消息通信的概念----生产者与消费者

        一般的通信,例如B/S或C/S构架都会有客户端与服务端的概念,客户端发送请求,服务端接收并处理请求。这种可以称为快餐车模式,但Rabbitmq并不是这种模式,它更像是一种投递服务模式,应用从rabbitmq中获取的消息并不是rabbit产生的,就像是你收到的快递并不是快递员生产的一样。因此,可以把rabbit当作是一种投递服务,类似快递员。在客户端与服务器之间扮演路由器的角色。所以应用程序连接到rabbit时就必须做成决定,是发送数据还是接收数据?从AMQP角度看,就是自己是一个生产者还是一个消费者。

生产者(producer)创建消息,然后发布到代理服务器(rabbit),消息包含两部分:

  • 有效载荷(payload):就是你需要传输的数据,它可以是任何内容,一个字符串或一个JSON格式的数据或者是二进制流数据等等都可以。rabbitmq不关注这些
  • 标签(label):它用来描述payload,并且rabbitmq用它来决定消息的流向,把消息发送给感兴趣的一方。

这是一种“发后即忘”的单向方式。从上面可知:生产者会创建消息并设置标签,然后交给rabbitmq代理服务器。

消费者(consumer)连接到代理服务器并且订阅感兴趣的消息队列,每当消息到达此队列时rabbitmq都会将其发送给其中一个订阅者,当消息者接收到消息时,它只得到消息的一部分,即:有效载荷部分,而消息标签并没有随消息一同投递。甚至rabbit都不会告诉你生产者是谁。要想知道生产者是谁的唯一方式就是在消息里(有效载荷中)签名(在信封(envelope)上签名)。

       可以看出这个过程很简单,就是生产者创建消息,消息者接收处理消息。应用程序可以在这里扮演生产者,同时也可以扮演消息者接收其它应用程序发送的消息。那么消息是如何传递的呢?那就是通过信道传递的,什么是信道?

信道(channel)

       无论是生产者还是消费者都必须先连接到rabbitmq代理服务器,这个连接就是一个TCP连接,而信道就是建立在TCP连接内的虚拟连接。消息都是通过这些不同的信道投递完成的,每个信道都会被指派一个唯一的ID标识。我们有了TCP连接为什么还是信道呢?主要原因就是对操作系统来说建立和销毁TCP会话是非常高的开销。假设只使用TCP连接,那么每个线程都需要自行连接到rabbit。在高峰时期可能会有上千条这样的连接,不仅造成TCP连接的浪费而且操作系统每秒创建的TCP连接也是有限的。因此,这将成为一个瓶颈。如果我们为所有线程只使用一条TCP连接以满足性能方面的要求,但又能确保每个线程的私密性。这就是要引入信道概念的原因,线程启动后,会在现成的连接上创建一条信道。所以,你可以每秒成百上千次地创建信道而不会影响操作系统。在一条TCP连接上创建多少条信道是没有限制的。AMQP连接就像是一条光缆,信道就是光纤。

从总体上来说,消息通信,特别是AMQP,可以被当作加强版的传输层。使用信道,你能够根据应用需要,尽可能多地创建并行的传输层,而不会被TCP连接约束所限制。当理解了这些概念后,你就可以把rabbitmq看作软件的路由器了。

AMQP协议概念

AMQP消息路由包含三个部分:

  • 交换器
  • 队列
  • 绑定

队列

       生产者把消息发送到交换器后,消息最终到达队列当中,它是AMQP消息通信的基础模块。为消息提供了处所,消息在此等待消费。而对负载均衡来说,队列也是一个绝佳的方案,只需要附加一堆消费者,并让rabbitmq以循环的方式均匀地分配 发来的消息。它是消息的最后的终点。

      消息者通过AMQP的basic.consume命令订阅队列,当消息队列中的消息后,将会自动接收下一条消息。如果消费者处理队列消息时需要在消息一到达队列后就自动接收处理,应该使用此命令订阅。但如果只想从队列中获得单条消息而不是持续订阅的话可以使用basic.get命令获取。有区别于basic.consume命令,不能使用循环basic.get代替它。get命令每次会订阅消息,获得单条消息,然后取消订阅。所以性能方面考虑不使用循环。

        如果一个列表上没有消费者,那么消息将会在队列中等待。直到有至少有一位消费者订阅。当有一个订阅者时,消息会立即发送给此订阅者。但如果有多个订阅者呢,队列中的消息将如何发送?当有多个消费者时,队列收到的消息将以循环的方式发送给消费者。每条消息只会发送给一个订阅的消费者。消费者接收到的每一条消息都必须进行确认,必须通过AMQP的basic.ack命令显式地向rabbitmq发送一个确认,或者在订阅到队列时将auto_ack参数设置为true,此时一旦消费者接收消息,rabbitmq会自动视其确认了消息。当rabbitmq收到了确认接收消息后,才安全地把消息从队列中删除。消费者对消息的确认和告诉生产者消息已经被接收了这两件事没有关系。但如果由于某些原因消费者没有确认会发生什么样的事情呢?rabbit将不会给此消费者发送更多消息了。我们可以利用这个功能,当处理的消息非常耗时,可以延迟确认消息直到消息处理完成。可以防止rabbit持续不断的消息涌向你的应用导致过载。

      在rabbitmq2.0.0或更新的版本,增加了basic.reject命令,允许消费者拒绝消息,如果把reject命令的requeue参数设置成true的话,rabbitmq会将消息重新发送给下一个订阅的消费者。如果设置成false的话,rabbitmq立即会把消息从队列中移除。

队列创建

       生产者和消费者都 可以使用AMQP的queue.declare命令来创建队列,如果队列已经被声明,将不能被重复声明。声明时可以指定队列的名称,若不指定名称,则会返回一个随机名称。还有其它可设置参数:

  • exclusive,如果为true的话,队列将变成私有的,此时只有你的应用程序才能消费队列中的消息。
  • auto-delete,当最后一个消费者取消订阅的时候,队列就会自动移除。如果你需要临时队列只为一个消费者服务的话,可以结合使用auto-delete和exclusive。当消费者断开连接时,队列就会被自动删除。
  • passive,为true,如果队列已经存在那么queue.declare命令会成功返回,如果队列不存在的话命令不会创建队列而会返回一个错误。

如果生产者发送的消息路由到一个不存在的队列,则rabbit会忽略它们,即消息进入了“黑洞”不见了。

交换器(exchange)

       当你想要将消息投递到队列时,通过把消息发送给交换器来完成。然后,根据确定的规则,rabbitmq将会决定消息应该投递到哪个队列。这些规则被称作路由键(routing key)队列通过路由键绑定到交换器。空也是一种路由键。

rabbit会根据路由键将消息从交换器路由到队列,协议中定义了不同类型的交换器。每种类型就是一种投递方式:

  • direct(point to point),如果路由键routing-key与绑定到队列上的binding-key匹配的话就把消息投递到对应的队列。
  • fanout(multicast),这种类型的交换器会将收到的消息广播到绑定的队列上。当你发送一条消息到fanout交换器时,它会把消息投递给所有附加在此交换器上的队列。
  • topic(publish-subscribe),它可以使得来自不同源头的消息能够到达同一个队列,“.”把路由键分为了几部分,“*”匹配特定位置的任意文本。为了实现匹配所有规则,你可以使用“#”字符。
  • headers,允许匹配AMQP消息的header而非路由键 。除此之外,headers交换器和direct交换器完全一致,但性能会差很多。所以不太实用。

多租户模式:虚拟主机与隔离

      vhost是AMQP概念的基础,在连接到rabbit时必须进行指定。由于rabbitmq包含了开箱即用的默认vhost:"/",因此使用起来非常简单。每个rabbitmq服务器都能创建多个虚拟消息服务器,其本质上是一个mini版本的rabbitmq服务器。这样很有用,它既能将同一rabbit的众多客户区分开来,又可以避免队列和交换器的命名冲突。否则你可能不得不运行多个rabbit。相反,你可以只运行一个rabbit,然后按需要启动或关闭vhost。rabbit中的权限控制是以vhost为单位的。

当创建一个用户时,用户通常会被指派给至少一个vhost,并且只能访问被指派vhost内的队列、交换器和绑定。vhost之间是绝对隔离的,既保证了安全性,又确保了可移植性(当需要扩展的时候可以直接移植vhost到其它rabbit服务器)。

持久化

持久化的对象有两种

  • 交换器和队列的持久化
    • 默认情况下,rabbit重启后它们将会消失。其原因就在于durable属性,若为true则rabbit会把交换器或队列持久化到磁盘。
  • 消息持久化
    • 若要持久化消息,就要在消息发布前设置它的投递模式(delivery mode)为持久化,而且前提条件必须是所到达的交换器和队列也要持久化。

         rabbit确保消息能够恢复的做法是,将消息写入磁盘上的一个持久化日志文件。当发布一条持久化性的消息到交换器上时,rabbit会在消息提交到日志文件后才发送响应。但如果此消息被路由到了一个非持久化的队列上,它会自动从持久化日志中移除并且无法从rabbit重启中恢复。一旦消费了此持久化消息rabbit会在持久化日志中把这条消息标记为等待垃圾回收。鉴于持久化消息后,若发生宕机等情况消息可以恢复的好处。但同时也要带来性能上的问题,毕竟写磁盘和操作内存的效率上相关还是很大的。那么,如何考虑消息是否需要被持久化呢?

       权衡取舍,需要先分析应用对性能的需求,如果需要单台rabbit处理100000+/s的消息,那么持久化将是最大的一个瓶颈,需要考虑使用其它方式来确保消息投递的正确性,例如:使用更快速的存储系统。或者另开一个信道当消费者消费了消息后在规定的时间内通知生产者,若生产者未收到确认消费的信息,将会重发,以确保消息不会丢失。rabbit能帮助确保投递,但这也不是万无一失的。硬盘崩溃等原因,也有可能使得持久化消息的丢失。最终确保消息安全到达都将取决于你的策略。

       另一个与持久化相关的是AMQP的事务,把信道设置为事务模式后,发送那些想要确认的消息,可以发送多条AMQP命令,这些命令是执行带是忽略取决于第一条消息发送是否成功。如果成功,将会执行其它的命令,若投递不成功将不会执行。虽然事务是AMQP0-9-1规范的一部分,但使用它几乎将rabbit的性能吸干。所以可以使用其它的方式来代替事务,例如:发送方确认模式。

       与事务相仿,需要要先把信道设置成confirm模式,而且你只能通过重新创建信首来关闭此设置。一旦进入了confirm模式,所有在信道上发布的消息都会被指派一个ID号。当消息被投递完成后,信道会发送一个发送方确认模式给生产者。这样生产者就知晓消息已经安全到达队列中了。如果消息和队列都是持久化的,那么会在写入磁盘后才会发出确认。它是异步的,如果rabbit发生投递错误,将会发送一条nack未确认的消息,生产者可以根据此消息来决定是重新处理还是其它。此模式,由于没有消息回滚的概念,更加轻量级,同时也对rabbit服务器的性能影响也可以忽略不计。

总结

本章主要是了解rabbitmq中的相关元素,及各元素之间是如何协同工作的,消息是怎样被安全投递的等等。了解这些基本的概念后,就会对rabbitmq的整个工作流程有一个清楚的认识。在应用程序编码时先干什么,后干什么,是否需要持久化等等也会有一个清晰的思路。

猜你喜欢

转载自blog.csdn.net/xpsallwell/article/details/84786046