RabbitMQ的使用与相关问题浅谈

本文详细介绍了RabbitMQ的五种工作模式:简单模式、工作队列、广播模式、路由模式和主题模式,并通过实例代码展示了其实现。同时,讨论了消息队列的面试相关问题,如消息防丢失、幂等性和高可用性,并介绍了死信队列的设置和使用,以处理消息过期和积压的情况。此外,还提供了相关的Maven依赖和实用工具类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

rabbitmq相关介绍

最近写的东西比较散,也是工作需要啥就写啥,工作中相关技术用完之后就没再更新了.这里就从自己感兴趣的地方开始写的,会更新到贴近企业运用吧

RabbitMQ: 功能介绍应该不需要多说,是消息中间件的一种,通常会与kafka,RocketMQ做对比.说白了就是服务间的数据传输.消息中间件的主要优点就是流量削峰,服务解耦,异步调用,缺点是增加的系统的复杂性

我会从他的几种工作模式开始讲解.到死信队列,到消息防丢失等等,最后会专门出一篇帖子说他和SpringBoot整合后的使用

首先先得了解一下他的相关名词
Broker: mq的服务器用于接收和分发消息,包含了交换机Exchange和队列Queue
Exchange: 交换机,按照一定的规则讲消息发送到绑定交换机的某个队列
Queue:存储消息的队列
Producer:消息的生产者
Consumer:消息的消费者
Connection:生产者和消费者和对Broker的连接
channel:信道,Connection里包含了多个信道,减少了connection的创建

在这里插入图片描述

rabbitmq常用模式和使用

基本概念了解完之后,在了解一下他的常用工作模式
在这里插入图片描述

常用的也就前五种,最后一个暂不研究

相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>rabbitMQ</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.8.0</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>


    </dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>11</source>
                <target>11</target>
            </configuration>
        </plugin>
    </plugins>
</build>

</project>

第一种:Hello World 简单模式

在这里插入图片描述
生产者生产消息到队列上,注意哦,没有使用交换机,但是底层会默认一个交换机,由一个消费者接收

开始编写生产者代码

package com.ax.product;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class HelloProduct {
    public static void main(String[] args) throws IOException, TimeoutException {
        //队列名
        final   String  QUERY_NAME="hello";
        //创建连接工厂
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("1.116.130.252");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        //创建连接
        Connection connection=connectionFactory.newConnection();
        //获取信道
        Channel channel = connection.createChannel();
        //生成队列
        // 队列名称 是否持久化 是否允许多个消费者消费  是否自动删除  参数
        channel.queueDeclare(QUERY_NAME,false,false,false,null);

        String message="hello world";
        //交换机  队列  消息持久化保存在磁盘上 消息体
        channel.basicPublish("",QUERY_NAME,null,message.getBytes());

        System.out.println("消息发送完毕");
    }
}

消费者代码

package com.ax.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class HelloConsumer {
    public static void main(String[] args) throws IOException, TimeoutException {
         String  QUEUE_NAME="hello";
         //创建工厂
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setPassword("guest");
        connectionFactory.setUsername("guest");
        connectionFactory.setHost("1.116.130.252");
        //获取连接
        Connection connection = connectionFactory.newConnection();
        //获取信道
        Channel channel = connection.createChannel();

        //接受消息的回调
        DeliverCallback deliverCallback=(consumerTag,message)->{
            System.out.println(new String(message.getBody()));
        };
        //取消消息时的回调
        CancelCallback cancelCallback=consumerTag->{
            System.out.println("消息消费被中断");
        };
        //消费队列  消费成功之后是否自动应答  消费者没有成功消费回调 消费者取消消费回调
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }

小结一下:小结的都是重点
生产者步骤说明
1:创建连接工厂
2:获取连接
3:获取信道
4:由信道声明队列,绑定队列名,在企业中,会有相关的枚举类来声明队列名和交换机名

        channel.queueDeclare(QUERY_NAME,false,false,false,null);

简单说明一下声明队列需要的几个参数

参数名解释
String queue队列名称
boolean durable是否持久化
boolean exclusive是否共享
boolean autoDelete是否自动删除消息
Map<String, Object> arguments其他参数,后面说明使用

5:指定消息内容
6;发送消息

  channel.basicPublish("",QUERY_NAME,null,message.getBytes());

发送消息参数有

参数解释
String exchange交换机名
String routingKey此处是队列名
BasicProperties props消息做什么处理
byte[] body消息体.字节数组

消费者的步骤说明
1创建工厂
2获取连接
3.获取信道
4.消息消费

  channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
参数解释
String queue队列名
boolean autoAck是否自动应答
DeliverCallback deliverCallback接受成功的消息回调
CancelCallback cancelCallback取消回调

小结结束

这里给一个工具类,方便后面使用

package com.ax.utils;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class MQUtils {
    public static Channel MQUtil() throws IOException, TimeoutException {
        //创建连接工厂
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("1.116.130.252");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        //创建连接
        Connection connection=connectionFactory.newConnection();
        //获取信道
        return connection.createChannel();
    }
}

第二种:Work queues 工作队列模式

在这里插入图片描述
一个生产者发送消息到一个队列上,由多个消费者消费
生产者代码

package com.ax.product;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.Channel;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class WorkProduct {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        String QUEUE_NAME="hello work";

        Channel channel = MQUtils.MQUtil();

        //声明队列 队列名称 是否持久化 是否允许多个消费者消费  是否自动删除  参数
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //发布消息
        for (int i = 0; i < 10; i++) {
            String message=i+"号种子选手";
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
        }
        System.out.println("发送消息完成");

    }
}

