RabbitMQ史上最全核心知识讲解

文章目录

  • 1:软件的下载以及安装
  • 2.几个常用消息中间件的对比
  • 3:队列的类型
  • 4.简单队列
    • 4.1简单队列之公平转发
  • 5.订阅模式
  • 6.Routing模式
  • 7.主题模式
  • 8.RabbitMQ的消息事务处理
  • 9.消息确认机制之串行confirm
  • 10.消息确认机制之Confirm异步
  • 11.Springboot整合RabbitMQ
    • 11.1 单生产者和单消费者
  • 12.SpringBoot当中开启ACK应答模式
  • 13.RabbitMQ如何保证消费消息的幂等性
  • 14.RabbitMQ的重试机制

1:软件的下载以及安装

参考博客:https://william.blog.csdn.net/article/details/97509675

2.几个常用消息中间件的对比

RabbitMQ史上最全核心知识讲解_第1张图片

3:队列的类型

RabbitMQ史上最全核心知识讲解_第2张图片
1:第一种,我们称之为简单队列,他有一个发送者和消费者还有一个队列
2:第二种:我们称之为工作队列,他和第一种不同的是,一个队列有多个消费者
3:第三种:我们称之为发布订阅:一个队列对应一个消费者,且发送者把消息发给转换机,由他进行转发队列
4:第四种:我们称之为:Routing,在第三种的基础上,我们加入了Routing,那个队列有对应exchange对应的Routing即发送给对方
5:第五种:我们称之为主推,在第三种的基础上,队列利用正则进行Routing的匹配
6:第六种:基本上很少用到,这里不在阐述

4.简单队列

依赖

	<dependencies>
		<dependency>
			<groupId>com.rabbitmqgroupId>
			<artifactId>amqp-clientartifactId>
			<version>3.6.5version>
		dependency>
	dependencies>

工具类 用于连接初始化

public class MQConnectionUtils {
	// 创建新的MQ连接
	public static Connection newConnection() throws IOException, TimeoutException {
		// 1.创建连接工厂
		ConnectionFactory factory = new ConnectionFactory();
		// 2.设置连接地址
		factory.setHost("127.0.0.1");
		// 3.设置用户名称
		factory.setUsername("guest");
		// 4.设置用户密码
		factory.setPassword("guest");
		// 5.设置amqp协议端口号
		factory.setPort(5672);
		// 6.设置VirtualHost地址
		Connection connection = factory.newConnection();
		return connection;
	}

}

生产者用于发送消息

public class Product {
    public static final String QUEUE_NAME = "william_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        //创建一个链接
        Connection connection = MQConnectionUtils.newConnection();
        //创建一个通道
        Channel channel = connection.createChannel();
//        * @param queue队列名称
//        * @param持久如果我们声明一个持久队列,则为true(该队列将在服务器重启后继续存在)
//        * @param Exclusive如果我们声明一个排他队列,则为true(仅限此连接)
//        * @param autoDelete如果我们声明一个自动删除队列,则为true(服务器将在不再使用它时将其删除)
//        * @param参数队列的其他属性(构造参数)
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        String msg = "hello everyone";
//        * @param exchange交流将消息发布到  他不能为空 所以这里添加个"",类似等于null
//        * @param routingKey路由密钥  他不能为空 所以这里添加个"",类似等于null
//        * @param支持消息的其他属性-路由标头等
//        * @param正文消息正文
        channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
        channel.close();
        connection.close();

    }
}

消费者消费消息

public class Consumer {
    public static final String QUEUE_NAME = "william_queue";

    public static void main(String[] args) throws IOException {
        Connection connection = MQConnectionUtils.newConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(MQConstants.QUEUE_NAME, false, false, false, null);
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String s = new String(body, "UTF-8");
                System.out.println("接收到的消息为" + s);
            }
        };
        //* @param queue队列名称
        //* @param autoAck如果服务器应考虑消息,则为true
        //  *交付后确认; 如果服务器应该期望则返回false
        //  *明确的确认
        //* @param回调用户对象的接口
        channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
    }
}

4.1简单队列之公平转发

生产者

