rabbitmq入门

在用户请求订单系统的时候,订单系统的上限设置了10万个请求,如果超过了这个请求,那么订单系统可能会崩溃,这时候我们使用mq,用户先访问mq,mq在去访问订单系统,mq内部自带排队,这样就可以解决订单系统崩溃的问题,这就是流量削峰

订单系统在调用,库存系统,支付系统的时候,任何一个系统发送了故障都会导致下单失败,
当订单系统使用mq的时候,订单系统完成之后,发送到mq,mq再去监督库存系统,和支付系统,
有一个子系统出现故障,中单用户是无感知的,用户可以正常完成订单,mq会监督子系统的修复,这就是mq的应用解耦

A服务调用B服务的时候,等待处理特别耗时间,
这个时候我们使用mq,A服务调用B服务,B服务先返回一个成功给A,A不需要在继续等待,
B继续处理任务,处理成功把数据发送给mq,然后mq再发送给A服务,这就是mq的异步处理

首先关闭防火墙

systemctl stop firewalld.service

账号admin,密码123456 ,端口号15672,安装rabbitmq

docker run -id --name myrabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=123456 -p 5672:5672 -p 15672:15672 rabbitmq:3-management

接下来我们访问浏览器

http://192.168.184.142:15672/

输入账号密码

 接下来我们先看下简单模式,

简单模式就是一个生产者对应一个消费者

开始编写java代码,

在pom.xml引入

  <!--rabbitmq 依赖客户端-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.8.0</version>
        </dependency>
        <!--操作文件流的一个依赖-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

java 访问rabbitmq的端口是5672

我们先创建生产者

package com.example.demo.mq;

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

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

//生产者
public class Producer {

    //队列名称
    private static final  String QUEUE_NAME="hello";


    public static void main(String[] args) throws IOException, TimeoutException {
        //创建一个连接工厂
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("192.168.184.142");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("123456");
        //创建连接
        Connection connection = connectionFactory.newConnection();
        //获取通道
        Channel channel = connection.createChannel();

        //队列里面的消息是否持久化到(磁盘),默认在内存中 是否用完就删除
        boolean durable=false;
        //true:队列支持多个消费者消费,false:队列的消息只能有一个消费者消费
        boolean exclusive=false;
        //最后一个消费者断开连接后,该队列是否自动删除,true:自动删除
        boolean autoDelete=false;
        //生成一个队列
        channel.queueDeclare( QUEUE_NAME,  durable,  exclusive,  autoDelete,null);
        //消息内容
        String message="你好";
        //发送一个消息
        channel.basicPublish("", QUEUE_NAME,null,message.getBytes());
        System.out.println("消息发送完毕");
    }
}

可以看到,hello队列被创建,有1条消息未消费

 接下来我们创建消费者

package com.example.demo.mq;

import com.rabbitmq.client.*;

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

//消费者
public class Consumer {

    //队列名称
    private static final  String QUEUE_NAME="hello";


    public static void main(String[] args) throws IOException, TimeoutException {
        //创建一个连接工厂
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("192.168.184.142");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("123456");
        //创建连接
        Connection connection = connectionFactory.newConnection();
        //获取通道
        Channel channel = connection.createChannel();

        //回调消息
        DeliverCallback deliverCallback=(String var1, Delivery var2)->{
            String str=new String(var2.getBody());
            System.out.println(str);
        };
        //取消回调
        CancelCallback cancelCallback=(String var1)->{
            System.out.println("消息消费被中断");
        };
        //消费消息   true:表示自动应答,false表示手动应答
        channel.basicConsume(QUEUE_NAME,true,  deliverCallback,cancelCallback);
    }
}

 可以看到,消费者已经成功消费消息

接下来我们在看下工作模式

工作模式就是一个生产者,对应多个消费者,消费者接收对应的消息

package com.example.demo.mq;

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

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

/**
 * mq工具类
 */
public class RabbitmqUtils {

    /**
     * 获取通道
     * @return
     * @throws IOException
     * @throws TimeoutException
     */
    public static Channel getChannel() throws IOException, TimeoutException {
        //创建一个连接工厂
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("192.168.184.142");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("123456");
        //创建连接
        Connection connection = connectionFactory.newConnection();
        //获取通道
        Channel channel = connection.createChannel();
        return channel;
    }
}

 生产者发送大量的消息

package com.example.demo.mq;

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

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

//生产者
public class Producer {

    //队列名称
    private static final  String QUEUE_NAME="hello";


    public static void main(String[] args) throws IOException, TimeoutException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();

        //队列里面的消息是否持久化到(磁盘),默认在内存中 是否用完就删除
        boolean durable=false;
        //true:队列支持多个消费者消费,false:队列的消息只能有一个消费者消费
        boolean exclusive=false;
        //最后一个消费者断开连接后,该队列是否自动删除,true:自动删除
        boolean autoDelete=false;
        //生成一个队列
        channel.queueDeclare( QUEUE_NAME,  durable,  exclusive,  autoDelete,null);
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入");
        while (scanner.hasNext()){
            //消息内容
            String message=scanner.next();
            //发送一个消息
            channel.basicPublish("", QUEUE_NAME,null,message.getBytes());
            System.out.println("消息发送完毕"+message);
        }
    }
}

创建多个消费者接收

package com.example.demo.mq;

import com.rabbitmq.client.*;

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

//消费者
public class Consumer {

    //队列名称
    private static final  String QUEUE_NAME="hello";


    public static void main(String[] args) throws IOException, TimeoutException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();

        //回调消息
        DeliverCallback deliverCallback=(String var1, Delivery var2)->{
            String str=new String(var2.getBody());
            System.out.println(str);
        };
        //取消回调
        CancelCallback cancelCallback=(String var1)->{
            System.out.println("消息消费被中断");
        };
        //消费消息   true:表示自动应答,false表示手动应答
        channel.basicConsume(QUEUE_NAME,true,  deliverCallback,cancelCallback);
        System.out.println("消费者1接收消息");
    }
}

选中这里,就可以启动多个消费者

 接下来在生产者发送消息

可以看到消费者1成功接收aa,cc

 消费者2成功消费bb,dd

消费者消费消息的时候,消费者挂掉了,这时候,可能存在消息丢失的问题,rabbitmq引入了消息应答的机制;

消息应答:当消费者成功消费消息后,rabbitmq才把这条消息删除掉。

自动应答:消息者接收消息,就会删除消息,但是在代码还没有处理完,可能会存在消息丢失;

手动应答:消费者接收消息,代码处理完之后,成功消费消息,不会存在消息丢失的问题,在工作中,尽量使用手动应答;

接下来我们看下手动应答的代码

package com.example.demo.mq;

import com.rabbitmq.client.*;

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

//消费者 消费失败 重新放入队列 由其他消费者进行消费
public class Consumer {

    //队列名称
    private static final  String QUEUE_NAME="hello";


    public static void main(String[] args) throws IOException, TimeoutException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();

        //回调消息
        DeliverCallback deliverCallback=(String var1, Delivery var2)->{
            String str=new String(var2.getBody());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消费者1接收到消息:"+str);
            //手动应答消息
            //第一个参数消息标记 tag
            //第二个参数是否批量应答消息,true:批量,false:不是批量
            channel.basicAck(var2.getEnvelope().getDeliveryTag(),false);
        };
        //取消回调
        CancelCallback cancelCallback=(String var1)->{
            System.out.println("消息消费被中断");
        };
        //消费消息   true:表示自动应答,false表示手动应答
        channel.basicConsume(QUEUE_NAME,false,  deliverCallback,cancelCallback);
    }
}
package com.example.demo.mq;

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

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

//消费者 消费失败 重新放入队列 由其他消费者进行消费
public class Consumer2 {

