RMQ从入门到精通

一.概述与安装

//RabbitMQ
//1.核心部分-高级部分-集群部分
//2.什么是MQ 消息队列message queue 先入先出原则;消息通信服务
//3.MQ的大三功能 流量消峰 应用解耦 消息中间件
//(1)人-订单系统(1万次/S)—> 人 - MQ(流量消峰,对访问人员进行排队) -订单系统(保护系统不宕机)
//(2)订单系统-支付/库存/物流系统—> 订单系统-MQ-支付/库存/物流系统
//(3)A -API -B  —> A - MQ -B  这样可以通过MQ完成时告知A
//4.MQ的分类 ActiveMQ(老) Kafka(大数据的杀手锏) RocketMQ RabbitMQ(中小型公司推荐)
//5.RabbitMQ概念
//(1)就是一个快递站  发件人-快递员-快递站MQ-快递员-收件人
//(2)生产者-MQ(1交换机、N队列)-1个队列对应1个消费者
//6.核心部分 6大模式
//(1)简单模式Hello World
//(2)工作模式Work queues
//(3)发布订阅模式Publish/Subscribe
//(4)路由模式Routing
//(5)主体模式Topics
//(6)发布确认模式Publisher Confirms
//(7)Broker消息实体(可以有多个交换机Exchange(可以有多个队列Queue));Connection(多个Channel)
// Producer生产者 Consumer消费者 Binding绑定,交换机和queue之间的虚拟连接
//7.RMQ的安装
//集中下载 链接:https://pan.baidu.com/s/1NJfYnLT4DN-uu-uyIXzA4w  提取码:HIT0
//(1)先安装erlang  yum -y install gcc glibc-devel make ncurses-devel openssl-devel xmlto perl wget gtk2-devel binutils-devel
//(2)下载 wget http://erlang.org/download/otp_src_22.0.tar.gz
//(3)解压 tar -zxvf otp_src_22.0.tar.gz
//(4)移动 mv otp_src_22.0 /usr/local/
//(5)切换目录 cd /usr/local/otp_src_22.0/
//(6)创建即将安装的目录 mkdir ../erlang
//(7)配置安装路径 ./configure --prefix=/usr/local/erlang
//(8)安装 make install
//(9)查看一下是否安装成功 ll /usr/local/erlang/bin
//(10)添加环境变量 echo 'export PATH=$PATH:/usr/local/erlang/bin' >> /etc/profile
//(11)刷新环境变量 source /etc/profile
//(12)甩一条命令 erl    halt(). 退出
//---------------------安装RMQ-------------------------
//(13)下载 wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.15/rabbitmq-server-generic-unix-3.7.15.tar.xz
//(14)由于是tar.xz格式的所以需要用到xz,没有的话就先安装  yum install -y xz
//(15)第一次解压 /bin/xz -d rabbitmq-server-generic-unix-3.7.15.tar.xz
//(16)第二次解压 tar -xvf rabbitmq-server-generic-unix-3.7.15.tar
//(17)移动 mv rabbitmq_server-3.7.15/ /usr/local/
//(18)改名 mv /usr/local/rabbitmq_server-3.7.15  /usr/local/rabbitmq
//(19)配置环境变量 echo 'export PATH=$PATH:/usr/local/rabbitmq/sbin' >> /etc/profile
//(20)刷新环境变量 source /etc/profile
//(21)创建配置目录 mkdir /etc/rabbitmq
//(22)启动:rabbitmq-server -detached
//(23)停止:rabbitmqctl stop
//(24)状态:rabbitmqctl status
//(25)开放端口 firewall-cmd --zone=public --add-port=15672/tcp --permanent     firewall-cmd --zone=public --add-port=5672/tcp --permanent
//(26)开启web插件 rabbitmq-plugins enable rabbitmq_management
//(27)访问 http://wdfgdzx.top:15672/   默认账号密码:guest guest(这个账号只允许本机访问)
//(28)查看所有用户 rabbitmqctl list_users
//(29)添加一个用户 rabbitmqctl add_user xlliu24 s19911009!
//(30)配置权限 rabbitmqctl set_permissions -p "/" xlliu24 ".*" ".*" ".*"
//(31)查看用户权限 rabbitmqctl list_user_permissions xlliu24
//(32)设置tag   rabbitmqctl set_user_tags xlliu24 administrator
//(33)删除用户(安全起见,删除默认用户) rabbitmqctl delete_user guest
//(34)然后用新用户登录,成功后看到界面

二.如何使用RMQ

//1.Hello World
//(1)引入maven包  amqp-client commons-io
//(2)P(生产者) -发消息-队列hello(中间件)- 接受消息-C(消费者)
//生产者
package com.day.controller;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
//发消息
public class Producer {
    //队列名称
    public static final String QUEUE_NAME="hello";
    //发消息
    public static void main(String[] args) throws Exception {
        //创建工厂
        ConnectionFactory connectionFactory=new ConnectionFactory();
        //工厂IP 连接RMQ的队列
        connectionFactory.setHost("47.105.174.97");
        //用户名密码
        connectionFactory.setUsername("xlliu24");
        connectionFactory.setPassword("s19911009!");
        //创建连接
        Connection connection =connectionFactory.newConnection();
        //获取信道
        Channel channel =connection.createChannel();
        //创建队列
        /**
         * 1.队列名称
         * 2.队列里面的消息是否持久化 默认情况消息存储在内存中 持久化存储在磁盘
         * 3.该队列是否只供一个消费者进行消费 是否进行消息共享 true可以多个消费者消费 false则不允许
         * 4.是否自动删除 最后一个消费者断开后 改队列是否自动删除 true自动删除 false反
         * 5.其他参数
        * */
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //发消息
        String message="Hello World";
        /**
         * 1.发送到那个交换机
         * 2.路由的key值 本次是队列的名称
         * 3.其他参数信息
         * 4.发送的消息
        * */
        channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
        System.out.println("消息发送完毕...");
    }
}
//消费者
package com.day.controller;
import com.rabbitmq.client.*;
//消费者
public class Consumer {
    //队列的名称
    public static final String QUEUE_NAME="Hello";
    //接收消息
    public static void main(String[] args) throws Exception {
        //创建连接工厂
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("47.105.174.97");
        connectionFactory.setUsername("xlliu24");
        connectionFactory.setPassword("s19911009!");
        //创建新链接
        Connection connection=connectionFactory.newConnection();
        Channel channel=connection.createChannel();
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println(new String(message.getBody()));
        };
        //取消消费
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println("消费消息被中断...");
        };
        //消费者消费消息
        /**
         * 1.消费哪个队列
         * 2.消费成功之后是否要自动应答 true代表自动应答 false代表手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         *
         * */
        channel.basicConsume("hello",true,deliverCallback,cancelCallback);
    }
}
------------------------------------
//2.Work Queues 工作队列
//(1)生产者-大量发消息-队列hello-接受消息-N个消费者(工作线程)
//(2)注意:一个消息只能被处理一次,不能被处理多次。所以工作线程采用的是轮训分发消息
// 抽取信道工具类
package com.day.controller;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class RMQUtil{
   public static Channel getChannel() throws Exception{
       //创建连接工厂
       ConnectionFactory connectionFactory=new ConnectionFactory();
       connectionFactory.setHost("47.105.174.97");
       connectionFactory.setUsername("xlliu24");
       connectionFactory.setPassword("s19911009!");
       //创建新链接
       Connection connection=connectionFactory.newConnection();
       return connection.createChannel();
   }
}
-----------------------
package com.day.controller;
import com.rabbitmq.client.Channel;
//生产者,发送大量的消息
public class Producer {
    //队列名称
    public static final String QUEUE_NAME="hello";
    //发消息
    public static void main(String[] args) throws Exception {
        //获取信道
        Channel channel =RMQUtil.getChannel();
        //创建队列
        /**
         * 1.队列名称
         * 2.队列里面的消息是否持久化 默认情况消息存储在内存中 持久化存储在磁盘
         * 3.该队列是否只供一个消费者进行消费 是否进行消息共享 true可以多个消费者消费 false则不允许
         * 4.是否自动删除 最后一个消费者断开后 改队列是否自动删除 true自动删除 false反
         * 5.其他参数
        * */
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //发消息
        String message="Hello World";
        /**
         * 1.发送到那个交换机
         * 2.路由的key值 本次是队列的名称
         * 3.其他参数信息
         * 4.发送的消息
        * */
        for(int i=0;i<1000;i++){
            channel.basicPublish("",QUEUE_NAME,null,(message+" "+i).getBytes());
            System.out.println("消息发送完毕...");
        }
    }
}
-------------------
package com.day.controller;
import com.rabbitmq.client.*;
//消费者
public class Consumer {
    //队列的名称
    public static final String QUEUE_NAME="hello";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println("C0接收到的消息: "+new String(message.getBody()));
        };
        //取消消费
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        //消费者消费消息
        /**
         * 1.消费哪个队列
         * 2.消费成功之后是否要自动应答 true代表自动应答 false代表手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         *
         * */
        System.out.println("C0等待接受消息...");
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}
----------------
package com.day.controller;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
//消费者
public class Consumer1 {
    //队列的名称
    public static final String QUEUE_NAME="hello";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println("C1接收到的消息: "+new String(message.getBody()));
        };
        //取消消费
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        //消费者消费消息
        /**
         * 1.消费哪个队列
         * 2.消费成功之后是否要自动应答 true代表自动应答 false代表手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         *
         * */
        System.out.println("C1等待接受消息...");
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}
----------------------
package com.day.controller;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
//消费者
public class Consumer2 {
    //队列的名称
    public static final String QUEUE_NAME="hello";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println("C2接收到的消息: "+new String(message.getBody()));
        };
        //取消消费
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        //消费者消费消息
        /**
         * 1.消费哪个队列
         * 2.消费成功之后是否要自动应答 true代表自动应答 false代表手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         *
         * */
        System.out.println("C2等待接受消息...");
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}
-----------------控制台打印信息如下----------------------
C0接收到的消息: Hello World 0
C0接收到的消息: Hello World 3
C0接收到的消息: Hello World 6
C0接收到的消息: Hello World 9
----------
C1接收到的消息: Hello World 1
C1接收到的消息: Hello World 4
C1接收到的消息: Hello World 7
C1接收到的消息: Hello World 10
-----------
C2接收到的消息: Hello World 2
C2接收到的消息: Hello World 5
C2接收到的消息: Hello World 8
C2接收到的消息: Hello World 11