public class Product {
    public static final String QUEUE_NAME = "william_queue";
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建一个链接
        Connection connection = MQConnectionUtils.newConnection();
        //创建一个通道
        Channel channel = connection.createChannel();
//        * @param queue队列名称
//        * @param持久如果我们声明一个持久队列,则为true(该队列将在服务器重启后继续存在)
//        * @param Exclusive如果我们声明一个排他队列,则为true(仅限此连接)
//        * @param autoDelete如果我们声明一个自动删除队列,则为true(服务器将在不再使用它时将其删除)
//        * @param参数队列的其他属性(构造参数)
        channel.basicQos(1);  // 一次只发送一个
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        String msg = "hello everyone";
//        * @param exchange交流将消息发布到  他不能为空 所以这里添加个"",类似等于null
//        * @param routingKey路由密钥  他不能为空 所以这里添加个"",类似等于null
//        * @param支持消息的其他属性-路由标头等
//        * @param正文消息正文
        System.out.println("发送了消息"+msg);
        channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
        channel.close();
        connection.close();
    }
}

消费者1 (消费者2和消费者1代码一样)

public class Consumer1 {
    public static final String QUEUE_NAME = "william_queue";

    public static void main(String[] args) throws IOException {
        Connection connection = MQConnectionUtils.newConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(MQConstants.QUEUE_NAME, false, false, false, null);
        channel.basicQos(1); // 一次只消费1个
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String s = new String(body, "UTF-8");
                System.out.println("Consumer1接收到的消息为" + s);
                  channel.basicAck(envelope.getDeliveryTag(),false);  //消费完啦,进行回执
            }
        };
        //* @param queue队列名称
        //* @param autoAck如果服务器应考虑消息,则为true
        //  *交付后确认; 如果服务器应该期望则返回false
        //  *明确的确认
        //* @param回调用户对象的接口
        channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
    }
}

5.订阅模式

public class Product {
    public static final String QUEUE_NAME = "william_queue";
    public static final String EXCHANGE_NAME = "william_exchange";
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建一个链接
        Connection connection = MQConnectionUtils.newConnection();
        //创建一个通道
        Channel channel = connection.createChannel();
//        * @param queue队列名称
//        * @param持久如果我们声明一个持久队列,则为true(该队列将在服务器重启后继续存在)
//        * @param Exclusive如果我们声明一个排他队列,则为true(仅限此连接)
//        * @param autoDelete如果我们声明一个自动删除队列,则为true(服务器将在不再使用它时将其删除)
//        * @param参数队列的其他属性(构造参数)
        channel.basicQos(1);
        channel.exchangeDeclare(EXCHANGE_NAME,"direct");
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        String msg = "hello everyone";
//        * @param exchange交流将消息发布到  他不能为空 所以这里添加个"",类似等于null
//        * @param routingKey路由密钥  他不能为空 所以这里添加个"",类似等于null
//        * @param支持消息的其他属性-路由标头等
//        * @param正文消息正文
        System.out.println("发送了消息"+msg);
        
        channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());  //这里没有关联队列 让交换机关联
        channel.close();
        connection.close();
    }
}

消费者

public class Consumer2 {
    public static final String QUEUE_NAME = "william_queue";
    public static final String EXCHANGE_NAME = "william_exchange";
    public static void main(String[] args) throws IOException {
        Connection connection = MQConnectionUtils.newConnection();
        final Channel channel = connection.createChannel();
        channel.queueDeclare(MQConstants.QUEUE_NAME, false, false, false, null);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");  // 主要变化
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String s = new String(body, "UTF-8");
                System.out.println("Consumer2接收到的消息为" + s);
                channel.basicAck(envelope.getDeliveryTag(),false);  //反馈消息的消费状态
            }
        };
        //* @param queue队列名称
        //* @param autoAck如果服务器应考虑消息,则为true
        //  *交付后确认; 如果服务器应该期望则返回false
        //  *明确的确认
        //* @param回调用户对象的接口
        channel.basicConsume(QUEUE_NAME, false, defaultConsumer);  //监听队列,手动返回完成状态
    }
}

6.Routing模式

public class Product {
    public static final String QUEUE_NAME = "william_queue";
    public static final String EXCHANGE_NAME = "william_exchange";
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建一个链接
        Connection connection = MQConnectionUtils.newConnection();
        //创建一个通道
        Channel channel = connection.createChannel();
//        * @param queue队列名称
//        * @param持久如果我们声明一个持久队列,则为true(该队列将在服务器重启后继续存在)
//        * @param Exclusive如果我们声明一个排他队列,则为true(仅限此连接)
//        * @param autoDelete如果我们声明一个自动删除队列,则为true(服务器将在不再使用它时将其删除)
//        * @param参数队列的其他属性(构造参数)
        channel.basicQos(1);
        channel.exchangeDeclare(EXCHANGE_NAME,"direct");
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        String msg = "hello everyone";
//        * @param exchange交流将消息发布到  他不能为空 所以这里添加个"",类似等于null
//        * @param routingKey路由密钥  他不能为空 所以这里添加个"",类似等于null
//        * @param支持消息的其他属性-路由标头等
//        * @param正文消息正文
        System.out.println("发送了消息"+msg);
        
        channel.basicPublish(EXCHANGE_NAME, "error", null, msg.getBytes());  //这里没有关联队列 让交换机关联
        channel.close();
        connection.close();
    }
}

消费者

public class Consumer2 {
    public static final String QUEUE_NAME = "william_queue";
    public static final String EXCHANGE_NAME = "william_exchange";
    public static void main(String[] args) throws IOException {
        Connection connection = MQConnectionUtils.newConnection();
        final Channel channel = connection.createChannel();
        channel.queueDeclare(MQConstants.QUEUE_NAME, false, false, false, null);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,error);  // 主要变化,添加了routingkey
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String s = new String(body, "UTF-8");
                System.out.println("Consumer2接收到的消息为" + s);
                int i = 1/0;
                channel.basicAck(envelope.getDeliveryTag(),false);  //反馈消息的消费状态
            }
        };
        //* @param queue队列名称
        //* @param autoAck如果服务器应考虑消息,则为true
        //  *交付后确认; 如果服务器应该期望则返回false
        //  *明确的确认
        //* @param回调用户对象的接口
        channel.basicConsume(QUEUE_NAME, false, defaultConsumer);  //监听队列,手动返回完成状态
    }
}

7.主题模式

生产者

public class Product {
    public static final String QUEUE_NAME = "william_queue";
    public static final String EXCHANGE_NAME = "william_exchange";
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建一个链接
        Connection connection = MQConnectionUtils.newConnection();
        //创建一个通道
        Channel channel = connection.createChannel();
//        * @param queue队列名称
//        * @param持久如果我们声明一个持久队列,则为true(该队列将在服务器重启后继续存在)
//        * @param Exclusive如果我们声明一个排他队列,则为true(仅限此连接)
//        * @param autoDelete如果我们声明一个自动删除队列,则为true(服务器将在不再使用它时将其删除)
//        * @param参数队列的其他属性(构造参数)
        channel.basicQos(1);
        channel.exchangeDeclare(EXCHANGE_NAME,"topic");
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        String msg = "hello everyone";
//        * @param exchange交流将消息发布到  他不能为空 所以这里添加个"",类似等于null
//        * @param routingKey路由密钥  他不能为空 所以这里添加个"",类似等于null
//        * @param支持消息的其他属性-路由标头等
//        * @param正文消息正文
        System.out.println("发送了消息"+msg);
        channel.basicPublish(EXCHANGE_NAME, "goods.add", null, msg.getBytes());
        channel.close();
        connection.close();
    }
}

消费者

public class Consumer2 {
    public static final String QUEUE_NAME = "william_queue";
    public static final String EXCHANGE_NAME = "william_exchange";
    public static void main(String[] args) throws IOException {
        Connection connection = MQConnectionUtils.newConnection();
        final Channel channel = connection.createChannel();
        channel.queueDeclare(MQConstants.QUEUE_NAME, false, false, false, null);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.#");  // 利用正则进行匹配
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String s = new String(body, "UTF-8");
                System.out.println("Consumer2接收到的消息为" + s);
                int i = 1/0;
                channel.basicAck(envelope.getDeliveryTag(),false);  //反馈消息的消费状态
            }
        };
        //* @param queue队列名称
        //* @param autoAck如果服务器应考虑消息,则为true
        //  *交付后确认; 如果服务器应该期望则返回false
        //  *明确的确认
        //* @param回调用户对象的接口
        channel.basicConsume(QUEUE_NAME, false, defaultConsumer);  //监听队列,手动返回完成状态
    }
}

8.RabbitMQ的消息事务处理

当生产者发送消息之后,此时生产者出现异常。但是异常是在发送消息之后的,所以消息还是在队列中的,此时消费者是可以进行消息消费的,但是这样并不满足我们的理念
需求:当生产者发送消息的时候,如果此时报错,生产者进行回滚
代码演示

package com.disney.queue;