    //队列名称
    private static final  String QUEUE_NAME="hello";


    public static void main(String[] args) throws IOException, TimeoutException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();

        //回调消息
        DeliverCallback deliverCallback=(String var1, Delivery var2)->{
            String str=new String(var2.getBody());
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消费者2接收到消息:"+str);
            //手动应答消息
            //第一个参数消息标记 tag
            //第二个参数是否批量应答消息,true:批量,false:不是批量
            channel.basicAck(var2.getEnvelope().getDeliveryTag(),false);
        };
        //取消回调
        CancelCallback cancelCallback=(String var1)->{
            System.out.println("消息消费被中断");
        };
        //消费消息   true:表示自动应答,false表示手动应答
        channel.basicConsume(QUEUE_NAME,false,  deliverCallback,cancelCallback);
    }
}

然后启动生产者和2个消费者,发送2条消息

我们可以看到队列还有一条消息没有被消费

 这时候,我们把消费者2宕机,可以看到,消费者1接收到了消息,这就是手动应答

接下来我们在看下持久化

队列持久化:mq宕机后,如果不开启持久化,那么队列就会丢失,如果开启持久化Features就会显示一个大写的D

消息持久化:mq的消息默认在内存中,当mq宕机后,消息保存在磁盘中,mq启动后,消息恢复

我们先把mq进行重启,可以看到在没有持久化之前,队列已经丢失了

docker stop myrabbit

docker restart myrabbit

 接下来我们对生产者的代码进行修改,把箭头这里改成true,报证队列的持久化

 接下来我们在重启mq,可以看到队列持久化了,但是消息丢失了

 接下来对生产者代码进行修改,在箭头这里加入

MessageProperties.PERSISTENT_TEXT_PLAIN 开启消息的持久化,到磁盘中

 接下来我们在重启mq,可以看到消息也恢复了,这就是消息的持久化

接下来我们看下,不公平分发

如果有一个消费者消费快,一个消费者消费慢,那么这时候我想要消费快的那个多干点活,

这就是不公平分发

在消费者1和消费者2代码进行修改,箭头部分加入

channel.basicQos(1);

这时候在启动生产者和2个消费者

可以看到消息,大部分都被消费者1消费了,这就是不公平分发

接下来在看下预取值,生产者发送5条数据

设置消费者1,消费3条数据

channel.basicQos(3);

设置消费者2,消费2条数据

channel.basicQos(2);

不管消费者1消费多块,总有2条数据会是到消费者2的;

 在通道这里,就可以看到预取值

 消费者1消费3条数据

可以看到消费者2的消息一直在这里堆积

最后消费者2消费2条数据

  遇到mq的消息堆积,可以先把消费者的预取值设置为1,不公平分发,这样一个消费者堆积消息,在开启一个新的消费者来消费消息,这样控制台最终会剩下1条消息没有被消费,这时候在控制台点击get message获取消息,手动处理,然后把这条队列删除掉,这样就可以解决消息堆积

接下来我们在看下发布确认 

要想真正的保证消息的持久化,消息不丢失,

首先队列持久化,消息持久化,手动应答,发布确认,只有发布确认了,才能保证消息到磁盘上了

单个确认:发送一条,确认一条,速度慢一点

批量确认:批量发送,一次确认,但是有发送出错,无法感知

异步确认:批量发送,异步监听,成功和失败的回调,成功确认删除map中的数据,剩下的就是未确认的数据,失败回调,重新发送对应的消息,建议使用异步回调

接下来看下代码,进行对比

package com.example.demo.mq;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeoutException;

//生产者
public class Producer {

    //队列名称
    private static final  String QUEUE_NAME="hello";


    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //单个确认 耗时808毫秒
        //singleConfirmation();
        //批量确认 耗时28毫秒
        //batchConfirmation();
        //异步确认 耗时257毫秒
        asynchronousConfirmation();
    }

    //单个确认
    public static void singleConfirmation() throws IOException, TimeoutException, InterruptedException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
        //队列里面的消息是否持久化到(磁盘),默认在内存中 是否用完就删除
        boolean durable=true;
        //true:队列支持多个消费者消费,false:队列的消息只能有一个消费者消费
        boolean exclusive=false;
        //最后一个消费者断开连接后,该队列是否自动删除,true:自动删除
        boolean autoDelete=false;
        //生成一个队列
        channel.queueDeclare( QUEUE_NAME,  durable,  exclusive,  autoDelete,null);
        //开启发布确认
        channel.confirmSelect();

        long begin = System.currentTimeMillis();
        for (int i = 0; i <1000 ; i++) {
            String message="消息内容"+i;
            //MessageProperties.PERSISTENT_TEXT_PLAIN 消息持久化到磁盘中
            channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
            //如果返回false,或者超时时间内未返回,可以消息重新发送
            boolean flag = channel.waitForConfirms();
            if(flag){
                System.out.println("消息发送成功");
            }
        }

        long end = System.currentTimeMillis();
        System.out.println("发布单独确认消息,耗时" + (end - begin)+"毫秒");
    }

    //批量确认发布
    public static void batchConfirmation() throws IOException, TimeoutException, InterruptedException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
        //队列里面的消息是否持久化到(磁盘),默认在内存中 是否用完就删除
        boolean durable=true;
        //true:队列支持多个消费者消费,false:队列的消息只能有一个消费者消费
        boolean exclusive=false;
        //最后一个消费者断开连接后,该队列是否自动删除,true:自动删除
        boolean autoDelete=false;
        //生成一个队列
        channel.queueDeclare( QUEUE_NAME,  durable,  exclusive,  autoDelete,null);
        //开启发布确认
        channel.confirmSelect();

        long begin = System.currentTimeMillis();
        for (int i = 0; i <1000 ; i++) {
            String message="消息内容"+i;
            //MessageProperties.PERSISTENT_TEXT_PLAIN 消息持久化到磁盘中
            channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
        }
        //全部发送完了 在进行确认
        //如果返回false,或者超时时间内未返回,可以消息重新发送
        boolean flag = channel.waitForConfirms();
        if(flag){
            System.out.println("消息发送成功");
        }

        long end = System.currentTimeMillis();
        System.out.println("发布批量确认消息,耗时" + (end - begin)+"毫秒");
    }

    //异步确认
    public static void asynchronousConfirmation() throws IOException, TimeoutException, InterruptedException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
        //队列里面的消息是否持久化到(磁盘),默认在内存中 是否用完就删除
        boolean durable=true;
        //true:队列支持多个消费者消费,false:队列的消息只能有一个消费者消费
        boolean exclusive=false;
        //最后一个消费者断开连接后,该队列是否自动删除,true:自动删除
        boolean autoDelete=false;
        //生成一个队列
        channel.queueDeclare( QUEUE_NAME,  durable,  exclusive,  autoDelete,null);
        //开启发布确认
        channel.confirmSelect();

        //将序号和消息进行关联,只要给到序号,批量删除条目, 支持高并发(多线程)
        ConcurrentSkipListMap<Long,String>map=new ConcurrentSkipListMap<>();


        //成功确认的回调
        ConfirmCallback ackCallback=(long var1, boolean var3)->{
            System.out.println("成功确认消息"+var1);
            //删除确认的消息,剩下的都是未确认的消息
            if(var3){
                //如果是批量确认 删除批量的消息
                ConcurrentNavigableMap<Long, String> longStringConcurrentNavigableMap = map.headMap(var1);
                longStringConcurrentNavigableMap.clear();
            }else {
                //删除单个确认的消息
                map.remove(var1);
            }
        };
        //消息确认失败,的回调
        ConfirmCallback nackCallback=(long var1, boolean var3)->{
            String message = map.get(var1);
            System.out.println("未确认的消息是:"+message+",序号:"+var1);
            //在这里可以重新发消息
            channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
        };
        //异步监听消息的确认
        channel.addConfirmListener(ackCallback,nackCallback);

        long begin = System.currentTimeMillis();
        for (int i = 0; i <1000 ; i++) {
            String message="消息内容"+i;
            //MessageProperties.PERSISTENT_TEXT_PLAIN 消息持久化到磁盘中
            channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
            //记录发送的消息和序号
            map.put(channel.getNextPublishSeqNo(),message);
        }


        long end = System.currentTimeMillis();
        System.out.println("发布异步确认消息,耗时" + (end - begin)+"毫秒");
    }
}

  临时队列:就是没有持久化,队列名称随机