消费者代码模拟多个消费者

package com.ax.consumer;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class WorkConsumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        String QUEUE_NAME="hello work";

        Channel channel = MQUtils.MQUtil();

        DeliverCallback deliverCallback=(consumerTag, message)->{

            System.out.println(new String(message.getBody()));
        };
        //取消消息时的回调
        CancelCallback cancelCallback= consumerTag->{
            System.out.println(consumerTag+"消息消费被中断");
        };
        System.out.println("1号等待消息");
        //消费队列  消费成功之后是否自动应答  消息消费回调 消费者取消消费回调
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

package com.ax.consumer;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.concurrent.TimeoutException;
//对应着WorkConsumer1和2
public class WorkConsumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        String QUEUE_NAME="hello work";

        Channel channel = MQUtils.MQUtil();

        DeliverCallback deliverCallback=(consumerTag, message)->{
            System.out.println(new String(message.getBody()));
        };
        //取消消息时的回调
        CancelCallback cancelCallback= consumerTag->{
            System.out.println(consumerTag+"消息消费被中断");
        };
        System.out.println("2号等待消息");
        //消费队列  消费成功之后是否自动应答  消息消费回调 消费者取消消费回调
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

首先启动两个消费者,会报错,因为队列还没被创建.先启动生产者,等启动完一个消费者之后他会消费掉所所有消息之后.在重启两个消费者,再重启生产者
在这里插入图片描述
小结:工作队列模式是采用轮循的方式来消费的,那么如何能者多劳呢,后面基本工作模式讲完再做解答

第三种: Publish/Subscribe 广播模式/也有叫扇出模式

在这里插入图片描述
他是由一个生产者发送消息到绑定交换机的队列上.每个队列收到的消息都是一样的.每个消费者消费的消息也自然一样
生产者代码

package com.ax.product;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.Channel;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ExchangeFanoutProduct {
    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = MQUtils.MQUtil();
        //声明交换机  交换机名  交换机类型
        channel.exchangeDeclare("fanout","fanout");
        for (int i = 0; i < 10; i++) {
            String message="消息"+i;
            channel.basicPublish("fanout","",null,message.getBytes());
        }
    }
}

说明,生产者不在直接绑定队列,而是绑定交换机

 channel.exchangeDeclare("fanout","fanout");
参数解释
String exchange交换机名
String type交换机类型,有枚举类BuiltinExchangeType
   channel.basicPublish("fanout","",null,message.getBytes());
参数解释
String exchange交换机名
String routingKey路由key.此处不指定
BasicProperties props消息如何处理
byte[] body消息内容

消费者代码多个消费者

package com.ax.consumer;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ExchangeFanoutConsumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = MQUtils.MQUtil();
        //声明交换机  交换机名  交换机类型
        channel.exchangeDeclare("fanout","fanout");
        //声明一个临时队列  队列名随机 当消费者断开连接,队列自动删除
        String queue = channel.queueDeclare().getQueue();
        //绑定交换机和队列
        channel.queueBind(queue,"fanout","");
        //接受消息
        DeliverCallback deliverCallback=(consumerTag, message)->{
            System.out.println("接受消息"+new String(message.getBody()));
        };
        channel.basicConsume(queue,true,deliverCallback,consumerTag -> {});

    }
}

