RabbitMQ学习笔记

一、消息队列概述

1、简介:

消息队列是用来发送消息的消息中间件,本质上是队列,有先进先出的特点。

2、功能:

服务削峰
程序解耦
异步消息

3、MQ分类:

ActiveMQ
RocketMQ
Kafka
RabbitMQ

4、RabbitMQ安装

(一)安装并运行
(1)、在docker hub 中查找rabbitmq镜像

docker search rabbitmq:3-management

(2)、从docker hub 中拉取rabbitmq镜像

docker pull rabbitmq:3-management

(3)、查看拉取的rabbitmq镜像

docker  images

(4)、运行 rabbitmq服务端

docker run -d \
-v /opt/rabbitmq/data:/var/lib/rabbitmq \
-p 5672:5672 -p 15672:15672 --name rabbitmq --restart=always \
--hostname myRabbit rabbitmq:3-management

参数解释:

docker run :启动命令
--name :给容器起名字
 -p :★映射端口号,主机端口:容器端口
-v : 将主机中指定目录的挂载到容器的目录
-i : 以交互模式运行。
-t : 进入终端。
-d : 以守护模式后台运行。
-e XXX_XXX="xxxxxxxxxxx" : 指定环境变量

(5)、查看正在运行的容器

docker ps 

(6)、容器运行成功之后,在浏览器访问:
http://192.168.1.100:15672
账号 guest , 密码 guest

(二)其他操作:
(1)、重新启动 rabbitmq 容器

docker   restart   <容器id>

(2)、结束正在运行的容器

docker  stop  <容器id>    容器优雅退出
docker  kill  <容器id>    容器直接退出

(3)、删除 docker 容器 (容器在删除前要先结束)

docker  rm   <容器id>  [ <容器id> ...] 

(4)、删除 docker 镜像

docker  rmi  <镜像id>  [ <镜像id> ...] 

(5)、查看正在运行的 rabbitmq 进程

 ps -ef | grep   rabbitmq

(6)、进入容器内部

docker exec -it  <容器id>  /bin/bash

(7)、查看容器内网ip地址

 docker  inspect <容器id> 

(8)、查看docker 镜像的版本

docker image inspect <镜像名称>:latest|grep -i version

5、RabbitMQ的工作原理

(一)下图是RabbitMQ的基本结构:
在这里插入图片描述

组成部分说明:
Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue
Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费者
Producer:消息生产者,即生产方客户端,生产方客户端将消息发送
Consumer:消息消费者,即消费方客户端,接收MQ转发的消息。

(二)生产者发送消息流程:
(1)、生产者和Broker建立TCP连接。
(2)、生产者和Broker建立通道。
(3)、生产者通过通道消息发送给Broker,由Exchange将消息进行转发。
(4)、Exchange将消息转发到指定的Queue(队列)

(三)消费者接收消息流程:
(1)、消费者和Broker建立TCP连接
(2)、消费者和Broker建立通道
(3)、消费者监听指定的Queue(队列)
(4)、当有消息到达Queue时Broker默认将消息推送给消费者。
(5)、消费者接收到消息。
(6)、ack回复

二、简易队列

1、简介:

channel.basicPublish()方法第一个参数为空字符串,消息生产者绑定默认交换机,第二个参数填入队列名称作为routingKey,就是简易队列模式。

2、代码:

(1)生产者:

public class Producer {
    private static final String QUEUE_NAME = "hello";
    public static void main(String[] args) throws Exception {
        ConnectionFactory fact = new ConnectionFactory();
        fact.setHost("192.168.1.100");
        fact.setUsername("guest");
        fact.setPassword("guest");
        Channel channel = fact.newConnection().createChannel();
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        String mess = "hello world";
        channel.basicPublish("", QUEUE_NAME, null, mess.getBytes());
        System.out.println("消息发送完毕");
    }
}
// 第一个参数是交消息换机名称。如果为空字符串则队列模式为简易队列模式,绑定默认交换机,第二个参数填入队列名称作为routingKey。
// 第二个参数是路由键routingKey。
// 第三个参数是消息的属性.
// 第四个参数是消息的内容.
void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)

(2)消费者:

public class Consumer {
    private static final String QUEUE_NAME = "hello";
    public static void main(String[] args) throws Exception {

        ConnectionFactory fact = new ConnectionFactory();
        fact.setHost("192.168.1.100");
        fact.setUsername("guest");
        fact.setPassword("guest");
        Connection connection = fact.newConnection();
        Channel channel = connection.createChannel();
        DeliverCallback d = (consumerTag, message) -> {
            System.out.println(new String(message.getBody()));
        };
        CancelCallback c = consumerTag -> {
            System.out.println("消息被中断");
        };
        channel.basicConsume(QUEUE_NAME, true, d, c);
    }
}