//3.消息应答
//(1)即消费者处理完毕后-应答-生产者删除消息
//(2)自动应答对环境要求高,并不可取;
//(3)手动应答:basicAck(肯定) basicNack basicReject
//(4)批量应答multiple 当前8 true 5 6 7 8都确认, false只会应答8,建议使用false
//(5)消息自动重新入队,当某个通道发生异常时,RMQ将了解到消息未完全处理,并将对其重新排队。让其他通道处理,保证消息的不丢失与处理。
//(6)目的:手动应答保证消息的不丢失。
// 抽取信道工具类
package com.day.controller;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class RMQUtil{
   public static Channel getChannel() throws Exception{
       //创建连接工厂
       ConnectionFactory connectionFactory=new ConnectionFactory();
       connectionFactory.setHost("47.105.174.97");
       connectionFactory.setUsername("xlliu24");
       connectionFactory.setPassword("s19911009!");
       //创建新链接
       Connection connection=connectionFactory.newConnection();
       return connection.createChannel();
   }
}
--------
package com.day.controller;
import com.rabbitmq.client.Channel;
//生产者,发送大量的消息
public class Producer {
    //队列名称
    public static final String QUEUE_NAME="hello";
    //发消息
    public static void main(String[] args) throws Exception {
        //获取信道
        Channel channel =RMQUtil.getChannel();
        //创建队列
        /**
         * 1.队列名称
         * 2.队列里面的消息是否持久化 默认情况消息存储在内存中 持久化存储在磁盘
         * 3.该队列是否只供一个消费者进行消费 是否进行消息共享 true可以多个消费者消费 false则不允许
         * 4.是否自动删除 最后一个消费者断开后 改队列是否自动删除 true自动删除 false反
         * 5.其他参数
        * */
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //发消息
        String message="Hello World";
        /**
         * 1.发送到那个交换机
         * 2.路由的key值 本次是队列的名称
         * 3.其他参数信息
         * 4.发送的消息
        * */
        for(int i=0;i<10;i++){
            channel.basicPublish("",QUEUE_NAME,null,(message+" "+i).getBytes("UTF-8"));
            System.out.println((message+" "+i)+" 消息发送完毕...");
        }
    }
}
--------
package com.day.controller;
import com.rabbitmq.client.*;
//消费者
public class Consumer {
    //队列的名称
    public static final String QUEUE_NAME="hello";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            try {
                Thread.sleep(30000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("C0接收到的消息: "+new String(message.getBody(),"UTF-8"));
            // 1.标记tag 2.不批量应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };
        //取消消费
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        //消费者消费消息
        /**
         * 1.消费哪个队列
         * 2.消费成功之后是否要自动应答 true代表自动应答 false代表手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         *
         * */
        System.out.println("C0等待接受消息...");
        channel.basicConsume(QUEUE_NAME,false,deliverCallback,cancelCallback);
    }
}
-------------
package com.day.controller;

import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

//消费者
public class Consumer1 {
    //队列的名称
    public static final String QUEUE_NAME="hello";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("C1接收到的消息: "+new String(message.getBody(),"UTF-8"));
            // 1.标记tag 2.不批量应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };
        //取消消费
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        //消费者消费消息
        /**
         * 1.消费哪个队列
         * 2.消费成功之后是否要自动应答 true代表自动应答 false代表手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         *
         * */
        System.out.println("C1等待接受消息...");
        channel.basicConsume(QUEUE_NAME,false,deliverCallback,cancelCallback);
    }
}
------------如果C0在等待过程中宕机或者发生异常,全部消息有C1处理------------------
C0等待接受消息...
C0接收到的消息: Hello World 0
--------------------
C1等待接受消息...
C1接收到的消息: Hello World 1
C1接收到的消息: Hello World 3
C1接收到的消息: Hello World 5
C1接收到的消息: Hello World 7
C1接收到的消息: Hello World 9
C1接收到的消息: Hello World 2
C1接收到的消息: Hello World 4
C1接收到的消息: Hello World 6
C1接收到的消息: Hello World 8

//4.队列RMQ持久化
//(1)channel.queueDeclare(QUEUE_NAME, true,false,false,null);
//(2)持久化后重启RMQ后仍然存在

//5.消息持久化
//(1)channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,(message+" "+i).getBytes("UTF-8"));
//(2)但是不是绝对的,如果想绝对要参考后面的发布确认章节

//6.不公平分发
//(1)轮训分发—>
//(2)问题在两个消费者就会出现,C1处理快,C0处理慢,而分发任务一致,就会出现能者不多劳。
//(3)由消费者设置分发策略: channel.basicQos(1);
package com.day.controller;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
//生产者,发送大量的消息
public class Producer {
    //队列名称
    public static final String QUEUE_NAME="hello";
    //发消息
    public static void main(String[] args) throws Exception {
        //获取信道
        Channel channel =RMQUtil.getChannel();
        //创建队列
        /**
         * 1.队列名称
         * 2.队列里面的消息是否持久化 默认情况消息存储在内存中 持久化存储在磁盘
         * 3.该队列是否只供一个消费者进行消费 是否进行消息共享 true可以多个消费者消费 false则不允许
         * 4.是否自动删除 最后一个消费者断开后 改队列是否自动删除 true自动删除 false反
         * 5.其他参数
        * */
        channel.queueDeclare(QUEUE_NAME, true,false,false,null);
        //发消息
        String message="Hello World";
        /**
         * 1.发送到那个交换机
         * 2.路由的key值 本次是队列的名称
         * 3.其他参数信息
         * 4.发送的消息
        * */
        for(int i=0;i<10;i++){
            channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,(message+" "+i).getBytes("UTF-8"));
            System.out.println((message+" "+i)+" 消息发送完毕...");
        }
    }
}
package com.day.controller;
import com.rabbitmq.client.*;
//消费者
public class Consumer {
    //队列的名称
    public static final String QUEUE_NAME="hello";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        //设置分发策略
        channel.basicQos(1);
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            try {
                Thread.sleep(30000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("C0接收到的消息: "+new String(message.getBody(),"UTF-8"));
            // 1.标记tag 2.不批量应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };
        //取消消费
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        //消费者消费消息
        /**
         * 1.消费哪个队列
         * 2.消费成功之后是否要自动应答 true代表自动应答 false代表手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         *
         * */
        System.out.println("C0等待接受消息...");
        channel.basicConsume(QUEUE_NAME,false,deliverCallback,cancelCallback);
    }
}
package com.day.controller;

import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
//消费者
public class Consumer1 {
    //队列的名称
    public static final String QUEUE_NAME="hello";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        //设置分发策略
        channel.basicQos(1);
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("C1接收到的消息: "+new String(message.getBody(),"UTF-8"));
            // 1.标记tag 2.不批量应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };
        //取消消费
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        //消费者消费消息
        /**
         * 1.消费哪个队列
         * 2.消费成功之后是否要自动应答 true代表自动应答 false代表手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         *
         * */
        System.out.println("C1等待接受消息...");
        channel.basicConsume(QUEUE_NAME,false,deliverCallback,cancelCallback);
    }
}
-------------------控制台信息---------------------------------
C0等待接受消息...
C0接收到的消息: Hello World 0
C1接收到的消息: Hello World 1
C1接收到的消息: Hello World 2
C1接收到的消息: Hello World 3
C1接收到的消息: Hello World 4
C1接收到的消息: Hello World 5
C1接收到的消息: Hello World 6
C1接收到的消息: Hello World 7
C1接收到的消息: Hello World 8
C1接收到的消息: Hello World 9

//7.预取值
//(1)就是预先指定C0分到多少,C1分到多少
//(2)也是消费者设置 channel.basicQos(3); channel.basicQos(7);
package com.day.controller;
import com.rabbitmq.client.*;
//消费者
public class Consumer {
    //队列的名称
    public static final String QUEUE_NAME="hello";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        //设置分发策略/预取值
        channel.basicQos(3);
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            try {
                Thread.sleep(30000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("C0接收到的消息: "+new String(message.getBody(),"UTF-8"));
            // 1.标记tag 2.不批量应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };
        //取消消费
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        //消费者消费消息
        /**
         * 1.消费哪个队列
         * 2.消费成功之后是否要自动应答 true代表自动应答 false代表手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         *
         * */
        System.out.println("C0等待接受消息...");
        channel.basicConsume(QUEUE_NAME,false,deliverCallback,cancelCallback);
    }
}
package com.day.controller;

import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
//消费者
public class Consumer1 {
    //队列的名称
    public static final String QUEUE_NAME="hello";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        //设置分发策略/预取值
        channel.basicQos(7);
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("C1接收到的消息: "+new String(message.getBody(),"UTF-8"));
            // 1.标记tag 2.不批量应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };
        //取消消费
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        //消费者消费消息
        /**
         * 1.消费哪个队列
         * 2.消费成功之后是否要自动应答 true代表自动应答 false代表手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         *
         * */
        System.out.println("C1等待接受消息...");
        channel.basicConsume(QUEUE_NAME,false,deliverCallback,cancelCallback);
    }
}
---控制台信息---
C1等待接受消息...
C1接收到的消息: Hello World 1
C1接收到的消息: Hello World 3
C1接收到的消息: Hello World 5
C1接收到的消息: Hello World 6
C1接收到的消息: Hello World 7
C1接收到的消息: Hello World 8
C1接收到的消息: Hello World 9
---
C0接收到的消息: Hello World 0
C0接收到的消息: Hello World 2
C0接收到的消息: Hello World 4