接下来我们看下发布订阅模式 

发布订阅模式就是生产者发送消息,可以多个消费者接收这一条消息

生产者发送给交换机,交换机绑定路由key,到队列,然后再发送到对应的消费者

我们默认的交换机就是AMQP default

接下来我们看下交换机的类型扇出(fanout) ,就是把消息广播到所有队列中

我们来看下代码

package com.example.demo.mq;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeoutException;

//生产者
public class Producer {

    //交换机名称
    private static final  String EXCHANGE_NAME="jiaohuanji";


    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
       //声明一个交换机,第一个参数是交换机的名称, 第二个参数是交换机的类型
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入信息");
        while (sc.hasNext()) {
            String message = sc.nextLine();
            //发出消息  第二个参数是路由key,可以为空串
            channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息" + message);
        }

    }


}
package com.example.demo.mq;

import com.rabbitmq.client.*;

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

//消费者
public class Consumer {

    //交换机名称
    private static final  String EXCHANGE_NAME="jiaohuanji";


    public static void main(String[] args) throws IOException, TimeoutException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
        //声明交换机类型
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
        //声明一个临时的队列名称,消费者和该队列断开连接的时候,自动删除
        String queueName=channel.queueDeclare().getQueue();
        //把交换机和路由key 对应的队列进行绑定,路由key可以为空
        channel.queueBind(queueName, EXCHANGE_NAME, "");



        //回调消息
        DeliverCallback deliverCallback=(String var1, Delivery var2)->{
            String str=new String(var2.getBody());
            System.out.println("消费者1接收到消息:"+str);
        };
        //取消回调
        CancelCallback cancelCallback=(String var1)->{
            System.out.println("消息消费被中断");
        };
        //消费消息   true:表示自动应答,false表示手动应答
        channel.basicConsume(queueName,true,  deliverCallback,cancelCallback);
    }
}
package com.example.demo.mq;

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

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

//消费者
public class Consumer2 {

    //交换机名称
    private static final  String EXCHANGE_NAME="jiaohuanji";


    public static void main(String[] args) throws IOException, TimeoutException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
        //声明交换机类型
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
        //声明一个临时的队列名称,消费者和该队列断开连接的时候,自动删除
        String queueName=channel.queueDeclare().getQueue();
        //把交换机和路由key 对应的队列进行绑定,路由key可以为空
        channel.queueBind(queueName, EXCHANGE_NAME, "");

        //回调消息
        DeliverCallback deliverCallback=(String var1, Delivery var2)->{
            String str=new String(var2.getBody());
            System.out.println("消费者2接收到消息:"+str);
        };
        //取消回调
        CancelCallback cancelCallback=(String var1)->{
            System.out.println("消息消费被中断");
        };
        //消费消息   true:表示自动应答,false表示手动应答
        channel.basicConsume(queueName,true,  deliverCallback,cancelCallback);
    }
}

 

可以看到交换机和队列进行了绑定

生产者发送消息,消费者1,2都接收到了消息

接下来我们在看下直接交换机(direct),就是路由key不一样,如果路由key一样那么就是扇出了

package com.example.demo.mq;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeoutException;

//生产者
public class Producer {

    //交换机名称
    private static final  String EXCHANGE_NAME="jiaohuanji";


    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
       //声明一个交换机,第一个参数是交换机的名称, 第二个参数是交换机的类型
        channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.DIRECT);
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入信息");
        while (sc.hasNext()) {
            String message = sc.nextLine();
            //发出消息  第二个参数是路由key
            channel.basicPublish(EXCHANGE_NAME,"k1",null,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息" + message);
        }

    }


}
package com.example.demo.mq;

import com.rabbitmq.client.*;

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

//消费者
public class Consumer {

    //交换机名称
    private static final  String EXCHANGE_NAME="jiaohuanji";
    //队列的名称
    private static final  String QUEUE_NAME="q1";

    public static void main(String[] args) throws IOException, TimeoutException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
        //声明交换机类型
        channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.DIRECT);
        //声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        //把交换机和路由key 对应的队列进行绑定,第三个参数路由key
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "k1");

        //回调消息
        DeliverCallback deliverCallback=(String var1, Delivery var2)->{
            String str=new String(var2.getBody());
            System.out.println("消费者1接收到消息:"+str);
        };
        //取消回调
        CancelCallback cancelCallback=(String var1)->{
            System.out.println("消息消费被中断");
        };
        //消费消息   true:表示自动应答,false表示手动应答
        channel.basicConsume(QUEUE_NAME,true,  deliverCallback,cancelCallback);
    }
}
package com.example.demo.mq;

import com.rabbitmq.client.*;

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

//消费者
public class Consumer2 {

    //交换机名称
    private static final  String EXCHANGE_NAME="jiaohuanji";
    //队列的名称
    private static final  String QUEUE_NAME="q2";

    public static void main(String[] args) throws IOException, TimeoutException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
        //声明交换机类型
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        //声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        //把交换机和路由key 对应的队列进行绑定,第三个参数路由key
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "k2");

        //回调消息
        DeliverCallback deliverCallback=(String var1, Delivery var2)->{
            String str=new String(var2.getBody());
            System.out.println("消费者2接收到消息:"+str);
        };
        //取消回调
        CancelCallback cancelCallback=(String var1)->{
            System.out.println("消息消费被中断");
        };
        //消费消息   true:表示自动应答,false表示手动应答
        channel.basicConsume(QUEUE_NAME,true,  deliverCallback,cancelCallback);
    }
}

先把之前的交换机删除掉

接下来我们在看下k1的路由key

可以看到只有消费者1接收到消息了

 接下来我们把生产者的代码路由key改成k2,再次启动

 可以看到消费者2接收到消息了

 在交换机这里就可以看到绑定的信息

 这就是交换机的直接类型

接下来我们在看下主题模式,也叫主题交换机

主题交换机的路由key可以使用*表示一个单词,#表示多个单词,进行模糊匹配,包含扇出和直接交换机的功能,主题模式的命名规则 xx.xx.xx 最多不能超过255个字符

先把交换机删除掉

接下来看下代码

package com.example.demo.mq;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeoutException;

//生产者
public class Producer {

    //交换机名称
    private static final  String EXCHANGE_NAME="jiaohuanji";


    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
       //声明一个交换机,第一个参数是交换机的名称, 第二个参数是交换机的类型
        channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.TOPIC);

        String str="aa";
        String str2="bb";
        String str3="cc";
        //发出消息  第二个参数是路由key
        channel.basicPublish(EXCHANGE_NAME,"aa.11.22",null,str.getBytes("UTF-8"));

        channel.basicPublish(EXCHANGE_NAME,"bb.33.44",null,str2.getBytes("UTF-8"));

        channel.basicPublish(EXCHANGE_NAME,"bb.77.88.99",null,str3.getBytes("UTF-8"));
        System.out.println("生产者发出消息");
    }


}
package com.example.demo.mq;

import com.rabbitmq.client.*;

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