(3)编写工具类:

public class ConnectUtils {
    public static Channel connect() throws Exception {

        ConnectionFactory fact = new ConnectionFactory();
        fact.setHost("192.168.1.100");
        fact.setUsername("guest");
        fact.setPassword("guest");

        Connection connection = fact.newConnection();
        Channel channel = connection.createChannel();
        return channel;
    }
}
public class SleepUtils {
    public static void sleep(int second) {
        try {
            Thread.sleep(second * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

三、相关设置

3、消息应答:

分为自动应答和手动应答。
不建议自动应答。
手动应答:
channel.basicAsk() (用于确认应答)
channel.basicNask() (用于否认应答)
channel.basicReject() (用于否认应答)

multiple 批量应答 true false 建议使用false

4、消息重新入队:

设置为手动应答后,当一个消费者 断连,mq会将消息重新入队,交给其他的消费者。

public class Producer {
    private final static String ACK_QUEUE_NAME = "ack_queue";
    
    public static void main(String[] args) throws Exception {
        Channel channel = ConnectUtils.connect();
        channel.queueDeclare(ACK_QUEUE_NAME, false, false, false, null);
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String message = scanner.next();
            channel.basicPublish("", ACK_QUEUE_NAME, null, message.getBytes("UTF-8"));
        }
    }
}
public class Consumer1 {
    private static final String QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws Exception {

        Channel channel = ConnectUtils.connect();
        System.out.println("Consumer1等待接收消息的时间较短");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody());
            SleepUtils.sleep(1);
            System.out.println("C1接收到的消息:" + message);

            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), true);
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息被中断");
        };

        channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
    }
}
public class Consumer2 {
    private static final String QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws Exception {

        Channel channel = ConnectUtils.connect();
        System.out.println("Consumer2等待接收消息的时间较长");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody());
            SleepUtils.sleep(10);
            System.out.println("C2接收到的消息:" + message);

            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), true);
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息被中断");
        };

        channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
    }
}

在Producer 控制台依次输入
aa 消息被C1接收到, 等待1秒,打印出“C1接收到的消息:aa”
bb 消息被C2接收到, 等待10秒,打印出“C2接收到的消息:bb”
cc 消息被C1接收到, 等待1秒,打印出“C1接收到的消息:cc”
dd 消息被C2接收到, 等待10秒的过程中关闭C2服务,消息重新进入队列,分配到C1服务。
消息被C1接收到, 等待1秒,打印出“C1接收到的消息:dd”

5、持久化

(1)队列持久化:
在生产者中:
channel.queueDeclare(ACK_QUEUE_NAME, true, false, false, null) 第二个参数为 boolean durable,值为true则进行持久化,在图形化界面队列后面显示大写字母‘D’

(2)消息持久化:
channel.basicPublish(“”, ACK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(“UTF-8”));

6、不公平分发:处理速度快的消费者多接受消息(能者多劳)。

与其相对的是轮询分发,完全按照一个消费者轮一次的方式接收信息。
在消息消费者设置
channel.basicQos(1);

Qos: Quality of Service

预取值Prefetch :信道的消息容量

在消息消费者C1设置
channel.basicQos(2)
在消息消费者C2设置
channel.basicQos(5);

在这里插入图片描述

四、发布确认

三种方式:
单个发布确认、批量发布确认、异步发布确认

public class Producer {
    public static void main(String[] args) throws Exception {
//        confirm01();  //单个发布确认耗时920ms
//        confirm02();    //批量发布确认耗时92ms
        confirm03();  // 批量发布确认耗时66ms
    }

    //单个发布确认
    public static void confirm01() throws Exception {
        Channel channel = ConnectUtils.connect();

        channel.confirmSelect();
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName, true, false, false, null);