//8.发布确认
//(1)是解决消息不丢失的重要环节
//(2)生产者-发消息-队列hello-
//(3)1设置队列持久化->2设置消息持久化—>3发布确认(这里第3条才能确认消息真的保存在磁盘上了)
//(4)开启发布确认的方法 channel.confirmSelect();
//(5)单个确认发布 批量确认发布 异步确认发布三种方法
//(6)单个确认发布: 发一条确认一条,速度慢
//(7)批量确认发布: 34集
//(8)异步确认发布: 第三种最牛批,性能最厉害
package com.day.controller;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmCallback;
import com.rabbitmq.client.MessageProperties;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
//生产者,发送大量的消息
public class Producer {
    //队列名称
    public static final String QUEUE_NAME="hello";
    //发消息
    public static void main(String[] args) throws Exception {
        //单个确认 耗时31830ms
        //publicMessageIndividually();
        //批量确认 耗时1660ms
        //publicMessageBatch();
        //异步确认 耗时1102ms
        publicMessageAsync();
    }
    //单个确认
    public static void publicMessageIndividually() throws Exception{
        long startTime=System.currentTimeMillis();
        //获取信道
        Channel channel =RMQUtil.getChannel();
        //开启发布确认
        channel.confirmSelect();
        //创建队列
        /**
         * 1.队列名称
         * 2.队列里面的消息是否持久化 默认情况消息存储在内存中 持久化存储在磁盘
         * 3.该队列是否只供一个消费者进行消费 是否进行消息共享 true可以多个消费者消费 false则不允许
         * 4.是否自动删除 最后一个消费者断开后 改队列是否自动删除 true自动删除 false反
         * 5.其他参数
         * */
        channel.queueDeclare(QUEUE_NAME, true,false,false,null);
        //发消息
        String message="Hello World";
        /**
         * 1.发送到那个交换机
         * 2.路由的key值 本次是队列的名称
         * 3.其他参数信息
         * 4.发送的消息
         * */
        for(int i=0;i<1000;i++){
            channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,(message+" "+i).getBytes("UTF-8"));
            System.out.println((message+" "+i)+" 消息发送完毕...");
            //进行发布确认
            boolean flag=channel.waitForConfirms();
            if(flag){
                System.out.println("确认发送成功...");
            }else{
                System.err.println("不确认发送成功...");
            }
        }
        long endTime=System.currentTimeMillis();
        System.out.println("耗时"+(endTime-startTime)+"ms");
    }
    //批量发布确认
    public static void publicMessageBatch() throws Exception{
        long startTime=System.currentTimeMillis();
        //获取信道
        Channel channel =RMQUtil.getChannel();
        //开启发布确认
        channel.confirmSelect();
        //创建队列
        channel.queueDeclare(QUEUE_NAME, true,false,false,null);
        //发消息
        //批量确认消息大小
        int batchSize=100;
        String message="Hello World";
        for(int i=0;i<1000;i++){
            channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,(message+" "+i).getBytes("UTF-8"));
            System.out.println((message+" "+i)+" 消息发送完毕...");
            //判断达到100条,批量确认一次
            if(i%batchSize==0){
                boolean flag=channel.waitForConfirms();
                if(flag){
                    System.out.println("确认100条发送成功...");
                }else{
                    System.err.println("不确认100条发送成功...");
                }
            }
        }
        long endTime=System.currentTimeMillis();
        System.out.println("耗时"+(endTime-startTime)+"ms");
    }
    //异步确认发布
    //生产消息的时候一一编号,也叫给ID或者提取特征,有无问题broker会通知你
    //速度最快,效率最高,实用性最大
    public static void publicMessageAsync() throws Exception{
        long startTime=System.currentTimeMillis();
        //线程安全有序的一个哈希表 适用于高并发的情况下
        ConcurrentSkipListMap<Long,Object> concurrentSkipListMap=new ConcurrentSkipListMap<>();
        //获取信道
        Channel channel =RMQUtil.getChannel();
        //开启发布确认
        channel.confirmSelect();
        //消息监听器,监听消息是否发送成功了
        //确认成功的回调函数
        ConfirmCallback ackCallBack=(deliveryTag,multiple) ->{
            System.out.println("确认发送成功的消息 "+deliveryTag);
            //2.删除已经确认的消息
            //是否批量确认
            if(multiple){
                ConcurrentNavigableMap<Long,Object> concurrentNavigableMap=
                        concurrentSkipListMap.headMap(deliveryTag);
                System.out.println("即将删除 "+concurrentNavigableMap);
                concurrentNavigableMap.clear();
            }else{
                System.out.println("即将删除 "+concurrentSkipListMap.get(deliveryTag));
                concurrentSkipListMap.remove(deliveryTag);
            }
        };
        //确认失败的回调函数
        ConfirmCallback nackCallBack=(deliveryTag,multiple) ->{
            System.err.println("不能确认发送成功的消息 "+deliveryTag);
            //打印一下未确认的消息
            String temp= (String) concurrentSkipListMap.get(deliveryTag);
            System.out.println("不能确认发送成功的消息 "+temp);
        };
        channel.addConfirmListener(ackCallBack,nackCallBack);
        //创建队列
        channel.queueDeclare(QUEUE_NAME, true,false,false,null);
        String message="异步确认... Hello World";
        for(int i=0;i<1000;i++){
            //1.此处记录所有要发送的消息
            concurrentSkipListMap.put(channel.getNextPublishSeqNo()-2,(message+" "+i));
            channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,(message+" "+i).getBytes("UTF-8"));
            //System.out.println(channel.getNextPublishSeqNo());
        }
        Thread.sleep(5000);
        System.err.println(concurrentSkipListMap.size());
        long endTime=System.currentTimeMillis();
        System.out.println("耗时"+(endTime-startTime)+"ms");
    }
}

三.发布订阅

//1.交换机
//(1)生产者-发消息-队列-消费者
//(2)原来的消息只能被消费一次,如果做到1个消息被多个消费者消费呢?
//(3)生产者-交换机-RoutingKey-队列(仍是消息只能被消费一次)-消费者
//          -交换机-RoutingKey-队列(仍是消息只能被消费一次)-消费者...
//(4)RMQ把消息给队列,必须走交换机,不指定是默认的交换机。
//(5)交换机的类型:直接direct 主题topic 标题headers 扇出fanout 无名""
//(6)绑定bindings 用RoutingKey可以区分队列

//2.Fanout 广播 发布订阅模式 根源是发把消息发给交换机,然后用RoutingKey关联到多个队列,给多个队列同时发消息
//(1)P-X-n个Q-n个消费者
package com.day.controller;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
import java.util.Scanner;
//生产者,发送大量的消息
public class Producer {
    //交换机名
    public static final String EXCHANGE_NAME="logs";
    //发消息
    public static void main(String[] args) throws Exception {
        Channel channel =RMQUtil.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
        Scanner scanner=new Scanner(System.in);
        while(scanner.hasNext()){
            String message=scanner.next();
            channel.basicPublish(EXCHANGE_NAME,"", MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息: "+message);
        }
    }
}
--------------
package com.day.controller;
import com.rabbitmq.client.*;
//消费者
public class Consumer {
    //队列的名称
    public static final String QUEUE_NAME="public0";
    //交换机名
    public static final String EXCHANGE_NAME="logs";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        //队列,生成临时队列,队列名称是随机的
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //绑定交换机与队列
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
        System.out.println("C0等待,把接受到的消息打印在屏幕上...");
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println("C0接收到的消息: "+new String(message.getBody(),"UTF-8"));
        };
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}
--------------------------------
package com.day.controller;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
//消费者
public class Consumer1 {
    //队列的名称
    public static final String QUEUE_NAME="public1";
    public static final String EXCHANGE_NAME="logs";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        //队列,生成临时队列,队列名称是随机的
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //绑定交换机与队列
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
        System.out.println("C1等待,把接受到的消息打印在屏幕上...");
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println("C1接收到的消息: "+new String(message.getBody(),"UTF-8"));
        };
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

//3.Direct Exchange直接交换机
//(1)路由模式,想给谁传给谁传,根据RoutingKey不同指定轻松实现。绑定相同的RoutingKey就是fanout模式了
package com.day.controller;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
import java.util.Scanner;
//生产者,发送大量的消息
public class Producer {
    //交换机名
    public static final String EXCHANGE_NAME="direct_logs";
    public static final String CONSOLE_INFO="info";
    public static final String CONSOLE_WARNING="warning";
    public static final String DISK_ERROR="error";
    //发消息
    public static void main(String[] args) throws Exception {
        Channel channel =RMQUtil.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        Scanner scanner=new Scanner(System.in);
        System.out.println("生产者准备从控制台获取信息发送...");
        while(scanner.hasNext()){
            String message=scanner.next();
            channel.basicPublish(EXCHANGE_NAME,DISK_ERROR, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息: "+message);
        }
    }
}
----------
package com.day.controller;
import com.rabbitmq.client.*;
//消费者
public class Consumer {
    //队列的名称
    public static final String QUEUE_NAME="console";
    //交换机名
    public static final String EXCHANGE_NAME="direct_logs";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.DIRECT);
        //队列,生成临时队列,队列名称是随机的
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //绑定交换机与队列
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"info");
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"warning");
        System.out.println("C0等待,把接受到的消息打印在屏幕上...");
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println("C0接收到的消息: "+new String(message.getBody(),"UTF-8"));
        };
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}
----------
package com.day.controller;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
//消费者
public class Consumer1 {
    //队列的名称
    public static final String QUEUE_NAME="disk";
    public static final String EXCHANGE_NAME="direct_logs";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        //队列,生成临时队列,队列名称是随机的
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //绑定交换机与队列
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
        System.out.println("C1等待,把接受到的消息打印在屏幕上...");
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println("C1接收到的消息: "+new String(message.getBody(),"UTF-8"));
        };
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

//4.Topics 主题交换机
//(1)P-X-RoutingKey-
//(2)主题交换机的RoutingKey不能随意写,必须是.隔开的单词列表;*代表一个单词,#代表0或多个单词
//(3)支持匹配模式,*.orange.* lazy.# *.*.rabbit
//(4)如果绑定# 则这个对垒将接受所有数据,有点像fanout
//(5)如果没有# *就是direct交换机,这不就是多个正则吗
package com.day.controller;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
//生产者,发送大量的消息
public class Producer {
    //交换机名
    public static final String EXCHANGE_NAME="topic_logs";
    //发消息
    public static void main(String[] args) throws Exception {
        Channel channel =RMQUtil.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        Map<String,String> bindingKeyMap=new HashMap<>();
        bindingKeyMap.put("quick.orange.rabbit","被队列Q1Q2接收到");
        bindingKeyMap.put("lazy.orange.elephant","被队列Q1Q2接收到");
        bindingKeyMap.put("quick.orange.fox","被队列Q1接收到");
        bindingKeyMap.put("lazy.brown.fox","被队列Q2接收到");
        bindingKeyMap.put("lazy.pink.rabbit","Q2接收一次");
        bindingKeyMap.put("quick.brown.fox","被丢弃");
        bindingKeyMap.put("quick.orange.male.rabbit","被丢弃");
        bindingKeyMap.put("lazy.orange.male.rabbit","Q2");
        for(String key:bindingKeyMap.keySet()){
            String message=bindingKeyMap.get(key);
            //队列名称是通过key指定的哦
            channel.basicPublish(EXCHANGE_NAME,key,null,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息: "+message);
        }
    }
}
------
package com.day.controller;
import com.rabbitmq.client.*;
//消费者
public class Consumer {
    //队列的名称
    public static final String QUEUE_NAME="Q1";
    //交换机名
    public static final String EXCHANGE_NAME="topic_logs";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.TOPIC);
        //队列,生成临时队列,队列名称是随机的
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //绑定交换机与队列
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"*.orange.*");
        System.out.println("C0等待,把接受到的消息打印在屏幕上...");
        //接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println("C0接收到的消息: "+new String(message.getBody(),"UTF-8")
            +"。通过队列 "+QUEUE_NAME+"。绑定键: "+message.getEnvelope().getRoutingKey());
        };
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}
------
package com.day.controller;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
//消费者
public class Consumer1 {
    //队列的名称
    public static final String QUEUE_NAME="Q2";
    public static final String EXCHANGE_NAME="topic_logs";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.TOPIC);
        //队列,生成临时队列,队列名称是随机的
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //绑定交换机与队列
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"*.*.rabbit");
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"lazy.#");
        System.out.println("C1等待,把接受到的消息打印在屏幕上...");
        //接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println("C1接收到的消息: "+new String(message.getBody(),"UTF-8")
                    +"。通过队列 "+QUEUE_NAME+"。绑定键: "+message.getEnvelope().getRoutingKey());
        };
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