//消费者
public class Consumer {

    //交换机名称
    private static final  String EXCHANGE_NAME="jiaohuanji";
    //队列的名称
    private static final  String QUEUE_NAME="q1";

    public static void main(String[] args) throws IOException, TimeoutException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
        //声明交换机类型
        channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.TOPIC);
        //声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        //把交换机和路由key 对应的队列进行绑定,第三个参数路由key
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "*.*.22");

        //回调消息
        DeliverCallback deliverCallback=(String var1, Delivery var2)->{
            String str=new String(var2.getBody());
            System.out.println("消费者1接收到消息:"+str+",路由key:"+var2.getEnvelope().getRoutingKey());
        };
        //取消回调
        CancelCallback cancelCallback=(String var1)->{
            System.out.println("消息消费被中断");
        };
        //消费消息   true:表示自动应答,false表示手动应答
        channel.basicConsume(QUEUE_NAME,true,  deliverCallback,cancelCallback);
    }
}
package com.example.demo.mq;

import com.rabbitmq.client.*;

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

//消费者
public class Consumer2 {

    //交换机名称
    private static final  String EXCHANGE_NAME="jiaohuanji";
    //队列的名称
    private static final  String QUEUE_NAME="q2";

    public static void main(String[] args) throws IOException, TimeoutException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
        //声明交换机类型
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        //声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        //把交换机和路由key 对应的队列进行绑定,第三个参数路由key
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "bb.#");

        //回调消息
        DeliverCallback deliverCallback=(String var1, Delivery var2)->{
            String str=new String(var2.getBody());
            System.out.println("消费者2接收到消息:"+str+",路由key:"+var2.getEnvelope().getRoutingKey());
        };
        //取消回调
        CancelCallback cancelCallback=(String var1)->{
            System.out.println("消息消费被中断");
        };
        //消费消息   true:表示自动应答,false表示手动应答
        channel.basicConsume(QUEUE_NAME,true,  deliverCallback,cancelCallback);
    }
}

接下来先启动消费者,在启动生产者

消费者2接收到2条

消费者1接收到1条

 在这里一定要注意,#结尾可以匹配多个单词,*只能匹配一个

接下来我们看一下死信队列

死信队列就是普通队列没有接收到消息,然后放入到死信队列中

场景有3种

ttl过期,消息被拒绝,队列超过最大长度

接下来我们先看下ttl过期

package com.example.demo.mq;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeoutException;

//生产者
public class Producer {

    //普通交换机的名称
    private static final  String PT_EXCHANGE="pt_exchange";


    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
       //声明一个交换机,第一个参数是交换机的名称, 第二个参数是交换机的类型
        channel.exchangeDeclare(PT_EXCHANGE,BuiltinExchangeType.DIRECT);
        //设置消息的ttl过期时间为10秒
        AMQP.BasicProperties basicProperties=new AMQP.BasicProperties()
                //这里是10000毫秒
                .builder().expiration("10000").build();

        for (int i = 0; i < 10; i++) {
            String message="str"+i;
            //发出消息  第二个参数是路由key
            channel.basicPublish(PT_EXCHANGE,"pt_key",basicProperties,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息");
        }
    }


}

 

package com.example.demo.mq;

import com.rabbitmq.client.*;

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

//消费者
public class Consumer {

    //普通交换机的名称
    private static final  String PT_EXCHANGE="pt_exchange";
    //普通队列的名称
    private static final  String PT_QUEUE_NAME="pt_c1";
    //死信交换机的名称
    private static final  String DEAD_EXCHANGE="dead_exchange";
    //死信队列的名称
    private static final  String DEAD_QUEUE_NAME="dead_c1";

    public static void main(String[] args) throws IOException, TimeoutException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
        //声明普通交换机类型 和死信交换机类型
        channel.exchangeDeclare(PT_EXCHANGE,BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
        //声明死信队列
        channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);
        //绑定死信队列                         第三个参数 路由key
        channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE, "dead_key");

        //普通队列和死信队列进行绑定
        Map<String,Object> map=new HashMap<>();
        //普通队列设置死信交换机 参数 key 是固定值
        map.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        //普通队列设置死信 routing-key 参数 key 是固定值
        map.put("x-dead-letter-routing-key", "dead_key");

        //声明普通队列
        channel.queueDeclare(PT_QUEUE_NAME,false,false,false,map);
        //绑定普通队列  第三个参数 路由key
        channel.queueBind(PT_QUEUE_NAME,PT_EXCHANGE,"pt_key");

        //回调消息
        DeliverCallback deliverCallback=(String var1, Delivery var2)->{
            String str=new String(var2.getBody());
            System.out.println("消费者1接收到消息:"+str);
        };
        //取消回调
        CancelCallback cancelCallback=(String var1)->{
            System.out.println("消息消费被中断");
        };
        //消费消息   true:表示自动应答,false表示手动应答
        channel.basicConsume(PT_QUEUE_NAME,true,  deliverCallback,cancelCallback);
    }
}
package com.example.demo.mq;

import com.rabbitmq.client.*;

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

//消费者
public class Consumer2 {
    //死信交换机的名称
    private static final  String DEAD_EXCHANGE="dead_exchange";
    //死信队列的名称
    private static final  String DEAD_QUEUE_NAME="dead_c1";

    public static void main(String[] args) throws IOException, TimeoutException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
        //声明交换机类型
        channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
        //声明队列
        channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);

        //把交换机和路由key 对应的队列进行绑定,第三个参数路由key
        channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE, "dead_key");

        //回调消息
        DeliverCallback deliverCallback=(String var1, Delivery var2)->{
            String str=new String(var2.getBody());
            System.out.println("消费者2接收到消息:"+str);
        };
        //取消回调
        CancelCallback cancelCallback=(String var1)->{
            System.out.println("消息消费被中断");
        };
        //消费消息   true:表示自动应答,false表示手动应答
        channel.basicConsume(DEAD_QUEUE_NAME,true,  deliverCallback,cancelCallback);
    }
}

接下来我们先启动消费者1,然后关掉,模拟死信队列

 接下来我们在启动生产者的代码,看下10秒钟后是否进入死信队列中,

 

 10秒钟之后果然进入了死信队列中

接下来我们启动消费者2,去进行消费死信队列的数据 

 可以看到,数据已经被消费

 接下来我们在看下,队列超过最大长度,进入死信队列

package com.example.demo.mq;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeoutException;

//生产者
public class Producer {

    //普通交换机的名称
    private static final  String PT_EXCHANGE="pt_exchange";


    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
       //声明一个交换机,第一个参数是交换机的名称, 第二个参数是交换机的类型
        channel.exchangeDeclare(PT_EXCHANGE,BuiltinExchangeType.DIRECT);
        for (int i = 0; i < 10; i++) {
            String message="str"+i;
            //发出消息  第二个参数是路由key
            channel.basicPublish(PT_EXCHANGE,"pt_key",null,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息");
        }
    }


}
package com.example.demo.mq;

import com.rabbitmq.client.*;

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

//消费者
public class Consumer {

    //普通交换机的名称
    private static final  String PT_EXCHANGE="pt_exchange";
    //普通队列的名称
    private static final  String PT_QUEUE_NAME="pt_c1";
    //死信交换机的名称
    private static final  String DEAD_EXCHANGE="dead_exchange";
    //死信队列的名称
    private static final  String DEAD_QUEUE_NAME="dead_c1";