        long begin = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            String message = "消息" + i;
            System.out.println("生产者发送消息:" + message);
            channel.basicPublish("", queueName, null, message.getBytes("UTF-8"));
            channel.waitForConfirms();
        }
        long end = System.currentTimeMillis();
        System.out.println("单个发布确认耗时" + (end - begin) + "ms");
    }

    //批量发布确认
    public static void confirm02() throws Exception {
        Channel channel = ConnectUtils.connect();
        channel.confirmSelect();
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName, true, false, false, null);

        long begin = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            String message = "消息" + i;
            System.out.println("生产者发送消息:" + message);
            channel.basicPublish("", queueName, null, message.getBytes("UTF-8"));
            if ((i + 1) % 100 == 0) {
                channel.waitForConfirms();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("批量发布确认耗时" + (end - begin) + "ms");
    }

    //异步发布确认
    public static void confirm03() throws Exception {
        Channel channel = ConnectUtils.connect();

        channel.confirmSelect();
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName, true, false, false, null);

        ConfirmCallback c1 = (deliveryTag, multiple) -> {
            System.out.println("确认的消息:" + deliveryTag);
        };

        ConfirmCallback c2 = (deliveryTag, multiple) -> {
            System.out.println("未确认的消息:" + deliveryTag);
        };
        channel.addConfirmListener(c1, c2);         // 异步通知
        long begin = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            String message = "消息" + i;
            System.out.println("生产者发送消息:" + message);
            channel.basicPublish("", queueName, null, message.getBytes("UTF-8"));
        }
        long end = System.currentTimeMillis();
        System.out.println("批量发布确认耗时" + (end - begin) + "ms");
    }
}

五、交换机 Exchange:

1、作用:通过交换机,能够让消息被多个消费者接收

消息不能由生产者直接发送到队列,而是由生产者发送到交换机,再由交换机发送到队列。

2、交换机类型:

direct(直接)、topic(主题)、headers(标题)、fanout(扇出)

3、无名交换机:

channel.basicPublish("", queueName, null, message.getBytes("UTF-8"));

不指定交换机名称则使用默认交换机(AMQP default)

4、临时队列:

不进行持久化的队列

5、fanout类型:发布订阅模式

将消息发送到同一个交换机下不同队列的消费者

public class Producer {
    private static final String EXCHANGE_NAME = "fanout_exchange";

    public static void main(String[] args) throws Exception {
        Channel channel = ConnectUtils.connect();
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String message = scanner.next();
            System.out.println("生产者发送消息:" + message);
            channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
        }
    }
}
public class Consumer1 {
    private static final String EXCHANGE_NAME = "fanout_exchange";

    public static void main(String[] args) throws Exception {

        Channel channel = ConnectUtils.connect();
        System.out.println("Consumer1等待接收消息的时间较短");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody());
            System.out.println("C1接收到的消息:" + message);
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息被中断");
        };

        String queueName = channel.queueDeclare().getQueue();
        channel.queueBind(queueName, EXCHANGE_NAME, "key001");
        channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
    }
}
public class Consumer2 {
    private static final String EXCHANGE_NAME = "fanout_exchange";

    public static void main(String[] args) throws Exception {

        Channel channel = ConnectUtils.connect();
        System.out.println("Consumer2等待接收消息的时间较短");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody());
            System.out.println("C2接收到的消息:" + message);
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息被中断");
        };

        String queueName = channel.queueDeclare().getQueue();
        channel.queueBind(queueName, EXCHANGE_NAME, "key001");
        channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
    }
}

生产者控制台:
aa
生产者发送消息:aa
bb
生产者发送消息:bb
cc
生产者发送消息:cc
dd
生产者发送消息:dd
ff
生产者发送消息:ff

消费者C1控制台:
Consumer1等待接收消息的时间较短
C1接收到的消息:aa
C1接收到的消息:bb
C1接收到的消息:cc
C1接收到的消息:dd
C1接收到的消息:ff

消费者C2控制台:
Consumer1等待接收消息的时间较短
C1接收到的消息:aa
C1接收到的消息:bb
C1接收到的消息:cc
C1接收到的消息:dd
C1接收到的消息:ff

6、direct类型:路由模式

与交换机绑定的队列的routingKey不相同,在发送消息时指定交换机和routingKey就能找到唯一的队列。

消息生产者:

public class Producer {
    private static final String EXCHANGE_NAME = "direct_exchange";

    public static void main(String[] args) throws Exception {

        Channel channel = ConnectUtils.connect();
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String message = scanner.next();
            System.out.println("生产者发送消息:" + message);
            channel.basicPublish(EXCHANGE_NAME, "key201", null, message.getBytes("UTF-8"));
        }
    }
}

消费者C1:

public class Consumer1 {
    private static final String EXCHANGE_NAME = "direct_exchange";

    public static void main(String[] args) throws Exception {
        Channel channel = ConnectUtils.connect();

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody());
            System.out.println("C1接收到的消息:" + message);
        };
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息被中断");
        };
        String queueName = "direct1-1";
        channel.queueDeclare(queueName, false, false, false, null);
        channel.queueBind(queueName, EXCHANGE_NAME, "key101");
        channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
    }
}

消费者C2:

public class Consumer2 {
    private static final String EXCHANGE_NAME = "direct_exchange";