package com.ax.consumer;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ExchangeFanoutConsumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = MQUtils.MQUtil();
        //声明交换机  交换机名  交换机类型
        channel.exchangeDeclare("fanout","fanout");
        //声明一个临时队列  队列名随机 当消费者断开连接,队列自动删除
        String queue = channel.queueDeclare().getQueue();
        //绑定交换机和队列
        channel.queueBind(queue,"fanout","");
        //接受消息
        DeliverCallback deliverCallback=(consumerTag, message)->{
            System.out.println("接受消息"+new String(message.getBody()));
        };
        channel.basicConsume(queue,true,deliverCallback,consumerTag -> {});

    }
}

说明:消费者的从队列上消费消息,但是队列需要绑定交换机

channel.queueBind(queue,"fanout","");
参数解释
String queue队列名
String exchange交换机名
String routingKey路由key

在这里插入图片描述

第四种:Routing 路由模式

生产者代码

package com.ax.product;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class RoutingExchangeProduct {
    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = MQUtils.MQUtil();
        channel.exchangeDeclare("routing_exchange", BuiltinExchangeType.DIRECT);
        for (int i = 0; i < 10; i++) {
            String message="消息"+i;
            channel.basicPublish("routing_exchange","123",null,message.getBytes());
        }
    }
}

参数解释
String exchange交换机名
String type交换机类型,有枚举类BuiltinExchangeType
    channel.basicPublish("routing_exchange","123",null,message.getBytes());
参数解释
String exchange交换机名
String routingKey路由key,转发到相符合的队列上
BasicProperties props消息如何处理
byte[] body消息内容

消费者代码多个消费者

package com.ax.consumer;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class RoutingExchangeConsumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {

        String EXCHANGE_NAME="routing_exchange";
        Channel channel = MQUtils.MQUtil();

        //声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        //声明队列
        channel.queueDeclare("duilie1",false,false,false,null);
        //绑定
        channel.queueBind("duilie1",EXCHANGE_NAME,"123");

        DeliverCallback deliverCallback=(consumerTag, message)->{
            System.out.println("消息1号线"+new String(message.getBody()));
        };
        //取消消息时的回调
        CancelCallback cancelCallback= consumerTag->{
            System.out.println(consumerTag+"消息消费被中断");
        };
        channel.basicConsume("duilie1",true,deliverCallback,cancelCallback);
    }
}

package com.ax.consumer;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class RoutingExchangeConsumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {

        String EXCHANGE_NAME="routing_exchange";
        Channel channel = MQUtils.MQUtil();

        //声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        //声明队列
        channel.queueDeclare("duilie",false,false,false,null);
        //绑定
        channel.queueBind("duilie",EXCHANGE_NAME,"345");

        DeliverCallback deliverCallback=(consumerTag, message)->{
            System.out.println("消息1号线"+new String(message.getBody()));
        };
        //取消消息时的回调
        CancelCallback cancelCallback= consumerTag->{
            System.out.println(consumerTag+"消息消费被中断");
        };
        channel.basicConsume("duilie",true,deliverCallback,cancelCallback);
    }
}

消费者需要将交换机和队列做个绑定,并指定路由key.相同的路由key的队列才能接受到消息

第五种 Topics 主题模式

主题模式在于routingKey的区别.不同的routingKey也能接受到相同的消息
生产者代码

package com.ax.product;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;

import java.io.IOException;
import java.util.concurrent.TimeoutException;


public class TopicExchangeProduct {
    public static void main(String[] args) throws IOException, TimeoutException {
        String TOPIC_EXCHANGE = "topic";
        Channel channel = MQUtils.MQUtil();

        channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);

        for (int i = 0; i < 10; i++) {
            String message="消息"+i;
            channel.basicPublish(TOPIC_EXCHANGE, "topic.exchange.hah", null, message.getBytes());
        }
    }
}

消费者代码也是多个消费者

package com.ax.consumer;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class TopicExchangeConsumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        String TOPIC_EXCHANGE = "topic";
        Channel channel = MQUtils.MQUtil();

        channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);

        channel.queueDeclare("d1",true,false,false,null);

        channel.queueBind("d1",TOPIC_EXCHANGE,"*.exchange.*");

        DeliverCallback deliverCallback=(consumerTag, message)->{
            System.out.println("消息"+new String(message.getBody()));
        };
        //取消消息时的回调
        CancelCallback cancelCallback= consumerTag->{
            System.out.println(consumerTag+"消息消费被中断");
        };

        channel.basicConsume("d1",true,deliverCallback,cancelCallback);
    }
}

