RabbitMQ03-RabbitMQ基础原理
1、RabbitMQ架构
- RabbitMQ是一个生产者与消费者模型,主要负责接收、存储和转发消息。
- RabbitMQ的整体架构模型,如图2-1所示。
- AMQP消息路由必须有三部分:交换器、队列和绑定。
1.1、生产者和消费者
- 生产者(Producer):创建消息,然后将消息发布(发送)到RabbitMQ。
- 消息包含2部分:消息体(payload)和标签(Label)。
- 消息体是一个带有业务逻辑结构的数据,可以是任何内容,比如一个JSON数组或一个简单的字符串。
- 标签是用来描述消息体的,并且RabbitMQ会根据标签(一个交换器名称和路由键)将消息分发到不同的消费者。
- 消费者(Consumer):连接到RabbitMQ服务器,并订阅到队列上,接收来自队列的消息(注意此时消息只有消息体了,标签在消息路由的过程中已经被丢弃)。
- 图2-2 展示了生产者将消息存入RabbitMQ Broker,以及消费者从Broker中消费数据的整个流程。
1.2、信道
- 生产者或消费者是通过TCP连接到RabbitMQ进行通信的。一旦TCP连接建立成功(通过了认证),生产者或消费者就可以创建一条AMQP信道。
- 信道是建立在“真实的”TCP连接内的虚拟连接。AMQP命令都是通过信道发送出去的。每条信道都会被指派一个唯一ID(AMQP库会帮你记住ID的)。不论是发布消息、订阅队列或是接收消息,这些动作都是通过信道完成的。
- 为什么需要信道,而不直接通过TCP连接发送AMQP命令呢?
- 主要原因在于对操作系统来说建立和销毁TCP会话是非常昂贵的开销。而且操作系统每秒也就只能建立一定数量的TCP连接。因此,如果每个线程都需要自行通过TCP连接到RabbitMQ,会造成TCP连接的巨大浪费,也会很快就碰到性能瓶颈。
- 如果所有线程只使用一条TCP连接以满足性能方面的要求,但又能确保每个线程的私密性,就像独立拥有TCP连接一样的话,那不就非常完美吗?这就是要引入信道概念的原因。
- 每秒成百上千次地创建信道也不会影响操作系统,并且在一条TCP连接上创建多少条信道是没有限制的。
1.3、队列
- 队列(Queue)是AMQP消息通信的基础模块:
- 在RabbitMQ上的消息都只能存储在队列中。RabbitMQ的生产者生产消息并投递到队列中,消费者可以从队列中获取消息并消费。
- 对负载均衡来说,队列是绝佳方案。只需附加一堆消费者,并让RabbitMQ以循环的方式均匀地分配发来的消息。
- 队列是消息在RabbitMQ中的最后终点(除非消息进入了“黑洞”)。
1.3.1、队列与消费者
- 消费者有两种方式从特定的队列中接收消息:
- (1)通过AMQP的basic.consume命令订阅。这样做会将信道置为接收模式,直到取消对队列的订阅为止。订阅消息后,消费者在消费(或者拒绝)最近接收的那条消息后,就能从队列中(可用的)自动接收下一条消息。如果消费者处理队列消息,需要在消息一到达队列时就自动接收的话,就应该使用basic.consume。
- (2)某些时候,你只想从队列获得单条消息而不是持续订阅。通过AMQP的basic.get命令实现向队列请求单条消息。这样做可以让消费者接收队列中的下一条消息。如果要获得下一条消息,就需要再次发送basic.get命令。不应该将basic.get放在一个循环里来替代basic.consume。因为这样做会影响RabbitMQ的性能。大致上讲,basic.get命令会订阅消息,获得单条消息,然后取消订阅。
- 消费者理应始终使用basic.consume来实现高吞吐量。
- 队列如何将消息分发给消费者:
- 如果某个队列只有一个消费者订阅,那么该队列只会将收到的消息发送给这个消费者。
- 如果某个队列有多个消费者订阅,那么该队列会将收到的消息以轮询(round-robin)的方式发送给消费者。即每条消息只会发送给一个消费者。
- 如果消息到达了无人订阅的队列,队列会先存储消息。一旦有消费者订阅了该队列,那么队列上的消息就会发送给消费者。
- 消费者对接收到的每一条消息都必须进行确认。
- 消费者必须通过AMQP的basic.ack命令显式地向RabbitMQ发送一个确认,或者在订阅到队列的时候将auto_ack参数设置为true。当设置auto_ack=true时,一旦消费者接收消息,RabbitMQ会自动视其确认了消息。
- 消费者通过确认命令告诉RabbitMQ它已经正确地接收了消息,同时RabbitMQ才能安全地把消息从队列中删除。
- 注意,消费者对消息的确认和告诉生产者消息已经被接收了这两件事毫不相关。
- 如果消费者没有对接收的消息进行确认会怎样?
- 如果消费者在确认之前从RabbitMQ断开连接(或者从队列上取消订阅),RabbitMQ会认为这条消息没有被分发,然后重新分发给下一个消费者。
- 如果消费者没有从RabbitMQ断开连接(或者没有从队列上取消订阅),并且也一直没有对消息进行确认,那么RabbitMQ将不会再给该消费者发送消息了。如果处理的消息内容非常耗时,消费者可以延迟确认该消息,直到消息处理完成,这样可以防止RabbitMQ持续不断地将消息发送给消费者而导致(应用程序)过载。
- 在消息处理完成之前,不对消息进行确认。只要消息尚未确认,消费者就可以明确拒绝而不是确认收到消息。有两种方式:
- (1)把消费者的连接从RabbitMQ服务器断开连接。这会导致RabbitMQ自动重新把消息入队并发送给另一个消费者。这样做的好处是所有的RabbitMQ版本都支持。缺点是,这样断开连接的方式会额外增加RabbitMQ的负担(如果消费者在处理每条消息时都遇到错误的话,会导致潜在的重大负荷)。
- (2)如果使用RabbitMQ 2.0.0或者更新的版本,那就使用AMQP的basic.reject命令,basic.reject允许消费者拒绝RabbitMQ发送的消息。
- 如果把reject命令的requeue参数设置成true的话,RabbitMQ会将消息重新发送给下一个订阅的消费者。
- 如果设置成false的话,RabbitMQ立即会把消息从队列中移除,而不会把它发送给下一个订阅的消费者。
- 也可以通过对消息确认的方式来忽略该消息(这种忽略消息的方式的优势在于所有版本的RabbitMQ都支持)。如果你检测到一条格式错误的消息而任何一个消费者都无法处理的时候,这样做就十分有用。
1.3.2、创建队列
- 消费者和生产者都能使用AMQP的queue.declare命令创建队列。
- 如果消费者在同一条信道上订阅了另一个队列的话,就无法再声明队列了。此时消费者要想创建队列,就必须先取消订阅,然后将信道置为“传输”模式。
- 如果在创建队列时不指定队列名称,RabbitMQ会分配一个随机名称并在queue.declare命令的响应中返回(对于构建在AMQP上的RPC应用来说,使用临时“匿名”队列很有用)。(消费者订阅队列时需要队列名称,并在创建绑定时也需要指定队列名称。)
- 创建队列时比较有用的参数:
- exclusive:如果设置为true的话,队列将变成私有的,此时只有你的应用程序才能够消费队列消息。当你想要限制一个队列只有一个消费者的时候很有帮助。
- auto-delete:当最后一个消费者取消订阅的时候,队列就会自动移除。
- 如果你需要临时队列只为一个消费者服务的话,请结合使用auto-delete和exclusive。当消费者断开连接时,队列就被移除了。
- 如果尝试声明一个已经存在的队列会发生什么呢?
- 如果声明参数完全匹配已有的队列,RabbitMQ就什么都不做,并成功返回,就好像这个队列已经创建成功样(如果参数不匹配的话,队列声明尝试会失败)。
- 如果你只是想检测队列是否存在,则可以设置queue.declare的passive选项为true。
- 如果队列存在,那么queue.declare命令会成功返回。
- 如果队列不存在,queue.declare命令不会创建队列而会返回一个错误。
- 谁来创建所需的队列,是生产者,还是消费者?
- 首先需要想清楚消息的生产者能否承担得起丢失消息。发送出去的消息如果路由到了不存在的队列,RabbitMQ会忽略它们。
- 如果不能承担得起消息进人“黑洞”而丢失的话,生产者和消费者都应该尝试去创建队列。
- 如果能承担得起丢失消息,或者可以实现一种方法来重新发布未处理的消息(我们会向,你展现如何做到这一点),可以只让消费者来声明队列。
1.4、交换器、路由键和绑定
1.4.1、如何将消息发送到队列
- 交换器(Exchange):实际上,生产者并不会直接将消息发送到队列,而是将消息发送到交换器,然后再由交换器将消息路由到一个或者多个队列中。
- 绑定(Binding):RabbitMQ中通过Binding将交换器与队列关联起来,在绑定的时候一般会指定一个绑定键(BindingKey),这样Exchange就知道如何正确地将消息路由到队列了。(绑定决定了消息被交换器路由到哪些队列)
- 将多个队列绑定到同一个交换器的时候,允许使用相同的BindingKey。
- 路由键(RoutingKey):生产者将消息发给交换器的时候,一般会指定一个路由键。(在某些情形下,可以将RoutingKey和BindingKey当作同一个东西)
- 路由键用来指定消息的路由规则,路由键需要与交换器类型和绑定键联合使用才能生效。
- 当RoutingKey与BindingKey匹配时,消息会被路由到对应的队列中。
- BindingKey并不是在所有的情况下都生效,它依赖于交换器的类型。比如fanout交换器就会无视BindingKey,将消息路由到所有绑定到该交换器的队列中。
- 路由键用来指定消息的路由规则,路由键需要与交换器类型和绑定键联合使用才能生效。
1.4.2、交换器的类型
- RabbitMQ有四种交换器:fanout、direct、topic、headers(AMQP协议中还提到另外两种System和自定义)
1、direct交换器
- direct:把发送到该交换器的消息路由到RoutingKey与BindingKey完全匹配的队列中。
- 应用程序必须实现direct类型交换器,包含一个空白字符串名称的默认交换器。当声明一个队列时,它会自动绑定到默认交换器,并以队列名称作为路由键。
channel --> basic_publish($msg, '', 'queue_name'); \\第一个参数是消息体; \\第二个参数是一个空的字符串,指定了默认交换器; \\第三个参数是路由键,这个路由键就是之前用来声明队列的名称。
- 当默认的direct交换器无法满足需求时,可以使用exchange.declare命令切换其他的交换器。
2、fanout交换器
- fanout:把发送到该交换器的消息路由到所有与该交换器绑定的队列中。
- 可以使不同的消费者对同一条消息做不同的反应。例如,一个Web应用程序可能需要在用户上传新的图片时,用户相册必须清除缓存,同时用户应该得到些积分奖励。可以将两个队列绑定到图片上传交换器上。一个用于清除缓存,另一个用于增加用户积分。
3、topic交换器
- topic:与direct交换器相似,也是将消息路由到BindingKey和RoutingKey相匹配的队列中,但topic支持模糊匹配。
- 将RoutingKey以实心点"."分隔成多个字符串。
- BindingKey和RoutingKey一样也使用实心点"."进行分割,不过可以在BindingKey中使用"#"和"*"通配符。
- "*":通配一个字符串
- "#":通配多个字符串(包含实心点)
- topic交换器可以将不同源头的消息发送到同一个队列。
示例:
- 以Web应用程序日志系统作为例,web拥有多个不同的日志级别,比如info、warning和error。与此同时,web有下几个模块:user-profile、image-gallery和msg-inbox等。
- 如果发送消息的动作失败时,需要报告一个error,可以使用下面的编码。
//1、msg-inbox发送错误日志(声明一个交换器logs-exchange,并指定一个路由键error.msg-inbox) channel --> basic_publish($msg, 'logs-exchange', 'error.msg-inbox') //2、收集msg-inbox的错误日志(声明一个队列msg-inbox-errors,并将其绑定到交换器logs-exchange上,以及指定一个绑定键error.msg-inbox) channel --> queue_bind('msg-inbox-errors', 'logs-exchange', 'error.msg-inbox') //收集msg-inbox的所有级别的日志(声明一个队列msg-inbox-logs,并将其绑定到交换器logs-exchange上,以及指定一个绑定键*.msg-inbox) channel --> queue_bind('msg-inbox-logs', 'logs-exchange', '*.msg-inbox') //收集所用模块的所有级别的日志(声明一个队列all-logs,并将其绑定到交换器logs-exchange上,以及指定一个绑定键#) channel --> queue_bind('all-logs', 'logs-exchange', '#')
- 如果user-profile模块发送的日志信息会分别指定路由键info.user-profile、warning.user-profile、error.user-profile;image-gallery模块发送的日志信息会分别指定路由键info.image-gallery、warning.image-gallery、error.image-gallery;msg-inbox模块发送的日志信息会分别指定路由键info.msg-inbox、warning.msg-inbox、error.msg-inbox。五个队列分别使用不同的绑定键绑定到交换机。那么五个队列将收到不同的消息。
-
- Queue1将收到user-profile模块发送的所有的日志消息。(使用路由键代表不同的消息)
- info.user-profile
- warning.user-profile
- error.user-profile
- Queue2将收到image-gallery模块发送的所有的日志消息。
- info.image-gallery
- warning.image-gallery
- error.image-gallery
- Queue3将收到msg-inbox模块发送的所有的日志消息。
- info.msg-inbox
- warning.msg-inbox
- error.msg-inbox
- Queue4将收到user-profile、image-gallery和msg-inbox模块发送的所有的错误日志消息。
- error.user-profile
- error.image-gallery
- error.msg-inbox
- Queue5将收到user-profile、image-gallery和msg-inbox模块发送的所有的日志消息。
- info.user-profile
- warning.user-profile
- error.user-profile
- info.image-gallery
- warning.image-gallery
- error.image-gallery
- info.msg-inbox
- warning.msg-inbox
- error.msg-inbox
- Queue1将收到user-profile模块发送的所有的日志消息。(使用路由键代表不同的消息)
4、headers交换器
- headers交换器的性能会很差,而且也不实用。
- headers:不使用路由键的匹配规则来路由消息,而是根据消息的headers属性进行匹配。
- 在绑定队列和交换器时制定一组键值对,当发送消息到交换器时,RabbitMQ会获取到消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。
1.5、多租户模式:虚拟主机和隔离
- 每一个RabbitMQ服务器都能创建虚拟的消息服务器,称为虚拟主机(virtual host),简称为vhost。
- 每一个vhost本质上是一个独立的小型RabbitMQ服务器,拥有自己独立的队列、交换器及绑定关系等,并且它拥有自己独立的权限。
- 一个RabbitMQ服务器可以安全地服务多个应用程序。(每个vhost对应一个应用程序)
- vhost于RabbitMQ就像虚拟机于物理服务器一样,它们在各个实例间提供逻辑上的分离,为不同应用程序安全保密地运行数据。
- 它既能将同一个RabbitMQ中的众多客户区分开,又可以避免队列和交换器等的命名冲突。
- 否则可能不得不运行多个RabbitMQ,并忍受随之而来的令人头疼的管理问题。相反,就可以只运行一个RabbitMQ,然后按需启动或关闭vhost。
- vhost之间是绝对隔离的,无法将vhost1中的交换器与vhost2中的队列进行绑定。这样既保证了安全性,又可以确保可移植性。如果在使用RabbitMQ达到一定规模的时候,建议用户对业务功能、场景进行归类区分,并为之分配独立的vhost。
- vhost是AMQP概念的基础,客户端在连接RabbitMQ的时候必须指定一个vhost,并且只能访问该vhost中的队列、交换器和绑定。
- RabbitMQ默认创建的vhost为"/",如果不需要多个vhost或者对vhost的概念不是很理解,那么用这个默认的vhost也是可以的,使用默认的用户名guest和密码guest就可以访问它。但是为了安全和方便,建议创建一个新的用户来访问它。
- 在RabbitMQ集群上创建vhost时,整个集群上都会创建该vhost。vhost不仅避免了为每个应用程序运行一个RabbitMQ服务器的需要,同样也避免了为每一个应用程序创建不同的集群。
- vhost和其权限控制非常独特,它们是AMQP中唯一无法通过AMQP协议创建的基元(不同于队列,交换器和绑定)。必须使用命令行工具rabbitmqctl管理它们。
//创建vhost rabbitmqctl addvhost [vhost_name] //删除vhost rabbitmgctl delete_vhost [vhost_name] //查看vhost列表 rabbitmqctl list_vhosts
2、RaabitMQ的持久化
2.1、持久化的消息、交换器和队列
- 默认情况下,重启RabbitMQ服务器后,队列和交换器都消失。
- 这是因为每个队列和交换器的durable属性(默认为false),该属性决定了RabbitMQ在崩溃或重启之后是否重新创建队列(或者交换器)。
- 将durable属性设置为true,这样在服务器重启后RabbitMQ就会自动重新创建队列和交换器了。
- 能从AMQP服务器崩溃中恢复的消息,称为持久化消息。
- 要将消息持久化就必须在消息发布前,先把消息标记成持久化的(将它的“投递模式”(delivery mode)设置为2),然后将消息发布到持久化的交换器,最后并被路由到持久化的队列中。
- 如果不是持久化的交换器和队列,在RabbitMQ崩溃重启后交换器和队列将不复存在,从而导致消息成为孤儿。
- 如果想将消息从Rabbit崩溃中恢复,就必须满足以下三点:
- 投递模式设置为2(持久)
- 发送到持久化的交换器(将交换器的durable属性设置为true)
- 路由到持久化的队列(将队列的durable属性设置为true)
- RabbitMQ确保持久化消息能从服务器重启后恢复,是将它们写入磁盘上的一个持久化日志文件。
- 当发布一条持久性消息到持久交换器上时,RabbitMQ会在消息提交到日志文件后才发送响应给生产者。
- 将消息路由到持久队列中。如果是非持久队列,消息会自动从持久化日志文件中移除。
- 如果要将消息持久化,就必须做到之前提到的持久化消息的那三点(在怎么强调也不为过)。
- 一旦从持久化队列中消费了一条持久化消息(并且确认了它),RabbitMQ会在持久化日志中把这条消息标记为等待垃圾收集。
- 在消费持久性消息前,如果RabbitMQ重启的话,服务器会自动重建交换器和队列(以及绑定),重播持久性日志文件中的消息到合适的队列或者交换器上(取决于RabbitMQ服务器宕机的时候,消息处在路由过程的哪个环节)。
- 持久化机制的劣势:
- 持久化机制会降低RabbitMQ的吞吐量(低至少10倍的情况并不少见)。这是因为写入磁盘要比写入内存慢上很多。
- 持久化机制在RabbitMQ集群环境下工作得并不好。虽然RabbitMQ集群允许你和集群中的任何节点的任一队列进行通信,但是事实上那些队列均匀地分布在各个节点而没有冗余(在集群中任何一个队列都没有备份的拷贝)。当节点宕机时,其上的队列也都不可用了,而且持久化队列也无法重建。
- 什么情况下应该使用持久通信呢?首先,你需要分析(并测试)性能需求。你是否需要单台Rabbit服务器每秒处理100000条消息呢?
- 运行两种类型的Rabbit集群:非持久化消息通信的传统RabbitMQ集群和持久化消息通信的热备非集群RabbitMQ服务器(使用负载均衡)。
- 这样做确保了为持久化消息通信处理负载不会减慢非持久化消息的处理。这也意味着RabbitMQ集群的节点宕机时不会让持久性消息消失。
2.2、事务和发送方确认模式
- 发送方确认模式是用来替代事务模式的。建议使用发送方确认模式来保证消息的投递,不使用事务。
2.2.1、事务
- 和消息持久化相关的一个概念是AMQP事务(transaction),用来保证消息投递。事务填补了生产者发布消息以及RabbitMQ将它们提交到磁盘上这两者之间“最后一公里”。
- 由于发布操作不返回任何信息给生产者,那你怎么知道服务器是否已经持久化了持久消息到硬盘呢?服务器可能会在把消息写人磁盘前就宕机了,消息因此而丢失,而你却不知道。这就是事务发挥作用的地方。
- 在AMQP中,在把信道设置成事务模式后,你通过信道发送那些想要确认的消息,之后还有多个其他AMQP命令。这些命令是执行还是忽略,取决于第一条消息发送是否成功。一旦你发送完所有命令,就可以提交事务了。如果事务中的首次发布成功了,那么信道会在事务中完成其他AMQP命令。如果发送失败的话,其他AMQP命令将不会执行。
- 使用事务不但会降低RabbitMQ的大约2~10倍的消息吞吐量,而且会使生产者应用程序产生同步。而使用RabbitMQ就是想要避免同步。
2.2.2、发送方确认模式
- RabbitMQ团队决定拿出更好的方案来保证消息投递:发送方确认模式。
- 将RabbitMQ的信道设置成confirm模式,而且只能通过重新创建信道来关闭该设置。
- 一旦信道进入confirm模式,所有在信道上发布的消息都会被指派一个唯一的ID(从1开始)。
- 一旦消息被投递给所有匹配的队列后,就会发送一个确认消息给生产者应用程序(包含消息的唯一ID)。这使得生产者知晓消息已经安全到达目的队列了。如果消息和队列是可持久化的,那么确认消息只会在队列将消息写入磁盘后才会发出。
- 发送方确认模式的最大好处是它们是异步的。一旦发布了一条消息,生产者应用程序就可以在等待确认的同时继续发送下一条。
- 如果RabbitMQ内部发生了错误从而导致了消息的丢失,RabbitMQ会发送一条nack(not acknowledged,未确认)消息。就像发送方确认消息那样,只不过这次说明的是消息已经丢失了。
- 同时,由于没有消息回滚的概念(同事务相比),因此发送方确认模式更加轻量级,同时对RabbitMQ服务器的性能影响几乎可以忽略不计。
3、客户端开发
3.1、连接RabbitMQ
ConnectionFactory factory = new ConnectionFactory(); factory.setHost(IP_ADDRESS); factory.setPort(PORT) ; factory.setUsername(USERNAME); factory.setPassword(PASSWORD); factory.setVirtualHost(vhost); Connection conn = factory.newConnection();
3.2、使用交换器和队列
3.2.1、exchangeDeclare方法
- Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments) throws IOException;
- exchange:交换器的名称。
- type:交换器的类型,常见的如fanout、direct、topic。
- durable:是否持久化。设置为true表示持久化。持久化可以将交换器存储在磁盘中,在服务器重启的时候不会丢失相关信息。
- autoDelete:是否自动删除。设置为true则表示自动删除。自动删除的前提是至少有一个队列或者交换器与这个交换器绑定,之后所有与这个交换器绑定的队列或者交换器都与此解绑。注意不要错误地理解为:"当与此交换器连接的客户端都断开时,RabbitMQ会自动删除本交换器"。
- internal:是否是内置的。设置为true则表示是内置交换器,客户端无法直接发送消息到这个交换器中,只能通过其他外置交换器路由到这个内置交换器。
- argument:其他一些结构化参数,比如alternate-exchange。
3.2.2、queueDeclare方法
- Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) throws IOException;
- queue:队列的名称。
- durable:是否持久化。设置为true表示持久化。持久化的队列会存储在磁盘中,在服务器重启的时候可以保证不丢失相关信息。
- exclusive:是否排他。设置为true则队列为排他的。如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。这里需要注意三点:排他队列是基于连接(Connection)可见的,同一个连接的不同信道(Channel)是可以同时访问同一连接创建的排他队列;"首次"是指如果一个连接己经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同;即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除,这种队列适用于一个客户端同时发送和读取消息的应用场景。
- autoDelete:是否自动删除。设置为true则表示自动删除。自动删除的前提是:至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除。不能把错误地理解为:"当连接到此队列的所有客户端断开时,这个队列自动删除",因为生产者客户端创建这个队列,或者没有消费者客户端与这个队列连接时,都不会自动删除这个队列。
- argurnents:设置队列的其他一些参数,如x-rnessage-ttl、x-expires、x-max-length、x-max-length-bytes、x-dead-letter-exchange、x-deadletter-routing-key,x-max-priority等。
3.2.3、queueBind方法
- Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map<String, Object> arguments) throws IOException;
- queue:队列名称。
- exchange:交换器的名称。
- routingKey:用来绑定队列和交换器的路由键。
- argument:定义绑定的一些参数。
3.2.4、exchangeBind方法
- Exchange.BindOk exchangeBind(String destination, String source, String routingKey, Map<String, Object> arguments) throws IOException;
- 绑定之后,消息从source交换器转发到destination交换器,某种程度上来说destination交换器可以看作一个队列。
3.3、发送消息
- void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body) throws IOException;
- exchange:交换器的名称,指明消息需要发送到哪个交换器中。如果设置为空字符串,则消息会被发送到RabbitMQ默认的交换器中。
- routingKey:路由键,交换器根据路由键将消息存储到相应的队列之中。
- props:消息的基本属性集,其包含14个属性成员,分别有contentType、contentEncoding、headers(Map<String, Object>)、deliveryMode、priority、correlationId、replyTo、expiration、messageId、timestamp、type、userId、appId、clusterId。
- byte[] body:消息体(pay1oad) ,真正需要发送的消息。
- mandatory和immediate。
3.4、消费消息
- RabbitMQ的消费模式有两种:推(Push)模式和拉(Pull)模式。
- 推模式是消费者调用Basic.Consume进行消费,拉模式是消费者调用Basic.Get进行消费。
3.4.1、推模式
- String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map<String, Object> arguments, Consumer callback) throws IOException;
- queue:队列的名称。
- autoAck:设置是否自动确认。建议设成false,即不自动确认。
- 当autoAck等于false时,RabbitMQ会等待消费者显式地回复确认信号后才从内存(或者磁盘)中移去消息(实质上是先打上删除标记,之后再删除)。
- 当autoAck等于true时,RabbitMQ会自动把发送出去的消息置为确认,然后从内存(或者磁盘)中删除,而不管消费者是否真正地消费到了这些消息。
- consumerTag:消费者标签,用来区分多个消费者。
- noLocal:设置为true,则表示不能将同一个Connection中生产者发送的消息传送给这个Connection中的消费者。
- exclusive:设置是否排他。
- arguments:设置消费者的其他参数。
- callback:设置消费者的回调函数。用来处理RabbitMQ推送过来的消息,比如DefaultConsumer,使用时需要客户端重写(override)其中的方法。
3.4.2、拉模式
- GetResponse basicGet(String queue, boolean autoAck) throws IOException;
- queue:队列的名称。
- autoAck:设置是否自动确认。建议设成false,即不自动确认。
3.5、关闭连接
- 显式地关闭Channel是个好习惯,但这不是必须的,在Connection关闭的时候,Channel也会自动关闭。
- channel.close();
- conn.close();
# #