    public static void main(String[] args) throws Exception {
        Channel channel = ConnectUtils.connect();

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody());
            System.out.println("C2接收到的消息:" + message);
        };
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息被中断");
        };

        String queueName1 = "direct2-1";
        channel.queueDeclare(queueName1, false, false, false, null);
        channel.queueBind(queueName1, EXCHANGE_NAME, "key201");
        channel.basicConsume(queueName1, true, deliverCallback, cancelCallback);

        String queueName2 = "direct2-2";
        channel.queueDeclare(queueName2, false, false, false, null);
        channel.queueBind(queueName2, EXCHANGE_NAME, "key202");
        channel.basicConsume(queueName2, true, deliverCallback, cancelCallback);
    }
}

在生产者控制台输入:
aaa
生产者发送消息:aaa
bbb
生产者发送消息:bbb
ccc
生产者发送消息:ccc
ddd
生产者发送消息:ddd

消费者控制台显示:
C2接收到的消息:aaa
C2接收到的消息:bbb
C2接收到的消息:ccc
C2接收到的消息:ddd

7、topic类型:主题模式

消费者订阅主题,获取符合条件的队列中的消息

  • 匹配一个单词

匹配一个或多个单词

例:
cat.*.pop cat.temp.pop
cat.# cat.end.yield
*.keep.believe queen.keep.believe

代码示例:
生产者:

public class Producer {
    private static final String EXCHANGE_NAME = "topic_exchange";

    public static void main(String[] args) throws Exception {
        Channel channel = ConnectUtils.connect();
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String message = scanner.next();
            System.out.println("生产者发送消息:" + message);
            channel.basicPublish(EXCHANGE_NAME, "start.sss.so", null, message.getBytes("UTF-8"));
        }
    }
}

消费者C1 :

public class Consumer1 {
    private static final String EXCHANGE_NAME = "topic_exchange";

    public static void main(String[] args) throws Exception {
        Channel channel = ConnectUtils.connect();
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        String queueName = "Queue1";
        channel.queueDeclare(queueName, false, false, false, null);

        channel.queueBind(queueName, EXCHANGE_NAME, "*.middle.*");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody());
            System.out.println("C1接收到的消息:" + message);
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息被中断");
        };
        channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
    }
}

消费者C2 :

public class Consumer2 {
    private static final String EXCHANGE_NAME = "topic_exchange";

    public static void main(String[] args) throws Exception {
        Channel channel = ConnectUtils.connect();
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        String queueName = "Queue2";
        channel.queueDeclare(queueName, false, false, false, null);

        channel.queueBind(queueName, EXCHANGE_NAME, "#.end");
        channel.queueBind(queueName, EXCHANGE_NAME, "start.*.*");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody());
            System.out.println("C2接收到的消息:" + message);
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息被中断");
        };

        channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
    }
}

在生产者控制台输入:
hello
生产者发送消息:hello
nihao
生产者发送消息:nihao
zaima
生产者发送消息:zaima

消费者C1控制台没有显示内容。

消费者C2控制台显示:
C2接收到的消息:hello
C2接收到的消息:nihao
C2接收到的消息:zaima

六、死信队列:

1、死信:不能被接受的消息

2、造成死信的原因:

消息TTL过期,
队列达到最大长度,
消息被拒绝,

3、消息TTL过期

代码:

生产者:

public class Producer {

    private static final String EXCHANGE_NAME = "normal_exchange";

    public static void main(String[] args) throws Exception {
        Channel channel = ConnectUtils.connect();

        AMQP.BasicProperties pro = new AMQP.BasicProperties().builder().expiration("10000").build();
        for (int i = 0; i < 10; i++) {
            String message = "消息" + i;

            System.out.println("生产者发送消息:" + message);
            channel.basicPublish(EXCHANGE_NAME, "zhangsan", pro, message.getBytes("UTF-8"));
        }

    }
}

消费者1:

public class Consumer1 {

    private static final String NORMAL_EXCHANGE = "normal_exchange";
    private static final String NORMAL_QUEUE = "normal_queue";

    private static final String DEAD_EXCHANGE = "dead_exchange";
    private static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws Exception {

        Channel channel = ConnectUtils.connect();
        channel.exchangeDeclare(NORMAL_EXCHANGE, "direct");
        channel.exchangeDeclare(DEAD_EXCHANGE, "direct");
        Map<String, Object> map = new HashMap<>();
        map.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        map.put("x-dead-letter-routing-key", "lisi");

        channel.queueDeclare(NORMAL_QUEUE, false, false, false, map);
        channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");

        channel.queueDeclare(DEAD_QUEUE, false, false, false, null);
        channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String mes = new String(message.getBody(), "UTF-8");
            System.out.println("C1接收到的消息:" + mes);
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息被中断");
        };

