回顾

RabbitMQ遵循了AMQP协议AMQP协议详解,下面借助RabbitMQ回顾一下AQMP协议的要点:交换器、队列、绑定、路由键 队列通过路由键(routing key,某种确定的规则)绑定到交换器,生产者将消息发布到交换器,交换器根据绑定的路由键将消息路由到特定队列, 然后由订阅这个队列的消费者进行接收。 (routing_key 和 绑定键 binding_key 的最大长度是 255 个字节)

rabbitTemplate 给消息增加过期时间 rabbitmq 消息长度_推送

RabbitMQ知识

1.消费者消息的确认 消费者收到的每一条消息都必须进行确认(自动确认和自行确认)。 消费者在声明队列时,可以指定 autoAck 参数,当 autoAck=false 时,RabbitMQ 会等待消费者显式发回 ack 信号后才从内存(和磁盘,如果是持久化消 息的话)中移去消息。否则,RabbitMQ 会在队列中消息被消费后立即删除它。 采用消息确认机制后,只要令 autoAck=false,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题, 因为 RabbitMQ 会一直持有消息直到消费者显式调用 basicAck 为止。 当 autoAck=false 时,对于 RabbitMQ 服务器端而言,队列中的消息分成了两部分:一部分是等待投递给消费者的消息;一部分是已经投递给消费者, 但是还没有收到消费者 ack 信号的消息。如果服务器端一直没有收到消费者的 ack 信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消 息重新进入队列,等待投递给下一个消费者(也可能还是原来的那个消费者)。 RabbitMQ 不会为未 ack 的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。这么 设计的原因是 RabbitMQ 允许消费者消费一条消息的时间可以很久很久。

所以,当一个消费者设置为手动提交,但是处理完业务后,忘记手动提交时,这个消息是不会发送给下一个消费者的。直到这个消费者关闭了连接,消息才会发送给下一个消费者,此时如果没有做好处理,会出现重复消费消息的情况

2.常见问题如果消息达到无人订阅的队列会怎么办? 消息会一直在队列中等待,RabbitMq 默认队列是无限长度的。

多个消费者订阅到同一队列怎么办? 消息以循环的方式发送给消费者,每个消息只会发送给一个消费者。负载给每个消费者。

消息路由到了不存在的队列怎么办? 消息会丢失

3.虚拟主机

虚拟消息服务器,vhost,本质上就是一个 mini 版的 mq 服务器,有自己的队列、交换器和绑定,最重要的,自己的权限机制。Vhost 提供了逻辑上的 分离,可以将众多客户端进行区分,又可以避免队列和交换器的命名冲突。Vhost 必须在连接时指定,rabbitmq 包含缺省 vhost:“/”,通过缺省用户和 口令 guest 进行访问。

rabbitTemplate 给消息增加过期时间 rabbitmq 消息长度_rabbitmq_02

4.交换器类型 RabbitMQ交换器类型就是AMQP协议交换器类型的实现,我们再重温一下。 共有四种 direct,fanout,topic,headers,其种 headers(几乎和 direct 一样)不实用,可以忽略。

  • Direct 路由键完全匹配,消息被投递到对应的队列, direct 交换器是默认交换器。声明一个队列时,会自动绑定到默认交换器,并且以队列名称作为路由 键
  • Fanout 消息广播到绑定的队列,不管队列绑定了什么路由键,消息经过交换器,每个队列都有一份。
  • Topic 通过使用“”和“#”通配符进行处理,使来自不同源头的消息到达同一个队列,”.”将路由键分为了几个标识符,“”匹配 1 个,“#”匹配一个 或多个。

5.在 RabbitMQ 中实际项目中,生产者和消费者都是客户端,它们都可以完成申明交换器、申明队列和绑定关系,但是在我们的实战过程中,我们在生 产者代码中申明交换器,在消费者代码中申明队列和绑定关系。 另外还要申明的就是,生产者发布消息时不一定非得需要消费者,对于 RabbitMQ 来说,如果是单纯的生产者你只需要生产者客户端、申明交换器、 申明队列、确定绑定关系,数据就能从生产者发送至 RabbitMQ。

6.信道(channel)

在rabbitmq中,无论是生产者还是消费者,都要创建信道和连接。其实只创建连接就可以完成工作了,为什么要引入信道的概念呢?我们看下图:

rabbitTemplate 给消息增加过期时间 rabbitmq 消息长度_java_03

每个线程对应一个信道,但是多个信道对应一个connection(本质是tcp连接)。也就是说,多个信道之间公用一个tcp连接。这就减少了tcp数量,节约了资源,提高了性能。当线程很多时,channel很多,一个tcp连接不够用时,会增加tcp的数量,来均衡这些连接。

7.事务 事务严重影响MQ的性能,在实际应用中,基本不使用事务来保证消息的安全性。所以这里我们不对事务进行记录。

8.生产者发送确认模式 生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消息都将会被指派一个唯一的 ID(从 1 开始),由这 个 id 在生产者和RabbitMQ 之间进行消息的确认。 不可路由的消息,当交换器发现,消息不能路由到任何队列,会进行确认操作,表示收到了消息。如果发送方设置了 mandatory 模式,则会先调用 addReturnListener 监听器。 可路由的消息,要等到消息被投递到所有匹配的队列之后,broker 会发送一个确认给生产者(包含消息的唯一 ID),这就使得生产者知道消息已经正确 到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker 回传给生产者的确认消息中 delivery-tag 域包含了 确认消息的序列号。