    public static void main(String[] args) throws IOException, TimeoutException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
        //声明普通交换机类型 和死信交换机类型
        channel.exchangeDeclare(PT_EXCHANGE,BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
        //声明死信队列
        channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);
        //绑定死信队列                         第三个参数 路由key
        channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE, "dead_key");

        //普通队列和死信队列进行绑定
        Map<String,Object> map=new HashMap<>();
        //普通队列设置死信交换机 参数 key 是固定值
        map.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        //普通队列设置死信 routing-key 参数 key 是固定值
        map.put("x-dead-letter-routing-key", "dead_key");
        //设置普通队列的长度限制,例如发10个,4个则为死信
        map.put("x-max-length",6);

        //声明普通队列
        channel.queueDeclare(PT_QUEUE_NAME,false,false,false,map);
        //绑定普通队列  第三个参数 路由key
        channel.queueBind(PT_QUEUE_NAME,PT_EXCHANGE,"pt_key");

        //回调消息
        DeliverCallback deliverCallback=(String var1, Delivery var2)->{
            String str=new String(var2.getBody());
            System.out.println("消费者1接收到消息:"+str);
        };
        //取消回调
        CancelCallback cancelCallback=(String var1)->{
            System.out.println("消息消费被中断");
        };
        //消费消息   true:表示自动应答,false表示手动应答
        channel.basicConsume(PT_QUEUE_NAME,true,  deliverCallback,cancelCallback);
    }
}

注意要把之前的队列删除掉,然后模拟发送10条消息,超过6条的都放入死信队列

先启动消费者1,然后关闭,在启动生产者,可以看到6条在普通队列,

4条在死信队列 

如果消费死信队列的数据,还是启动消费者2就可以了 

接下来,我们看一下,消息被拒绝,放入死信队列

删除之前的队列

package com.example.demo.mq;

import com.rabbitmq.client.*;

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

//消费者
public class Consumer {

    //普通交换机的名称
    private static final  String PT_EXCHANGE="pt_exchange";
    //普通队列的名称
    private static final  String PT_QUEUE_NAME="pt_c1";
    //死信交换机的名称
    private static final  String DEAD_EXCHANGE="dead_exchange";
    //死信队列的名称
    private static final  String DEAD_QUEUE_NAME="dead_c1";

    public static void main(String[] args) throws IOException, TimeoutException {
        //获取通道
        Channel channel = RabbitmqUtils.getChannel();
        //声明普通交换机类型 和死信交换机类型
        channel.exchangeDeclare(PT_EXCHANGE,BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
        //声明死信队列
        channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);
        //绑定死信队列                         第三个参数 路由key
        channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE, "dead_key");

        //普通队列和死信队列进行绑定
        Map<String,Object> map=new HashMap<>();
        //普通队列设置死信交换机 参数 key 是固定值
        map.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        //普通队列设置死信 routing-key 参数 key 是固定值
        map.put("x-dead-letter-routing-key", "dead_key");

        //声明普通队列
        channel.queueDeclare(PT_QUEUE_NAME,false,false,false,map);
        //绑定普通队列  第三个参数 路由key
        channel.queueBind(PT_QUEUE_NAME,PT_EXCHANGE,"pt_key");

        //回调消息
        DeliverCallback deliverCallback=(String var1, Delivery var2)->{
            String str=new String(var2.getBody());
            if(str.equals("str3")){
                //模拟一个消息被拒绝放入死信队列  第二个参数 是否放入普通队列 true:放入,false: 不放入普通队列,如果配置了死信交换机,放入死信队列
                channel.basicReject(var2.getEnvelope().getDeliveryTag(),false);
            }else {
                //手动应答 第二个参数是否批量应答,true:批量  false:单个应答
                channel.basicAck(var2.getEnvelope().getDeliveryTag(),false);
                System.out.println("消费者1接收到消息:"+str);
            }

        };
        //取消回调
        CancelCallback cancelCallback=(String var1)->{
            System.out.println("消息消费被中断");
        };
        //消费消息   true:表示自动应答,false表示手动应答  这里要改成手动应答
        channel.basicConsume(PT_QUEUE_NAME,false,  deliverCallback,cancelCallback);
    }
}

启动生产者,消费者1,可以看到有1条进入了死信队列,启动消费者2就可以把死信队列进行消费

接下来我们在看下延迟队列

延迟队列可以说是在ttl过期的基础上进行的

使用延时队列的场景;
1.说说发送定时任务;
2.会议预定开始前,提前提醒;
3.下单之后,10分钟内未支付;
4.发起退款,30分钟内未响应;
5.领导3天内没有审批;
6.预约成功后,30分钟内,没有签到;

接下来我们创建一个新的springboot项目,先把之前的队列都删除掉

引入pom.xml

<dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <!--RabbitMQ 依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-amqp</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.47</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <!--RabbitMQ 测试依赖-->
            <dependency>
                <groupId>org.springframework.amqp</groupId>
                <artifactId>spring-rabbit-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>

配置文件

spring.rabbitmq.host=192.168.184.142
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123456

编写mq配置类

package com.example.demo.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 org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

//ttl队列配置类
@Configuration
public class TtlQueueConfig {


    //普通交换机
    public static final String P_EXCHANGE="p_exchange";
    //普通队列
    public static final String Q1_QUEUE_NAME="q1";
    //普通路由key
    public static final String P_ROUTING_KEY="p_key";

    //死信交换机
    public static final String D_EXCHANGE="d_exchange";
    //死信队列
    public static final String S1_QUEUE_NAME="s1";
    //死信路由key
    public static final String D_ROUTING_KEY="d_key";

    //声明普通交换机
    @Bean
    public DirectExchange pExchange(){
        return new DirectExchange(P_EXCHANGE);
    }

    //声明死信交换机
    @Bean
    public DirectExchange dExchange(){
        return new DirectExchange(D_EXCHANGE);
    }

    //声明普通队列
    @Bean
    public Queue queueA(){
        Map<String,Object> map=new HashMap<>();
        //声明当前队列绑定的死信交换机
        map.put("x-dead-letter-exchange", D_EXCHANGE);
        //声明当前队列的死信路由 key
        map.put("x-dead-letter-routing-key", D_ROUTING_KEY);
        //这里不需要设置ttl,我们在生产者发送消息的时候设置

        //构建队列
        return QueueBuilder.durable(Q1_QUEUE_NAME).withArguments(map).build();
    }

    //声明死信队列
    @Bean
    public Queue queueB(){
        return new Queue(S1_QUEUE_NAME);
    }


    //绑定普通队列和普通交换机 普通路由key
    @Bean
    public Binding queryBindA(@Qualifier("queueA") Queue queue,
                             @Qualifier("pExchange") DirectExchange pExchange){
            return BindingBuilder.bind(queue).to(pExchange).with(P_ROUTING_KEY);
    }

    //绑定死信队列和死信交换机 和死信路由key
    @Bean
    public Binding queryBindB(@Qualifier("queueB") Queue queue,
                             @Qualifier("dExchange") DirectExchange dExchange){
        return BindingBuilder.bind(queue).to(dExchange).with(D_ROUTING_KEY);
    }




}

创建生产者

package com.example.demo.controller;

import com.example.demo.config.TtlQueueConfig;
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.RestController;

import java.util.Date;

@RestController
public class TestController {


    @Autowired
    private RabbitTemplate rabbitTemplate;


    /**
     * 模拟延时队列
     * @param str 内容
     * @param ttl 延时的毫秒
     */
    @GetMapping("send/{str}/{ttl}")
    public void send(@PathVariable("str") String str,@PathVariable("ttl") String ttl){
        //普通交换机
        String exchange= TtlQueueConfig.P_EXCHANGE;
        //普通路由key
        String routingKey=TtlQueueConfig.P_ROUTING_KEY;
        rabbitTemplate.convertAndSend(exchange,routingKey,str,x->{
            //设置消息的过期时间
            x.getMessageProperties().setExpiration(ttl);
            return x;
        });
        System.out.println("当前时间:"+new Date()+",发送内容:"+str+",延时时间"+ttl+"毫秒");
    }
}