        channel.basicConsume(NORMAL_QUEUE, true, deliverCallback, cancelCallback);
        channel.basicConsume(DEAD_QUEUE, true, deliverCallback, cancelCallback);
    }
}

消费者2:

public class Consumer2 {

    private static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = ConnectUtils.connect();
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody());
            System.out.println("C2接收到的消息:" + message);
        };
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息被中断");
        };
        channel.basicConsume(DEAD_QUEUE, true, deliverCallback, cancelCallback);
    }
}

操作流程:
(1)先运行消费者1,声明 普通交换机、普通队列、死信交换机、死信队列

(2)然后关闭消费者1,运行生产者,发送消息

生产者控制台:
生产者发送消息:消息0
生产者发送消息:消息1
生产者发送消息:消息2
生产者发送消息:消息3
生产者发送消息:消息4
生产者发送消息:消息5
生产者发送消息:消息6
生产者发送消息:消息7
生产者发送消息:消息8

(3)然后运行消费者2,接收消息

消费者2控制台:
C2接收到的消息:消息0
C2接收到的消息:消息1
C2接收到的消息:消息2
C2接收到的消息:消息3
C2接收到的消息:消息4
C2接收到的消息:消息5
C2接收到的消息:消息6
C2接收到的消息:消息7
C2接收到的消息:消息8
C2接收到的消息:消息9

4、队列达到最大长度:

C1消费者添加代码:
map.put(“x-max-length”, 6);

5、消息被拒绝:

public class Consumer1 {

    private static final String NORMAL_EXCHANGE = "normal_exchange";
    private static final String NORMAL_QUEUE = "normal_queue";

    private static final String DEAD_EXCHANGE = "dead_exchange";
    private static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws Exception {

        Channel channel = ConnectUtils.connect();
        channel.exchangeDeclare(NORMAL_EXCHANGE, "direct");
        channel.exchangeDeclare(DEAD_EXCHANGE, "direct");
        Map<String, Object> map = new HashMap<>();
        map.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        map.put("x-dead-letter-routing-key", "lisi");

        channel.queueDeclare(NORMAL_QUEUE, false, false, false, map);
        channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");

        channel.queueDeclare(DEAD_QUEUE, false, false, false, null);
        channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String mes = new String(message.getBody(), "UTF-8");
            if (mes.equals("消息6")) {
                System.out.println("C1拒绝接收的消息:" + mes);
                channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
            } else {
                System.out.println("C1接收到的消息:" + mes);
                channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
            }
        };
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息被中断");
        };
        channel.basicConsume(NORMAL_QUEUE, false, deliverCallback, cancelCallback);
        channel.basicConsume(DEAD_QUEUE, true, deliverCallback, cancelCallback);
    }
}

操作步骤:
先打开C1、C2消费者等待接收消息,然后打开生产者
生产者控制台:
生产者发送消息:消息0
生产者发送消息:消息1
生产者发送消息:消息2
生产者发送消息:消息3
生产者发送消息:消息4
生产者发送消息:消息5
生产者发送消息:消息6
生产者发送消息:消息7
生产者发送消息:消息8
生产者发送消息:消息9

C1消费者控制台:
C1接收到的消息:消息0
C1接收到的消息:消息1
C1接收到的消息:消息2
C1接收到的消息:消息3
C1接收到的消息:消息4
C1接收到的消息:消息5
C1拒绝接收的消息:消息6
C1接收到的消息:消息7
C1接收到的消息:消息8
C1接收到的消息:消息9

C2消费者控制台:
C2接收到的消息:消息6

七、Springboot整合RabbitMQ:

pom.xml


org.springframework.boot
spring-boot-starter-web
2.4.6


org.springframework.boot
spring-boot-starter-test
test


org.springframework.boot
spring-boot-starter-amqp
2.2.2.RELEASE


org.springframework.boot
spring-boot-starter
2.4.6


com.alibaba
fastjson
1.2.76

application.properties spring.rabbitmq.host=192.168.1.100 spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest

主启动类;
@SpringBootApplication
public class RabbitApplication {
public static void main(String[] args) {
SpringApplication.run(RabbitApplication.class, args);
}
}

八、延迟队列:

优化版:添加QC队列

声明队列、交换机的配置类

