rabbitMQ Work Queues

工作队列(又称任务队列)的主要思想是避免立即执行资源密集型任务,而不得不等待它完成。相反我们安排任务在之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。 当有多个工作线程时,这些工作线程将一起处理这些任务。

消息应答

消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成了部分突然它挂掉了,会发生什么情况?

RabbitMQ 一旦向消费者传递了一条消息,便立即将该消息标记为删除。在这种情况下,突然有个消费者挂掉了,我们将丢失正在处理的消息。以及后续发送给该消费这的消息,因为它无法接收到。为了保证消息在发送过程中不丢失, rabbitmq 引入消息应答机制

消息应答就是:消费者在接

收到消息并且处理该消息之后,告诉 rabbitmq 它已经处理了, rabbitmq 可以把该消息删除了。

自动应答

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

手动应答

消息应答的方法
  1. Channel.basicAck(用于肯定确认) RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了
  2. Channel.basicNack(用于否定确认)
  3. Channel.basicReject(用于否定确认)
Multiple 的解释

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

java 复制代码
/**
 * 第二个参数 multiple 是否开启批量应答
 */
channel.basicAck(deliveryTag,true);

消息自动重新入队

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

消息手动应答代码

java 复制代码
package com.example.ysxrabbitmq.three;

import com.example.ysxrabbitmq.utils.RabbitMqUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import lombok.SneakyThrows;

public class Work02 {
    public static final String task_queue_name="ack_queue";

    @SneakyThrows
    public static void main(String[] args) {
        Channel channel = RabbitMqUtil.getChannel();
        System.out.println("C2等待接收消息....");
        DeliverCallback deliverCallback=(consumerTag,message)->{
            try {
                Thread.sleep(30000);
                System.out.println("接收到的消息为:"+new String(message.getBody(),"UTF-8"));
                channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        boolean autoAck=false;
        channel.basicConsume(task_queue_name,autoAck,deliverCallback,consumerTag->{});
    }
}

RabbitMQ 持久化

如何保障当 RabbitMQ 服务停掉以后消息生产者发送过来的消息不丢失 ?

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

队列实现持久化

如果要队列实现持久化 需要在声明队列的时候把 durable 参数设置为持久化

java 复制代码
//durable:是否开启队列持久化,true开启,false不开启
channel.queueDeclare(QUEUE_NAME,durable,exculsive,autoDelete,arguments);

消息实现持久化

要想让消息实现持久化需要在消息生产者修改代码,MessageProperties.PERSISTENT_TEXT_PLAIN 添加这个属性。

java 复制代码
//props:MessageProperties.PERSISTENT_TEXT_PLAIN表示开启消息持久化
channel. basicPublish(exchange,QUEUE_NAME,props,message);

将消息标记为持久化并不能完全保证不会丢失消息。尽管它告诉 RabbitMQ 将消息保存到磁盘,但是这里依然存在当消息刚准备存储在磁盘的时候 但是还没有存储完,消息还在缓存的一个间隔点。此时并没有真正写入磁盘。持久性保证并不强,但是对于我们的简单任务队列而言,这已经绰绰有余了。如果需要更强有力的持久化策略, 参考发布确认。 发布确认

不公平分发

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

为了避免这种情况,我们可以设置参数 channel.basicQos(1);

java 复制代码
//设置在消费者端
//prefetchCount:0表示轮询分发,1表示不公平分发
channel.basicQos(prefetchCount);

预取值

设置channel.basicQos(prefetchCount)的参数prefetchCount可以达到预取值的目的。当prefetchCount>1时为预取值设置的数值。

预取值是指通道中的消息条数,不是消费者消费的条数,当消费者通道中的条数不足预取值的条数时消息会被添加到这个通道中。

相关推荐
Java 码农18 小时前
RabbitMQ集群部署方案及配置指南05
分布式·rabbitmq
Java 码农1 天前
RabbitMQ集群部署方案及配置指南01
linux·服务器·rabbitmq
Overt0p1 天前
抽奖系统(6)
java·spring boot·redis·设计模式·rabbitmq·状态模式
Java 码农1 天前
RabbitMQ集群部署方案及配置指南04
分布式·rabbitmq
独自破碎E1 天前
在RabbitMQ中,怎么确保消息不会丢失?
分布式·rabbitmq
Java 码农1 天前
RabbitMQ集群部署方案及配置指南02
分布式·rabbitmq
bentengjiayou1 天前
Kafka和RabbitMQ相比有什么优势?
分布式·kafka·rabbitmq
零度@1 天前
Java 消息中间件 - RabbitMQ 全解(保姆级 2026)
java·rabbitmq·java-rabbitmq
奋进的芋圆1 天前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq