RabbitMQ

一、消息队列

1.1 MQ的相关概念

1.1.1 什么是MQ

1.1.2 为什么要用MQ

 

 

 1.1.3 MQ的分类

 

 

 

 1.1.4 MQ的选择

1.2 RabbitMQ

1.2.1 RabbitMQ的概念

 

1.2.2 四大核心概念

 

 

 1.2.3 RabbitMQ 核心部分

1.2.4 各个名词介绍

 

 

 二、Hello World

2.1 导入依赖

<!--指定 jdk 编译版本-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
</plugin>
</plugins>
</build>

    <dependencies>
    <!--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>
</dependencies>

 2.2 消息生产者

public class Producer {
    private static  final String QUEUE_NAME="hello";//队列名称
    public static void main(String[] args) throws Exception {
        //1创建一个连接工厂
        ConnectionFactory factory=new ConnectionFactory();
        factory.setHost("192.168.160.139");
        factory.setUsername("guest");
        factory.setPassword("guest");
        try(
                //2创建连接:将连接放到try()里,会自动关闭资源
                Connection connection= factory.newConnection();
                //3创建信道
                Channel channel=connection.createChannel();
                ){
            //4创建一个队列
            /**
             * Queue.DeclareOk queueDeclare(
             * String queue, 队列名称
             * boole只供an durable, 是否持久化(将消息存在磁盘中)
             * boolean exclusive,  队列中的消息是否只供一个消费者来消费
             * boolean autoDelete, 是否要自我删除,也就是当最后一个消费端断开连接后,该队列是否自动删除
             *Map<String, Object> arguments) throws IOException;  其他参数
             */
            channel.queueDeclare(QUEUE_NAME,false,false,false,null);
            //5 发送消息
            String message="hello world!";
            /**
             * void basicPublish(
             * String exchange, 交换机
             * String routingKey, 路由键
             * BasicProperties props, 其他的参数信息
             * byte[] body  发送消息的信息体
             * ) throws IOException;
             */
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
            System.out.println("消息发送完毕!");
        }

    }
}

2.3  消息消费者

/**
 * 消费者
 */
public class Consumer {
    private final static String QUEUE_NAME="hello";
    public static void main(String[] args) throws Exception {
        //1创建连接工厂
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("192.168.160.139");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        //2创建连接和信道
        Connection connection=connectionFactory.newConnection();
        Channel channel=connection.createChannel();
        System.out.println("等待接收消息。。。。。");
            //消费者消息接收成功后调用的回调函数
        DeliverCallback deliverCallback=((consumerTag, message) -> {
            String message1=new String(message.getBody());
                System.out.println("接收到消息:"+message1);
            });
            //消息消费失败后调用的回调方法
        CancelCallback cancelCallback=(consumerTag -> {
                System.out.println("消息消费失败:"+consumerTag);
            });
            //3接收消息
            /**
             * String basicConsume(
             * String queue, 队列名
             * boolean autoAck, 消费成功后是否要自动应答 true-自动应答 false-手动应答
             * DeliverCallback deliverCallback, 消费成功后的回调
             * CancelCallback cancelCallback 消费未成功后的回调
             * ) throws IOException;
             */
            channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
        }
    }

三、Work Queues

 3.1 轮询分发消息

在这个案例中我们会启动两个工作线程,一个消息发送线程,我们来看看他们两个工作线程
是如何工作的。

3.1.1 抽取工具类

每次要一大段的代码来创建信道麻烦了,直接将其抽取出来。

/**
 * RabbitMQ 工具类
 */
public class RabbitMqUtils {
    public static Channel getChannel() throws Exception{
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("192.168.160.139");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        Connection connection= connectionFactory.newConnection();
        return connection.createChannel();
    }
}

3.1.2 启动两个工作线程(消费线程)

public class Worker1 {
    private static final String QUEUE_NAME="hello1";
    public static void main(String[] args) throws Exception {
                Channel channel=RabbitMqUtils.getChannel();
            DeliverCallback deliverCallback=((ConsumerTag,message)->{
                String message1=new String(message.getBody());
                System.out.println("C1接收到消息"+message1);
            });
            CancelCallback cancelCallback=(ConsumerTag->{
                System.out.println("消息消费失败:"+ConsumerTag);
            });
            System.out.println("C1开始接收消息");
            channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
            System.out.println("成功消费");
        }
    }

3.1.3  创建一个生产者线程(发送线程)

/**
 * 发送消息者(生产者)
 */
public class Producer {
    private static final String QUEUE_NAME="hello1";
    public static void main(String[] args) throws Exception {
        try(
                Channel channel=RabbitMqUtils.getChannel();
                ) {
            channel.queueDeclare(QUEUE_NAME,false,false,false,null);
            Scanner scanner=new Scanner(System.in);
            while(scanner.hasNext()){
                String message= scanner.next();
                channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
                System.out.println("发送消息完成:"+message);
            }

        }
    }
}

3.1.4 结果展示

 3.2 消息应答

3.2.1 概念

 3.2.2 自动应答

 3.2.3 消息(手动)应答的方法

 3.2.4 Multiple的解释

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

 

 3.2.5 消息自动重新入队

 

 3.2.6 消息手动应答代码

 3.2.7  手动应答效果演示

 

3.3 RabbitMQ持久化

3.3.1 概念

3.3.2 队列如何实现持久化

 

3.3.3 消息实现持久化

 3.3.4 不公平分发

 

3.3.5  预取值

 四、发布确认

4.1 发布确认原理

 4.2 发布确认的策略

4.2.1 开启发布确认的方法

 4.2.2 单个确认发布

package com.wangbijun.mq;

import com.rabbitmq.client.Channel;

import java.util.UUID;

/**
 * 单个确认发布
 */
public class publishMessageIndividually {
    private static final int MESSAGE_COUNT=10;
    public static void main(String[] args) throws Exception {
        try(
                Channel channel=RabbitMqUtils.getChannel();
                ) {
            String queueName= UUID.randomUUID().toString();
            channel.queueDeclare(queueName,false,false,false,null);
            //开启发布确认
            channel.confirmSelect();
            long begin=System.currentTimeMillis();
            for(int i=0;i<MESSAGE_COUNT;i++){
                String message= i + "";
                channel.basicPublish("",queueName,null,message.getBytes());
                boolean flag = channel.waitForConfirms();
                if(flag){
                    System.out.println(i+"消息发送成功~");
                }
            }
            long end=System.currentTimeMillis();
            System.out.println("发布"+MESSAGE_COUNT+"个,单独确认消息,耗时"+(end-begin)+"ms");
        }
    }
}

 4.2.3 批量确认发布

 

package com.wangbijun.mq;

import com.rabbitmq.client.Channel;

import java.util.UUID;

public class publishMessageBatch {
    public static final int MESSAGE_COUNT=10;
    public static void main(String[] args) throws Exception {
        try(
                Channel channel=RabbitMqUtils.getChannel();
                ) {
            String queuename= UUID.randomUUID().toString();
            channel.queueDeclare(queuename,false,false,false,null);
            channel.confirmSelect();
            //批量确认消息大小
            int batchSize=100;
            //为确认消息个数
            int outstandingMessageCount=0;
            long begin=System.currentTimeMillis();
            for(int i=0;i<MESSAGE_COUNT;++i){
                String message=i+"";
                channel.basicPublish("",queuename,null,message.getBytes());
                outstandingMessageCount++;
                if(outstandingMessageCount==batchSize){
                    channel.waitForConfirms();
                    outstandingMessageCount=0;
                }
            }
            //为了确保还有剩余没有确认消息,再次确认
            if(outstandingMessageCount>0){
                channel.waitForConfirms();
            }
            long end=System.currentTimeMillis();
            System.out.println("发布"+MESSAGE_COUNT+"个批量确认消息,耗时"+(end-begin)+"ms");
        }
    }
}

 4.2.4 异步确认发布

 

package com.wangbijun.mq;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmCallback;

import java.util.UUID;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;

/**
 * 异步确认发布
 */
public class publishMessageAsync {
    private static final int MESSAGE_COUNT=10;
    public static void main(String[] args) throws Exception {
        try(
                Channel channel=RabbitMqUtils.getChannel();
                ) {
            String queuename= UUID.randomUUID().toString();
            channel.queueDeclare(queuename,false,false,false,null);
            //开启发布确认
            channel.confirmSelect();
            /**
             * 线程安全有序的一个哈希表,适用于高并发的情况
             * 1.轻松的将序号与消息进行关联
             * 2.轻松批量删除条目 只要给到序列号
             * 3、支持并发访问
             */
            ConcurrentSkipListMap<Long,String> outstandingConfirms = new ConcurrentSkipListMap<>();
            /**
             * 确认收到消息的一个回调
             * 1.消息序列号
             * 2.true 可以确认小于等于当前序列号的信息
             * 3.false 确认当前序列号消息
             */
            ConfirmCallback ackCallback=(deliveryTag,multiple)->{
                if(multiple){
                    //返回的是小于等于当前序列号的未确认消息 是一个map
                    ConcurrentNavigableMap<Long, String> confirmed = outstandingConfirms.headMap(deliveryTag, true);
                    //清除该部分确认消息
                    confirmed.clear();
                }else{
                    //只清除当前序列号的消息
                    outstandingConfirms.remove(deliveryTag);
                }
            };
            /**
             * 消息未被确认的一个回调函数
             */
            ConfirmCallback nackCallback=(deliveryTag, multiple) -> {
                String message=outstandingConfirms.get(deliveryTag);
                System.out.println("发布的消息"+message+"未被确认,序列号"+deliveryTag);
            };

            /**
             * 添加一个异步确认的监听器
             * 1、确认收到消息的回调
             * 2.未收到消息的回调
             */
            channel.addConfirmListener(ackCallback,null);
            long begin=System.currentTimeMillis();
            for(int i=0;i<MESSAGE_COUNT;i++){
                String message="消息"+i;
                /**
                 * channel.getNextPublishSeqNo() 获取下一个消息的序列号
                 * 通过序列号与一个消息题进行一个关联
                 * 全部都是未确认的消息体。
                 */
                outstandingConfirms.put(channel.getNextPublishSeqNo(),message);
                channel.basicPublish("",queuename,null,message.getBytes());
            }
            long end=System.currentTimeMillis();
            System.out.println("发布"+MESSAGE_COUNT+"个异步消息,耗时"+(end-begin)+"ms");
        }
    }
}

4.2.5 如何处理异步未确认消息

最好的解决的解决方案就是把未确认的消息放到一个基于内存的能被发布线程访问的队列,
比如说用 ConcurrentLinkedQueue 这个队列在 confirm callbacks 与发布线程之间进行消息的传
递。

4.2.6 以上3种发布确认速度对比

 

 补充:

 

 五、交换机

在上一节中,我们创建了一个工作队列。我们假设的是工作队列背后,每个任务都恰好交付给一个消 费者( 工作进程 ) 。在这一部分中,我们将做一些完全不同的事情 - 我们将消息传达给多个消费者。这种模式 称为 ” 发布/订阅 ”.
为了说明这种模式,我们将构建一个简单的日志系统。它将由两个程序组成 :
第一个程序将发出日志消息,
第二个程序是消费者。
其中我们会启动两个消费者,其中一个消费者接收到消息后把日志存储在磁盘,
另外一个消费者接收到消息后把消息打印在屏幕上,事实上第一个程序发出的日志消息将广播给所有消费者

5.1 Exchanges

5.1.1 Exchanges概念

 5.1.2 Exchanges的类型

总共有以下类型:
直接 (direct),
主题 (topic) ,
标题 (headers) ,
扇出 (fanout)

5.1.3 无名exchange

5.2 临时队列

 

 5.3 绑定(Bindings)

 5.4 Fanout

5.4.1 Fanout介绍

 5.4.2 Fanout实战

 

 1、EmitLog发送消息给两个消费者接收

/**
 * EmitLog发送消息给两个消费者
 */
public class EmitLog {
    private static final String EXCHANGE_Name="logs";
    public static void main(String[] args) throws Exception {
        try (
                Channel channel= RabbitMqUtils.getChannel();
                ){
            //1声明一个fanout型的交换机exchange
            channel.exchangeDeclare(EXCHANGE_Name,"fanout");
            Scanner sc=new Scanner(System.in);
            System.out.println("请输入您要发送的信息:");
            while (sc.hasNext()){
           String message=sc.nextLine();
           channel.basicPublish(EXCHANGE_Name,"",null,message.getBytes("UTF-8"));
           System.out.println("生产者发出消息:"+message);
            }

        }
    }
}

2、ReceiveLogs01 将接收到的消息打印在控制台

/**
 * 消费者1号:将接收到的信息打印在控制台
 */
public class ReceiveLogs01 {
    private static final String EXCHANGE_NAME="logs";
    public static void main(String[] args) throws Exception {
        Channel channel= RabbitMqUtils.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
        //生成一个临时队列
        String queue = channel.queueDeclare().getQueue();
        //将队列和交换机进行绑定
        /**
         *  Queue.BindOk queueBind(
         *  String queue,
         *  String exchange,
         *  String routingKey) throws IOException;
         */
        channel.queueBind(queue,EXCHANGE_NAME,"");
        System.out.println("等待接收消息,把接收到的消息打印在屏幕。。。。。");
        DeliverCallback deliverCallback=((consumerTag, message) -> {
            String message1=new String(message.getBody(),"UTF-8");
            System.out.println("控制台打印接收到的消息"+message1);
        });
        channel.basicConsume(queue,true,deliverCallback,consumerTag -> {});
    }
}

3、ReceiveLogs02 将接收到的消息存储在磁盘

public class ReceiveLogs02 {
    private static final String EXCHANGE_NAME="logs";
    public static void main(String[] args) throws Exception {
        Channel channel= RabbitMqUtils.getChannel();
        //创建临时队列
        String queue = channel.queueDeclare().getQueue();
        //将临时队列和交换机进行绑定
        channel.queueBind(queue,EXCHANGE_NAME,"");
        System.out.println("等待接收消息,把接收到的消息写到文件。。。。。");
        DeliverCallback deliverCallback=((consumerTag, message) -> {
            String message1=new String(message.getBody());
           File file=new File("D:\\rabbitmq_info.txt");
            FileUtils.writeStringToFile(file,message1);
            System.out.println("数据写入文件成功");
        });
        channel.basicConsume(queue,deliverCallback,consumerTag -> {});
    }
}

 5.5 Direct exchange

5.5.1 回顾

 5.5.2 Direct exchange 介绍

 

 

 5.5.3 多重绑定

 5.5.4 实战

 1、生产者代码

/**
 * 消息的发送者
 */
public class EmitLog {
    private static final String EXCHANGE_NAME="direct_logs";
    public static void main(String[] args) throws Exception{
        try(
                //1获取管道
                Channel channel= RabbitMqUtils.getChannel();
                ) {
            //2创建交换机
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            //创建多个路由键
            Scanner scanner=new Scanner(System.in);
            System.out.println("请输入您要发送的消息:");
            while(scanner.hasNext()){
                String message=scanner.nextLine();
                System.out.println("请输入要发送的路由键(info,warning,error):");
                String rouKey=scanner.nextLine();
                channel.basicPublish(EXCHANGE_NAME,rouKey,null,message.getBytes());
                System.out.println("发送消息:"+message+",路由键为:"+rouKey);
            }
        }
    }
}

2、消费者1(将error信息存到磁盘中)

/**
 * 消费者1:只将error的信息存到磁盘中
 */
public class ReceiveLogsDirect1 {
    private static final String EXCHANGE_NAME="direct_logs";
    private static final String QUEUE_NAME="disk";
    private static final String ROUTIEKEY="error";
    public static void main(String[] args) throws Exception {
        //1 获取管道
        Channel channel= RabbitMqUtils.getChannel();
        //2创建队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //3将交换机和队列进行绑定
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,ROUTIEKEY,null);
        DeliverCallback deliverCallback=(consumerTag,message)->{
            String message1=new String(message.getBody());
            File file=new File("D:\\rabbitmq_direct_error_logs.txt");
            FileUtils.writeStringToFile(file,message1);
        };
        /**
         * String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException;
         */
        channel.basicConsume(QUEUE_NAME,false,deliverCallback,consumerTag -> {});
    }
}

3、消费者2(将warning和info日志打印到控制台)

/**
 * 消费者2:将warning和info的日志打印到控制台
 */
public class ReceiveDirect2 {
    private static final String EXCHANGE_NAME="direct_logs";
    private static final String QUEUE_NAME="console";
    private static final String KEY1="warning";
    private static final String KEY2="info";
    public static void main(String[] args) throws Exception{
        Channel channel= RabbitMqUtils.getChannel();
        //创建队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //将交换机和队列进行绑定
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,KEY1);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,KEY2);
        //接收消息
        DeliverCallback deliverCallback=(consumerTag,message)->{
            String message1=new String(message.getBody());
            System.out.println(message.getEnvelope().getRoutingKey()+"-接收到消息:"+message1);
        };
        channel.basicConsume(QUEUE_NAME,false,deliverCallback,consumerTag -> {});
    }
}

 5.6 Topics

5.6.1 之前类型的问题

 5.6.2 Topic的要求

 5.6.3 Topic匹配案例

 5.6.4 实战

1、生产者代码

package com.wangbijun.mq.topic;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.wangbijun.mq.RabbitMqUtils;

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

public class EmitTopic {
    private static final String EXCHANGE_NAME="topic_logs";
    public static void main(String[] args) throws Exception {
        try(
                Channel channel=RabbitMqUtils.getChannel();
                ) {
            //创建交换机
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
            //创建绑定
            Map<String,String> map=new HashMap<>();
            map.put("quick.orange,rabbit","被队列Q1Q2接收到");
            map.put("lazy.orange.elephant","被队列 Q1Q2 接收到");
            map.put("quick.orange.fox","被队列 Q1 接收到");
            map.put("lazy.brown.fox","被队列 Q2 接收到");
            map.put("lazy.pink.rabbit","虽然满足Q2的两个绑定但只被队列 Q2 接收一次");
            map.put("quick.brown.fox","不匹配任何绑定不会被任何队列接收到会被丢弃");
            map.put("quick.orange.male.rabbit","是四个单词不匹配任何绑定会被丢弃");
            map.put("lazy.orange.male.rabbit","是四个单词但匹配 Q2");
            for (Map.Entry<String, String> entry : map.entrySet()) {
                //发送消息
                channel.basicPublish(EXCHANGE_NAME,entry.getKey(),null,entry.getValue().getBytes());
            }

        }
    }
}

2、Q1代码

package com.wangbijun.mq.topic;


import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rabbitmq.client.Delivery;
import com.wangbijun.mq.RabbitMqUtils;

/**
 * 队列1
 */
public class Q1 {
    private static final String QUEUR_NAME="Q1";
    private static final String KEY="*.orange.*";
    private static final String EXCHANGE_NAME="topic_logs";
    public static void main(String[] args) throws Exception {
        Channel channel= RabbitMqUtils.getChannel();
        //创建队列
        channel.queueDeclare(QUEUR_NAME,false,false,false,null);
        channel.queueBind(QUEUR_NAME,EXCHANGE_NAME,KEY);
        //接收消息
        DeliverCallback deliverCallback=(consumerTag,message)->{
            String message1=new String(message.getBody());
            System.out.println("接收到消息:"+message1);
        };
        channel.basicConsume(QUEUR_NAME,false,deliverCallback,consumerTag -> {});
    }
}

3、Q2代码

package com.wangbijun.mq.topic;


import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.wangbijun.mq.RabbitMqUtils;

/**
 * 队列1
 */
public class Q2 {
    private static final String QUEUR_NAME="Q2";
    private static final String KEY1="*.*.rabbit";
    private static final String KEY2="lazy.#";
    private static final String EXCHANGE_NAME="topic_logs";
    public static void main(String[] args) throws Exception {
        Channel channel= RabbitMqUtils.getChannel();
        //创建队列
        channel.queueDeclare(QUEUR_NAME,false,false,false,null);
        channel.queueBind(QUEUR_NAME,EXCHANGE_NAME,KEY1);
        channel.queueBind(QUEUR_NAME,EXCHANGE_NAME,KEY2);
        //接收消息
        DeliverCallback deliverCallback=(consumerTag,message)->{
            String message1=new String(message.getBody());
            System.out.println("接收到消息:"+message1);
        };
        channel.basicConsume(QUEUR_NAME,false,deliverCallback,consumerTag -> {});
    }
}