@Configuration
public class RabbitmqConfig {

public static final String A_QUEUE = "aQueue";
public static final String B_QUEUE = "bQueue";
public static final String C_QUEUE = "cQueue";
public static final String D_QUEUE = "dQueue";

public static final String X_EXCHANGE = "xExchange";
public static final String Y_EXCHANGE = "yExchange";

@Bean("aQueue")
public Queue aQueue() {
    HashMap<String, Object> map = new HashMap<>(3);
    map.put("x-dead-letter-exchange", Y_EXCHANGE);
    map.put("x-dead-letter-routing-key", "YD");
    map.put("x-message-ttl", 10000);
    return QueueBuilder.durable(A_QUEUE).withArguments(map).build();
}

@Bean("bQueue")
public Queue bQueue() {
    HashMap<String, Object> map = new HashMap<>(3);
    map.put("x-dead-letter-exchange", Y_EXCHANGE);
    map.put("x-dead-letter-routing-key", "YD");
    map.put("x-message-ttl", 40000);
    return QueueBuilder.durable(B_QUEUE).withArguments(map).build();
}

@Bean("cQueue")
public Queue cQueue() {
    HashMap<String, Object> map = new HashMap<>(3);
    map.put("x-dead-letter-exchange", Y_EXCHANGE);
    map.put("x-dead-letter-routing-key", "YD");
    return QueueBuilder.durable(C_QUEUE).withArguments(map).build();
}

@Bean("dQueue")
public Queue dQueue() {
    return QueueBuilder.durable(D_QUEUE).build();
}

@Bean("xExchange")
public DirectExchange xExchange() {
    return new DirectExchange(X_EXCHANGE);
}

@Bean("yExchange")
public DirectExchange yExchange() {
    return new DirectExchange(Y_EXCHANGE);
}

@Bean
public Binding aQueueBindX(@Qualifier("aQueue") Queue aQueue, @Qualifier("xExchange") DirectExchange xExchange) {
    return BindingBuilder.bind(aQueue).to(xExchange).with("XA");
}

@Bean
public Binding bQueueBindX(@Qualifier("bQueue") Queue bQueue, @Qualifier("xExchange") DirectExchange xExchange) {
    return BindingBuilder.bind(bQueue).to(xExchange).with("XB");
}

@Bean
public Binding cQueueBindX(@Qualifier("cQueue") Queue cQueue, @Qualifier("xExchange") DirectExchange xExchange) {
    return BindingBuilder.bind(cQueue).to(xExchange).with("XC");
}

@Bean
public Binding dQueueBindY(@Qualifier("dQueue") Queue dQueue, @Qualifier("yExchange") DirectExchange yExchange) {
    return BindingBuilder.bind(dQueue).to(yExchange).with("YD");
}

}

消息生产者:
@RestController
@RequestMapping(“/ttl”)
public class SendMsgController {
@Autowired
private RabbitTemplate rabbitTemplate;

@GetMapping("/sendMsg/{message}")
public void sendMsg(@PathVariable("message") String message) {
    System.out.println("当前时间:" + new Date().toString() +",发送的消息是:" + message);
    rabbitTemplate.convertAndSend("xExchange", "XA", "消息来自ttl为10秒的队列:" + message);
    rabbitTemplate.convertAndSend("xExchange", "XB", "消息来自ttl为40秒的队列:" + message);

}

@GetMapping("/sendMsg/{message}/{ttl}")
public void sendMsg(@PathVariable("message") String message, @PathVariable("ttl") String ttl) {
    System.out.println("当前时间:" + new Date().toString() + ",发送的消息是:" + message);
    rabbitTemplate.convertAndSend("xExchange", "XC", "消息来自队列C:" + message,
            msg -> {
                msg.getMessageProperties().setExpiration(ttl);
                return msg;
            });
}

}
消息消费者;
@Component
public class MsgConsumer {
@RabbitListener(queues = “dQueue”)
public void deliverMsg(Message message, Channel channel) {
String s = new String(message.getBody());
System.out.println(“当前时间:” + new Date().toString() + “,接收到的队列消息—>” + s);
}
}

九、发布确认高级篇:

消息发布者发布消息之后,在消息队列服务器遇到中断,消息会丢失。
消息队列服务器中断的情形有两种:
(1)在交换机环节中断
(2)在队列环节中断

代码示例:
application.properties

开启发布确认回调方法

spring.rabbitmq.publisher-confirm-type=correlated

开启发布返回回调方法

spring.rabbitmq.publisher-returns=true

@Configuration
public class ConfirmConfig {
private static final String CONFIRM_EXCHANGE_NAME = “confirm_exchange”;
private static final String CONFIRM_QUEUE_NAME = “confirm_queue”;
private static final String ROUTING_KEY_NAME = “key1”;

@Bean("confirmExchange")
public DirectExchange confirmExchange() {
    return new DirectExchange(CONFIRM_EXCHANGE_NAME);
}

@Bean("confirmQueue")
public Queue confirmQueue() {
    return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}

@Bean
public Binding queueBindExchange(@Qualifier("confirmQueue") Queue confirmQueue,
                                 @Qualifier("confirmExchange") DirectExchange confirmExchange) {
    return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(ROUTING_KEY_NAME);
}

}