创建消费者

package com.example.demo.config;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Date;

//消费者
@Component
public class Consumer {

    //监听死信队列
    @RabbitListener(queues = TtlQueueConfig.S1_QUEUE_NAME)
    public void receive(Message message, Channel channel){
        System.out.println("接收到消息,当前时间:"+new Date()+",内容:"+new String(message.getBody()));
    }
}

启动项目,浏览器输入这2个请求


一个模拟20秒
localhost:8080/send/nihao/20000

一个模拟2秒
localhost:8080/send/nihao3/2000

可以看到延时20秒的成功了,但是延时2秒的在没有先出来,是因为在mq的队列中,先进先出,

所以设置短时间的也要等前面的出去了,才能往下走。

 接下来我们进行优化,在mq中安装延时队列插件

打开Community Plugins — RabbitMQ

下载rabbitmq_delayed_message_exchange插件

选择你的mq的版本,否则会报下面的错误

Enabling plugins on node rabbit@99f955bd4205:
rabbitmq_delayed_message_exchange
Error:
Failed to enable some plugins: 
    rabbitmq_delayed_message_exchange:
        Plugin doesn't support current server version. Actual broker version: "3.9.11", supported by the plugin: ["3.11.0-3.11.x"]
 

我的版本是3.9.11所以,我选择3.9.0

进入容器的plugins目录就可以看到是使用的哪个版本

把文件上传到服务器

然后执行下面的命令

拷贝到容器里面

docker cp rabbitmq_delayed_message_exchange-3.9.0.ez myrabbit:/plugins

进入容器里面
docker exec -it myrabbit /bin/bash

cd plugins

安装

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

出现下面的就表示安装成功了

 然后退出容器,重启mq

exit

docker restart myrabbit

然后看下控制台出现这个箭头指向的类型, 表示已经安装成功

 接下来我们在对代码进行下改进

新建mq配置类

package com.example.demo.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 {

    //延迟交换机名称
    public static final String DE_EXCHANGE="de_exchange";
    //延迟队列的名称
    public static final String DE_QUEUE_NAME="de_q";
    //延迟路由key
    public static final String DE_ROUTING_KEY="de_key";

    //声明队列
    @Bean
    public Queue queueC(){
        return new Queue(DE_QUEUE_NAME);
    }

    /**
     * 自定义交换机 该类型消息支持延迟投递,消息传递后,并不会立即投递到目标队列中,而是存储在一个分布式数据系统表(mnesia)中,当达到
     * 投递时间时,才投递到目标队列中
     * @return
     */
    @Bean
    public CustomExchange customExchange(){
        Map<String,Object> map=new HashMap<>();
        //自定义交换机类型
        map.put("x-delayed-type","direct");
        //x-delayed-message 交换机类型 固定名称
        return new CustomExchange(DE_EXCHANGE,"x-delayed-message",true,false,map);
    }

    //绑定队列和交换机和路由key
    @Bean
    public Binding bindingC(@Qualifier("queueC")Queue queue,
                            @Qualifier("customExchange")CustomExchange customExchange){
        return BindingBuilder.bind(queue).to(customExchange).with(DE_ROUTING_KEY).noargs();
    }

}

创建生产者

package com.example.demo.controller;

import com.example.demo.config.DelayedQueueConfig;
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.RestController;

import java.util.Date;

@RestController
public class TestController {


    @Autowired
    private RabbitTemplate rabbitTemplate;


    /**
     * 模拟延时队列
     * @param str 内容
     * @param ttl 延时的毫秒
     */
    @GetMapping("send/{str}/{ttl}")
    public void send(@PathVariable("str") String str,@PathVariable("ttl") Integer ttl){
        //交换机
        String exchange= DelayedQueueConfig.DE_EXCHANGE;
        //路由key
        String routingKey=DelayedQueueConfig.DE_ROUTING_KEY;
        rabbitTemplate.convertAndSend(exchange,routingKey,str,x->{
            //设置消息的过期时间
            x.getMessageProperties().setDelay(ttl);
            return x;
        });
        System.out.println("当前时间:"+new Date()+",发送内容:"+str+",延时时间"+ttl+"毫秒");
    }
}

创建消费者

package com.example.demo.config;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Date;

//消费者
@Component
public class Consumer {

    //监听延迟队列
    @RabbitListener(queues = DelayedQueueConfig.DE_QUEUE_NAME)
    public void receive(Message message, Channel channel){
        System.out.println("接收到消息,当前时间:"+new Date()+",内容:"+new String(message.getBody()));
    }
}

启动项目,再次浏览器访问

一个模拟20秒
localhost:8080/send/nihao/20000

一个模拟2秒
localhost:8080/send/nihao3/2000

可以看到,2秒的先打印出来,20秒的,等一会才显示

 在代码中使用延时队列,就使用延时队列插件来比较高效

接下来我们看下发布确认高级

mq重启导致生产者发送消息失败,消息丢失,我们来看下代码,先把之前的队列都删掉

在配置文件添加

spring.rabbitmq.host=192.168.184.145
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123456

#发布消息成功后,到交换机,触发回调方法 就是会调用MyCallback类的confirm方法
spring.rabbitmq.publisher-confirm-type=correlated

 创建配置类

package com.example.demo.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;

//mq配置
@Configuration
public class ConfirmConfig {

    //交换机名称
    public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";

    //队列名称
    public static final String CONFIRM_QUEUE_NAME = "confirm.queue";

    //路由key
    public static final String ROUTING_KEY = "ke1";

    //声明业务 Exchange
    @Bean
    public DirectExchange confirmExchange() {
        return new DirectExchange(CONFIRM_EXCHANGE_NAME);
    }

    // 声明确认队列
    @Bean
    public Queue confirmQueue() {
        return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
    }

    // 声明确认队列绑定关系
    @Bean
    public Binding queueBinding(@Qualifier("confirmQueue") Queue queue,
                                @Qualifier("confirmExchange") DirectExchange exchange) {
        //队列和交换机,路由key绑定
        return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
    }
}

创建交换机回调类

package com.example.demo.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Slf4j
@Component
public class MyCallback implements RabbitTemplate.ConfirmCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        //把当前类注入到回调中
        rabbitTemplate.setConfirmCallback(this);


    }

    /**
     * 交换机不管是否收到消息的一个回调方法
     * @param correlationData 消息相关数据
     * @param b 交换机是否收到消息 true:收到, false:未收到
     * @param s 未收到的原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        String id="";
        if(correlationData!=null){
            id=correlationData.getId();
        }
        if(b){
           log.info("交换机收到消息,id为{}:",id);
        }else {
            log.info("交换机没有收到消息,id为{id},原因是:{}",id,s);
        }


    }
}

创建生产者

package com.example.demo.controller;

import com.example.demo.config.ConfirmConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
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.RestController;

import java.util.Date;

/**
 * 生产者
 */
@Slf4j
@RestController
public class TestController {


    @Autowired
    private RabbitTemplate rabbitTemplate;


    /**
     * 消息回调和退回
     * @param str 内容
     */
    @GetMapping("send/{str}")
    public void send(@PathVariable("str") String str){
        //指定消息id为1
        CorrelationData correlationData=new CorrelationData("1");
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME,ConfirmConfig.ROUTING_KEY,str,correlationData);
        log.info("发送消息内容:{},路由key:{}",str,ConfirmConfig.ROUTING_KEY);

        //发送一个错误的路由key
        //指定消息id为2
        CorrelationData correlationData2=new CorrelationData("2");
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME,"key2",str,correlationData2);
        log.info("发送消息内容:{},路由key:{}",str,"key2");




    }
}