六、死信队列

6.1 死信的概念

 

 6.2 死信的来源

 6.3 死信实战

6.3.1 代码架构图

 6.3.2 消息TTL过期

1、生产者代码

/**
 * 生产者:设置消息的TTL为10s
 */
public class ProducerTTL {
    private static final String NORMAL_EXCHANGE="normal_exchange";
    public static void main(String[] args) throws Exception {
        try(
                Channel channel= RabbitMqUtils.getChannel();
                ) {
            channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
            //设置消息的TTL
            AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
            //向正常队列中发送10条信息
            for(int i=0;i<10;++i){
                String message="info"+i;
                channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes("UTF-8"));
                System.out.println("生产者发送消息:"+message);
            }

        }

    }
}

重要代码:

AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes("UTF-8"));

2、消费者 C1 代码(启动之后关闭该消费者 模拟其接收不到消息)

/**
 * 消费者C1:监听正常队列,当启动后关闭该消费者,模拟其收不到消息,使信息超时变成死信
 */
public class C1 {
    private static final String QUEUE_NAME="normal_queue";
    private static final String KEY="zhangsan";
    private static final String EXCHANGE_NAME="normal_exchange";
    private static final String DEAD_EXCHANGE="dead_exchange";//死信交换机
    private static final String DEAD_QUEUE="dead_queue";//死信队列
    private static final String DEAD_KEY="lisi";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        //创建死信交换机和正常交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
        //创建正常队列和死信队列
        Map<String,Object> params=new HashMap<>();
        params.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        params.put("x-dead-letter-routing-key",DEAD_KEY);
        channel.queueDeclare(QUEUE_NAME,false,false,false,params);
        channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
        //创建队列和交换机的绑定关系
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,KEY);
        channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,DEAD_KEY);
        System.out.println("等待接收信息。。。");
        DeliverCallback deliverCallback=((consumerTag, message) -> {
            String message1=new String(message.getBody());
            System.out.println("接收到消息:"+message1);
        });
        channel.basicConsume(QUEUE_NAME,false,deliverCallback,consumerTag -> {});

    }

}

3、消费者 C2 代码(以上步骤完成后 启动 C2 消费者 它消费死信队列里面的消息)

package com.wangbijun.mq.dead;


import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.wangbijun.mq.RabbitMqUtils;

/**
 * C2:死信队列监听者
 */
public class C2 {
    private static final String DEAD_QUEUE="dead_queue";//死信队列
    public static void main(String[] args) throws Exception {
        Channel channel= RabbitMqUtils.getChannel();
        System.out.println("开始接收死信信息");
        DeliverCallback deliverCallback=((consumerTag, message) -> {
            String message1=new String(message.getBody());
            System.out.println("接收到消息:"+message1);
        });
        channel.basicConsume(DEAD_QUEUE,deliverCallback,consumerTag -> {});
    }
}

6.3.3 队列达到最大长度

 1. 消息生产者代码去掉 TTL 属性

 2. C1 消费者修改以下代码(启动之后关闭该消费者 模拟其接收不到消息)

3.C2消费者代码不变

 

 6.3.4 消息被拒

1. 消息生产者代码同上生产者一致
2. C1 消费者代码( 启动之后关闭该消费者 模拟其接收不到消息 )
3. C2 消费者代码不变
启动消费者 1 然后再启动消费者 2

七、延迟队列

1、延迟队列概念

 2、延迟队列使用场景

 

 

 3、RabbitMQ中的TTL

 

 7.3.1 消息设置TTL

 7.3.2 队列设置TTL

 7.3.3 两者的区别

 7.4 整合springboot

7.4.1. 创建项目

7.4.2 导入依赖

<!--        RabbitMQ依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
<!--        rabbitMQ测试依赖-->
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>
<!--        swagger:生成api文档-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>

7.4.3 编写YML配置文件

spring:
  rabbitmq:
    host: 192.168.160.139
    port: 5672
    username: guest
    password: guest

7.4.4 添加Swagger配置类

@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("rabbitmq接口文档")
                .description("本文档描述了rabbitmq微服务接口定义")
                .version("1.0")
                .contact(new Contact("enjoy6288","http://atguigu.com","wangbijun0413@qq.com"))
                .build();
    }
}

7.5 队列TTL

7.5.1 代码架构图

7.5.2  配置文件类代码

package com.wangbijun.springboot_rabbitmq.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
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;

@Component
public class TTLQueueConfig {
    public static final String X_EXCHANGE="X";//交换机
    public static final String Queue_A="QA";
    public static final String QUEUE_B="QB";
    public static final String Y_DEAD_LETTER_EXCHANGE="Y";//死信交换机
    public static final String DEAD_LETTER_QUEUE="QD";
    /**
     * 创建交换机
     * **/
    @Bean
    public Exchange XExchange(){
        return new DirectExchange(X_EXCHANGE);
    }
    @Bean
    public Exchange YExchange(){
        return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
    }