import com.disney.utils.MQConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

public class queProducer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //建立连接
        Connection connection = MQConnectionUtils.newConnection();
         //创建通道
        Channel channel =  null;
       try {
           channel = connection.createChannel();
           //创建队列
           channel.queueDeclare("william",false,false,false,null);
           //发送的消息
           //开启事务
           channel.txSelect();
           String msg =   "hello world";
           //发送消息
           channel.basicPublish("","sms",null,msg.getBytes());
           int i = 1/0;
           //开始事务
           channel.txCommit();
       } catch (Exception e){
           System.out.println("事务开始回滚啦");
           Channel.
       }

        channel.close();
        connection.close();
    }
}

9.消息确认机制之串行confirm

生产者confirm模式的实现原理
生产者将channel设置为confirm模式,所有通过channel的消息都会标识一个id.到了所匹配的队列之后,mq就会发送一个确认给生产者也包含这个id, 也让生产者知道了消息到了相应的队列.如果消息和队列是可持久话,会在消息写入磁盘之后,MQ进行回执. 如果MQ因为自身问题,导致崩溃,导致消息丢失,他就会发送一条nack消息,生产者则处理这个nack消息.
Confrim模式最大的好处就是他是异步

public class Product {
    public static final String QUEUE_NAME = "william_queue";
    public static final String EXCHANGE_NAME = "william_exchange";
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //创建一个链接
        Connection connection = MQConnectionUtils.newConnection();
        //创建一个通道
        Channel channel = connection.createChannel();
//        * @param queue队列名称
//        * @param持久如果我们声明一个持久队列,则为true(该队列将在服务器重启后继续存在)
//        * @param Exclusive如果我们声明一个排他队列,则为true(仅限此连接)
//        * @param autoDelete如果我们声明一个自动删除队列,则为true(服务器将在不再使用它时将其删除)
//        * @param参数队列的其他属性(构造参数)
        channel.confirmSelect();  // 将channel变为confirm模式
        channel.basicQos(1);
        channel.exchangeDeclare(EXCHANGE_NAME,"direct");
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        String msg = "hello everyone";
//        * @param exchange交流将消息发布到  他不能为空 所以这里添加个"",类似等于null
//        * @param routingKey路由密钥  他不能为空 所以这里添加个"",类似等于null
//        * @param支持消息的其他属性-路由标头等
//        * @param正文消息正文
        System.out.println("发送了消息"+msg);

        if (!channel.waitForConfirms()){  //confirmSelect  单个消息  waitForConfirmsOrDie 批量消息 
           //以上代码可以看出来channel.waitForConfirmsOrDie(),使用同步方式等所有的消息发送之后才会执行后面代码,只要有一个消息未被确认就会抛出IOException异常。
            System.out.println("发送失败");
        } else{
            System.out.println("发送成功");
        }

        channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());  //这里没有关联队列 让交换机关联
        channel.close();
        connection.close();
    }
}

消费者 (不变)

public class Consumer2 {
    public static final String QUEUE_NAME = "william_queue";
    public static final String EXCHANGE_NAME = "william_exchange";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = MQConnectionUtils.newConnection();
        final Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");  // 主要变化
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String s = new String(body, "UTF-8");
                System.out.println("Consumer2接收到的消息为" + s);
                int i = 1/0;
                channel.basicAck(envelope.getDeliveryTag(),false);  //反馈消息的消费状态
            }
        };
        //* @param queue队列名称
        //* @param autoAck如果服务器应考虑消息,则为true
        //  *交付后确认; 如果服务器应该期望则返回false
        //  *明确的确认
        //* @param回调用户对象的接口
        channel.basicConsume(QUEUE_NAME, false, defaultConsumer);  //监听队列,手动返回完成状态
    }
}

10.消息确认机制之Confirm异步

Channel对象提供的ConfirmListener()回调方法之包含DeliveryTag(当前channel发送的消息序列号),
我们需要自己为每个Channel维护一个unconfirm的消息序号集合,
每个publish一条数据,集合元素中加1,每次回调handleAck方法,unconfirm集合删除相应的一条(multiple=false) 或 多条
(multiple=false)记录,从程序的运行效率来讲,这个unconfirm集合最好采用有序集合sortSet存储.