创建消费者

package com.example.demo.init;

import com.example.demo.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;

/**
 * 消费者
 */
@Component
@Slf4j
public class Consumer {


    //监听队列
    @RabbitListener(queues = ConfirmConfig.CONFIRM_QUEUE_NAME)
    public void receive(Message message){
        String str=new String(message.getBody());
        log.info("消费者接收到消息:{}",str);
    }
}

访问浏览器

localhost:8080/send/你好

 可以看到,发送了2条消息,消费者只接收了1条,另一个不存在的路由key,发送的消息,就丢失了

消息丢失,交换机是不知道的,我们要告诉生产者

接下来进行,回退消息

在配置文件加入

spring.rabbitmq.host=192.168.184.145
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123456

#发布消息成功后,到交换机,触发回调方法 就是会调用MyCallback类的confirm方法
spring.rabbitmq.publisher-confirm-type=correlated


#消息退回 触发MyCallback类的returnedMessage方法
spring.rabbitmq.publisher-returns=true

修改回调类,加入回退

package com.example.demo.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * 回调类
 */
@Slf4j
@Component
public class MyCallback implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnsCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        //把当前类注入到回调中
        rabbitTemplate.setConfirmCallback(this);
        //把当前类注入到回退中
        rabbitTemplate.setReturnsCallback(this);
    }

    /**
     * 交换机不管是否收到消息的一个回调方法
     * @param correlationData 消息相关数据
     * @param b 交换机是否收到消息 true:收到, false:未收到
     * @param s 未收到的原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        String id="";
        if(correlationData!=null){
            id=correlationData.getId();
        }
        if(b){
           log.info("交换机收到消息,id为{}:",id);
        }else {
            log.info("交换机没有收到消息,id为{id},原因是:{}",id,s);
        }


    }

    /**
     * 当消息无法路由的时候,回调方法,把消息退回给生产者
     * 就是路由key,发送消息,消费者接收不到,消息退回的方法
     * @param returnedMessage
     */
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        //消息内容
        String str=new String(returnedMessage.getMessage().getBody());
        log.info("消息内容:{},被交换机:{},退回,原因是:{},路由key是:{},code:{}",str,returnedMessage.getExchange(),returnedMessage.getReplyText(),
                returnedMessage.getRoutingKey(),returnedMessage.getReplyCode());
    }
}

再次执行

localhost:8080/send/你好

可以看到,错误的路由key,发送消息失败,被退回了

虽然消息回退了,但是怎么处理这些消息呢?
我们来创建一个备份交换机,备份交换机的类型为扇出(fanout),就是广播的意思,

当路由key发送失败,都进入备份交换机,
这样,备份交换机的路由key,都可以为空串,然后把消息发送到绑定的队列,

当然我们还可以建立一个报警队列,用来独立的消费者进行监测和报警

接下来我们对mq的配置类进行修改,先把交换机和队列都删除掉,防止报错

package com.example.demo.config;

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//mq配置
@Configuration
public class ConfirmConfig {

    //交换机名称
    public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";

    //队列名称
    public static final String CONFIRM_QUEUE_NAME = "confirm.queue";

    //路由key
    public static final String ROUTING_KEY = "ke1";

    //备份交换机
    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";

//    //声明业务 Exchange
//    @Bean
//    public DirectExchange confirmExchange() {
//        return new DirectExchange(CONFIRM_EXCHANGE_NAME);
//    }

    // 声明确认队列
    @Bean
    public Queue confirmQueue() {
        return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
    }

    // 声明确认队列绑定关系
    @Bean
    public Binding queueBinding(@Qualifier("confirmQueue") Queue queue,
                                @Qualifier("confirmExchange") DirectExchange exchange) {
        //队列和交换机,路由key绑定
        return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
    }

    //声明备份交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange(BACKUP_EXCHANGE_NAME);
    }

    //声明备份普通队列
    @Bean
    public Queue backupQueue(){
        return QueueBuilder.durable(BACKUP_QUEUE_NAME).build();
    }

    //声明报警队列
    @Bean
    public Queue warningQueue(){
        return QueueBuilder.durable(WARNING_QUEUE_NAME).build();
    }

    //声明备份普通队列绑定备份交换机
    @Bean
    public Binding backupBind(@Qualifier("backupQueue")Queue queue,
                              @Qualifier("fanoutExchange")FanoutExchange fanoutExchange){
        return BindingBuilder.bind(queue).to(fanoutExchange);
    }

    //声明报警队列和备份交换机绑定
    @Bean
    public Binding warningBind(@Qualifier("warningQueue")Queue queue,
                              @Qualifier("fanoutExchange")FanoutExchange fanoutExchange){
        return BindingBuilder.bind(queue).to(fanoutExchange);
    }

    //声明普通交换机的备份交换机
    @Bean
    public DirectExchange confirmExchange(){
        ExchangeBuilder exchangeBuilder=ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME)
                .durable(true)
                //设置该交换机的备份交换机 alternate-exchange 固定的key 不可以改动
                .withArgument("alternate-exchange",BACKUP_EXCHANGE_NAME);
        return exchangeBuilder.build();
    }


}

再次发送

localhost:8080/send/你好

可以看到,错误的路由key发送到了备份交换机,备份交换机发送到了绑定的队列

因为备份交换机的优先级比较高,所以Mycallback类的returnedMessage回退消息的方法就不会在触发了,因为扇出(fanout)类型的交换机是广播,所以多个消费者可以接收到消息

接下来我们看下消息幂等性

什么是幂等性,就是一条消息,被重复消费了,但是还没有签收消息,这时候消费者挂掉了,

然后消费者启动之后,再次收到了这条消息,这就是消息幂等,接下来代码演示一遍

之前的队列,交换机都删除掉

创建mq配置类

package com.example.demo.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;

//mq配置
@Configuration
public class MqConfig {

    //交换机名称
    public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";

    //队列名称
    public static final String CONFIRM_QUEUE_NAME = "confirm.queue";

    //路由key
    public static final String ROUTING_KEY = "ke1";


    //声明业务 Exchange
    @Bean
    public DirectExchange confirmExchange() {
        return new DirectExchange(CONFIRM_EXCHANGE_NAME);
    }

    // 声明确认队列
    @Bean
    public Queue confirmQueue() {
        return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
    }

    // 声明确认队列绑定关系
    @Bean
    public Binding queueBinding(@Qualifier("confirmQueue") Queue queue,
                                @Qualifier("confirmExchange") DirectExchange exchange) {
        //队列和交换机,路由key绑定
        return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
    }








}

创建生产者

package com.example.demo.controller;

import com.example.demo.config.MqConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.connection.CorrelationData;
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.RestController;

import java.util.Date;
import java.util.UUID;

/**
 * 生产者
 */
@Slf4j
@RestController
public class TestController {


    @Autowired
    private RabbitTemplate rabbitTemplate;


    /**
     * 消息回调和退回
     * @param str 内容
     */
    @GetMapping("send/{str}")
    public void send(@PathVariable("str") String str){
        //指定随机消息id
        String messageId=UUID.randomUUID().toString();
        //把消息id,放入消息中
        Message message = MessageBuilder.withBody(str.getBytes())
                .setMessageId(messageId).build();

        rabbitTemplate.convertAndSend(MqConfig.CONFIRM_EXCHANGE_NAME,MqConfig.ROUTING_KEY,message);
        log.info("发送消息内容:{},路由key:{},消息id:{}",str,MqConfig.ROUTING_KEY,messageId);


    }
}

创建消费者

package com.example.demo.init;
import com.example.demo.config.MqConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.stereotype.Component;
import java.io.IOException;

/**
 * 消费者
 */