    /**
     * 创建队列
     * @return
     */
    @Bean
    public Queue QAQueue(){
        /**
         * public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete,
         *                        @Nullable Map<String, Object> arguments)
         */
        Map<String,Object> params=new HashMap<>();
        //声明当前队列绑定的死信交换机
        params.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
        //声明当前队列的死信路由key
        params.put("x-dead-letter-routing-key","YD");
        //声明队列的TTL为10s
        params.put("x-message-ttl",10000);
        return new Queue(Queue_A,false,false,false,params);
    }
    @Bean
    public Queue QBQueue(){
        Map<String,Object> params=new HashMap<>();
        params.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
        params.put("x-dead-letter-routing-key","YD");
        params.put("x-message-ttl",40000);
        return new Queue(QUEUE_B,false,false,false,params);
    }
    @Bean
    public Queue QDQueue(){
        return new Queue(DEAD_LETTER_QUEUE,false,false,false,null);
    }
    /**
     * 将队列和交换机绑定
     */
    @Bean
    public Binding X_QA_binding(){
        /**
         *lic Binding(
         * String destination,目的地
         *  DestinationType destinationType,目的地类型
         *  String exchange,交换机
         *  String routingKey,路由键
         *  @Nullable Map<String, Object> arguments 参数
         *  )
         */
        return new Binding(Queue_A, Binding.DestinationType.QUEUE,X_EXCHANGE,"XA",null);
    }

    @Bean
    public Binding X_QB_binding(){
        return new Binding(QUEUE_B, Binding.DestinationType.QUEUE,X_EXCHANGE,"XB",null);
    }

    @Bean
    public Binding Y_QD_binding(){
        return new Binding(DEAD_LETTER_QUEUE, Binding.DestinationType.QUEUE,Y_DEAD_LETTER_EXCHANGE,"YD",null);
    }
}

7.5.3 消息生产者代码

/**
 * 生产者代码
 */
@RestController
@RequestMapping("ttl")
@Slf4j
public class SendMsgController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @GetMapping("sendMsg/{message}")
    public void sendMsg(@PathVariable("message") String message){
        log.info("当前时间:{},发送一条信息给两个TTL队列:{}",new Date(),message);
        rabbitTemplate.convertAndSend("X","XA","消息来自ttl为10s的队列:"+message);
        rabbitTemplate.convertAndSend("X","XB","消息来自ttl为40s的队列:"+message);
    }
}

7.5.4 消息消费者代码

@Component
@Slf4j
public class DeadLetterQueueConsumer {
    @RabbitListener(queues = "QD")
    public void receiveD(Message message, Channel channel){
        String mess=new String(message.getBody());
        log.info("当前时间为{},收到死信队列信息{}",new Date(),mess);
    }
}

 

 7.6 延迟队列优化

7.6.1 代码架构图

在这里新增了一个队列 QC, 绑定关系如下 , 该队列不设置 TTL 时间

 7.6.2 配置文件类代码

    /**
     * 新添加一个队列QC
     */
    
    @Bean
    public Queue QCQueue(){
        Map<String,Object> params=new HashMap<>();
        params.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
        params.put("x-dead-letter-routing-key","YD");
        return new Queue(QUEUE_C,false,false,false,params);
    }
    
    @Bean
    public Binding X_QC_Binding(){
        return new Binding(QUEUE_C, Binding.DestinationType.QUEUE,X_EXCHANGE,"XC",null);
    }

7.6.3 消息生产者代码

发消息时给消息设置过期时间

   @GetMapping("sendExpirationMsg/{message}/{ttlTime}")
    public void sendMsg(@PathVariable String message,@PathVariable String ttlTime){
        rabbitTemplate.convertAndSend("X","XC",message, correlationData->{
            correlationData.getMessageProperties().setExpiration(ttlTime);
            return correlationData;
        });
        log.info("当前时间:{},发送一条时长{}毫秒TTL信息给队列C:{}",new Date(),ttlTime,message);
    }

 7.7 RabbitMQ插件实现延迟队列

7.7.1 安装延迟队列插件

 

7.7.2 代码架构图

 7.7.3 配置文件类代码

@Component
public class DelayedQueueConfig {
    public static final String DELAYED_QUEUE_NAME="delayed.queue";
    public static final String DELAYED_EXCHANGE_NAME="delayed.exchange";
    public static final String DELAYED_ROUTING_KEY="delayed.routingkey";
    @Bean
    public Queue delayedQueue(){
        return new Queue(DELAYED_QUEUE_NAME,false,false,false,null);
    }
    @Bean
    public Exchange delayedExchange(){
        /**
         * public CustomExchange(String name, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments)
         */
        Map<String,Object> args=new HashMap<>();
        args.put("x-delayed-type","direct");
        return new CustomExchange(DELAYED_EXCHANGE_NAME,"x-delayed-message",false,false,args);
    }
    @Bean
    public Binding bindingDelayQueue(){
        return new Binding(DELAYED_QUEUE_NAME, Binding.DestinationType.QUEUE,DELAYED_EXCHANGE_NAME,DELAYED_ROUTING_KEY,null);
    }
}