rabbitTemplate 给消息增加过期时间 rabbitmq 消息长度_服务器端_04

confirm 模式最大的好处在于他可以是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最 终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果 RabbitMQ 因为自身内部错误导致消息丢失,就会发送一条 nack 消息,生产 者应用程序同样可以在回调方法中处理该 nack 消息决定下一步的处理。

Confirm 的三种实现方式: 方式一:channel.waitForConfirms()普通发送方确认模式;消息到达交换器,就会返回 true。 方式二:channel.waitForConfirmsOrDie()批量确认模式;使用同步方式等所有的消息发送之后才会执行后面代码,只要有一个消息未到达交换器就会 抛出IOException 异常。

方式三:channel.addConfirmListener()异步监听发送方确认模式;

9.备用交换器 在第一次声明交换器时被指定,用来提供一种预先存在的交换器,如果主交换器无法路由消息,那么消息将被路由到这个新的备用交换器。 如果发布消息时同时设置了 mandatory 会发生什么?如果主交换器无法路由消息,RabbitMQ 并不会通知发布者,因为,向备用交换器发送消息,表 示消息已经被路由了。注意,新的备用交换器就是普通的交换器,没有任何特殊的地方。 使用备用交换器,向往常一样,声明 Queue 和备用交换器,把 Queue 绑定到备用交换器上。然后在声明主交换器时,通过交换器的参数, alternate-exchange,,将备用交换器设置给主交换器。 建议备用交换器设置为 faout 类型,Queue 绑定时的路由键设置为“#”

10.消费者获取消息的方式

  • 拉取 Get 属于一种轮询模型,发送一次 get 请求,获得一个消息。如果此时 RabbitMQ 中没有消息,会获得一个表示空的回复。总的来说,这种方式性能比较 差,很明显,每获得一条消息,都要和 RabbitMQ 进行网络通信发出请求。而且对 RabbitMQ 来说,RabbitMQ 无法进行任何优化,因为它永远不知道应用 程序何时会发出请求。具体使用,参见代码 native 模块包 cn.enjoyedu.consumer_balance.GetMessage 中。对我们实现者来说,要在一个循环里,不断去服 务器 get 消息。
  • 推送 Consume 属于一种推送模型。注册一个消费者后,RabbitMQ 会在消息可用时,自动将消息进行推送给消费者。

11.消息的拒绝

  • Reject 和 Nack 消息确认可以让 RabbitMQ 知道消费者已经接受并处理完消息。但是如果消息本身或者消息的处理过程出现问题怎么办?需要一种机制,通知 RabbitMQ,这个消息,我无法处理,请让别的消费者处理。这里就有两种机制,Reject 和 Nack。

Reject 在拒绝消息时,可以使用 requeue 标识,告诉 RabbitMQ 是否需要重新发送给别的消费者。如果是 false 则不重新发送,一般这个消息就会被 RabbitMQ 丢弃。Reject 一次只能拒绝一条消息。如果是 true 则消息发生了重新投递。

Nack 跟 Reject 类似,只是它可以一次性拒绝多个消息。也可以使用 requeue 标识,这是 RabbitMQ 对 AMQP 规范的一个扩展

12.死信交换器 DLX RabbitMQ 作为一个高 级消息中间件,提出了死信交换器的概念,死信,意思就是死了的信息。这种交换器专门处理死了的信息(被拒绝可以重新投递的信息不能算死的)。 死信交换器是 RabbitMQ 对 AMQP 规范的一个扩展,往往用在对问题消息的诊断上(主要针对消费者),还有延时队列的功能。 消息变成死信一般是以下三种情况: 消息被拒绝,并且设置 requeue 参数为 false 消息过期(默认情况下 Rabbit 中的消息不过期,但是可以设置队列的过期时间和消息的过期时间以达到消息过期的效果) 队列达到最大长度(一般当设置了最大队列长度或大小并达到最大值时)

死信交换器仍然只是一个普通的交换器,创建时并没有特别要求和操作。在创建队列的时候,声明该交换器将用作保存被拒绝的消息即可,相关的 参数是 x-dead-letter-exchange。

13.备用交换器和死信交换器的区别 1、备用交换器是主交换器无法路由消息,那么消息将被路由到这个新的备用交换器,而死信交换器则是接收过期或者被拒绝的消息。 2、备用交换器是在声明主交换器时发生联系,而死信交换器则声明队列时发生联系。 备用交换器一般是用于生产者生产消息时,确保消息可以尽量进入 RabbitMQ,而死信交换器主要是用于消费者消费消息的万不一失性的场 景(比如消息过期,队列满了,消息拒绝且不重新投递)

14.消息的持久化 默认情况下,队列和交换器在服务器重启后都会消失,消息当然也是。将队列和交换器的 durable 属性设为 true,缺省为 false,但是消息要持久化还 不够,还需要将消息在发布前,将投递模式设置为 2。消息要持久化,必须要有持久化的队列、交换器和投递模式都为 2。

15.Request-Response 模式

我们前面的学习模式中都是一方负责发送消息而另外一方负责处理。而我们实际中的很多应用相当于一种一应一答的过程,需要双方都能给对方发 送消息。于是请求-应答的这种通信方式也很重要。它也应用的很普遍。

rabbitTemplate 给消息增加过期时间 rabbitmq 消息长度_推送_05

rabbitmq提供了这种应答模式机制。这点需要注意一下

16.RabbitMQ 常用端口

client 端通信端口: 5672 管理端口 : 15672 server 间内部通信端口: 25672