@Component
@Slf4j
public class Consumer {


    //监听队列
    @RabbitListener(queues = MqConfig.CONFIRM_QUEUE_NAME)
    public void receive(Message message, Channel channel){
        //消息属性
        MessageProperties messageProperties = message.getMessageProperties();
        //消息id
        String messageId =messageProperties.getMessageId();
        //消息内容
        String str=new String(message.getBody());
        log.info("消费者接收到消息:{},消息id为:{}",str,messageId);
        try {
            //模拟耗时
            try {
                Thread.sleep(20*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //手动应答  false表示单个应答 true表示批量应答
            channel.basicAck(messageProperties.getDeliveryTag(),false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

配置文件开启手动应答

spring.rabbitmq.host=192.168.184.145
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123456

#发布消息成功后,到交换机,触发回调方法 就是会调用MyCallback类的confirm方法
#spring.rabbitmq.publisher-confirm-type=correlated


#消息退回 触发MyCallback类的returnedMessage方法
#spring.rabbitmq.publisher-returns=true

#消息开启手动确认
spring.rabbitmq.listener.direct.acknowledge-mode=manual

然后浏览器打开

localhost:8080/send/你好

收到消息后,立马关闭项目,可以看到有1条消息,没有被签收

 我们在启动项目,可以看到消费者再次消息了这条消息,这就是重复消费,也叫幂等性

 那怎么解决呢?我们在服务器装一下redis

docker run -d -p 6379:6379 --name redis-node-1 -v /data/redis/share/redis-node-1:/data --privileged=true  redis 

利用setnx的原子性,判断key是否存在,如果存在返回0,如果不存在,就创建一条数据,返回1

 在pom.xml加入

     <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>

配置文件

spring.rabbitmq.host=192.168.184.145
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123456

#发布消息成功后,到交换机,触发回调方法 就是会调用MyCallback类的confirm方法
#spring.rabbitmq.publisher-confirm-type=correlated


#消息退回 触发MyCallback类的returnedMessage方法
#spring.rabbitmq.publisher-returns=true

#消息开启手动确认
spring.rabbitmq.listener.direct.acknowledge-mode=manual


spring.redis.host=192.168.184.145
spring.redis.port=6379

修改消费者

package com.example.demo.init;
import com.example.demo.config.MqConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.io.IOException;

/**
 * 消费者
 */
@Component
@Slf4j
public class Consumer {


    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //监听队列
    @RabbitListener(queues = MqConfig.CONFIRM_QUEUE_NAME)
    public void receive(Message message, Channel channel){
        //消息属性
        MessageProperties messageProperties = message.getMessageProperties();
        //消息id
        String messageId =messageProperties.getMessageId();

        //把消息id 当作key和value,
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(messageId, messageId);
        if(!flag){
            //如果返回false,说明存在
            //模拟从数据库查询出来的数据,如果没有进行数据库操作,可以继续往下走
            int count=1;
            if(count>0){
                //数据库已存在,业务已经被处理
                return;
            }
        }
        //如果不存在,那么会自动添加到redis一条消息
        //消息内容
        String str=new String(message.getBody());
        log.info("消费者接收到消息:{},消息id为:{}",str,messageId);
        try {
            //模拟耗时
            try {
                Thread.sleep(20*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //手动应答  false表示单个应答 true表示批量应答
            channel.basicAck(messageProperties.getDeliveryTag(),false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

浏览器打开
localhost:8080/send/你好

执行后立马关闭项目,然后再次启动项目,就可以看到,重复的消息,已经被拦截了,并且重复的消息也被签收了,这就解决了消息幂等

 接下来我们在看下优先级队列,就是设置在生产者设置优先级,和队列进行绑定,

然后在消费者接收的时候,优先级比较高的,先消费

package com.example.demo.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;

//mq配置
@Configuration
public class MqConfig {

    //交换机名称
    public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";

    //队列名称
    public static final String CONFIRM_QUEUE_NAME = "confirm.queue";

    //路由key
    public static final String ROUTING_KEY = "ke1";


    //声明业务 Exchange
    @Bean
    public DirectExchange confirmExchange() {
        return new DirectExchange(CONFIRM_EXCHANGE_NAME);
    }

    // 声明确认队列
    @Bean
    public Queue confirmQueue() {
        //设置队列的优先级,最大可以设置255,官网推荐设置1-10,如果设置太高比较吃内存和cpu
        Map<String, Object> params = new HashMap();
        params.put("x-max-priority", 10);
        return QueueBuilder.durable(CONFIRM_QUEUE_NAME).withArguments(params).build();
    }

    // 声明确认队列绑定关系
    @Bean
    public Binding queueBinding(@Qualifier("confirmQueue") Queue queue,
                                @Qualifier("confirmExchange") DirectExchange exchange) {
        //队列和交换机,路由key绑定
        return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
    }








}
package com.example.demo.init;
import com.example.demo.config.MqConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.io.IOException;

/**
 * 消费者
 */
@Component
@Slf4j
public class Consumer {

    //监听队列
    @RabbitListener(queues = MqConfig.CONFIRM_QUEUE_NAME)
    public void receive(Message message){
        //消息内容
        String str=new String(message.getBody());
        log.info("消费者接收到消息:{}",str);

    }
}
package com.example.demo.controller;

import com.example.demo.config.MqConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.connection.CorrelationData;
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.RestController;

import java.util.Date;
import java.util.UUID;

/**
 * 生产者
 */
@Slf4j
@RestController
public class TestController {


    @Autowired
    private RabbitTemplate rabbitTemplate;


    /**
     * 发送消息
     */
    @GetMapping("send")
    public void send(){
        for (int i = 0; i < 10; i++) {
            if(i==6){
                //模拟第6个消息,先消费
                rabbitTemplate.convertAndSend(MqConfig.CONFIRM_EXCHANGE_NAME,MqConfig.ROUTING_KEY,i+"",x->{
                    //设置优先级为6
                    x.getMessageProperties().setPriority(6);
                    return x;
                });
            }else {
                rabbitTemplate.convertAndSend(MqConfig.CONFIRM_EXCHANGE_NAME,MqConfig.ROUTING_KEY,i+"");
            }
        }
        log.info("发送成功");
    }
}

先把之前的交换机,队列删除掉,否则报错

消费者先把监听注释掉,先发送生产者消息,让消息先有个排序的时间

浏览器打开

localhost:8080/send

然后开启消费者,可以看到6,被先消费了

接下来我们看下mq集群,先把之前的mq删除掉

配置文件修改




#rabbitmq集群配置
spring.rabbitmq.addresses=192.168.184.145:8071,192.168.184.143:8072,192.168.184.144:8073
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin

参考这篇文章安装好集群

安装好之后就可以通过任意端口访问

http://192.168.184.145:8081/#/

http://192.168.184.145:8082/#/

http://192.168.184.145:8083/#/

 在别的端口也能看到其他的队列消息

 接下来我们把mq1宕机,可以在其他端口上看到,mq1显示宕机

接下来我们看下镜像队列

就是mq1的队列挂掉了,会自动备份到其他节点上

在admin->策略这里

backup-name 随机起的名字

Pattern是规则的意思,队列的名字以^backup开头

ha-mode:exactly

ha-params:2

ha-sync-mode:automatic

自动备份,主机1份,从机1份

 添加之后,就会显示一个策略

 接下来我们在发送一个消息,队列这里有一个加1的操作,就是备份了,点进去

 可以看到在mq3上备份了一份

 接下来我们把mq1宕机,可以看到在mq2上备份了一份

 接下来我们在把mq1的机器启动起来,并进行消费,可以看到,已经把数据给消费掉了

我们还可以在其他节点的控制台,获取消息

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值