@RestController
@RequestMapping(“/confirm”)
public class ConfirmController {
@Autowired
private RabbitTemplate rabbitTemplate;

@GetMapping("/sendMsg/{message}")
public void sendMsg(@PathVariable("message") String message) {

// Ⅰ、正确的发送消息
CorrelationData cor = new CorrelationData(“10001”);
System.out.println(“发送的消息是:” + message + “001”);
rabbitTemplate.convertAndSend(“confirm_exchange”, “key1”, “消息:” + message + “001”, cor);
// Ⅱ、交换机错误
CorrelationData cor2 = new CorrelationData(“10002”);
System.out.println(“发送的消息是:” + message + “002”);
rabbitTemplate.convertAndSend(“confirm_exchange123”, “key1”, “消息:” + message, cor2);

// Ⅲ、队列错误
CorrelationData cor3 = new CorrelationData(“10003”);
System.out.println(“发送的消息是:” + message + “003”);
rabbitTemplate.convertAndSend(“confirm_exchange”, “key3”, “消息:” + message + “003”, cor3);
}
}

@Component
public class ConfirmConsumer {

@RabbitListener(queues = "confirm_queue")
public void deliverMsg2(Message message, Channel channel) {
    String s = new String(message.getBody());
    System.out.println("接收到的队列消息--->" + s);
}

}

@Component
public class ConfirmCallback implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {

@Autowired
private RabbitTemplate rabbitTemplate;

@PostConstruct
private void init() {
      rabbitTemplate.setConfirmCallback(this);
     rabbitTemplate.setReturnsCallback(this);
}

@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
    String id = correlationData != null ? correlationData.getId() : "";
    if (ack) {
        System.out.println("已经接收id为" + id + "的消息");
    } else {
        System.out.println("没有接收到id为" + id + "的消息,原因为" + cause);
    }
}

@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
    String message = new String(returnedMessage.getMessage().getBody());
    String exchange = returnedMessage.getExchange();
    String replyText = returnedMessage.getReplyText();
    String routingKey = returnedMessage.getRoutingKey();

    System.out.println("消息" + message + "被交换机" + exchange + "退回,原因是" + replyText + ",路由key是" + routingKey);
}

}

浏览器访问: http://localhost:8080/confirm/sendMsg/chrome

ConfirmController 只放开 Ⅰ ,控制台显示
发送的消息是:chrome001
接收到的队列消息—>消息:chrome001
已经接收id为10001的消息

ConfirmController 只放开 Ⅱ ,控制台显示
发送的消息是:chrome002
2022-06-06 19:34:47.360 ERROR 18088 — [.168.1.100:5672] o.s.a.r.c.CachingConnectionFactory : Shutdown Signal: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange ‘confirm_exchange123’ in vhost ‘/’, class-id=60, method-id=40)
没有接收到id为10002的消息,原因为channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange ‘confirm_exchange123’ in vhost ‘/’, class-id=60, method-id=40)

ConfirmController 只放开 Ⅲ,控制台显示
发送的消息是:chrome003
消息消息:chrome003被交换机confirm_exchange退回,原因是NO_ROUTE,路由key是key3
已经接收id为10003的消息

备份交换机:

代码:
ConfirmConfig :添加备份交换机、备份队列、报警队列,绑定队列和交换机
@Configuration
public class ConfirmConfig {
private static final String CONFIRM_EXCHANGE_NAME = “confirm_exchange”;
private static final String CONFIRM_QUEUE_NAME = “confirm_queue”;
private static final String ROUTING_KEY_NAME = “key1”;
private static final String BACKUP_EXCHANGE_NAME = “backup_exchange”;
private static final String BACKUP_QUEUE_NAME = “backup_queue”;
private static final String WARNING_QUEUE_NAME = “warning_queue”;

@Bean("confirmExchange")
public DirectExchange confirmExchange() {
    return ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME)
            .durable(true).alternate(BACKUP_EXCHANGE_NAME).build();
}

@Bean("confirmQueue")
public Queue confirmQueue() {
    return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}

@Bean
public Binding confirmQueueBindConfirmExchange(@Qualifier("confirmQueue") Queue confirmQueue, @Qualifier("confirmExchange") DirectExchange confirmExchange) {
    return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(ROUTING_KEY_NAME);
}

