RabbitMQ-Work Queues

  • 一旦遇到消息处理比较耗时的情况,这时候生产消息速度会远远大于消息的消费速度;随着时间流逝队列中的消息就会堆积的越来越多
  • Work queues是一种消息分发模型:让多个消费者绑定到一个队列,共同消费队列中的消息
  • 队列中的消息一旦被消费就会从队列中删除,因此任务是不会被重复执行的
  • 默认情况下,RabbitMQ按顺序将每个消息发送给下一个使用者。平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环(轮询分发)

消息应答

为了保证消息在发送过程中不丢失,rabbitmq引入消息应答机制,消息应答就是:消费者在接收到消息并且处理完成该消息之后,告诉rabbitmq该消息已经处理了,可以将其删除

自动应答

  • 消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权衡,因为这种模式如果消息在接收到之前,消费者那边出现连接或者channel关闭,那么消息就丢失了
  • 这种模式消费者那边可以传递过载的消息,没有对传递的消息数量进行限制,当然这样有可能使得消费者这边由于接收太多还来不及处理的消息,导致这些消息的积压,最终使得内存耗尽,然后这些消费者线程被操作系统杀死,所以这种模式仅适用在消费者可以高效并以某种速率能够处理这些消息的情况下使用

手动应答

  • Channel.basicAck(用于肯定确认):已知道该消息并且成功的处理消息,可以将其丢弃了
  • Channel.basicNack(用于否定确认)
  • Channel.basicReject(用于否定确认):与Channel.basicNack相比少一个参数(multiple);不处理该消息了直接拒绝,可以将其丢弃了

手动应答的好处:可以批量应答并且减少网络拥堵

  • true:代表批量应答channel上未应答的消息,比如说channel上有传送tag的消息5,6,7,8,当前tag是8那么此时5-8的这些还未应答的消息都会被确认收到消息应答
  • false:同上面相比只会应答tag-8的消息,5,6,7这三个消息依然不会被确认收到消息应答

手动应答示例

消息自动重新入队

  • 如果消费者由于某些原因失去连接(其通道已关闭、连接已关闭或TCP连接丢失),导致消息未发送ACK确认 ,RabbitMQ将了解到消息未完全处理,并将对其重新排队
  • 如果此时其他消费者可以处理,它将很快将其重新分发给另一个消费者。这样,即使某个消费者偶尔死亡,也可以确保不会丢失任何消息

持久化

默认情况下RabbitMQ退出或由于某种原因崩溃时,它忽视队列和消息,除非告知它不要这样做。确保消息不会丢失需要做两件事:我们需要将队列和消息 都标记为持久化

队列持久化

  • rabbitmq如果重启,该队列就会被删除掉,如果要队列实现持久化需要在声明队列的时候把durable参数设置为持久化。这样重启之后,队列才不会被删除
  • 如果之前声明的队列不是持久化的,需要把原先队列先删除,或者重新创建一个持久化的队列,不然就会出现错误
  • Web界面变化 ,持久化标识D

消息持久化

  • 生产者发送消息时,MessageProperties.PERSISTENT_TEXT_PLAIN添加这个属性
  • 存在当消息刚准备存储在磁盘的时候但是还没有存储完,消息还在缓存的一个间隔点。此时并没有真正写入磁盘。持久性保证并不强,只能适用于简单任务队列
java 复制代码
//从控制台输入发送消息
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
	String message = scanner.next();
	//添加参数使得消息持久化
	channel.basicPublish("", QUEUE_NAME, 
			MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
	System.out.println("消息发送完成..."+message);
}

不公平分发