package com.ax.consumer;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class TopicExchangeConsumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        String TOPIC_EXCHANGE = "topic";
        Channel channel = MQUtils.MQUtil();

        channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);

        channel.queueDeclare("d2",true,false,false,null);

        channel.queueBind("d2",TOPIC_EXCHANGE,"topic.#");

        DeliverCallback deliverCallback=(consumerTag, message)->{
            System.out.println("消息"+new String(message.getBody()));
        };
        //取消消息时的回调
        CancelCallback cancelCallback= consumerTag->{
            System.out.println(consumerTag+"消息消费被中断");
        };

        channel.basicConsume("d2",true,deliverCallback,cancelCallback);
    }
}

两个消费者的区别是routingKey的区别

*主题交换机.routingKey的编写规则
多个单词.号隔开,不能超过255字节
号代表一个
#代表零个或者多个单词

五种模式到此搞定

面试相关

消息队列,如何能者多劳

生产者代码

package com.ax.product;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.Channel;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

//消息手动应答生产者  对应 ManualAnswerConsumer1和2
public class ManualAnswerProduct {

    public static void main(String[] args) throws IOException, TimeoutException {
        String ACK_QUEUE_NAME="ack_queue";

        Channel channel = MQUtils.MQUtil();
        //声明队列 队列名称 是否队列持久化 是否允许多个消费者消费  是否自动删除  参数
        channel.queueDeclare(ACK_QUEUE_NAME,true,false,false,null);
        //发布消息
        for (int i = 0; i < 10; i++) {
            String message=i+"线路";
            channel.basicPublish("",ACK_QUEUE_NAME,null,message.getBytes());
        }
        System.out.println("发送消息完成");

    }
}

消费者代码

package com.ax.consumer;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ManualAnswerConsumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        final String ACK_QUEUE_NAME="ack_queue";

        Channel channel = MQUtils.MQUtil();

        DeliverCallback deliverCallback=(consumerTag, message)->{
            try {
                //沉睡30秒
                Thread.sleep(30000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消息1号线"+new String(message.getBody()));
            //参数: 消息标记 是否批量应答  该代码就是手动应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);

        };
        //取消消息时的回调
        CancelCallback cancelCallback= consumerTag->{
            System.out.println(consumerTag+"消息消费被中断");
        };
        System.out.println("1号等待消息");
        //不公平分发,不写默认轮询
        channel.basicQos(1);
        //消费队列  消费成功之后是否自动应答  消息消费回调 消费者取消消费回调
        channel.basicConsume(ACK_QUEUE_NAME,false,deliverCallback,cancelCallback);
    }
}

package com.ax.consumer;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ManualAnswerConsumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        final String ACK_QUEUE_NAME="ack_queue";

        Channel channel = MQUtils.MQUtil();

        DeliverCallback deliverCallback=(consumerTag, message)->{
            try {
                //沉睡1秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消息2号线"+new String(message.getBody()));
            //手动应答
            //参数: 消息标记 是否批量应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);

        };
        //取消消息时的回调
        CancelCallback cancelCallback= consumerTag->{
            System.out.println(consumerTag+"消息消费被中断");
        };
        //不公平分发,不写默认轮询
        channel.basicQos(1);
        System.out.println("2号等待消息");
        //消费队列  消费成功之后是否自动应答  消息消费回调 消费者取消消费回调
        channel.basicConsume(ACK_QUEUE_NAME,false,deliverCallback,cancelCallback);
    }
}

说明:两个消费者沉睡的时间不一样,用来模拟性能.一个消费的快.一个消费的慢.

 channel.basicQos(1);

上面是能者多劳的核心代码.basicQos的值是0,表示轮循分发,是默认的,改成1.则是能者多劳模式

消息防丢失

首先消息防止丢失.可以开启队列持久化和消息持久化
生产者端在声明队列和发送消息的时候可以设置

	//声明队列 第二个参数 durable改为true
  channel.queueDeclare(ACK_QUEUE_NAME,true,false,false,null);
//发送消息时 第三个参数BasicProperties props传MessageProperties.PERSISTENT_TEXT_PLAIN
  channel.basicPublish("",ACK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());

说明:队列消息是依托队列来发送的,为了避免rabbitmq宕机恢复后,队列消息,故而开启队列持久化
消息持久化则是将发送到队列的消息保存在硬盘中,.反正宕机后消息消失于内存之中

