mandatory 以及 immediate 是 channel,basicPublish方法中的两个参数
他们具有 当消息不可达MQ时,将消息返回生产者的功能
如未能被交换机路由的消息(无绑定队列 或 无匹配的绑定)存储在备份交换机绑定的队列中。
1mandatory
为false时,如生产者生产的消息无法路由到队列,直接丢弃
为true时,将消息返回给生产者,而生产者通过调用channel.addReturnListener添加ReturnListener监听器实现。
2 immediate
为true时,匹配的队列都无消费者时将该消息返回给生产者。
新版本的兔子MQ去掉了对immediate的支持。可以通过TTL以及DLX进行实现
3 备胎交换机
在声明交换机时,将其中一个交换机声明为备胎交换机。
如不想实现mandatory 的ReturnListener监听器,可以使用 备胎交换机,将无法路由到队列的消息存储在备胎交换机的队列内,需要的时候进行消费备胎交换机中的消息。
备胎交换机一般采用fanout交换机,忽略bindKey,保证所有rountingKey的消息都能存储进来。
当备胎交换机与mandatory同时使用时mandatory参数无效。
TTL: 过期时间
过期时间分为两种:
1 队列中消息的统一过期时间
2 消息的过期时间
如果两者同时存在,则小的值有效。
过期后消息会进入死信队列中。
清除过期消息:
1 对于队列过期消息,即时清除过期消息。因为队列为先进先出队列,即将过期的消息在队列头部。只从头向后遍历一部分头部队列即可,无需全队列遍历。
2 对于消息过期,采用懒过期策略。在消费消息的时候,检查消息是否过期,如过期将其转移到死信队列,而不会发送给消费者。采用懒过期策略对MQ的CPU友好,避免全队列扫描。类似于Redis的过期策略
队列过期TTL:达到TTL后,将队列删除,对于持久化队列,TTL重新计算,不会删除。不能保证实时性。
死信队列:
1 消息被拒绝,且设置requeue参数为false
2 消息过期
3 队列达到最大长度
死信队列与TTL设置为0 进行配合弥补 immediate 的功能
死信队列可用于:
1 恢复异常场景:消费者消费不了,消息被拒绝
2 延迟队列
延迟队列:
消费者只能在t时间后消费到此消息
队列消息ttl设置时间为t。过期后,消息会进入死信队列。消费者接受死信队列的消息推送即可。
优先级队列:
优先级高的消息具有优先被消费到权利。通过设置x-max-priority参数进行实现。
在发送消息的时候对消息的优先级进行设置。
消息的持久化:
防止消息在服务器异常的情况下数据丢失。
分为
1 交换机的持久化
2 队列的持久化
3 消息的持久化
交换机的持久化:声明交换机时,将durable参数设置为true实现
当服务器重启后,如果交换机未能持久化,将会导致消费者不能给交换机发送消息。长期使用的交换机应该进行持久化
队列持久化:声明队列时,将durable参数设置为true实现
服务器重启后,如果队列未进行持久化,会导致队列中的数据丢失,队列也将不会存在
消息的持久化:投递消息时,将投递模式deliveryMode设置为2实现持久化
将消息进行持久化后会很大程度上影响MQ的吞吐量。需要在可靠性与吞吐量上进行权衡
如何保证消息不会丢失:
1 消费者: 当消息消费成功后进行手动ack,确保消息不会在消费者方丢失
2 设置为需持久化的消息存入兔子MQ后,不可能立即持久化到硬盘,(持久化应该是基于 时间及数目 批量写磁盘的 来保证服务器的吞吐量)如此时兔子MQ出现宕机,则消息会在服务端丢失。
解决问题2:
方法一:此时可以使用镜像队列解决此问题。从服务器保存主服务器队列的副本,主服务器宕机,从服务数据起作用
方法二:实现生产者确认机制
生产者确认机制:
1 事物机制,如发送失败回滚,吞吐量极其低,不建议使用
a设置 b提交 c确认/回滚
生产者线程会阻塞直到返回结果
2 发送方确认机制
生产者将信道设置为confirm模式
1 持久化消息,消息在写入磁盘之后 将消息id以及ack发送给生产者
2 非持久化消息,消息在写入队列后发送ack给生产者
同步阻塞:等到MQ返回结果后才执行之后的逻辑,性能差
优化:
1 批量确认机制:发送一批消息后,调用获取处理结果的方法
缺点:在出错(返回nack或超时)率较高时,性能会反而更差,批量再重发
2 异步回调机制:使用回调机制,将返回的结果(发送成功or失败)交给回调函数进行处理。性能大幅度提高
优点:性能极好
缺点:实现复杂度高
调用channel的 addConfirmListener方法 添加 ConfirmListener这个回调接口
ConfirmListener接口由两个方法:
handleAck: 处理消息确认成功逻辑
handleNack: 处理消息确认失败逻辑
消费者需要维护一个unconfirm集合(SortedSet数据结构)。
调用一次handleAck 删除对应的一条记录
消费者端要点:
手动ack取代自动ack
如果程序无法处理消息,将消息reject
消息消费负载均衡采用round-robin算法实现。当消息堆积时,增加消费者数目即可。
问题:有些消息消费的快,有些消费的慢。
消费者A: 消费一条 10ms
消费者B: 消费一条 10min
采用round-Robin将会给消费者b造成很大压力。
此时我们需要设置消费者所持有的最大的消息数目来解决此问题。限制消费者同一时刻未确认的消息的数目。
Rabbit会保存消费者列表,并记录发送给消费者但未ack的消息的数目。
建议只对单个消费者设置其可持有的未消费的消息上线。不建议将其与所有消费者之和消息个数上线一期使用,会降低MQ吞吐量。
兔子MQ 无法保证顺序性
1 生产者消息发送失败补偿发送导致消息错序
2 优先队列打破顺序性
3 多消费者消费同一队列打破顺序性
使用mq尽量避免有序 如需要有序必然带来吞吐量大幅度降低
消息堆积问题:
服务端消息堆积:采用增加消费者进行解决
消费端消息堆积:消费者用LinkedBlockingQueue保存未来得及消费的消息,当消息过多 导致out of memory
所以必须限制服务端发送给消费者还未ack的消息的数目。
消息传输保障:
消息类型可分为以下三中类型:
1 最多消费一次 :最简单,对三端无限制
2 最少消费一次 :
生产者进行异步确认,保证消息传入到MQ中
MQ 使用mandatory参数或者备胎交换机,确保路由不到队列时消息不丢失
MQ 对消息和队列进行持久化,保证重启后消息不丢失
消费端使用手动ack进行确认
3 仅可消费一次
除2外 业务层还要增加全局唯一id,保证消息幂等