@Bean("backupExchange")
public FanoutExchange backupExchange() {
    return new FanoutExchange(BACKUP_EXCHANGE_NAME);
}

@Bean("backupQueue")
public Queue backupQueue() {
    return QueueBuilder.durable(BACKUP_QUEUE_NAME).build();
}

@Bean("warningQueue")
public Queue warningQueue() {
    return QueueBuilder.durable(WARNING_QUEUE_NAME).build();
}

@Bean
public Binding backupQueueBindBackupExchange(@Qualifier("backupQueue") Queue backupQueue,
                                             @Qualifier("backupExchange") FanoutExchange backupExchange) {
    return BindingBuilder.bind(backupQueue).to(backupExchange);
}

@Bean
public Binding warningQueueBindBackupExchange(@Qualifier("warningQueue") Queue warningQueue,
                                              @Qualifier("backupExchange") FanoutExchange backupExchange) {
    return BindingBuilder.bind(warningQueue).to(backupExchange);
}

}

ConfirmController 放开Ⅲ。

MsgConsumer: 添加备份消费者和报警消费者

@Component
public class MsgConsumer {

@RabbitListener(queues = "dQueue")
public void deliverMsg(Message message, Channel channel) {
    String s = new String(message.getBody());
    System.out.println("当前时间:" + new Date().toString() + ",接收到的队列消息--->" + s);
}

@RabbitListener(queues = "confirm_queue")
public void deliverMsg2(Message message, Channel channel) {
    String s = new String(message.getBody());
    System.out.println("接收到的队列消息--->" + s);
}

@RabbitListener(queues = "backup_queue")
public void deliverMsg3(Message message, Channel channel) {
    String s = new String(message.getBody());
    System.out.println("备份队列接收到的消息--->" + s);
}

@RabbitListener(queues = "warning_queue")
public void deliverMsg4(Message message, Channel channel) {
    String s = new String(message.getBody());
    System.out.println("WARNING!WARNING!WARNING!报警队列接收到的消息--->" + s);
}

}

浏览器访问: http://localhost:8080/confirm/sendMsg/chrome
控制台显示:
发送的消息是:chrome003
已经接收id为10003的消息
WARNING!WARNING!WARNING!报警队列接收到的消息—>消息:chrome003
备份队列接收到的消息—>消息:chrome003

配置了备份交换机,则不走消息回退,而是走备份交换机。
表明:备份交换机比消息回退优先级高。

十、其他知识点

1、幂等性

对一个操作发起多次请求。

解决思路:使用全局ID或者唯一标识

解决办法:
(1)唯一ID+指纹锁机制
(2)redis的原子性
利用redis执行setx命令,天然具有幂等性,从而实现不重复消费

2、优先级队列:

生产者发送消息时,给消息设置优先级数,在队列中按照优先级对消息重新排序,优先级高的先发送到消费者,优先级低的后发送。

public class Producer {
private static final String QUEUE_NAME = “priority_queue”;

public static void main(String[] args) throws Exception {
    Channel channel = ConnectUtils.connect();
    HashMap<String, Object> map = new HashMap<>();
    map.put("x-max-priority", 10);

    channel.queueDeclare(QUEUE_NAME, false, false, false, map);
    for (int i = 0; i < 10; i++) {
        String msg = "hello" + i;
        if (i % 3 == 0) {
            AMQP.BasicProperties build = new AMQP.BasicProperties().builder().priority(6).build();
            channel.basicPublish("", QUEUE_NAME, build, msg.getBytes());
        } else if (i == 8) {
            AMQP.BasicProperties build = new AMQP.BasicProperties().builder().priority(3).build();
            channel.basicPublish("", QUEUE_NAME, build, msg.getBytes());
        } else {
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
        }
    }
    System.out.println("消息发送完毕");
}

}

public class Consumer {
private static final String QUEUE_NAME = “priority_queue”;

public static void main(String[] args) throws Exception {
    Channel channel = ConnectUtils.connect();
    DeliverCallback d = (consumerTag, message) -> {
        System.out.println(new String(message.getBody()));
    };
    CancelCallback c = consumerTag -> {
        System.out.println("消息被中断");
    };
    channel.basicConsume(QUEUE_NAME, true, d, c);
}

}

控制台显示:
hello0
hello3
hello6
hello9
hello8
hello1
hello2
hello4
hello5
hello7

3、惰性队列:

队列的模式分为默认(default)和惰性(lazy)
默认队列消息保存在内存中,惰性队列消息保存在磁盘中。
在声明队列时 ,添加 “x-queue-mode” 属性,设置值为 “lazy”

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹤冲天Pro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值