假设:当我生产者发送消息给broker时,发生了网络抖动,或者mq服务器宕机.那么我消息来不及保存,不还是丢失了吗.
所以这时可以在生产者开始一个发布确认模式.当我mq服务器收到消息,并且保存下来后,给生产者说我保存了.那么这条消息就过去了.

生产者端代码

package com.ax.product;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.Channel;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class WorkProduct {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        String QUEUE_NAME="hello work";

        Channel channel = MQUtils.MQUtil();
        //开启发布确认
        channel.confirmSelect();
        //声明队列 队列名称 是否持久化 是否允许多个消费者消费  是否自动删除  参数
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //发布消息
        for (int i = 0; i < 10; i++) {
            String message=i+"号种子选手";
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
            //等待确认 返回值是boolean
            channel.waitForConfirms();

        }
        System.out.println("发送消息完成");

    }
}

        //开启发布确认
        channel.confirmSelect();
            //等待确认 返回值是boolean
            channel.waitForConfirms();

confirm确认机制有三种模式.
1 同步单条确认,上述代码就是,效率最低,安全性最高
2 同步批量确认,效率比较高,某个消息出现问题无法确认
3.异步批量确认 效率最高他会在信道中发送map集合.key是消息的序号.value是消息(message),比较复杂
需要消息监听器,监听成功和失败的消息

问题接着来了,如果我消费者在消费消息时,网络抖动或者服务器宕机.那么我消息还是丢失了啊.所以需要在消费者端设置手动应答方式,

channel.basicConsume(ACK_QUEUE_NAME,false,deliverCallback,cancelCallback);

上面代码,第二个参数autoAck如果是true,那么这是自动应答.自动应答中,消费者接收到消息就会给mq发出响应,我已经接收到消息了.此时mq可以删除了.但是此时消息还没被处理啊,所以手动应答是有风险存在的.这里可以改成false,手动应答

手动应答的三种方式
1 Channel.basicAck(肯定确认) mq知道消息成功处理.将其丢失 可批量应答
2 Channel.basicNack(否定确认) mq知道消息未成功处理.将其丢失 可批量应答
3 Channel.basicReject(否定确认) mq知道消息未成功处理.将其丢失,同比上面少了批量的参数

批量应答的说明:
multiple的true和false
true:假设三条未处理,一条消息处理成功 都会给出mq确认的应答
false:假设三条未处理,一条消息处理成功 那么着三条未处理的消息,不会给出mq应答,成功的会给出
通常采用false

	//参数一  消息的标记   参数二 是否批量应答
 channel.basicAck(message.getEnvelope().getDeliveryTag(),false);

总结:消息防丢失 消费者端开启ACK确认机制 生产者端开启confirm确认机制 队列持久化.消息持久化

避免消息重复消费,保障消息的幂等性

原因 消费了消息给mq服务器做出应答时,网络波动导致应答没能收到,mq服务器会再发一条相同的消息.这就是重复消费的原因
解决 :
1当我消费完一条消息后,将其插入数据库,当重复消费时会插入不进去,也是保障了消息的唯一性
2在写入消息队列的数据做唯一标识,当消费消息时,根据唯一标识判断是否消费过,可以缓存在redis中

消息积压

1.修复消费者代码,是否给出ack确认
2,关闭无关紧要的消息持久化
3扩大mq集群,提高消息吞吐量.增加消费者家掳爱处理

消息过期

消息过期是设置了过期时间,可以配置死信队列用于接收过期消息

如何保障消息数据处理的一致性

最简单的模型 一个生产者对应一个队列对应一个消费者,问题是吞吐量低,容错性低

高可用

本人在这块也不是很熟悉,但是知道两种解决方案,一是搭建普通集群,二是镜像集群.镜像比较高可用

死信队列

生产者代码

package com.ax.delete;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class DeadLetterProduct {
    public static void main(String[] args) throws IOException, TimeoutException {
        //普通交换机
        String NORMAL_EXCHANGE="normal_exchange";

        //获取信道
        Channel channel = MQUtils.MQUtil();

        //设置过期时间
        AMQP.BasicProperties properties=new AMQP.BasicProperties().builder().expiration("5000").build();

        for (int i = 0; i < 10; i++) {
            String message="死信消息"+i;
            channel.basicPublish(NORMAL_EXCHANGE,"456",properties,message.getBytes());
        }
    }
}

消费者代码,一个是正常消费者,一个是死信消息消费

package com.ax.delete;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;