public class Product {
    public static final String QUEUE_NAME = "william_queue";
    public static final String EXCHANGE_NAME = "william_exchange";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //创建一个链接
        Connection connection = MQConnectionUtils.newConnection();
        //创建一个通道
        Channel channel = connection.createChannel();
//        * @param queue队列名称
//        * @param持久如果我们声明一个持久队列,则为true(该队列将在服务器重启后继续存在)
//        * @param Exclusive如果我们声明一个排他队列,则为true(仅限此连接)
//        * @param autoDelete如果我们声明一个自动删除队列,则为true(服务器将在不再使用它时将其删除)
//        * @param参数队列的其他属性(构造参数)
        channel.confirmSelect();  // 将channel变为confirm模式
        final SortedSet<Long> confirms = Collections.synchronizedSortedSet(new TreeSet<>());
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                if (multiple) {
                    System.out.println("------handlNacn------multiple");
                    confirms.headSet(deliveryTag + 1).clear();
                } else {
                    System.out.println("------handlNack------multiple false");
                    confirms.remove(deliveryTag);
                }
            }

            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                if (multiple) {
                    System.out.println("------handlAck------multiple");
                    confirms.headSet(deliveryTag + 1).clear();
                } else {
                    System.out.println("------handlAck------multiple false");
                    confirms.remove(deliveryTag);
                }
            }
        });
        String msg = "hello everyone";
//        * @param exchange交流将消息发布到  他不能为空 所以这里添加个"",类似等于null
//        * @param routingKey路由密钥  他不能为空 所以这里添加个"",类似等于null
//        * @param支持消息的其他属性-路由标头等
//        * @param正文消息正文
        System.out.println("发送了消息" + msg);

        while (true) {
            long nextPublishSeqNo = channel.getNextPublishSeqNo();
            channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());  //这里没有关联队列 让交换机关联
            confirms.add(nextPublishSeqNo);
        }
    }
}

11.Springboot整合RabbitMQ

接下来的Demo是一个简单的发布订阅的简单模型DEMO

MAVAN依赖

 <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>1.5.6.RELEASEversion>
 parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-starter-loggingartifactId>
                exclusion>
            exclusions>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-amqpartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-log4j2artifactId>
        dependency>
        <dependency>
            <groupId>com.rabbitmqgroupId>
            <artifactId>amqp-clientartifactId>
            <version>3.6.5version>
        dependency>
    dependencies>

applicatiom.yml 文件配置

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    publisher-confirms: true
    virtual-host: /

RabbitMQ的Config配置
下面的配置比较复杂,这里我做了个流程图帮助大家分析
RabbitMQ史上最全核心知识讲解_第3张图片