RabbitMQ 分发消息采用的轮询分发,但是在某种场景下这种策略并不是很好,比方说有两个消费者在处理任务,其中有个消费者 1 处理任务的速度非常快,而另外一个消费者 2 处理速度很慢,这个时候我们还是采用轮询分发的话就会使得处理速度快的这个消费者很大一部分时间处于空闲状态,而处理慢的那个消费者一直在干活,这种分配方式在这种情况下其实就不太好,但是 RabbitMQ 并不知道这种情况它依然很公平的进行分发。

  • 设置channel.basicQos(1) 就代表不公平分发,所有队列都需要设置
  • 意思就是如果这个任务还没有处理完,或者我还没有应答你,你先别分配给我,我目前只能处理一个任务,然后rabbitmq 就会把该任务分配给没有那么忙的那个空闲消费者
  • 当然如果所有的消费者都没有完成手上任务,队列还在不停的添加新任务,队列有可能就会遇到队列被撑满的情况,这个时候就只能添加新的worker 或者改变其他存储任务的策略
java 复制代码
//设置不公平分发
channel.basicQos(1);
//采用手动应答
boolean autoAck=false;
channel.basicConsume(ACK_QUEUE_NAME,autoAck,deliverCallback,(consumerTag)->{
	System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
});

预取值

  • 消息的发送是异步发送的,所以在任何时候,channel上肯定不止一个消息,另外来自消费者的手动确认本质上也是异步的。因此这里就存在一个未确认的消息缓冲区 ,通过使用basic.qos()限制此缓冲区的大小以避免缓冲区里面无限制的接收未确认消息问题
  • 预取值定义通道上允许未确认消息的最大数量。一旦数量达到配置的数量,RabbitMQ将停止在通道上传递更多消息,除非至少有一个未处理的消息被确认
  • 消息应答和QoS预取值对用户吞吐量有重大影响。通常,增加预取值将提高向消费者传递消息的速度。虽然自动应答传输消息速率是最佳的,但是在这种情况下已传递但尚未处理消息的数量也会增加,从而增加了消费者的RAM消耗(随机存取存储器),应该小心使用具有无限预处理的自动确认模式或手动确认模式
  • 消费者消费了大量的消息如果没有确认的话,会导致消费者连接节点的内存消耗变大,所以找到合适的预取值是一个反复试验的过程,不同的负载该值取值也不同,100到300范围内的值通常可提供最佳的吞吐量,并且不会给消费者带来太大的风险
  • 预取值为1是最保守的。当然这将使吞吐量变得很低,特别是消费者连接延迟很严重的情况下,或者是在消费者连接等待时间较长的环境中。对于大多数应用来说,稍微高一点的值将是最佳的
java 复制代码
//设置预取值
channel.basicQos(5);
//采用手动应答
boolean autoAck=false;
channel.basicConsume(ACK_QUEUE_NAME,autoAck,deliverCallback,(consumerTag)->{
	System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
});
相关推荐
上海锟联科技2 小时前
DAS一体化光模块
分布式·分布式光纤传感·ofdr·光频域反射·das
Java 码农3 小时前
RabbitMQ集群部署方案及配置指南01
linux·服务器·rabbitmq
Overt0p3 小时前
抽奖系统(6)
java·spring boot·redis·设计模式·rabbitmq·状态模式
Java 码农3 小时前
RabbitMQ集群部署方案及配置指南04
分布式·rabbitmq
独自破碎E3 小时前
在RabbitMQ中,怎么确保消息不会丢失?
分布式·rabbitmq
Java 码农3 小时前
RabbitMQ集群部署方案及配置指南02
分布式·rabbitmq
虫小宝3 小时前
京东返利app分布式追踪系统:基于SkyWalking的全链路问题定位
分布式·skywalking
星图易码4 小时前
星图云开发者平台功能详解 | IoT物联网平台:工业设备全链路智能管控中枢
分布式·物联网·低代码·低代码平台
王五周八4 小时前
基于 Redis+Redisson 实现分布式高可用编码生成器
数据库·redis·分布式
成为你的宁宁4 小时前
【Zabbix 分布式监控实战指南(附图文教程):Server/Proxy/Agent 三者关系解析 + Proxy 部署、Agent 接入及取数路径验证】
分布式·zabbix