//5.死信
//(1)消息拒绝 效果过期 队列达到最大长度
//(2)开启消费者0,然后关闭,然后运行生产者发消息,会发现消息在normal_queue,10S后在dead_queue的变化
//(3)无法被消费的消息,由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信
//(4)死信队列机制,消费时发生异常,将消息放到死信队列中,防止消息的丢失
//(5)死信的来源:消息TTL过期、队列达到最大长度、消息被拒绝并且requeue=false;
package com.day.controller;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import java.util.HashMap;
import java.util.Map;
//生产者,发送大量的消息
public class Producer {
    //交换机名
    public static final String NORMAL_EXCHANGE="normal_exchange";
    //发消息
    public static void main(String[] args) throws Exception {
        Channel channel =RMQUtil.getChannel();
        //单位是ms 即设置10S
        AMQP.BasicProperties basicProperties=new AMQP.BasicProperties()
                .builder().expiration("10000").build();
        for(int i=0;i<10;i++){
            String message="生产者发送的消息: "+i;
            //TTL -TIME TO LIVE
            channel.basicPublish(NORMAL_EXCHANGE,"normalBindLine",basicProperties,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息: "+message);
        }
    }
}
------
package com.day.controller;
import com.rabbitmq.client.*;
import java.util.HashMap;
import java.util.Map;
//消费者
public class Consumer {
    //普通交换机名
    public static final String NORMAL_EXCHANGE="normal_exchange";
    //死信交换机
    public static final String DEAD_EXCHANGE="dead_exchange";
    //两个队列
    public static final String NORMAL_QUEUE="normal_queue";
    public static final String DEAD_QUEUE="dead_queue";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        //-----------------------------------
        //声明交换机
        channel.exchangeDeclare(NORMAL_EXCHANGE,BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
        //-----------------------------------
        //声明队列-要使用特殊的参数才能转发到死信队列
        Map<String,Object> argumentsMap=new HashMap<>();
        //设置转发的交换机
        //argumentsMap.put("x-message-ttl",10000);//也可以在发消息时指定
        argumentsMap.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        //设置RoutingKey
        argumentsMap.put("x-dead-letter-routing-key","deadBindLine");
        channel.queueDeclare(NORMAL_QUEUE,true,false,false,argumentsMap);
        //死信队列
        channel.queueDeclare(DEAD_QUEUE,true,false,false,null);
        //-----------------------------------
        //绑定交换机与队列
        channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"normalBindLine");
        channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"deadBindLine");
        //-----------------------------------
        System.out.println("C0等待,把接受到的消息打印在屏幕上...");
        //接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println("C0接收到的消息: "+new String(message.getBody(),"UTF-8")
                    +"。通过队列 "+NORMAL_QUEUE+"。绑定键: "+message.getEnvelope().getRoutingKey());
        };
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,cancelCallback);
    }
}
------
package com.day.controller;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
//消费者
public class Consumer1 {
    public static final String DEAD_QUEUE="dead_queue";

    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();

        System.out.println("C1等待,把接受到的消息打印在屏幕上...");
        //接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println("C1接收到的消息: "+new String(message.getBody(),"UTF-8")
                    +"。通过队列 "+DEAD_QUEUE+"。绑定键: "+message.getEnvelope().getRoutingKey());
        };
        CancelCallback cancelCallback=consumerTag ->{
            System.out.println(consumerTag+" 消费消息被中断...");
        };
        channel.basicConsume(DEAD_QUEUE,true,deliverCallback,cancelCallback);
    }
}

//5.死信
//(1)队列达到最大长度
package com.day.controller;
import com.rabbitmq.client.Channel;
//生产者,发送大量的消息
public class Producer {
    //交换机名
    public static final String NORMAL_EXCHANGE="normal_exchange";
    //发消息
    public static void main(String[] args) throws Exception {
        Channel channel =RMQUtil.getChannel();
        //单位是ms 即设置10S
       /* AMQP.BasicProperties basicProperties=new AMQP.BasicProperties()
                .builder().expiration("10000").build();*/
        for(int i=0;i<10;i++){
            String message="生产者发送的消息: "+i;
            channel.basicPublish(NORMAL_EXCHANGE,"normalBindLine",null,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息: "+message);
        }
    }
}
------
package com.day.controller;
import com.rabbitmq.client.*;
import java.util.HashMap;
import java.util.Map;
//消费者
public class Consumer {
    //普通交换机名
    public static final String NORMAL_EXCHANGE="normal_exchange";
    //死信交换机
    public static final String DEAD_EXCHANGE="dead_exchange";
    //两个队列
    public static final String NORMAL_QUEUE="normal_queue";
    public static final String DEAD_QUEUE="dead_queue";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        //-----------------------------------
        //声明交换机
        channel.exchangeDeclare(NORMAL_EXCHANGE,BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
        //-----------------------------------
        //声明队列-要使用特殊的参数才能转发到死信队列
        Map<String,Object> argumentsMap=new HashMap<>();
        //设置转发的交换机
        //argumentsMap.put("x-message-ttl",10000);//也可以在发消息时指定
        argumentsMap.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        //设置RoutingKey
        argumentsMap.put("x-dead-letter-routing-key","deadBindLine");
        argumentsMap.put("x-max-length",6);
        channel.queueDeclare(NORMAL_QUEUE,true,false,false,argumentsMap);
        //死信队列
        channel.queueDeclare(DEAD_QUEUE,true,false,false,null);
        //-----------------------------------
        //绑定交换机与队列
        channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"normalBindLine");
        channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"deadBindLine");
        //-----------------------------------
        System.out.println("C0等待,把接受到的消息打印在屏幕上...");
        //接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println("C0接收到的消息: "+new String(message.getBody(),"UTF-8")
                    +"。通过队列 "+NORMAL_QUEUE+"。绑定键: "+message.getEnvelope().getRoutingKey());
        };
        CancelCallback cancelCallback=consumerTage ->{
            System.out.println(consumerTage+" 消费消息被中断...");
        };
        channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,cancelCallback);
    }
}
------
package com.day.controller;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
//消费者
public class Consumer1 {
    public static final String DEAD_QUEUE="dead_queue";

    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();

        System.out.println("C1等待,把接受到的消息打印在屏幕上...");
        //接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println("C1接收到的消息: "+new String(message.getBody(),"UTF-8")
                    +"。通过队列 "+DEAD_QUEUE+"。绑定键: "+message.getEnvelope().getRoutingKey());
        };
        CancelCallback cancelCallback=consumerTag ->{
            System.out.println(consumerTag+" 消费消息被中断...");
        };
        channel.basicConsume(DEAD_QUEUE,true,deliverCallback,cancelCallback);
    }
}

//5.死信
//(1)消息被拒绝
package com.day.controller;
import com.rabbitmq.client.Channel;
//生产者,发送大量的消息
public class Producer {
    //交换机名
    public static final String NORMAL_EXCHANGE="normal_exchange";
    //发消息
    public static void main(String[] args) throws Exception {
        Channel channel =RMQUtil.getChannel();
        //单位是ms 即设置10S
       /* AMQP.BasicProperties basicProperties=new AMQP.BasicProperties()
                .builder().expiration("10000").build();*/
        for(int i=0;i<10;i++){
            String message="生产者发送的消息: "+i;
            channel.basicPublish(NORMAL_EXCHANGE,"normalBindLine",null,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息: "+message);
        }
    }
}
------
package com.day.controller;
import com.rabbitmq.client.*;
import java.util.HashMap;
import java.util.Map;
//消费者
public class Consumer {
    //普通交换机名
    public static final String NORMAL_EXCHANGE="normal_exchange";
    //死信交换机
    public static final String DEAD_EXCHANGE="dead_exchange";
    //两个队列
    public static final String NORMAL_QUEUE="normal_queue";
    public static final String DEAD_QUEUE="dead_queue";
    //接收消息
    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();
        //-----------------------------------
        //声明交换机
        channel.exchangeDeclare(NORMAL_EXCHANGE,BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
        //-----------------------------------
        //声明队列-要使用特殊的参数才能转发到死信队列
        Map<String,Object> argumentsMap=new HashMap<>();
        //设置转发的交换机
        //argumentsMap.put("x-message-ttl",10000);//也可以在发消息时指定
        argumentsMap.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        //设置RoutingKey
        argumentsMap.put("x-dead-letter-routing-key","deadBindLine");
        channel.queueDeclare(NORMAL_QUEUE,true,false,false,argumentsMap);
        //死信队列
        channel.queueDeclare(DEAD_QUEUE,true,false,false,null);
        //-----------------------------------
        //绑定交换机与队列
        channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"normalBindLine");
        channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"deadBindLine");
        //-----------------------------------
        System.out.println("C0等待,把接受到的消息打印在屏幕上...");
        //接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            String msg=new String(message.getBody(),"UTF-8");
            if(msg.contains("5")){
                //拒绝且不放回原队列
                channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
            }else{
                System.out.println("C0接收到的消息: "+new String(message.getBody(),"UTF-8")
                        +"。通过队列 "+NORMAL_QUEUE+"。绑定键: "+message.getEnvelope().getRoutingKey());
                channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
            }
        };
        CancelCallback cancelCallback=consumerTag ->{
            System.out.println(consumerTag+" 消费消息被中断...");
        };
        //必须开启手动应答
        channel.basicConsume(NORMAL_QUEUE,false,deliverCallback,cancelCallback);
    }
}
------
package com.day.controller;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
//消费者
public class Consumer1 {
    public static final String DEAD_QUEUE="dead_queue";

    public static void main(String[] args) throws Exception {
        //信道
        Channel channel=RMQUtil.getChannel();

        System.out.println("C1等待,把接受到的消息打印在屏幕上...");
        //接受消息
        DeliverCallback deliverCallback=(consumerTag,message) ->{
            System.out.println("C1接收到的消息: "+new String(message.getBody(),"UTF-8")
                    +"。通过队列 "+DEAD_QUEUE+"。绑定键: "+message.getEnvelope().getRoutingKey());
        };
        CancelCallback cancelCallback=consumerTag ->{
            System.out.println(consumerTag+" 消费消息被中断...");
        };
        channel.basicConsume(DEAD_QUEUE,true,deliverCallback,cancelCallback);
    }
}
//56集

四.Spinrgboot整合RMQ

//1.延迟队列
//(1)延迟队列本质上就是TTL过期的死信队列
//(2)P-X(普通交换机)-Y(延迟交换机)-3个队列,实现两种不同的延迟效果10S和40S
//(3)花费了我2个小时因为Consumer类中的Channel导错了包,应该为import com.rabbitmq.client.Channel;
//(4)学会了lombok.extern.slf4j.Slf4j与yml文件配置log.info的使用
//(5)见识了Springboot注解配置的强大。只要功夫深,问题能解决
------POM.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>SpringBoot</groupId>
    <artifactId>springboot-maven</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-maven</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <distributionManagement>
        <repository>
            <id>releases</id>
            <name>Nexus Release Repository</name>
            <url>http://wdfgdzx.top:8081/nexus/content/repositories/releases/</url>
        </repository>
        <snapshotRepository>
            <id>snapshots</id>
            <name>Nexus Snapshot Repository</name>
            <url>http://wdfgdzx.top:8081/nexus/content/repositories/snapshots/</url>
        </snapshotRepository>
    </distributionManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
        </dependency>
        <!--整合MyBatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        <!--数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>6.0.6</version>
        </dependency>
        <!--redis依赖包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--   <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-data-mongodb</artifactId>
           </dependency>-->
        <!-- Thymeleaf 自动配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- 允许使用非严格的 HTML 语法 -->
        <dependency>
            <groupId>net.sourceforge.nekohtml</groupId>
            <artifactId>nekohtml</artifactId>
            <version>1.9.22</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>
        <!--SpringBoot热部署配置 -->
        <!--  <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-devtools</artifactId>
              <scope>runtime</scope>
              <optional>true</optional>
          </dependency>-->
        <dependency>
            <groupId>org.jetbrains</groupId>
            <artifactId>annotations</artifactId>
            <version>13.0</version>
            <scope>compile</scope>
        </dependency>
        <!--json-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.46</version>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.9</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.9.1</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.okio</groupId>
            <artifactId>okio</artifactId>
            <version>1.15.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.6</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.10</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
            <version>4.5.6</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>net.minidev</groupId>
            <artifactId>json-smart</artifactId>
        </dependency>

        <!--Rich文本开始-->
        <dependency>
            <groupId>com.gitee.qdbp.thirdparty</groupId>
            <artifactId>ueditor</artifactId>
            <version>1.4.3.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.json/json -->
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20160810</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.9</version>
        </dependency>
        <!--Rich文本结束-->
        <!-- 读取Excel -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.2</version>
        </dependency>
        <!--顺丰本地jar包放置与引入-->
        <dependency>
            <groupId>com.iflytek.msp.sfexpress</groupId>
            <artifactId>express-sdk</artifactId>
            <version>2.1.5</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/libs/sf-csim-express-sdk-V2.1.5.jar</systemPath>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.9.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
           <!-- <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.1.5.RELEASE</version>
            </plugin>-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <includeSystemScope>true</includeSystemScope>
                </configuration>
                <version>2.1.5.RELEASE</version>
            </plugin>
            <!-- <plugin>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
                 <configuration>
                     <fork>true</fork>
                     <addResources>true</addResources>
                 </configuration>
             </plugin>-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