package com.disney.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
    private static Logger log = LoggerFactory.getLogger(RabbitMQConfig.class);
    @Autowired
    private CachingConnectionFactory connectionFactory;
    
    public final static String queueName = "helloQuery";
    
    @Bean
    public Queue helloQueue() {
        return new Queue(queueName);
    }

    @Bean
    public Queue userQueue() {
        return new Queue("user");
    }

    @Bean
    public Queue dirQueue() {
        return new Queue("direct");
    }
    //===============以下是验证topic Exchange的队列==========
    // Bean默认的name是方法名
    @Bean(name="message")
    public Queue queueMessage() {
        return new Queue("topic.message");
    }

    @Bean(name="messages")
    public Queue queueMessages() {
        return new Queue("topic.messages");
    }
    //===============以上是验证topic Exchange的队列==========

    //===============以下是验证Fanout Exchange的队列==========
    @Bean(name="AMessage")
    public Queue AMessage() {
        return new Queue("fanout.A");
    }

    @Bean(name="BMessage")
    public Queue BMessage() {
        return new Queue("fanout.B");
    }

    @Bean(name="CMessage")
    public Queue CMessage() {
        return new Queue("fanout.C");
    }
    //===============以上是验证Fanout Exchange的队列==========

    /**
     *     exchange是交换机交换机的主要作用是接收相应的消息并且绑定到指定的队列.交换机有四种类型,分别为Direct,topic,headers,Fanout.
     *
     *   Direct是RabbitMQ默认的交换机模式,也是最简单的模式.即创建消息队列的时候,指定一个BindingKey.当发送者发送消息的时候,指定对应的Key.当Key和消息队列的BindingKey一致的时候,消息将会被发送到该消息队列中.
     *
     *   topic转发信息主要是依据通配符,队列和交换机的绑定主要是依据一种模式(通配符+字符串),而当发送消息的时候,只有指定的Key和该模式相匹配的时候,消息才会被发送到该消息队列中.
     *
     *   headers也是根据一个规则进行匹配,在消息队列和交换机绑定的时候会指定一组键值对规则,而发送消息的时候也会指定一组键值对规则,当两组键值对规则相匹配的时候,消息会被发送到匹配的消息队列中.
     *
     *   Fanout是路由广播的形式,将会把消息发给绑定它的全部队列,即便设置了key,也会被忽略.
     */
    @Bean
    DirectExchange directExchange(){
        return new DirectExchange("directExchange");
    }
    @Bean
    TopicExchange exchange() {
        // 参数1为交换机的名称
        return new TopicExchange("exchange");
    }

    /**
     * //配置广播路由器
     * @return FanoutExchange
     */
    @Bean
    FanoutExchange fanoutExchange() {
        // 参数1为交换机的名称
        return new FanoutExchange("fanoutExchange");
    }

    @Bean
    Binding bindingExchangeDirect(@Qualifier("dirQueue")Queue dirQueue,DirectExchange directExchange){
        return  BindingBuilder.bind(dirQueue).to(directExchange).with("direct");
    }

    /**
     * 将队列topic.message与exchange绑定,routing_key为topic.message,就是完全匹配
     * @param queueMessage
     * @param exchange
     * @return
     */
    @Bean
    // 如果参数名和上面用到方法名称一样,可以不用写@Qualifier
    Binding bindingExchangeMessage(@Qualifier("message")Queue queueMessage, TopicExchange exchange) {
        return BindingBuilder.bind(queueMessage).to(exchange).with("topic.message");
    }

    /**
     * 将队列topic.messages与exchange绑定,routing_key为topic.#,模糊匹配
     * @param queueMessages
     * @param exchange
     * @return
     */
    @Bean
    Binding bindingExchangeMessages(@Qualifier("messages")Queue queueMessages, TopicExchange exchange) {
        return BindingBuilder.bind(queueMessages).to(exchange).with("topic.#");
    }
    @Bean
    Binding bindingExchangeA(@Qualifier("AMessage")Queue AMessage,FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(AMessage).to(fanoutExchange);
    }

    @Bean
    Binding bindingExchangeB(Queue BMessage, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(BMessage).to(fanoutExchange);
    }

    @Bean
    Binding bindingExchangeC(Queue CMessage, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(CMessage).to(fanoutExchange);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(){
        //若使用confirm-callback或return-callback,必须要配置publisherConfirms或publisherReturns为true
        //每个rabbitTemplate只能有一个confirm-callback和return-callback,如果这里配置了,那么写生产者的时候不能再写confirm-callback和return-callback
        //使用return-callback时必须设置mandatory为true,或者在配置中设置mandatory-expression的值为true
        connectionFactory.setPublisherConfirms(true);
        connectionFactory.setPublisherReturns(true);
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMandatory(true);

//        /**
//         * 前面设置的setPublisherConfirms是解决消息发送到交换机,生产者的问题
//         * 如果消息没有到exchange,则confirm回调,ack=false
//         * 如果消息到达exchange,则confirm回调,ack=true
//         * 前面设置的setPublisherReturns是交换机发送到队列的问题,交换机的问题
//         * exchange到queue成功,则不回调return
//         * exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)
//         */
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                if(ack){
                    log.info("消息发送成功:correlationData({}),ack({}),cause({})",correlationData,ack,cause);
                }else{
                    log.info("消息发送失败:correlationData({}),ack({}),cause({})",correlationData,ack,cause);
                }
            }
        });


        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}",exchange,routingKey,replyCode,replyText,message);
            }
        });
        return rabbitTemplate;
    }
}

11.1 单生产者和单消费者

生产者投送消息

@Component

public class HelloSender1 {

    /**

     * AmqpTemplate可以说是RabbitTemplate父类,RabbitTemplate实现类RabbitOperations接口,RabbitOperations继承了AmqpTemplate接口

     */

    @Autowired

    private AmqpTemplate rabbitTemplate;

    @Autowired

    private RabbitTemplate rabbitTemplate1;

    public void send() {

        String sendMsg = "hello1 " + new Date();

        System.out.println("Sender1 : " + sendMsg);

        this.rabbitTemplate1.convertAndSend("helloQueue", sendMsg);

    }
}


消费者

@Component

@RabbitListener(queues = "helloQueue")
public class HelloReceiver1 {
    @RabbitHandler
    public void process(String hello) {
        System.out.println("Receiver1  : " + hello);
    }
}