/**
 * 死信队列:无法被消费的消息
 * 队列中的消息无法被消费者消费就成了死信,有死信就有了死信队列,专门用来处理死信消息的
 *
 * 死信的来源
 * 1,消息TTL过期
 * 2,队列达到最大长度,无法再添加数据到mq
 * 3,消息被拒绝(basic.reject或者basic.nack)并且requeue(重新排队)=false
 *
 *
 * 流程梳理
 * 生产者---> 交换机1 ----> 队列1 -----> 消费者1
 *                          |
 * 1,消息TTL过期
 * 2,队列达到最大长度,无法再添加数据到mq
 * 3,消息被拒绝(basic.reject或者basic.nack)并且requeue(重新排队)=false
 *                          |
 *                          |
 *                      死信交换机----->死信队列-------->消费者2
 *
 *
 *
 *
 *
 */
//TODO 这是普通消息消费
public class DeadLetterQueue {
    public static void main(String[] args) throws IOException, TimeoutException {
        //普通队列
        String NORMAL_QUEUE="normal_queue";
        //死信队列
        String DEAD_LETTER_QUEUE="dead_letter_queue";
        //普通交换机
        String NORMAL_EXCHANGE="normal_exchange";
        //死信交换机
        String DEAD_LETTER_EXCHANGE="dead_letter_exchange";
        //获取信道
        Channel channel = MQUtils.MQUtil();
        //声明普通交换机
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        //声明死信交换机
        channel.exchangeDeclare(DEAD_LETTER_EXCHANGE,BuiltinExchangeType.DIRECT);

        //设置参数.正常队列设置死信队列,设置路由key
        Map<String, Object> arguments=new HashMap<>();
        //arguments.put("x-message-ttl",3000); //TODO 过期时间可以在生产方设置.生产方优先
        arguments.put("x-dead-letter-exchange",DEAD_LETTER_EXCHANGE);
        arguments.put("x-dead-letter-routing-key","123");
        //arguments.put("x-max-length",6); //TODO 超出最大长度,生产者可以取消过期时间

        //声明普通队列
        channel.queueDeclare(NORMAL_QUEUE,true,false,false,arguments);
        //声明死信队列
        channel.queueDeclare(DEAD_LETTER_QUEUE,true,false,false,null);

        //绑定普通交换机和队列
        channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"456");
        //绑定死信交换机和队列
        channel.queueBind(DEAD_LETTER_QUEUE,DEAD_LETTER_EXCHANGE,"123");

        System.out.println("正常消费者等待接受消息");

        //接收成功回调
        DeliverCallback deliverCallback=(consumerTag,message)->{
            System.out.println(new String(message.getBody()));
            //消息拒收
//            String msg = new String(message.getBody());
//            if(msg.equals("死信消息0")){
//                //参数说明  标签号  是否重新放回队列
//                channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
//            }else {
//                System.out.println(new String(message.getBody()));
//            }
        };
        //接受消息
        //channel.basicConsume(NORMAL_QUEUE,true, deliverCallback,consumerTag -> {});
    }



}

package com.ax.delete;

import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;

/**
 * 死信队列:无法被消费的消息
 * 队列中的消息无法被消费者消费就成了死信,有死信就有了死信队列,专门用来处理死信消息的
 *
 * 死信的来源
 * 1,消息TTL过期
 * 2,队列达到最大长度,无法再添加数据到mq
 * 3,消息被拒绝(basic.reject或者basic.nack)并且requeue(重新排队)=false
 *
 *
 * 流程梳理
 * 生产者---> 交换机1 ----> 队列1 -----> 消费者1
 *                          |
 * 1,消息TTL过期
 * 2,队列达到最大长度,无法再添加数据到mq
 * 3,消息被拒绝(basic.reject或者basic.nack)并且requeue(重新排队)=false
 *                          |
 *                          |
 *                      死信交换机----->死信队列-------->消费者2
 *
 *
 *
 *
 *
 */
//TODO  死信消费
public class DeadLetterQueue2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //死信队列
        String DEAD_LETTER_QUEUE="dead_letter_queue";

        //获取信道
        Channel channel = MQUtils.MQUtil();


        System.out.println("死信消费者等待接受消息");

        //接收成功回调
        DeliverCallback deliverCallback=(consumerTag,message)->{
            System.out.println(new String(message.getBody()));
        };
        //接受消息
        channel.basicConsume(DEAD_LETTER_QUEUE,true, deliverCallback,consumerTag -> {});
    }



}

码云地址:: https://gitee.com/an-xing/rabbitmq.git

下一遍会讲解和springboot的整合

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值