7.7.4 消息生产者代码

   @GetMapping("sendDelayMsg/{message}/{delayTime}")
    public void sendMsg1(@PathVariable("message") String message,@PathVariable("delayTime") Integer delayTime){
        rabbitTemplate.convertAndSend(DelayedQueueConfig.DELAYED_EXCHANGE_NAME,DelayedQueueConfig.DELAYED_ROUTING_KEY,message,correlationData->{
            correlationData.getMessageProperties().setDelay(delayTime);//设置消息的过期时间
            return correlationData;
        });
        log.info("当前时间为:{},发送一条延迟{}毫秒的信息给队列delayed.queue:{}",new Date(),delayTime,message);
    }

7.7.5 消息消费者代码

@Component
@Slf4j
public class DelayQueueConsumer {
    @RabbitListener(queues = DelayedQueueConfig.DELAYED_QUEUE_NAME)
    public void receiveDelayedMsg(Message message, Channel channel){
        String msg=new String(message.getBody());
        log.info("当前时间:{},收到延迟队列的消息:{}",new Date(),msg);
    }
}

 7.8 总结

 八、发布确认高级

8.1 发布确认springboot 版本

 8.1.1 确认机制方案

 8.1.2 代码架构图

 8.1.3 配置文件(application.yml)

 

8.1.4 添加配置类

@Configuration
public class confirmConfig {
    public static final String CONFIRM_EXCHANGE_NAME="confirm.exchange";
    public static final String CONFIRM_QUEUE_NAME="confirm.queue";
    
    @Bean
    public Exchange confirmExchange(){
        return new DirectExchange(CONFIRM_EXCHANGE_NAME);
    }
    
    @Bean
    public Queue confirmQueue(){
        return new Queue(CONFIRM_QUEUE_NAME,false,false,false,null);
    }
    
    @Bean
    public Binding queueBinding(){
        return new Binding(CONFIRM_QUEUE_NAME, Binding.DestinationType.QUEUE,CONFIRM_EXCHANGE_NAME,"key1",null);
    }
}

8.1.5 回调接口

package com.wangbijun.springboot_rabbitmq.config;

import com.rabbitmq.client.ConfirmCallback;
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 java.io.IOException;

/**
 * 回调接口
 */
@Component
@Slf4j
public class MyCallBack implements RabbitTemplate.ConfirmCallback {
    /**
     * 交换机不管是否收到消息的一个回调方法
     * @param correlationData
     * @param ack
     * @param cause
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id=correlationData!=null?correlationData.getId():"";
        if(ack){
            log.info("交换机已经收到id为:{}的消息",id);
        }else{
            log.info("由于{}的原因,交换机还未收到id为{}的消息",cause,id);
        }
    }
}

8.1.6 消息生产者

package com.wangbijun.springboot_rabbitmq.Controller;

import com.wangbijun.springboot_rabbitmq.config.MyCallBack;
import com.wangbijun.springboot_rabbitmq.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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;

@RestController
@Slf4j
@RequestMapping("/confirm")
public class ProducerController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private MyCallBack myCallBack;
    @PostConstruct
    /**
     * 当 rabbitTemplate对象注入完成后,调用这个初始化方法,将我们编写的回调方法设置给他
     */
    public void init(){
        rabbitTemplate.setConfirmCallback(myCallBack);
    }

    @GetMapping("/sendMessage/{message}")
    public void sendMessage(@PathVariable String message){
        //指定发送的消息的ID为1
        CorrelationData correlationData1=new CorrelationData("1");
        rabbitTemplate.convertAndSend(confirmConfig.CONFIRM_EXCHANGE_NAME,"key1",message+"key1",correlationData1);
        //指定发送的消息的ID为1
        CorrelationData correlationData2=new CorrelationData("2");
        rabbitTemplate.convertAndSend(confirmConfig.CONFIRM_EXCHANGE_NAME,"key2",message+"key2",correlationData2);
        log.info("发送消息内容:{}",message);
    }
}

8.1.7 消息消费者

package com.wangbijun.springboot_rabbitmq.Consumer;

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 ConfirmConsumer {
    @RabbitListener(queues = "confirm.queue")
    public void receiveMsg(Message message){
        String msg=new String(message.getBody());
        log.info("接收到队列confirm.queue消息:{}",msg);
    }

}

8.1.8 结果分析

 

 8.2 回退消息

8.2.1 Mandatory参数

也就是当消息从交换机发送到队列中失败

8.2.2 回调接口

package com.wangbijun.springboot_rabbitmq.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class MyReturnCallBac implements RabbitTemplate.ReturnCallback {
    /**
     * 将不能成功放到队列中的消息回退给生产者
     * 当消息无法路由的时候的回调方法
     * @param message
     * @param replyCode
     * @param replyText
     * @param exchange
     * @param routingKey
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        log.error("消息{},被交换机{}退回。退回原因:{},路由键为:{}",new String(message.getBody()),exchange,replyText,routingKey);
    }
}

 8.2.3 消费者代码

package com.wangbijun.springboot_rabbitmq.Controller;

import com.wangbijun.springboot_rabbitmq.config.MyCallBack;
import com.wangbijun.springboot_rabbitmq.config.MyReturnCallBac;
import com.wangbijun.springboot_rabbitmq.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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;

@RestController
@Slf4j
@RequestMapping("/confirm")
public class ProducerController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private MyCallBack myCallBack;
    @Autowired
    private MyReturnCallBac myReturnCallBac;
    @PostConstruct
    /**
     * 当 rabbitTemplate对象注入完成后,调用这个初始化方法,将我们编写的回调方法设置给他
     */
    public void init(){

        rabbitTemplate.setConfirmCallback(myCallBack);
        rabbitTemplate.setReturnCallback(myReturnCallBac);
        rabbitTemplate.setMandatory(true);
    }

    @GetMapping("/sendMessage/{message}")
    public void sendMessage(@PathVariable String message){
        //指定发送的消息的ID为1
        CorrelationData correlationData1=new CorrelationData("1");
        rabbitTemplate.convertAndSend(confirmConfig.CONFIRM_EXCHANGE_NAME,"key1",message+"key1",correlationData1);
        //指定发送的消息的ID为1
        CorrelationData correlationData2=new CorrelationData("2");
        rabbitTemplate.convertAndSend(confirmConfig.CONFIRM_EXCHANGE_NAME,"key2",message+"key2",correlationData2);
        log.info("发送消息内容:{}",message);
    }
}