------yml
server:
  port: 8001
  tomcat:
    accesslog:
      buffered: true
      directory: /home/A/SpringBoot
      enabled: true
      file-date-format: .yyyy-MM-dd
      pattern: common
      prefix: access_log
      rename-on-rotate: false
      request-attributes-enabled: false
      rotate: true
      suffix: .log
spring:
  #devtools:
  #restart:
  #enabled=true: #支持热部署  可能导致重启,然后非实时语音转写报错。
  rabbitmq:
    host: wdfgdzx.top
    port: 5672
    username: xlliu24
    password: s19911009!
  redis: #配置redis
    host: wdfgdzx.top
    prot: 6379
  datasource:
    name: mydb
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://wdfgdzx.top:3306/mydb?serverTimezone=GMT%2b8
    username: root
    password: s19911009!
    driver-class-name: com.mysql.cj.jdbc.Driver
  thymeleaf:
    prefix: classpath:/site/
    check-template-location: true  #check-tempate-location: 检查模板路径是否存在
    enabled: true
    encoding: UTF-8
    content-type: text/html
    cache: false
    mode: HTML
    suffix: .html
  servlet:
    multipart: #配置文件上传
      max-file-size: 1000MB #设置上传的单个文件最大值,单位可以是 MB、KB,默认为 1MB
      max-request-size: 1024MB #设置多文件上传时,单次内多个文件的总量的最大值,单位可以是 MB、KB,默认为 10 M
