【攻克RabbitMQ】常见问题
消息什么情况下会丢失?配合mandatory参数或备份交换器来提⾼程序的健壮性
1. 发送消息的交换器并没有绑定任何队列,消息将会丢失
2. 交换器绑定了某个队列,但是发送消息时的路由键⽆法与现存的队列匹配
预估队列的使⽤情况?
在后期运⾏过程中超过预定的阈值,可以根据实际情况对当前集群进⾏扩容或者将相应的队列迁移到其他集群。
消费消息?
推模式,拉模式
保证消息的可靠性?
RabbitMQ 提供了消息确认机制( message acknowledgement)。 消费者在订阅队列时,可以指定 autoAck 参数,当 autoAck 等于
fal 时, RabbitMQ 会等待消费者显式地回复确认信号后才从内存(或者磁盘)中移去消息(实质上
是先打上删除标记,之后再删除)。当 autoAck 等于 true 时, RabbitMQ 会⾃动把发送出去的 消息置为确认,然后从内存(或者磁盘)中
删除,⽽不管消费者是否真正地消费到了这些消息。
在ack为fal的情况下,消费者获取消息迟迟没有发送消费者确认消息的信号或者消费者断开,怎么办?
当 autoAck 参数置为 fal,对于 RabbitMQ 服务端⽽⾔,队列中的消息分成了两个部分: ⼀部分是等待投递给消费者的消息:⼀部分是⼰
经投递给消费者,但是还没有收到消费者确认信号的消息。 如果 RabbitMQ ⼀直没有收到消费者的确认信号,并且消费此消息的消费者⼰
经 断开连接,则 RabbitMQ 会安排该消息重新进⼊队列,等待投递给下⼀个消费者,当然也有可 能还是原来的那个消费者。RabbitMQ
不会为未确认的消息设置过期时间,它判断此消息是否需要重新投递给消费者的唯⼀依据是消费该消息的消费者连接是否⼰经断开,这么设
计的原因是 RabbitMQ 允许消费者 消费⼀条消息的时间可以很久很久。
在消费者接收到消息后,如果想明确拒绝当前的消息⽽不是确认,那么应该怎么做呢?
RabbitMQ 在 2.0.0 版本开始引⼊了 Basic .Reject 这个命令,消费者客户端可以调⽤与其对 应的 channel.basicReject ⽅法来告诉
RabbitMQ 拒绝这个消息。
//Channel 类中的 basicReject ⽅法定义如下:
//其中 deliveryTag 可以看作消息的编号,它是⼀个 64 位的长整型值,最⼤值是 9223372036854775807。如果 //requeue 参数设置为 true,则 RabbitMQ 会重void basicReject(long deliveryTag, boolean requeue) throws IOException
注意:
Basic.Reject 命令⼀次只能拒绝⼀条消息 ,如果想要批量拒绝消息 ,则可以使⽤ Basic.Nack 这个命令
//消费者客户端可以调⽤ channel.basicNack ⽅法来实现,⽅法定义如下:
//其中 deliveryTag 和 requeue 的含义可以参考 basicReject ⽅法。 multiple 参数
//设置为 fal 则表⽰拒绝编号为 deliveryT坷的这⼀条消息,这时候 basicNack 和 basicReject ⽅法⼀样; //multiple 参数设置为 true 则表⽰拒绝 deliveryTag 编void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException
注意:
将 channel.basicReject 或者 channel.basicNack 中的 requeue 设直为 fal,可以启⽤”死信队列”的功能。死信队列可以通过检测
被拒绝或者未送达的消息来追踪问题
请求RabbitMQ重新发送还未被确认的消息?
//Basic.Recover 具备可重⼊队列的特性
Basic.RecoverOk basicRecover() throws IOException;
Basic.RecoverOk basicRecover(boolean requeue) throws IOException;
channel.basicRecover ⽅法⽤来请求 RabbitMQ 重新发送还未被确认的消息。 如果 requeue 参数设置为 true,则未被确认的消息会被重新加⼊到队列中,这样对于同⼀条消息 来说,可能会被分配给与之前不同的消费者。如果 requeue 参数设置为 fal,那么同⼀条消 息会被分配给与之前相同的消费者。默认情况下,如果不设置 requeue 这个参数,相当于
channel.basicRecover(true) ,即 requeue 默认为 true
交换器⽆法根据⾃⾝的类型和路由键找到⼀个符合条件 的队列
当 mandatory 参数设为 true 时,交换器⽆法根据⾃⾝的类型和路由键找到⼀个符合条件 的队列,那么 RabbitMQ 会调⽤ Basic.Return 命令将消息返回给⽣产者。当 mandatory 参 数设置为 fal 时,出现上述情形,则消息直接被丢弃
⽣产者如何获取到没有被正确路由到合适队列的消息呢?
可以通过调⽤channel.addReturnListener来添加ReturnListener监听器实现。RabbitMQ 会通过 Basic . Return 返回 “mandatory test” 这条消息,之后⽣产者客户端通过 ReturnListener 监昕到了这个事 件,上⾯代码的最后输出应该是”Basic.Retum 返回的结果是: mandatory test”
mandatory和immediate参数的区别
mandatory 参数告诉服务器⾄少将该消息路由到⼀个队列中, 否则将消息返 回给⽣产者。 immediate 参数告诉服务器, 如果该消息关联的队列上有消费者, 则⽴刻投递: 如果所有匹配的队列上都没有消费者,则直接将消息返还给⽣产者, 不⽤将消息存⼊队列⽽等 待消费者了。
杨乃斌未被路由到的消息应该怎么处理?
1. 发送消息的时候设置mandatory参数,添加ReturnListener监听器接收未被路由到的返回消息
2. 采⽤备份交换器AE,可以将未被路由的消息存储在RabbitMQ中,通过声明交换器的时候添加AE参数实现,或者通过策略的⽅式实
现,同时使⽤,前者优先级⾼,会覆盖掉Policy的设置
备份交换器需要注意?
1. 如果设置的备份交换器不存在,客户端和RabbitMQ服务端都不会有异常出现,此时消息会丢失
2. 如果备份交换器没有绑定任何队列,客户端和RabbitMQ服务端都不会有异常出现,此时消息会丢失
3. 如果备份交换器没有任何匹配的队列,客户端和RabbitMQ服务端都不会有异常出现,此时消息会丢失
4. 如果备份交换器和mandatory参数⼀起使⽤,那么mandatory参数⽆效
怎么为消息设置过期时间TTL?
1. 通过队列属性设置,队列中所有消息都有相同的过期时间,声明队列的时候在channel.queueDeclare加⼊TTL参数
2. 对消息本⾝进⾏单独设置,每条消息的TTL可以不同,在channel.basicPublish⽅法参数中设置
3. 同时使⽤以上两种⽅式设置过期时间,以较⼩的为准
4. 消息在队列中的⽣存时间⼀旦超过设置的TTL值,就变成死信,消费者⽆法再收到该消息(不是绝对的)
5. 如果不设置 TTL.则表⽰此消息不会过期;如果将 TTL 设置为 0,则表⽰除⾮此时可以直接将消息投递到消费者,否则该消息会被⽴即
丢弃,这个特性可以部分替代 RabbitMQ 3.0 版本之前的 immediate 参数
对过期消息处理?
1. 设置队列 TTL 属性的⽅法,⼀旦消息过期,就会从队列中抹去,队列中⼰过期的消息肯定在队 列头部, RabbitMQ 只要定期从队头
开始扫描是否有过期的消息即可,
2. 消息本⾝进⾏单独设置,即使消息过期,也不会马上从队列中抹去,因为每条消息是否过期是在即将投递到消费者之前判定的。每条
消息的过期时间不同,如果要删除所有过期消息势必要扫描整个队列,所以不如等到此消息即将 被消费时再判定是否过期, 如果过期再进⾏删除即可。
怎么设置队列的过期时间?
1. 通过 channel . queueDeclare ⽅法中的 x-expires 参数可以控制队列被⾃动删除前处 于未使⽤状态的时间。未使⽤的意思是队列上
没有任何的消费者,队列也没有被重新声明,并且在过期时间段内也未调⽤过 Basic . Get 命令。
2. RabbitMQ 会确保在过期时间到达后将队列删除,但是不保障删除的动作有多及时 。在 RabbitMQ 重启后,持久化的队列的过期时
间会被重新计算。
什么是死信队列?
1. DLX,全称为 Dead-Letter-Exchange,可以称之为死信交换器,也有⼈称之为死信邮箱。当消息在⼀个队列中变成死信 (dead
message) 之后,它能被重新被发送到另⼀个交换器中,这个交换器就是 DLX,绑定 DLX 的队列就称之为死信队列。
查看电脑显卡配置2. DLX 也是⼀个正常的交换器,和⼀般的交换器没有区别,它能在任何的队列上被指定, 实 际上就是设置某个队列的属性。当这个队
列中存在死信时 , RabbitMQ 就会⾃动地将这个消息重新发布到设置的 DLX 上去,进⽽被路由到另⼀个队列,即死信队列。
什么是延迟队列?
延迟队列存储的对象是对应的延迟消息,所谓“延迟消息”是指当消息被发送后,并不想让消费者⽴刻拿到消息,⽽是等待特定时间后,消费者才能拿到这个消息进⾏消费
延迟队列应⽤场景?
1. 订单系统,⽤延迟队列处理超时订单
2. ⽤户希望通过⼿机远程遥控家⾥的智能设备在指定的时间进⾏⼯作。这时候就可以将 ⽤户指令发送到延迟队列,当指令设定的时间到
了再将指令推送到智能设备。
持久化?
1. 交换器的持久化
交换器的持久化是通过在声明交换器时将 durable 参数置为 true 实现的,如果交换器不设置持久化,那么在 RabbitMQ 服务重启之后,相关的交换器元数据会丢失, 不过消息不会丢失,只是不能将消息发送到这个交换器中了。对⼀个长期使⽤的交换器来说,建议将其置为持久化的。
2. 队列的持久化
队列的持久化是通过在声明队列时将 durable 参数置为 true 实现的,如果队列不设置持久化,那么在 RabbitMQ 服务重启之后,相关队列的元数据会丢失,此时数据也会丢失。
3. 消息的持久化
通过将消息的投递模式 (BasicProperties 中的 deliveryMode 属性)设置为 2 即可实现消息的持久化。
社团章程在这段时间内 RabbitMQ 服务节点发⽣了岩机、重启等异常情况,消息保存还没来得及落盘,那么这些消息将RabbitMQ 实战指南会丢失。这个问题怎么解决呢?
可以引⼊ RabbitMQ 的镜像队列机制,相当于配置了副本,如果主节点 Cmaster) 在此特殊时间内挂掉,可以⾃动切换到从节点 Cslave ), 这样有效地保证了⾼可⽤性
当消息的⽣产者将消息发送出去之后,消息到底有没有正确地到达服务器呢?
1. 通过事务机制实现,⽐较消耗性能
1. 客户端发送 Tx.Select. 将信道置为事务模式;
2. Broker 回复 Tx. Select-Ok. 确认⼰将信道置为事务模式:
3. 在发送完消息之后,客户端发送 Tx.Commit 提交事务;
4. Broker 回复 Tx. Commi t-Ok. 确认事务提交。
2. 通过发送⽅确认机制实现
消费端对消息的处理?
心情复杂的图片
1. 过推模式或者拉模式的⽅ 式来获取井消费消息,当消费者处理完业务逻辑需要⼿动确认消息⼰被接收,这RabbitMQ才能把当前消息
从队列中标记清除
2. 如果消费者由于某些原因⽆法处理当前接收到的消息, 可以通过 channel . basicNack 或者 channel . basicReject 来拒绝掉。
消费端存在的问题?
1. 消息分发
同⼀个队列拥有多个消费者,会采⽤轮询的⽅式分发消息给消费者,若其中有的消费者任务重,有的消费者很快处理完消息,导致进程空闲,这样对导致整体应⽤吞吐量下降,为了解决上⾯的问题,⽤到channel.basicQos ⽅法允许限制信道上的消费者所能保持的最⼤未确认消息的数量。Basic.Qos 的使⽤对于拉模式的消费⽅式⽆效.
举例如下:
在订阅消费队列之前,消费端程序调⽤了 channel.basicQos(5) ,之后订 阅了某个队列进⾏消费。 RabbitMQ 会保存⼀个消费者的列表,每发送⼀条消息都会为对应的消费者计数,如果达到了所设定的上限,那么 RabbitMQ 就不会向这个消费者再发送任何消息。
直到消费者确认了某条消息之后 , RabbitMQ将相应的计数减1,之后消费者可以继续接收消息, 直到再次到达计数上限。这种机制可以类⽐于 TCP!IP中的”滑动窗⼝”。
2. 消息顺序性
1. ⽣产者使⽤了事务机制可能会破坏消息顺序性
2. ⽣产者发送消息设置了不同的超时时间,并且设置了死信队列
3. 消息设置了优先级
可以考虑在消息体内添加全局有序标识来实现
3. 弃⽤QueueingConsumer,Spring提供的RabbitMQ采⽤的是DefaultConsume
1. 内存溢出,由于某些原因,队列之中堆积了⽐较多的消息,可能导致消费者客户端内存溢出假死,发⽣恶性循环,使⽤ Basic .
Qos 来解决,⼀定要在调⽤ Basic . Consume 之前调⽤ Basic.Qos
才能⽣效。
2. 会拖累同⼀个connection下的所有信道,使其性能降低
3. 同步递归调⽤QueueingConsumer会产⽣死锁
4. RabbitMQ的⾃动连接恢复机制不⽀持QueueingConsumer这种形式
5. QueueingConsumer不是事件驱动的54精神
双鱼座金牛座消息传输保障?
⼀般消息中间件的消息传输保障分为三个等级
1. At most once: 最多⼀次。消息可能会丢失,但绝不会重复传输。
2. At least once: 最少⼀次。消息绝不会丢失,但可能会重复传输。
3. Exactly once: 恰好⼀次。每条消息肯定会被传输⼀次且仅传输⼀次。
RabbitMQ⽀持其中的“最多⼀次”和“最少⼀次”。
其中”最少⼀次”投递实现需要考虑 以下这个⼏个⽅⾯的内容:
1. 消息⽣产者需要开启事务机制或者 publisher confirm 机制,以确保消息可以可靠地传 输到 RabbitMQ 中。
泰国签证有效期2. 消息⽣产者需要配合使⽤ mandatory 参数或者备份交换器来确保消息能够从交换器 路由到队列中,进⽽能够保存下来
野芒坡⽽不会被丢弃。
3. 消息和队列都需要进⾏持久化处理,以确保 RabbitMQ 服务器在遇到异常情况时不会造成消息丢失。
4. 消费者在消费消息的同时需要将 autoAck 设置为 fal,然后通过⼿动确认的⽅式去 确认⼰经正确消费的消息,以避免
在消费端引起不必要的消息丢失。
“最多⼀次”的⽅式就⽆须考虑以上那些⽅⾯,⽣产者随意发送,消费者随意消费,不过这 样很难确保消息不会丢失。
提⾼数据可靠性途径?