8.2.4 结果分析

 8.3 备份交换机

 8.3.1 代码架构图

 8.3.2 修改配置类

package com.wangbijun.springboot_rabbitmq.config;

import com.rabbitmq.client.AMQP;
import org.springframework.amqp.core.*;
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;

@Component
public class confirmConfig {
    public static final String CONFIRM_EXCHANGE_NAME="confirm.exchange";
    public static final String CONFIRM_QUEUE_NAME="confirm.queue";
    public static final String BACKUP_EXCHANGE="backup.exchange";//备份交换机
    public static final String BACKUP_QUEUE="backup.queue";//备份队列
    public static final String WARNING_QUEUE="warning.queue";//报警队列


    @Bean
    public Queue confirmQueue(){
        return new Queue(CONFIRM_QUEUE_NAME,false,false,false,null);
    }

    @Bean
    public Binding queueBinding(){
        return new Binding(CONFIRM_QUEUE_NAME, Binding.DestinationType.QUEUE,CONFIRM_EXCHANGE_NAME,"key1",null);
    }
    /**
     * 声明备份交换机
     */
    @Bean
    public Exchange backupExchange(){
        return new FanoutExchange(BACKUP_EXCHANGE);
    }
    /**
     * 声明交换机,并将备份交换机设置成它的交换机
     */
    @Bean
    public Exchange confirmExchange(){
        Map<String,Object> args=new HashMap<>();
        args.put("alternate-exchange",BACKUP_EXCHANGE);
        return new DirectExchange(CONFIRM_EXCHANGE_NAME,false,false,args);
    }

    /**
     * 声明备份队列
     */
    @Bean
    public Queue backQueue(){
        return new Queue(BACKUP_QUEUE,false,false,false,null);
    }

    /**
     * 声明警告队列
     *
     */
    @Bean
    public Queue WarningQueue(){
        return new Queue(WARNING_QUEUE,false,false,false,null);
    }

    /**
     * 声明备份队列和备份交换机的绑定关系
     */
    @Bean
    public Binding BackQueue_BackExchange_Binding(){
        return new Binding(BACKUP_QUEUE, Binding.DestinationType.QUEUE,BACKUP_EXCHANGE,"",null);
    }
    /**
     * 声明警告队列和备份交换机的绑定关系
     */
    @Bean
    public Binding WarningQueue_BackExchange_Binding(){
        return new Binding(WARNING_QUEUE, Binding.DestinationType.QUEUE,BACKUP_EXCHANGE,"",null);
    }
}

8.3.3 报警消费者

@Component
@Slf4j
public class WarningQueueConsumer {
    @RabbitListener(queues = "warning.queue")
    public void receiveWarningMsg(Message message){
        String msg=new String(message.getBody());
        log.error("报警发现不可路由消息:{}",msg);
    }
}

8.3.4 测试注意事项

8.3.5 结果分析

 

 九、RabbitMQ其他知识点

9.1 幂等性

9.1.1 概念

 9.1.2 消息重复消费

 9.1.4 解决思路

 9.1.4 消费端的幂等性保障

 9.1.5 唯一ID+指纹码机制

 9.1.6 Redis原子性

 9.2 优先级队列

9.2.1 使用

 

 9.2.2 如何添加

 

 9.2.3 实战

 

 9.3. 惰性队列

9.3.1. 使用场景

9.3.2. 两种模式

9.3.3. 内存开销对比

 十、RabbitMQ集群

10.1 Clustering

10.1.1 使用集群的原因

 

 10.1.2. 搭建步骤

 

 

 

 10.2. 镜像队列

10.2.1. 使用镜像的原因

 10.2.2. 搭建步骤

 

 

 10.3. Haproxy+Keepalive 实现高可用负载均衡

10.3.1. 整体架构图

 10.3.2. Haproxy 实现负载均衡

 10.3.3. 搭建步骤

 

 10.3.4. Keepalived 实现双机(主备)热备

 10.3.5. 搭建步骤

 

10.4. Federation Exchange

 10.4.1. 使用它的原因

 

 10.4.2. 搭建步骤

 

 

 

10.5. Federation Queue

 10.5.1. 使用它的原因

10.5.2. 搭建步骤

 

 10.6. Shovel

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值