@RabbitListener注解是监听队列的,当队列有消息的时候. 它会自动获取.
@RabbitListener标注在类上面表示当有收到消息的时候.就交给 @RabbitHandler 的方法处理
具体使用哪个方法处理,根据MessageConverter 转换后的参数类型

消息处理方法参数是由 MessageConverter 转化,若使用自定义 MessageConverter 则需要在 RabbitListenerContainerFactory 实例中去设置(默认 Spring 使用的实现是 SimpleRabbitListenerContainerFactory)
消息的 content_type 属性表示消息 body 数据以什么数据格式存储,接收消息除了使用 Message 对象接收消息(包含消息属性等信息)之外,还可直接使用对应类型接收消息 body 内容,但若方法参数类型不正确会抛异常:
application/octet-stream:二进制字节数组存储,使用 byte[]
application/x-java-serialized-object:java 对象序列化格式存储,使用 Object、相应类型(反序列化时类型应该同包同名,否者会抛出找不到类异常)
text/plain:文本数据类型存储,使用 String
application/json:JSON 格式,使用 Object、相应类型

消费者的POM文件

<parent>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-parentartifactId>
		<version>2.0.0.RELEASEversion>
	parent>
	<dependencies>

		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-webartifactId>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-amqpartifactId>
		dependency>
		<dependency>
			<groupId>org.apache.commonsgroupId>
			<artifactId>commons-lang3artifactId>
		dependency>
		
		<dependency>
			<groupId>com.alibabagroupId>
			<artifactId>fastjsonartifactId>
			<version>1.2.49version>
		dependency>
	dependencies>

消费者的配置信息

 spring:
  rabbitmq:
  ####连接地址
    host: 127.0.0.1
   ####端口号   
    port: 5672
   ####账号 
    username: guest
   ####密码  
    password: guest
   ### 地址
    virtual-host: /

12.SpringBoot当中开启ACK应答模式

消费端代码

//邮件队列
@Component
public class FanoutEamilConsumer {
	@RabbitListener(queues = "fanout_email_queue")
	public void process(Message message, @Headers Map<String, Object> headers, Channel channel) throws Exception {
		System.out
				.println(Thread.currentThread().getName() + ",邮件消费者获取生产者消息msg:" + new String(message.getBody(), "UTF-8")
						+ ",messageId:" + message.getMessageProperties().getMessageId());
		// 手动ack
		Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
		// 手动签收
		channel.basicAck(deliveryTag, false);
	}
}

13.RabbitMQ如何保证消费消息的幂等性

在网络延迟的情况下,可能会造成mq消费的消息和发送的消息消费不一致的问题;
解决方案,在发送消息的时候,传一个全局唯一ID,进行发送
生产者代码

MessageBuilder.withBody(msg.getBytes()).setContentType(MessageProperties.CONTENT_TYPE_JSON).
                setContentEncoding("uft-8").setMessageId(UUID.randomUUID()+"").build();
        amqpTemplate.convertAndSend(queueanme,msg);

消费端代码

      String messageId = message.getMessageProperties().getMessageId();
      if(messageId){
       判断messageId 是否被被消费掉
      }

14.RabbitMQ的重试机制

**思考在进行消费的时候出现异常 会进行消费吗?

@Component
@RabbitListener(queues = "fanout_sms_queue")
public class FanoutSmsConsumer {
	@RabbitHandler
	public void process(String msg) {
		System.out.println("短信消费者获取生产者消息msg:" + msg);
		int i = 1/ 0 
	}
}

当运行代码进行消费时,发现代码一直寻宝报错. 在RabbitMQ当中,默认如果消费端出出现错误,MQ队列服务器会一直进行重试机制。消息会缓存到MQ的服务端进行存放。直到系统不抛出异常为止. 在正常环境当中,类似空指针,即便重试N次,也是失败,所以我们要对重试的次数进行控制

spring:
  rabbitmq:
  ####连接地址
    host: 127.0.0.1
   ####端口号
    port: 5672
   ####账号
    username: guest
   ####密码
    password: guest
   ### 地址
    virtual-host: /
    listener:
      simple:
        retry:
          enabled: true
          ##重试的次数
          max-attempts: 5
          ## 重试的间隔
          initial-interval: 3000

相同的如果想要MQ进行重试,直接抛出异常即可**

你可能感兴趣的:(java)