mybatis:
  mapper-locations: classpath*:/mybatis/*Mapper.xml
logging:
  level:
    root: info
-------交换机声明、队列声明、绑定
package com.day.controller;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RMQConfig {
    //普通交换机
    private static final String COMMON_EXCHANGE="X";
    //死信交换机
    private static final String DEAD_EXCHANGE="Y";
    //普通队列
    private static final String COMMON_QUEUE_A="QA";
    private static final String COMMON_QUEUE_B="QB";
    //死信队列
    private static final String DEAD_QUEUE_D="QD";
    //----------------------------------
    //声明交换
    @Bean("commonExchange")
    public DirectExchange commonExchange(){
        return new DirectExchange(COMMON_EXCHANGE);
    }
    @Bean("deadExchange")
    public DirectExchange deadExchange(){
        return new DirectExchange(DEAD_EXCHANGE);
    }
    //----------------------------------
    //声明队列
    @Bean("commonQueueA")
    public Queue commonQueueA(){
        Map<String,Object> arguments=new HashMap<>(3);
        //设置死信交换机
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        //设置死信Routing Key
        arguments.put("x-dead-letter-routing-key","YD");
        //设置TTL
        arguments.put("x-message-ttl",10000);
        return QueueBuilder.durable(COMMON_QUEUE_A)
                .withArguments(arguments)
                .build();
    }
    @Bean("commonQueueB")
    public Queue commonQueueB(){
        Map<String,Object> arguments=new HashMap<>(3);
        //设置死信交换机
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        //设置死信Routing Key
        arguments.put("x-dead-letter-routing-key","YD");
        //设置TTL
        arguments.put("x-message-ttl",40000);
        return QueueBuilder.durable(COMMON_QUEUE_B)
                .withArguments(arguments)
                .build();
    }
    @Bean("deadQueueD")
    public Queue deadQueueD(){
        return QueueBuilder.durable(DEAD_QUEUE_D)
                .build();
    }
    //----------------------------------
    //绑定
    @Bean
    public Binding commonQueueABindingCommonExchange(
            @Qualifier("commonQueueA") Queue commonQueueA,
            @Qualifier("commonExchange") DirectExchange commonExchange){
        return BindingBuilder
                .bind(commonQueueA).to(commonExchange)
                .with("XA");
    }
    @Bean
    public Binding commonQueueBBindingCommonExchange(
            @Qualifier("commonQueueB") Queue commonQueueB,
            @Qualifier("commonExchange") DirectExchange commonExchange){
        return BindingBuilder
                .bind(commonQueueB).to(commonExchange)
                .with("XB");
    }
    @Bean
    public Binding deadQueueDBindingDeadExchange(
            @Qualifier("deadQueueD") Queue deadQueueD,
            @Qualifier("deadExchange") DirectExchange deadExchange){
        return BindingBuilder
                .bind(deadQueueD).to(deadExchange)
                .with("YD");
    }
}
------生产者 http://localhost:8001/ttl/sendMsg/Hello
package com.day.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.Serializable;
import java.util.Date;
//发送延迟消息
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMsgController implements Serializable{
    @Autowired
    private RabbitTemplate rabbitTemplate;
    //开始发消息
    @GetMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable String message){
        //System.out.println("当前时间: "+new Date().toString()+"发送一条信息给两个TTL队列 "+message);
        log.info("当前时间: {},发送一条信息给两个TTL队列:{}",
                new Date().toString(),message);
        rabbitTemplate.convertAndSend("X","XA",
                "消息来自ttl为10s的队列: "+message);
        rabbitTemplate.convertAndSend("X","XB",
                "消息来自ttl为40s的队列: "+message);
    }
}
-----消费者
package com.day.controller;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Date;
//消费者
@Slf4j
@Component
public class Consumer implements Serializable {
    @RabbitListener(queues="QD")
    public void receiveD(Message message, Channel channel) throws Exception{
        byte[] body=message.getBody();
        String msg=new String(body, Charset.forName("UTF-8"));
        log.info("当前时间:{},收到死信队列的消息:{}",
                new Date().toString(),msg);
    }
}
------SwaggerConfig 非必须
package com.day.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket webApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .build();
    }
    private ApiInfo webApiInfo(){
        return new ApiInfoBuilder()
                .title("RMQ接口文档")
                .description("本文描述了RMQ的微服务接口定义")
                .version("1.0")
                .contact(new Contact("wdfgdzx","http://wdfgdzx.top","wdfgdzx@163.com"))
                .build();
    }
}

image.png

//2.延迟队列的优化
package com.day.controller;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RMQConfig {
    //普通交换机
    private static final String COMMON_EXCHANGE="X";
    //死信交换机
    private static final String DEAD_EXCHANGE="Y";
    //普通队列
    private static final String COMMON_QUEUE_A="QA";
    private static final String COMMON_QUEUE_B="QB";
    //延迟队列优化
    private static final String COMMON_QUEUE_C="QC";
    //死信队列
    private static final String DEAD_QUEUE_D="QD";
    //----------------------------------
    //声明交换
    @Bean("commonExchange")
    public DirectExchange commonExchange(){
        return new DirectExchange(COMMON_EXCHANGE);
    }
    @Bean("deadExchange")
    public DirectExchange deadExchange(){
        return new DirectExchange(DEAD_EXCHANGE);
    }
    //----------------------------------
    //声明队列
    @Bean("commonQueueA")
    public Queue commonQueueA(){
        Map<String,Object> arguments=new HashMap<>(3);
        //设置死信交换机
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        //设置死信Routing Key
        arguments.put("x-dead-letter-routing-key","YD");
        //设置TTL
        arguments.put("x-message-ttl",10000);
        return QueueBuilder.durable(COMMON_QUEUE_A)
                .withArguments(arguments).build();
    }
    //绑定
    @Bean
    public Binding commonQueueABindingCommonExchange(
            @Qualifier("commonQueueA") Queue commonQueueA,
            @Qualifier("commonExchange") DirectExchange commonExchange){
        return BindingBuilder
                .bind(commonQueueA).to(commonExchange).with("XA");
    }

    //声明队列
    @Bean("commonQueueB")
    public Queue commonQueueB(){
        Map<String,Object> arguments=new HashMap<>(3);
        //设置死信交换机
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        //设置死信Routing Key
        arguments.put("x-dead-letter-routing-key","YD");
        //设置TTL
        arguments.put("x-message-ttl",40000);
        return QueueBuilder.durable(COMMON_QUEUE_B)
                .withArguments(arguments).build();
    }
    //绑定
    @Bean
    public Binding commonQueueBBindingCommonExchange(
            @Qualifier("commonQueueB") Queue commonQueueB,
            @Qualifier("commonExchange") DirectExchange commonExchange){
        return BindingBuilder
                .bind(commonQueueB).to(commonExchange).with("XB");
    }

    //声明队列
    @Bean("commonQueueC")
    public Queue commonQueueC(){
        Map<String,Object> arguments=new HashMap<>(3);
        //设置死信交换机
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        //设置死信Routing Key
        arguments.put("x-dead-letter-routing-key","YD");
        return QueueBuilder.durable(COMMON_QUEUE_C)
                .withArguments(arguments).build();
    }
    //绑定
    @Bean
    public Binding commonQueueCBindingCommonExchange(
            @Qualifier("commonQueueC") Queue commonQueueC,
            @Qualifier("commonExchange") DirectExchange commonExchange){
        return BindingBuilder
                .bind(commonQueueC).to(commonExchange).with("XC");
    }

    //声明队列
    @Bean("deadQueueD")
    public Queue deadQueueD(){
        return QueueBuilder.durable(DEAD_QUEUE_D).build();
    }
    //绑定
    @Bean
    public Binding deadQueueDBindingDeadExchange(
            @Qualifier("deadQueueD") Queue deadQueueD,
            @Qualifier("deadExchange") DirectExchange deadExchange){
        return BindingBuilder
                .bind(deadQueueD).to(deadExchange).with("YD");
    }
}
-----------------------------------
package com.day.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Date;
//发送延迟消息
//打印日志的注解
@Slf4j
@RestController
@RequestMapping("/ttl")
public class ProducerController implements Serializable{
    @Resource
    private RabbitTemplate rabbitTemplate;
    //开始发消息
    @GetMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable String message){
        //System.out.println("当前时间: "+new Date().toString()+"发送一条信息给两个TTL队列 "+message);
        log.info("当前时间:{},发送一条信息给两个TTL队列:{}",
                new Date(),message);
        rabbitTemplate.convertAndSend("X","XA",
                "消息来自ttl为10s的队列: "+message);
        rabbitTemplate.convertAndSend("X","XB",
                "消息来自ttl为40s的队列: "+message);
    }
    //开始发消息 消息 TTL
    @GetMapping("/sendExpireMsg/{message}/{ttlTime}")
    public void sendMsg(@PathVariable String message,@PathVariable String ttlTime){
        log.info("当前时间:{},发送一条时长{}ms信息给TTL队列QC:{}",
                new Date(),ttlTime,message);
        rabbitTemplate.convertAndSend("X","XC",message,msg ->{
            msg.getMessageProperties().setExpiration(ttlTime);
            return msg;
        });
    }
}
------------------------------------------
package com.day.controller;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Date;
//消费者
@Slf4j
@Component
public class Consumer implements Serializable {
    @RabbitListener(queues="QD")
    public void receiveD(Message message, Channel channel) throws Exception{
        byte[] body=message.getBody();
        String msg=new String(body, Charset.forName("UTF-8"));
        log.info("当前时间:{},收到死信队列的消息:{}",
                new Date().toString(),msg);
    }
}

image.png

1635686907259023471.png

//3.日志文件配置与使用
配置-------------
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。
                 当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration  scan="true" scanPeriod="10 seconds">
    <contextName>logback-spring</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
    <property name="logging.path" value="src/main/resources/static/client" />
    <!--0. 日志格式和颜色渲染 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <!--1. 输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!--2. 输出到文档-->
    <!-- 2.1 level为 DEBUG 日志,时间滚动输出  -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文档的路径及文档名 -->
        <file>${logging.path}/web_debug.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志归档 -->
            <fileNamePattern>${logging.path}/web-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文档保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文档只记录debug级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 2.2 level为 INFO 日志,时间滚动输出  -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文档的路径及文档名 -->
        <file>${logging.path}/web_info.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${logging.path}/web-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文档保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文档只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 2.3 level为 WARN 日志,时间滚动输出  -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文档的路径及文档名 -->
        <file>${logging.path}/web_warn.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logging.path}/web-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文档保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文档只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 2.4 level为 ERROR 日志,时间滚动输出  -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文档的路径及文档名 -->
        <file>${logging.path}/web_error.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logging.path}/web-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文档保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文档只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!--
        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
        以及指定<appender>。<logger>仅有一个name属性,
        一个可选的level和一个可选的addtivity属性。
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
              还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
              如果未设置此属性,那么当前logger将会继承上级的级别。
        addtivity:是否向上级logger传递打印信息。默认是true。
        <logger name="org.springframework.web" level="info"/>
        <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
    -->
    <!--
        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
        第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
        第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
        【logging.level.org.mybatis=debug logging.level.dao=debug】
     -->
    <!--
        root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
        不能设置为INHERITED或者同义词NULL。默认是DEBUG
        可以包含零个或多个元素,标识这个appender将会添加到这个logger。
    -->
    <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
    <logger name="org.springframework" level="INFO"></logger>
    <logger name="org.mybatis" level="INFO"></logger>
    <logger name="org.apache.zookeeper" level="INFO"></logger>
    <!-- 4. 最终的策略 -->
    <!-- 4.1 开发环境:打印控制台-->
    <springProfile name="dev">
        <logger name="com.dowin.globalvillage.controller" level="debug"/><!-- 修改此处扫描包名 -->
    </springProfile>
    <root level="debug">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="DEBUG_FILE" />
        <appender-ref ref="INFO_FILE" />
        <appender-ref ref="WARN_FILE" />
        <appender-ref ref="ERROR_FILE" />
    </root>
    <!--4.2 生产环境:输出到文档-->
    <springProfile name="pro">
        <root level="info">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="DEBUG_FILE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="WARN_FILE" />
        </root>
    </springProfile>
</configuration>
使用------------
package com.day.controller;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Date;
//消费者
@Slf4j
@Component
public class Consumer implements Serializable {
    @RabbitListener(queues="QD")
    public void receiveD(Message message, Channel channel) throws Exception{
        byte[] body=message.getBody();
        String msg=new String(body, Charset.forName("UTF-8"));
        log.info("当前时间:{},收到死信队列的消息:{}",
                new Date().toString(),msg);
    }
}

//三.发布订阅

//1.延迟队列优化    
//(1)每个队列只对应一个延迟,如果面对变化的需求,怎么解决呢?    
//(2)写个传ttlTime的控制方法,但是发现发送20秒   发送2秒,都是一个时间接受到,为什么?    
//(3)RMQ只会检查第一个消息是否过期,就算是第二个消息延迟很短,第二个消息也不会优先执行,怎么弥补呢?    
//(4)延迟队列(基于插件的)下载插件rabbitmq_delayed_message_exchange-3.8.0.ez   百度网盘找    
//(5)放置到/usr/local/rabbitmq/plugins目录下    
//(6)安装 rabbitmq-plugins enable   rabbitmq_delayed_message_exchange    
//(7)rabbitmqctl stop(停止)    rabbitmq-server -detached(启动)    
//(8)重启看到x-delayed-message就代表成功。    
//(9)写代码    
------------配置
package com.day.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DelayedQueueConfig {
    //交换机 队列 routingKey
    //队列
    public static final String DELAYED_QUEUE_NAME="delayed.queue";
    //交换机
    public static final String DELAYED_EXCHANGE_NAME="delayed.exchange";
    //routingKey
    public static final String DELAYED_ROUTING_KEY="delayed.routingKey";
    //声明交换机
    @Bean
    public CustomExchange delayedExchange(){
        Map<String,Object> arguments=new HashMap<>();
        arguments.put("x-delayed-type","direct");
        //交换机名称、类型、持久化、自动删除、其他参数
        return new CustomExchange(DELAYED_EXCHANGE_NAME,"x-delayed-message",
                true,false,arguments);
    }
    //队列
    @Bean
    public Queue delayedQueue(){
        return new Queue(DELAYED_QUEUE_NAME);
    }
    //绑定
    @Bean
    public Binding delayedQueueBindingDelayedExchange(@Qualifier("delayedQueue") Queue delayedQueue,
                                                      @Qualifier("delayedExchange") CustomExchange delayedExchange){
        return BindingBuilder.bind(delayedQueue).to(delayedExchange)
                .with(DELAYED_ROUTING_KEY).noargs();
    }
}
-------------生产者
package com.day.controller;
import com.day.config.DelayedQueueConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Date;
//发送延迟消息
//打印日志的注解
@Slf4j
@RestController
@RequestMapping("/ttl")
public class ProducerController implements Serializable{
    @Resource
    private RabbitTemplate rabbitTemplate;
    //开始发消息
    @GetMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable String message){
        //System.out.println("当前时间: "+new Date().toString()+"发送一条信息给两个TTL队列 "+message);
        log.info("当前时间:{},发送一条信息给两个TTL队列:{}",
                new Date(),message);
        rabbitTemplate.convertAndSend("X","XA",
                "消息来自ttl为10s的队列: "+message);
        rabbitTemplate.convertAndSend("X","XB",
                "消息来自ttl为40s的队列: "+message);
    }
    //开始发消息 消息 TTL
    @GetMapping("/sendExpireMsg/{message}/{ttlTime}")
    public void sendMsg(@PathVariable String message,@PathVariable String ttlTime){
        log.info("当前时间:{},发送一条时长{}ms信息给TTL队列QC:{}",
                new Date(),ttlTime,message);
        rabbitTemplate.convertAndSend("X","XC",message,msg ->{
            msg.getMessageProperties().setExpiration(ttlTime);
            return msg;
        });
    }
    //开始发消息,基于插件的 消息及延迟的时间
    @GetMapping("/sendDelayMsg/{message}/{delayTime}")
    public void sendMsg(@PathVariable String message,@PathVariable int delayTime){
        log.info("当前时间:{},发送一条时长{}ms信息给延迟队列delayed.queue:{}",
                new Date(),delayTime,message);
        rabbitTemplate.convertAndSend(DelayedQueueConfig.DELAYED_EXCHANGE_NAME,
                DelayedQueueConfig.DELAYED_ROUTING_KEY,message, msg ->{
            msg.getMessageProperties().setDelay(delayTime);
            return msg;
        });
    }
}
------------消费者
package com.day.controller;
import com.day.config.DelayedQueueConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Date;
//消费者
@Slf4j
@Component
public class Consumer implements Serializable {
    @RabbitListener(queues= DelayedQueueConfig.DELAYED_QUEUE_NAME)
    public void receiveDelayQueue(Message message, Channel channel) throws Exception{
        byte[] body=message.getBody();
        String msg=new String(body, Charset.forName("UTF-8"));
        log.info("当前时间:{},收到死信队列的消息:{}",
                new Date().toString(),msg);
    }
}

image.png

//2.小结    
//(1)推荐使用RMQ解决延迟队列问题

四.发布确认高级

//1.概述    
//(1)RMQ重启期间,生产者消息投递失败,导致消息丢失,需要手动处理和恢复。    
//(2)交换机和队列有一个不在,都会导致消息的丢失。    
//(3)如果交换机收不到消息应该如何处理?    
//(4)生产者通过回调接口感知交换机是否接受消息成功。    
-------确认
package com.day.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConfirmConfig {
    //交换机
    public static final String CONFIRM_EXCHANGE_NAME="confirm_exchange";
    //队列
    public static final String CONFIRM_QUEUE_NAME="confirm_queue";
    //routingKey
    public static final String CONFIRM_ROUTING_KEY="key1";

    //声明交换机
    @Bean
    public DirectExchange confirmExchange(){
        return new DirectExchange(CONFIRM_EXCHANGE_NAME);
    }
    //队列
    @Bean
    public Queue confirmQueue(){
        return new Queue(CONFIRM_QUEUE_NAME);
    }
    //绑定
    @Bean
    public Binding queueBindingExchange(@Qualifier("confirmQueue")Queue confirmQueue,
                                        @Qualifier("confirmExchange")DirectExchange confirmExchange){
        return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_ROUTING_KEY);
    }
}
------------回调配置
package com.day.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback {
    @Resource
    private RabbitTemplate rabbitTemplate;
    //注入
    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(this);
    }
    //交换机确认回调方法
    //1.发消息 交换机接收到了 会回调
    //correlationData 保存回调消息的ID及相关信息
    // b true 交换机收到了消息
    // s 失败的原因,成功时为null
    //2.发消息 交换机接收失败了,也会回调
    // b为false
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String ID;
        if(correlationData!=null){
            ID= correlationData.getId();
        }else{
            ID="";
        }
        if(ack){
            log.info("交换机已经收到消息,ID为{}的消息",ID);
        }else{
            log.info("交换机还未收到消息,ID为{}的消息,原因为{}",ID,cause);
        }
    }
}
------------yml文件配置
spring:
  #devtools:
  #restart:
  #enabled=true: #支持热部署  可能导致重启,然后非实时语音转写报错。
  rabbitmq:
    host: wdfgdzx.top
    port: 5672
    username: xlliu24
    password: s19911009!
    publisher-confirm-type: correlated
------------生产者
package com.day.controller;
import com.day.config.ConfirmConfig;
import com.day.config.DelayedQueueConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Date;
//发送延迟消息
//打印日志的注解
@Slf4j
@RestController
@RequestMapping("/ttl")
public class ProducerController implements Serializable{
    @Resource
    private RabbitTemplate rabbitTemplate;
    //开始发消息
    @GetMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable String message){
        //System.out.println("当前时间: "+new Date().toString()+"发送一条信息给两个TTL队列 "+message);
        log.info("当前时间:{},发送一条信息给两个TTL队列:{}",
                new Date(),message);
        rabbitTemplate.convertAndSend("X","XA",
                "消息来自ttl为10s的队列: "+message);
        rabbitTemplate.convertAndSend("X","XB",
                "消息来自ttl为40s的队列: "+message);
    }
    //开始发消息 消息 TTL
    @GetMapping("/sendExpireMsg/{message}/{ttlTime}")
    public void sendMsg(@PathVariable String message,@PathVariable String ttlTime){
        log.info("当前时间:{},发送一条时长{}ms信息给TTL队列QC:{}",
                new Date(),ttlTime,message);
        rabbitTemplate.convertAndSend("X","XC",message,msg ->{
            msg.getMessageProperties().setExpiration(ttlTime);
            return msg;
        });
    }
    //开始发消息,基于插件的 消息及延迟的时间
    @GetMapping("/sendDelayMsg/{message}/{delayTime}")
    public void sendMsg(@PathVariable String message,@PathVariable int delayTime){
        log.info("当前时间:{},发送一条时长{}ms信息给延迟队列delayed.queue:{}",
                new Date(),delayTime,message);
        rabbitTemplate.convertAndSend(DelayedQueueConfig.DELAYED_EXCHANGE_NAME,
                DelayedQueueConfig.DELAYED_ROUTING_KEY,message, msg ->{
            msg.getMessageProperties().setDelay(delayTime);
            return msg;
        });
    }
    //开始发送消息 测试确认
    @GetMapping("/sendMessage/{message}")
    public void sendMessage(@PathVariable String message){
        CorrelationData correlationData=new CorrelationData("110161");
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME,
                ConfirmConfig.CONFIRM_ROUTING_KEY+"123",message,correlationData);
        log.info("发送消息内容为:{}",message);
    }
}
----------消费者
package com.day.controller;
import com.day.config.ConfirmConfig;
import com.day.config.DelayedQueueConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Date;
//消费者
@Slf4j
@Component
public class Consumer implements Serializable {
    @RabbitListener(queues= ConfirmConfig.CONFIRM_QUEUE_NAME)
    public void receiveConfirmMessage(Message message, Channel channel) throws Exception{
        byte[] body=message.getBody();
        String msg=new String(body, Charset.forName("UTF-8"));
        log.info("当前时间:{},接收到的队列confirm.queue消息:{}",
                new Date().toString(),msg);
    }
}

image.png

//2.回退消息    
//(1)如果发现交换机和信道之间不可路由,要通过设置Mandatory参数可以在不可送达时送回给生产者。 
------------生产者
package com.day.controller;
import com.day.config.ConfirmConfig;
import com.day.config.DelayedQueueConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Date;
//发送延迟消息
//打印日志的注解
@Slf4j
@RestController
@RequestMapping("/ttl")
public class ProducerController implements Serializable{
    @Resource
    private RabbitTemplate rabbitTemplate;
    //开始发消息
    @GetMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable String message){
        //System.out.println("当前时间: "+new Date().toString()+"发送一条信息给两个TTL队列 "+message);
        log.info("当前时间:{},发送一条信息给两个TTL队列:{}",
                new Date(),message);
        rabbitTemplate.convertAndSend("X","XA",
                "消息来自ttl为10s的队列: "+message);
        rabbitTemplate.convertAndSend("X","XB",
                "消息来自ttl为40s的队列: "+message);
    }
    //开始发消息 消息 TTL
    @GetMapping("/sendExpireMsg/{message}/{ttlTime}")
    public void sendMsg(@PathVariable String message,@PathVariable String ttlTime){
        log.info("当前时间:{},发送一条时长{}ms信息给TTL队列QC:{}",
                new Date(),ttlTime,message);
        rabbitTemplate.convertAndSend("X","XC",message,msg ->{
            msg.getMessageProperties().setExpiration(ttlTime);
            return msg;
        });
    }
    //开始发消息,基于插件的 消息及延迟的时间
    @GetMapping("/sendDelayMsg/{message}/{delayTime}")
    public void sendMsg(@PathVariable String message,@PathVariable int delayTime){
        log.info("当前时间:{},发送一条时长{}ms信息给延迟队列delayed.queue:{}",
                new Date(),delayTime,message);
        rabbitTemplate.convertAndSend(DelayedQueueConfig.DELAYED_EXCHANGE_NAME,
                DelayedQueueConfig.DELAYED_ROUTING_KEY,message, msg ->{
            msg.getMessageProperties().setDelay(delayTime);
            return msg;
        });
    }
    //开始发送消息 测试确认
    @GetMapping("/sendMessage/{message}")
    public void sendMessage(@PathVariable String message){
        CorrelationData correlationData=new CorrelationData("110161");
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME,
                ConfirmConfig.CONFIRM_ROUTING_KEY+"123",message,correlationData);
        log.info("发送消息内容为:{}",message);
    }
}   
------------回调配置
package com.day.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {
    @Resource
    private RabbitTemplate rabbitTemplate;
    //注入
    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
    }
    //交换机确认回调方法
    //1.发消息 交换机接收到了 会回调
    //correlationData 保存回调消息的ID及相关信息
    // b true 交换机收到了消息
    // s 失败的原因,成功时为null
    //2.发消息 交换机接收失败了,也会回调
    // b为false
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String ID;
        if(correlationData!=null){
            ID= correlationData.getId();
        }else{
            ID="";
        }
        if(ack){
            log.info("交换机已经收到消息,ID为{}的消息",ID);
        }else{
            log.info("交换机还未收到消息,ID为{}的消息,原因为{}",ID,cause);
        }
    }

    //在消息不可送达时,将消息返回给生产者,只有失败的时候才会回退
    @Override
    public void returnedMessage(Message message, int replyCode,
                                String replyText, String exchange,
                                String routingKey) {
        log.error("消息{},被交换机{}退回,退回原因:{},路由key:{}",
                new String(message.getBody()),exchange,replyText,routingKey);
    }
}
------------yml配置
spring:
  #devtools:
  #restart:
  #enabled=true: #支持热部署  可能导致重启,然后非实时语音转写报错。
  rabbitmq:
    host: wdfgdzx.top
    port: 5672
    username: xlliu24
    password: s19911009!
    publisher-confirm-type: correlated
    publisher-returns: true

image.png

//3.备份交换机    
//(1)先写交换机、路由、队列的绑定关系    
//(2)再写消费者,然后删除原有的确认交换机(因为他会转发,和之前的不同了)    
------交换机之间关系配置
package com.day.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConfirmConfig {
    //交换机
    public static final String CONFIRM_EXCHANGE_NAME="confirm_exchange";
    //队列
    public static final String CONFIRM_QUEUE_NAME="confirm_queue";
    //routingKey
    public static final String CONFIRM_ROUTING_KEY="key1";
    //备份交换机
    public static final String BACKUP_EXCHANGE_NAME="backup_exchange";
    //备份队列
    public static final String BACKUP_QUEUE_NAME="backup_queue";
    //报警队列
    public static final String WARNING_QUEUE_NAME="warning_queue";

    //声明交换机
    @Bean
    public DirectExchange confirmExchange(){
        return ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME).durable(true)
                .withArgument("alternate-exchange",BACKUP_EXCHANGE_NAME).build();
    }
    //队列
    @Bean
    public Queue confirmQueue(){
        return new Queue(CONFIRM_QUEUE_NAME);
    }
    //绑定
    @Bean
    public Binding queueBindingExchange(@Qualifier("confirmQueue")Queue confirmQueue,
                                        @Qualifier("confirmExchange")DirectExchange confirmExchange){
        return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_ROUTING_KEY);
    }
    //备份交换机
    @Bean
    public FanoutExchange backupExchange(){
        return new FanoutExchange(BACKUP_EXCHANGE_NAME);
    }
    //队列
    @Bean
    public Queue backupQueue(){
        return new Queue(BACKUP_QUEUE_NAME);
    }
    @Bean
    public Queue warningQueue(){
        return new Queue(WARNING_QUEUE_NAME);
    }
    @Bean
    public Binding backupQueueBindingBackupExchange(@Qualifier("backupQueue")Queue backupQueue,
                                        @Qualifier("backupExchange")FanoutExchange backupExchange){
        return BindingBuilder.bind(backupQueue).to(backupExchange);
    }
    @Bean
    public Binding warningQueueBindingBackupExchange(@Qualifier("warningQueue")Queue warningQueue,
                                                    @Qualifier("backupExchange")FanoutExchange backupExchange){
        return BindingBuilder.bind(warningQueue).to(backupExchange);
    }
}
-------报警消费者
package com.day.controller;
import com.day.config.ConfirmConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Date;

//报警消费者
@Slf4j
@Component
public class WarningConsumer {
    @RabbitListener(queues = ConfirmConfig.WARNING_QUEUE_NAME)
    public void receiveWarningMsg(Message message){
        String msg=new String(message.getBody());
        log.error("报警,发现不可路由消息:{}",msg);
        log.info("当前时间:{},接收到的队列warning_queue消息:{}",
                new Date().toString(),msg);
    }
}

image.png

//4.RMQ其他知识点    
//(1)消息被重复消费,重复扣了用户的钱。幂等性问题    
//(2)幂等性问题的解决一般使用全局ID,使用该ID判断该消息是否已消费过。    
//(3)唯一ID+指纹码,或利用redis的原子性去实现    
//(4)利用redis执行setnx命令,天然具有幂等性,从而实现不重复消费。
//5.优先级队列    
//(1)订单催付场景 RMQ进行改造和优化,对大客户的订单进行优先级的提升。    
//(2)生产者先把消息发到队列之中,然后消费者再消费。    
------交换机、队列、路由配置
package com.day.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class CommonConfig {
    //交换机
    public static final String EXCHANGE_NAME="exchange";
    //队列
    public static final String QUEUE_NAME="queue";
    //routingKey
    public static final String ROUTING_KEY="key";
    //声明交换机
    @Bean
    public DirectExchange exchange(){
        return new DirectExchange(EXCHANGE_NAME);
    }
    //队列
    @Bean
    public Queue queue(){
        Map<String,Object> arguments=new HashMap<>();
        //队列
        arguments.put("x-max-priority",10);//官方允许是0-255 此处设置10 允许0-10 不用设置过大 浪费CUP和内存
        return QueueBuilder.durable(QUEUE_NAME)
                .withArguments(arguments)
                .build();
    }
    //绑定
    @Bean
    public Binding queueBindingExchange(@Qualifier("queue")Queue queue,
                                        @Qualifier("exchange")DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
    }
}
------生产者
package com.day.controller;
import com.day.config.CommonConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Date;
//发送延迟消息
//打印日志的注解
@Slf4j
@RestController
@RequestMapping("/RMQ")
public class ProducerController implements Serializable{
    @Resource
    private RabbitTemplate rabbitTemplate;
    //开始发消息
    @GetMapping("/sendMessage/{message}")
    public void sendMsg(@PathVariable String message){
        for(int i=1;i<11;i++){
            if(i==5){
                rabbitTemplate.convertAndSend(
                        CommonConfig.EXCHANGE_NAME,
                        CommonConfig.ROUTING_KEY,
                        "生产者生产消息:"+message+i,
                        msg -> {
                            msg.getMessageProperties().setPriority(5);
                            return msg;
                        });
                log.info("当前时间:{},发送一条信息给队列:{}",
                        new Date(),message+i);
            }else{
                rabbitTemplate.convertAndSend(
                        CommonConfig.EXCHANGE_NAME,
                        CommonConfig.ROUTING_KEY,
                        "生产者生产消息:"+message+i);
                log.info("当前时间:{},发送一条信息给队列:{}",
                        new Date(),message+i);
            }
        }
    }
}
------消费者
package com.day.controller;
import com.day.config.CommonConfig;
import com.rabbitmq.client.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Date;
//消费者
/*
@Slf4j
@Component
public class Consumer implements Serializable {
    @RabbitListener(queues= CommonConfig.QUEUE_NAME)
    public void receiveConfirmMessage(Message message, Channel channel) throws Exception{
        byte[] body=message.getBody();
        String msg=new String(body, Charset.forName("UTF-8"));
        log.info("当前时间:{},接收到的队列confirm.queue消息:{}",
                new Date().toString(),msg);
    }
}
*/
//接收消息
public class Consumer {
    //接收消息
    public static void main(String[] args) throws Exception {
        //创建连接工厂
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("47.105.174.97");
        connectionFactory.setUsername("xlliu24");
        connectionFactory.setPassword("s19911009!");
        //创建新链接
        Connection connection=connectionFactory.newConnection();
        Channel channel=connection.createChannel();
        //声明 接受消息
        DeliverCallback deliverCallback=(consumerTag, message) ->{
            System.out.println(new String(message.getBody()));
        };
        //取消消费
        CancelCallback cancelCallback= consumerTage ->{
            System.out.println("消费消息被中断...");
        };
        //消费者消费消息
        /**
         * 1.消费哪个队列
         * 2.消费成功之后是否要自动应答 true代表自动应答 false代表手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         *
         * */
        channel.basicConsume(CommonConfig.QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

image.png

//6.惰性队列    
//(1)消息保存在内存中还是在磁盘上,正常消息是保存在内存中。在惰性中,消息是保存在磁盘中的。    
//(2)当有消费者宕机、大量消息积压时,才用惰性队列。

//五.集群

//1.集群原理    
//(1)集群可以不断的扩充,需要有3台机器    
//(2)修改三台机器的RMQ名称,分别为node1 node2 node3    
//(3)vim /etc/hosts  IP 节点名称对应。三个机器都需要这么配置    
//(4)远程复制命令 scp   /var/lib/rabbitmq/.erlang.cookie root@node2:/var/lib/rabbitmq/.erlang.cookie    
//(5)重启RMQ与erlang   rabbitmq-server -detached    
//(6)节点二操作rabbitmqctl   stop_app ; rabbitmqctl reset ;rabbitmqctl join_cluster rabbit@node1
            ;rabbitmqctl start_app   ;节点三操作相同    
//(7)rabbitmqctl cluster_status 查看集群状态    
//(8)rabbitmqctl add_user admin 123   ;rabbitmqctl set_user_tags admin administrator    
//(9)rabbitmqctl set_permissions -p   "/" admin ".*" ".*" ".*"    
//(10)登录后在Overview中可以看到节点    
//(11)也可以解除集群节点

image.png

//2.镜像队列    
//(1)如果有个节点宕机了,重启发现消息丢失了,镜像队列就是备份。    
//(2)发给1节点,但备份到2号节点。通过85集的设置可以实现。就算整个集群只剩下一台机器,也可以处理。
//3.负载均衡    
//(1)Haproxy实现负载均衡(比如Nginx),实现高并发高可用
//4.联邦交换机    
//(1)异地机房网络延迟的问题,北京ExchangeA  深圳ExchangeB,如果北京的用户访问深圳的RMQ怎么办?    
//(2)在每台机器上开启federation相关插件    
//(3)rabbitmq-plugins   enable rabbitmq_faderation ;rabbitmaq-plugins enable
            rabbitmq_federation_management   ; 自带的插件,能看到 Federation Status ;Federation Upstarems    
//(4)联邦队列 也可以同步数据,交换机也可以实现。
//5.Shovel    
//(1)铲子,可以将数据从一端挖到另一端    
//(2)rabbitmq-plugins   enable rabbitmq_shovel
            rabbitmq-plugins enable   rabbitmq_shovel_management    
//(3)node1 q1 同步到node2 q2中  实现跨地区数据同步

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/590313.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java 【数据结构】常见排序算法实用详解(上) 插入排序/希尔排序/选择排序/堆排序【贤者的庇护】

登神长阶 上古神器-常见排序算法 插入排序/选择排序/堆排序 &#x1f4d4; 一.排序算法 &#x1f4d5;1.排序的概念 排序 &#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&a…

【Python】函数设计

1.联系函数的设计 2.找质数 3.找因子 4.判断水仙花数 5.斐波拉契数列递归调用&#xff0c;并用数组存储已计算过的数&#xff0c;减少重复计算 1、计算利息和本息 编写两个函数分别按单利和复利计算利息,根据本金、年利率、存款年限得到本息和和利息。调用这两个函数计算1…

学习Rust的第22天:mini_grep第2部分

书接上文&#xff0c;在本文中&#xff0c;我们学习了如何通过将 Rust 程序的逻辑移至单独的库箱中并采用测试驱动开发 (TDD) 实践来重构 Rust 程序。通过在实现功能之前编写测试&#xff0c;我们确保了代码的可靠性。我们涵盖了基本的 Rust 概念&#xff0c;例如错误处理、环境…

【linux-汇编-点灯之思路-程序】

目录 1. ARM汇编中的一些注意事项2. IMXULL汇编点灯的前序&#xff1a;3. IMXULL汇编点灯之确定引脚&#xff1a;4. IMXULL汇编点灯之引脚功能编写&#xff1a;4.1 第一步&#xff0c;开时钟4.2 第二步&#xff0c;定功能&#xff08;MUX&#xff09;4.3 第三步&#xff0c;定电…

【笔试训练】day17

1.小乐乐该数字 遇到按位处理的情况可以考虑用字符串去读 代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include<string> using namespace std;int main() {string str;cin >> str;int ans 0;for (int i 0; i < str.siz…

JavaEE 初阶篇-深入了解 Junit 单元测试框架和 Java 中的反射机制(使用反射做一个简易版框架)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 Junit 单元测试框架概述 1.1 使用 Junit 框架进行测试业务代码 1.2 Junit 单元测试框架的常用注解&#xff08;Junit 4.xxx 版本&#xff09; 2.0 反射概述 2.1 获…

神经网络中的优化方法

一、引入 在传统的梯度下降优化算法中&#xff0c;如果碰到平缓区域&#xff0c;梯度值较小&#xff0c;参数优化变慢 &#xff0c;遇到鞍点&#xff08;是指在某些方向上梯度为零而在其他方向上梯度非零的点。&#xff09;&#xff0c;梯度为 0&#xff0c;参数无法优化&…

机器人系统ros2-开发实践04-ROS2 中 tf2的定义及示例说明

1. what ros2 tf2 &#xff1f; tf2的全称是transform2&#xff0c;在ROS&#xff08;Robot Operating System&#xff09;中&#xff0c;它是专门用于处理和变换不同坐标系间位置和方向的库。这个名字来源于“transform”这个词&#xff0c;表示坐标变换&#xff0c;而“2”则…

【Leetcode每日一题】 动态规划 - 简单多状态 dp 问题 - 删除并获得点数(难度⭐⭐)(70)

1. 题目解析 题目链接&#xff1a;740. 删除并获得点数 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 问题分析 本题是「打家劫舍」问题的变种&#xff0c;但核心逻辑依然保持一致。题目要求从给定的数组nums中选择…

C++ stack和queue的使用方法与模拟实现

文章目录 一、 stack的使用方法二、 queue的使用方法三、 容器适配器四、 stack的模拟实现五、 queue的模拟实现 一、 stack的使用方法 stack介绍文档 stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的…

Windows如何通过wsl2迅速启动Docker desktop的PHP的Hyperf项目容器?

一、安装WSL 什么是WSL&#xff1f; 官网&#xff1a;什么是WSL&#xff1f; Windows Subsystem for Linux (WSL) 是一个在Windows 10和Windows 11上运行原生Linux二进制可执行文件的兼容性层。 换句话说&#xff0c;WSL让你可以在Windows系统上运行Linux环境&#xff0c;而无需…

【linux】unzip解压乱码或者报错处理办法

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…

2023-2024年数字化转型报告/方案合集(精选198份)

数字化转型报告/方案&#xff08;精选198份&#xff09; 2023-2024年 来源&#xff1a;2023-2024年数字化转型报告/方案合集&#xff08;精选198份&#xff09; 【以下是资料目录】 2023-2024年度医药健康行业数字化调研报告 2023-2024中国财务数字化报告 2023⻝品饮料行业…

Redis运维篇-快速面试笔记(速成版)

文章目录 1. Redis的持久化1.1 RDB&#xff08;快照模式&#xff09;1.2 AOF 模式 2. Redis主从模型&#xff08;高可用&#xff09;2.1 Redis的主从复制2.2 Redis拓扑结构 3. Redis集群模式&#xff08;高并发&#xff09;3.1 Redis的Slots3.2 集群模式的常用命令3.3 多主多从…

使用 MediaMTX 和 FFmpeg 推拉 RTSP 流媒体

实时流传输协议 RTSP&#xff08;Real-Time Streaming Protocol&#xff09;是 TCP/IP 协议体系中的一个应用层协议&#xff0c;由哥伦比亚大学、网景和 RealNetworks 公司提交的 IETF RFC 标准。该协议定义了一对多应用程序如何有效地通过 IP 网络传送多媒体数据。RTSP 在体系…

Unity射击游戏开发教程:(8)构建 UI 元素:添加分数显示

用户界面决定用户如何与屏幕交互。UI 适用于所有类型的游戏和应用程序,在此示例中,我们将为我的太空射击游戏设置一个简单的记分板。 第一步是在层次结构中创建一个 UI 元素。只需在层次结构中右键单击,滚动 UI 并选择要添加的 UI 元素类型。在本例中,我们将使用文本元素。…

STM32入门_江协科技_3~4_OB记录的自学笔记_软件安装新建工程

3. 软件安装 3.1. 安装Keil5 MDK 作者的资料下载的连接如下&#xff1a;https://jiangxiekeji.com/download.html#32 3.2. 安装器件支持包 因为新的芯片层出不穷&#xff0c;所以需要安装Keil5提供的器件升级版对软件进行升级&#xff0c;从而支持新的芯片&#xff1b;如果不…

如何将 redis 快速部署为 docker 容器?

部署 Redis 作为 Docker 容器是一种快速、灵活且可重复使用的方式&#xff0c;特别适合开发、测试和部署环境。本文将详细介绍如何将 Redis 部署为 Docker 容器&#xff0c;包括 Docker 安装、Redis 容器配置、数据持久化、网络设置等方面。 步骤 1&#xff1a;安装 Docker 首…

ICode国际青少年编程竞赛- Python-1级训练场-基本操作

ICode国际青少年编程竞赛- Python-1级训练场-基本操作 1、 Dev.step(3)2、 Dev.step(1)3、 Dev.step(7)4、 Dev.step(-1)5、 Dev.step(-5)6、 Dev.step(3) Dev.step(-8)7、 Dev.turnRight() Dev.step(1)8、 Dev.turnLeft() Dev.step(1)9、 Dev.step(4) Dev.tur…

串的模式匹配之BF算法实现

概述 BF算法-暴力枚举 匹配失败处理 匹配成功结束 算法思想 代码实现 定义串的存储结构&#xff1a;装字符的ch数组标记长度的length 最坏时间复杂度分析 代码整合