消息
消费端应答
1、问题引入
(1)消费者完成一个任务需要一段时间,如果其中一个消费者处理一个长任务,并只完成部分但突然挂掉
(2)若 RabbitMQ 一旦向消费者传递一条消息,便立即将该消息标记为删除
(3)在这种情况下,突然其中一个消费者挂掉,将丢失正在处理的消息、后续发送给该消费者的消息
2、消息应答机制
(1)保证消息在发送过程中不丢失
(2)消费者在接收到消息,并且处理该消息之后,通知 RabbitMQ 已经处理该消息,可以把该消息删除
3、自动应答
(1)消息发送后,立即被认为已经传送成功
(2)需要权衡高吞吐量、数据传输安全性,因为该模式如果在接收到消息之前,消费者出现 Connection 或 Channel 关闭,则消息就丢失
(3)消费者可以传递过载的消息,不对传递的消息数量进行限制,有可能使得消费者由于接收太多消息,导致消息积压,使得内存耗尽,最终操作系统杀死消费者线程
(4)只在消费者可以高效,并以某种速率处理消息的情况下
4、消息自动重新入队
(1)如果消费者因某些原因(Chanel 关闭、Connection 关闭、TCP 连接丢失)失去连接,导致消息未发送 ACK 确认
(2)RabbitMQ 将获知消息未完全处理,并将对其重新排队
(3)如果此时其他消费者可以处理,它将很快将其重新分发给另一个消费者
(4)即使某个消费者偶尔死亡,可以确保不会丢失任何消息
5、手动应答消息
(1)默认采用自动应答消息
(2)DeliverCallback 接收消息,最终调用 basicAck,手动确认消息
(3)basicConsume 调用启动一个非本地的、非专属的消费者,其中 autoAck = false,表示服务器希望得到明确的确认,即手动应答
6、确认一个或几个收到的消息
void basicAck(long deliveryTag,
boolean multiple)
throws IOException
(1)提供来自 AMQP.Basic.GetOk 或 AMQP.Basic.Deliver 方法的 deliveryTag,该方法包含被确认的接收消息
(2)deliveryTag:从 AMQP.Basic.GetOk 或 AMQP.Basic.Deliver 收到的标签
(3)multiple:true,自动应答,一次性确认所有小于 deliveryTag 的消息,包括 deliveryTag 所提供的信息;false,手动应答,仅确认 deliveryTag 所提供的消息
7、拒绝一个或几个收到的消息
void basicNack(long deliveryTag,
boolean multiple,
boolean requeue)
throws IOException
(1)提供来自 AMQP.Basic.GetOk 或 AMQP.Basic.GetOk 方法的 deliveryTag,包含要拒绝的消息
(2)deliveryTag:从 AMQP.Basic.GetOk 或 AMQP.Basic.Deliver 收到的标签
(3)multiple:true,一次性拒绝所有小于 deliveryTag 的消息,包括 deliveryTag 所提供的信息;false,只拒绝 deliveryTag 所提供的消息
(4)requeue:true,被拒绝的消息被重新接收,添加在队列的末端;false:而不是被丢弃 / 废弃
8、拒绝一个消息
void basicReject(long deliveryTag,
boolean requeue)
throws IOException
(1)提供来自 AMQP.Basic.GetOk 或 AMQP.Basic.Deliver 方法的 deliveryTag,其中包含被拒绝的接收消息
(2)deliveryTag:从 AMQP.Basic.GetOk 或 AMQP.Basic.Deliver 收到的标签
(3)requeue:true,被拒绝的消息被重新接收,添加在队列的末端;false:而不是被丢弃 / 废弃
9、启动一个消费者
String basicConsume(String queue,
boolean autoAck,
String consumerTag,
boolean noLocal,
boolean exclusive,
Map<String,Object> arguments,
DeliverCallback deliverCallback,
CancelCallback cancelCallback,
ConsumerShutdownSignalCallback shutdownSignalCallback)
throws IOException
(1)调用消费者 Consumer.handleConsumeOk(java.lang.String),提供对 basic.deliver、basic.cancel、shutdown 信号回调的访问(这对大多数情况来说是足够的)
(2)queue:队列的名称
(3)autoAck:如果服务器认为消息一旦送达就会被确认,则为 true;如果服务器期望明确的确认,则为 false
(4)consumerTag:客户端生成的消费者标签,用于建立上下文
(5)noLocal:如果服务器不应该向这个消费者传递在这个通道的连接上发布的消息,则为 ture,请注意,RabbitMQ 服务器不支持这个标志
(6)exclusive:如果这是一个排他性消费者,则为 true
(7)arguments:用于消费的一组参数
(8)deliverCallback:当一个消息被传送时的回调
(9)cancelCallback:消费者被取消时的回调
(10)shutdownSignalCallback:当 Chanel / Connection 被关闭时的回调
(11)返回与新消费者相关 consumerTag
持久化
1、默认情况下,RabbitMQ 退出或崩溃时,它将忽视队列、消息
2、解决
(1)队列持久化
(2)消息持久化
3、声明一个队列
AMQP.Queue.DeclareOk queueDeclare(String queue,
boolean durable,
boolean exclusive,
boolean autoDelete,
Map<String,Object> arguments)
throws IOException
(1)queue:队列名称
(2)durable:如果声明的是一个持久队列,则为 true(该队列将在服务器重启后继续存在)
(3)exclusive:如果声明一个排他性的队列,则为 true(仅限于此连接)
(4)autoDelete:如果声明一个自动删除的队列,则为 true(当不再使用时,服务器将删除它)
(5)arguments:该队列的其他属性(构造参数)
(6)返回一个声明确认方法,表示队列被成功声明
4、队列持久化
(1)声明队列时,把 durable 参数设置为 true(持久化)
(2)注意:如果之前声明的队列不是持久化,需要把原先队列先删除,或重新创建一个持久化的队列,否则报错
5、发布一个消息
void basicPublish(String exchange,
String routingKey,
boolean mandatory,
AMQP.BasicProperties props,
byte[] body)
throws IOException
(1)如果资源驱动的警报生效,Channel.basicPublish 调用最终将会阻塞
(2)exchange:要发布消息的交换机
(3)routingKey:路由密钥
(4)mandatory:如果要设置 mandatory 标志,则为 true
(5)props:消息的其他属性,路由头信息等
(6)body:消息的主体
6、com.rabbitmq.client.MessageProperties
(1)Content-type "text/plain", deliveryMode 2 (persistent), priority zero
public static final AMQP.BasicProperties PERSISTENT_TEXT_PLAIN
7、消息实现持久化
(1)生产者推送消息时,props 添加 MessageProperties.PERSISTENT_TEXT_PLAIN
(2)将消息标记为持久化,并不能完全保证不会丢失消息
(3)尽管它告诉 RabbitMQ 将消息保存到磁盘,但依然存在问题:当消息刚准备存储在磁盘时,但还没有存储完,消息还在缓存的一个间隔点,此时并没有真正写入磁盘
(4)持久性保证并不强,但是对于简单任务队列而言足够
8、交换机持久化
(1)声明一个交换机
AMQP.Exchange.DeclareOk exchangeDeclare(String exchange,
String type,
boolean durable,
boolean autoDelete,
boolean internal,
Map<String,Object> arguments)
throws IOException
AMQP.Exchange.DeclareOk exchangeDeclare(String exchange,
BuiltinExchangeType type,
boolean durable,
boolean autoDelete,
boolean internal,
Map<String,Object> arguments)
throws IOException
(2)exchange:交换机的名称
(3)type:交换机的类型
(4)durable:如果声明的是一个持久的交换机,则为 true(交换机将在服务器重新启动后继续存在)
(5)autoDelete:如果服务器在不再使用该交换机时应删除它,则为 true
(6)internal:如果交换机是内部的,即不能被客户端直接发布,则为 true
(7)arguments:交换机的其他属性(构造参数)
(8)返回一个声明确认方法,表示交换机已经成功声明
消费端限流
1、请求特定“服务质量”设置
void basicQos(int prefetchSize,
int prefetchCount,
boolean global)
throws IOException
(1)这些设置对服务器在要求确认前,向消费者提供的数据量进行了限制,因此,它们提供一种由消费者发起的流量控制的手段
(2)注意:prefetchCount 必须在 0 到 65535 之间(AMQP 0-9-1 中的无符号 short)
(3)prefetchSize:服务器将交付的最大内容量(以 8 字节为单位),如果无限制,则为 0
(4)prefetchCount:服务器将传送的最大信息数量,如果没有限制,则为 0
(5)global:如果设置应该应用于整个 Channel,而不是每个消费者,则为 true
2、不公平分发
(1)RabbitMQ 采用轮询分发:当有多个消费者接入时,一个消费者分配一条,直至消息完成消费
(2)若有两个消费者在处理任务,其中一个消费者 1 处理速度快,另外一个消费者 2 处理速度慢,处理速度快的消费者大部分时间处于空闲状态,处理速度慢的消费者一直工作
(3)basicQos 的 prefetchCount 设置为 1,表示目前只能处理一个任务,然后 RabbitMQ 就会把该任务分配给空闲消费者
(4)如果所有的消费者都没有完成任务,队列不停的添加新任务,有可能队列被撑满,这时只能添加新的 Worker,或改变其他存储任务的策略
3、预取值
(1)因为消息为异步发送,所以在任何时候,Channel 上不只有一个消息,消费者的手动确认本质上也是异步的,因此存在一个未确认的消息缓冲区
(2)设置 basic.Qos 的 prefetchCount,该值定义 Channel 上允许的未确认消息的最大数量
(3)限制未确认的消息缓冲区的大小,避免缓冲区中存在无限制的未确认消息
(4)一旦数量达到配置的数量,RabbitMQ 将停止在 Channel 上传递更多消息,除非至少有一个未处理的消息被确认
(5)消息应答、QOS 预取值,会影响用户吞吐量
(6)增加预取值,将提高向消费者传递消息的速度,虽然自动应答传输消息速率为最佳,但在这种情况下,已传递但尚未处理的消息数量也会增加,从而增加消费者的 RAM 消耗
(7)谨慎使用无限预处理的自动确认模式、手动确认模式,若消费者消费大量消息却没有确认,会导致消费者连接节点的内存消耗变大
(8)100 ~ 300 范围内的值,通常可提供最佳的吞吐量,并且不会给消费者带来太大的风险
(9)预取值为 1 是最保守的,这将使吞吐量变得很低,特别是消费者连接延迟严重、连接等待时间较长的环境中
MQ 消费者的幂等性
1、幂等性
(1)一次和多次请求某一个资源,对于资源本身应该具有同样的结果,即其任意多次执行对资源本身所产生的影响,均与一次执行的影响相同
(2)在 MQ 中,当出现消费者对某条消息重复消费的情况时,重复消费的结果与消费一次的结果是相同的,并且多次消费并未对业务系统产生任何负面影响,那么这个消费者的处理过程就是幂等的
2、消息重复消费
(1)消费者在消费 MQ 中的消息时,MQ 已把消息发送给消费者
(2)消费者在给 MQ 返回 ack 时网络中断,导致 MQ 未收到确认信息
(3)该条消息会重新发给其他的消费者,或在网络重连后再次发送给该消费者,但实际上该消费者已成功消费该条消息
3、解决
(1)全局 ID / 时间戳 / UUID
(2)每次消费消息时,使用该 id 先判断该消息是否已消费
4、主流的幂等性操作
(1)唯一 ID + 指纹码机制,利用数据库主键去重
(2)Redis 原子性
5、唯一 ID + 指纹码机制
(1)指纹码:自定义规则或时间戳 + 其他服务 = 唯一信息码
(2)信息码不一定由系统生成,基本是业务规则拼接而来,但一定要保证唯一性
(3)优点:在数据库进行拼接,然后查询判断是否重复,实现简单
(4)缺点:高并发下,单个数据库有写入性能瓶颈
6、Redis 原子性
(1)如:setnx 命令
(2)自带幂等性,从而实现不重复消费
消息追踪
1、Firehose 机制
(1)将生产者投递给 RabbitMQ 的消息、RabbitMQ 投递给消费者的消息,按照指定的格式发送到默认的 exchange 上
(2)默认 exchange 名为 amq.rabbitmq.trace,为 topic 类型
(3)发送到 amq.rabbitmq.trace 上的消息的 routing key 为 publish.exchangename 和 deliver.queuename
(4)exchangename 和 queuename 为实际 exchange 和 queue 名称,分别对应生产者投递到 exchange 的消息,和消费者从 queue 上获取的消息
2、配置
(1)开启
rabbitmqctl trace_on -p [virtual host]
(2)关闭
rabbitmqctl trace_off -p [virtual host]
(3)vhost 默认为 /,节点默认为 rabbit@(hostname)
(4)-n:指定其他节点
(5)-p:指定其他虚拟主机
(6)开启前,清理任何用于消费 Firehose 事件的队列
(7)Firehose 状态不是持久的,它将在服务器启动时默认为关闭
3、Firehose 事件消息格式
(1)追踪消息:通过 Firehose 机制消费、检查的消息
(2)追踪消息的 routing key 将是 publish.{exchangename}(对于进入节点的消息),或 deliver.{queuename}(对于被交付给消费者的消息)
(3)被追踪的消息头,包含关于原始消息的元数据
(4)被追踪的信息体,对应原始信息体
4、GUI 插件
(1)rabbitmq_tracing 和 Firehose 在实现上相同,只多一层 GUI 包装,更容易使用和管理
(2)启用插件
rabbitmq-plugins enable rabbitmq_tracing
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战