RabbitMQ

RabbitMQ

参考文档:

https://www.kuangstudy.com/zl/rabbitmq#1365897801984241665

中间件和消息中间件

常见的中间件
在这里插入图片描述

对中间件的理解

为解决分布异构问题,人们提出了中间件(middleware)的概念。中间件是位于平台(硬件和操作系统)和应用之间的通用服务,如下图所示,这些服务具有标准的程序接口和协议。针对不同的操作系统和硬件平台,它们可以有符合接口和协议规范的多种实现。

简单说:中间件有个很大的特点,是脱离于具体设计目标,而具备提供普遍独立功能需求的模块。这使得中间件一定是可替换的。如果一个系统设计中,中间件是不可替换的,不是架构、框架设计有问题,那么就是这个中间件,在 别处可能是个中间件,在这个系统内是引擎。

消息中间件应用的场景

1:跨系统数据传递
2:高并发的流量削峰
3:数据的分发和异步处理
4:大数据分析与传递
5:分布式事务
比如你有一个数据要进行迁移或者请求并发过多的时候,比如你有10W的并发请求下订单,我们可以在这些订单入库之前,我们可以把订单请求堆积到消息队列中,让它稳健可靠的入库和执行。

消息中间件的核心组成部分

1:消息的协议
2:消息的持久化机制
3:消息的分发策略
4:消息的高可用,高可靠
5:消息的容错机制

消息队列协议

在这里插入图片描述

而消息中间件采用的并不是http协议,而常见的消息中间件协议有:OpenWire、AMQP、MQTT、Kafka,OpenMessage协议。

面试题:为什么消息中间件不直接使用http协议呢?

1: 因为http请求报文头和响应报文头是比较复杂的,包含了cookie,数据的加密解密,状态码,响应码等附加的功能,但是对于一个消息而言,我们并不需要这么复杂,也没有这个必要性,它其实就是负责数据传递,存储,分发就行,一定要追求的是高性能。尽量简洁,快速。
2:大部分情况下http大部分都是短链接,在实际的交互过程中,一个请求到响应很有可能会中断,中断以后就不会就行持久化,就会造成请求的丢失。这样就不利于消息中间件的业务场景,因为消息中间件可能是一个长期的获取消息的过程,出现问题和故障要对数据或消息就行持久化等,目的是为了保证消息和数据的高可靠和稳健的运行。

常用的消息队列协议

AMQP协议

AMQP:(全称:Advanced Message Queuing Protocol) 是高级消息队列协议。由摩根大通集团联合其他公司共同设计。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。
特性:
1:分布式事务支持。
2:消息的持久化支持。
3:高性能和高可靠的消息处理优势。

AMQP协议的支持者

在这里插入图片描述

MQTT协议

OpenMessage协议

Kafka协议

安装rabbitMQ

需要安装erlang和rabbitmq(linux系统)

在这里插入图片描述

rabbitmq: https://www.rabbitmq.com/download.html

  1. 安装erlang

    #下载
    wget https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm
    rpm -Uvh erlang-solutions-1.0-1.noarch.rpm
    #安装
    yum install -y erlang 	
    #安装成功
    erl -v
    
  2. 安装socat

    yum install -y socat
    
  3. 安装rabbitmq

    > wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.9.7/rabbitmq-server-3.9.7-1.el7.noarch.rpm
    > rpm -Uvh rabbitmq-server-3.9.7-1.el7.noarch.rpm
    

rabbitmq和erlang版本强相关https://www.rabbitmq.com/which-erlang.html

使用rabbitMQ

启动rabbitmq服务器

# 启动服务
> systemctl start rabbitmq-server
# 查看服务状态
> systemctl status rabbitmq-server
# 停止服务
> systemctl stop rabbitmq-server
# 开机启动服务
> systemctl enable rabbitmq-server

启动可视化客户端工具

rabbitmq-plugins enable rabbitmq_management

说明:rabbitmq有一个默认账号和密码是:guest 默认情况只能在localhost本机下访问,所以需要添加一

个远程登录的用户。

授权账号和密码

新增用户

rabbitmqctl add_user admin admin

设置用户分配操作权限

rabbitmqctl set_user_tags admin administrator

设置用户分配操作权限

rabbitmqctl set_user_tags admin administrator

在这里插入图片描述

在这里插入图片描述

登录客户端

在这里插入图片描述

搭建常用使用案例

rabbitmq支持消息的模式:参考官网:https://www.rabbitmq.com/getstarted.html

simple案例

图解

在这里插入图片描述

在这里插入图片描述

  1. 创建一个maven工程,导入相关依赖

在这里插入图片描述

  1. 生产者模块

    package rabbitmq.simple;
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    /**
     * @ClassName Producer
     * @Description TODO
     * @Author QiuYiping
     * @Date 2021/10/13 20:00
     */
    public class Producer {
        public static void main(String[] args) {
            //所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范
            //ip port
    
            //1.创建连接工程
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("192.168.129.128");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("admin");
            connectionFactory.setPassword("admin");
            connectionFactory.setVirtualHost("/");
    
            Connection connection = null;
            Channel channel = null;
            //2.创建连接connection
            try {
                connection = connectionFactory.newConnection("生产者");
                //3.通过连接获取通道channel
                channel = connection.createChannel();
                //4.通过通道创建交换机、声明队列、绑定关系、路由key、发送消息和接受消息
                String queueName = "queue1";
                /*
                 * @params1 队列的名称
                 * @params2 是否要持久化durable=true 所谓持久化消息是否存盘,如果false,则非持久化,如果true则持久化
                 * @params3 排他性 是否是独占独立
                 * @params4 是否自动删除,随着最后一个消费者消息完毕消以后是否把队列自动删除
                 * @params5 携带附属参数
                 *
                 */
                channel.queueDeclare(queueName, false, false, false, null);
                //5.准备消息内容
                String message = "Hello xuexiangban";
                //6.发送消息给队列queue
                channel.basicPublish("", queueName, null, message.getBytes());
                System.out.println("消息发送成功");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //7.关闭通道
                if (channel != null && channel.isOpen()) {
                    try {
                        channel.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                //8.关闭连接
                if (connection != null && connection.isOpen()) {
                    try {
                        connection.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  2. 消费者模块

    package rabbitmq.simple;
    
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    
    /**
     * @ClassName Consumer
     * @Description TODO
     * @Author QiuYiping
     * @Date 2021/10/13 20:00
     */
    public class Consumer {
        public static void main(String[] args) {
            //所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范
            //ip port
    
            //1.创建连接工程
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("192.168.129.128");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("admin");
            connectionFactory.setPassword("admin");
            connectionFactory.setVirtualHost("/");
    
            Connection connection = null;
            Channel channel = null;
            //2.创建连接connection
            try {
                connection = connectionFactory.newConnection("生产者");
                //3.通过连接获取通道channel
                channel = connection.createChannel();
                //4.通过通道创建交换机、声明队列、绑定关系、路由key、发送消息和接受消息
                channel.basicConsume("queue1", true, new DeliverCallback() {
                    public void handle(String consumerTag, Delivery message) throws IOException {
                        System.out.println("收到消息是:" + new String(message.getBody(), "UTF-8"));
                    }
                }, new CancelCallback() {
                    public void handle(String s) throws IOException {
                        System.out.println("接受失败了...");
                    }
                });
                System.out.println("开始接受消息");
                System.in.read();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //7.关闭通道
                if (channel != null && channel.isOpen()) {
                    try {
                        channel.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                //8.关闭连接
                if (connection != null && connection.isOpen()) {
                    try {
                        connection.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    运行结果:

    在这里插入图片描述

    在这里插入图片描述

  • 持久化队列和非持久化队列

在这里插入图片描述

channel.queueDeclare(queueName, false, false, false, null);

@params2 是否要持久化durable=true 所谓持久化消息是否存盘,如果false,则非持久化,如果true则持久化

非持久化队列会随着服务器的重启而队列丢失

fanout模式-发布订阅模式

图解

在这里插入图片描述

  1. 生产者

    package rabbitmq.fanout;
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    
    /**
     * @ClassName Producer
     * @Description TODO
     * @Author QiuYiping
     * @Date 2022/4/4 11:59
     */
    public class Producer {
        public static void main(String[] args) {
            // 1: 创建连接工厂
            ConnectionFactory connectionFactory = new ConnectionFactory();
            // 2: 设置连接属性
            connectionFactory.setHost("192.168.129.128");
            connectionFactory.setPort(5672);
            connectionFactory.setVirtualHost("/");
            connectionFactory.setUsername("admin");
            connectionFactory.setPassword("admin");
            Connection connection = null;
            Channel channel = null;
            try {
                // 3: 从连接工厂中获取连接
                connection = connectionFactory.newConnection("生产者");
                // 4: 从连接中获取通道channel
                channel = connection.createChannel();
                // 6: 准备发送消息的内容
                String message = "你好,学相伴!!!";
                String  exchangeName = "fanout-exchange";
                String routingKey = "";
                // 7: 发送消息给中间件rabbitmq-server
                // @params1: 交换机exchange
                // @params2: 队列名称/routingkey
                // @params3: 属性配置
                // @params4: 发送消息的内容
                channel.basicPublish(exchangeName, routingKey, null, message.getBytes());
                System.out.println("消息发送成功!");
            } catch (Exception ex) {
                ex.printStackTrace();
                System.out.println("发送消息出现异常...");
            } finally {
                // 7: 释放连接关闭通道
                if (channel != null && channel.isOpen()) {
                    try {
                        channel.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
                if (connection != null) {
                    try {
                        connection.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }
    }
    
  2. 消费者

    package rabbitmq.fanout;
    
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    
    /**
     * @ClassName Consumer
     * @Description TODO
     * @Author QiuYiping
     * @Date 2022/4/4 11:59
     */
    public class Consumer {
        static class Queue implements Runnable {
            public void run() {
                // 1: 创建连接工厂
                ConnectionFactory connectionFactory = new ConnectionFactory();
                // 2: 设置连接属性
                connectionFactory.setHost("192.168.129.128");
                connectionFactory.setPort(5672);
                connectionFactory.setVirtualHost("/");
                connectionFactory.setUsername("admin");
                connectionFactory.setPassword("admin");
                //获取队列的名称
                final String queueName = Thread.currentThread().getName();
                Connection connection = null;
                Channel channel = null;
                try {
                    // 3: 从连接工厂中获取连接
                    connection = connectionFactory.newConnection("生产者");
                    // 4: 从连接中获取通道channel
                    channel = connection.createChannel();
                    // 5: 申明队列queue存储消息
                    /*
                     *  如果队列不存在,则会创建
                     *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
                     *
                     *  @params1: queue 队列的名称
                     *  @params2: durable 队列是否持久化
                     *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
                     *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
                     *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
                     * */
                    // 这里如果queue已经被创建过一次了,可以不需要定义
                    //channel.queueDeclare("queue1", false, false, false, null);
                    // 6: 定义接受消息的回调
                    Channel finalChannel = channel;
                    finalChannel.basicConsume(queueName, true, new DeliverCallback() {
                        @Override
                        public void handle(String s, Delivery delivery) throws IOException {
                            System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        }
                    }, new CancelCallback() {
                        @Override
                        public void handle(String s) throws IOException {
                        }
                    });
                    System.out.println(queueName + ":开始接受消息");
                    System.in.read();
                } catch (Exception ex) {
                    ex.printStackTrace();
                    System.out.println("发送消息出现异常...");
                } finally {
                    // 7: 释放连接关闭通道
                    if (channel != null && channel.isOpen()) {
                        try {
                            channel.close();
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }
                    if (connection != null && connection.isOpen()) {
                        try {
                            connection.close();
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            // 启动三个线程去执行
            new Thread(new Queue(), "queue-1").start();
            new Thread(new Queue(), "queue-2").start();
            new Thread(new Queue(), "queue-3").start();
        }
    }
    
    1. 执行结果

    在这里插入图片描述

在这里插入图片描述

direct模式-路由key模式

图解

在这里插入图片描述

注:Direct模式是fanout模式上的一种叠加,增加了路由RoutingKey的模式。

  1. 生产者

    package rabbitmq.direct;
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    
    /**
     * @ClassName Producer
     * @Description TODO
     * @Author QiuYiping
     * @Date 2022/4/4 12:26
     */
    public class Producer {
        public static void main(String[] args) {
            // 1: 创建连接工厂
            ConnectionFactory connectionFactory = new ConnectionFactory();
            // 2: 设置连接属性
            connectionFactory.setHost("192.168.129.128");
            connectionFactory.setPort(5672);
            connectionFactory.setVirtualHost("/");
            connectionFactory.setUsername("admin");
            connectionFactory.setPassword("admin");
            Connection connection = null;
            Channel channel = null;
            try {
                // 3: 从连接工厂中获取连接
                connection = connectionFactory.newConnection("生产者");
                // 4: 从连接中获取通道channel
                channel = connection.createChannel();
                // 6: 准备发送消息的内容
                String message = "你好,学习rabbitmq的direct!!!";
                String exchangeName = "direct-exchange";
                String routingKey1 = "testkey";
                String routingKey2 = "testkey2";
                // 7: 发送消息给中间件rabbitmq-server
                // @params1: 交换机exchange
                // @params2: 队列名称/routingkey
                // @params3: 属性配置
                // @params4: 发送消息的内容
                channel.basicPublish(exchangeName, routingKey1, null, message.getBytes());
                channel.basicPublish(exchangeName, routingKey2, null, message.getBytes());
                System.out.println("消息发送成功!");
            } catch (Exception ex) {
                ex.printStackTrace();
                System.out.println("发送消息出现异常...");
            } finally {
                // 7: 释放连接关闭通道
                if (channel != null && channel.isOpen()) {
                    try {
                        channel.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
                if (connection != null) {
                    try {
                        connection.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }
    }
    
    
  2. 消费者

    package rabbitmq.direct;
    
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    
    /**
     * @ClassName Consumer
     * @Description TODO
     * @Author QiuYiping
     * @Date 2022/4/4 12:26
     */
    public class Consumer {
        private static Runnable runnable = () -> {
            // 1: 创建连接工厂
            ConnectionFactory connectionFactory = new ConnectionFactory();
            // 2: 设置连接属性
            connectionFactory.setHost("192.168.129.128");
            connectionFactory.setPort(5672);
            connectionFactory.setVirtualHost("/");
            connectionFactory.setUsername("admin");
            connectionFactory.setPassword("admin");
            //获取队列的名称
            final String queueName = Thread.currentThread().getName();
            Connection connection = null;
            Channel channel = null;
            try {
                // 3: 从连接工厂中获取连接
                connection = connectionFactory.newConnection("生产者");
                // 4: 从连接中获取通道channel
                channel = connection.createChannel();
                // 5: 申明队列queue存储消息
                /*
                 *  如果队列不存在,则会创建
                 *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
                 *
                 *  @params1: queue 队列的名称
                 *  @params2: durable 队列是否持久化
                 *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
                 *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
                 *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
                 * */
                // 这里如果queue已经被创建过一次了,可以不需要定义
                //channel.queueDeclare("queue1", false, false, false, null);
                // 6: 定义接受消息的回调
                Channel finalChannel = channel;
                finalChannel.basicConsume(queueName, true, new DeliverCallback() {
                    @Override
                    public void handle(String s, Delivery delivery) throws IOException {
                        System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                    }
                }, new CancelCallback() {
                    @Override
                    public void handle(String s) throws IOException {
                    }
                });
                System.out.println(queueName + ":开始接受消息");
                System.in.read();
            } catch (Exception ex) {
                ex.printStackTrace();
                System.out.println("发送消息出现异常...");
            } finally {
                // 7: 释放连接关闭通道
                if (channel != null && channel.isOpen()) {
                    try {
                        channel.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
                if (connection != null && connection.isOpen()) {
                    try {
                        connection.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }
        };
        public static void main(String[] args) {
            // 启动三个线程去执行
            new Thread(runnable, "queue-4").start();
            new Thread(runnable, "queue-5").start();
            new Thread(runnable, "queue-6").start();
        }
    }
    

    direct-exchange绑定的queue和routing(只有queue-4和queue-5满足条件)

    在这里插入图片描述

运行结果:

在这里插入图片描述

在这里插入图片描述

topics模式-通配符来匹配

图解

在这里插入图片描述

通配符匹配规则:

#:代表0到多个字符

*****:只能代表一个字符

:Topic模式是direct模式上的一种叠加,增加了模糊路由RoutingKey的模式

  1. 生产者
package rabbitmq.topics;

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

/**
 * @ClassName Producer
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/4 12:47
 */
public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("192.168.129.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            String message = "你好,学习rabbitmq的topic模式!!!";
            String exchangeName = "topic-exchange";
            String routingKey1 = "com.course.order";//都可以收到 queue-1 queue-2
            String routingKey2 = "com.order.user";//都可以收到 queue-1 queue-3
            // 7: 发送消息给中间件rabbitmq-server
            // @params1: 交换机exchange
            // @params2: 队列名称/routingkey
            // @params3: 属性配置
            // @params4: 发送消息的内容
            channel.basicPublish(exchangeName, routingKey1, null, message.getBytes());
            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}
  1. 消费者
package rabbitmq.topics;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @ClassName Consumer
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/4 12:47
 */
public class Consumer {
    private static Runnable runnable = () -> {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("192.168.129.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        //获取队列的名称
        final String queueName = Thread.currentThread().getName();
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, false, false, null);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicConsume(queueName, true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println(queueName + ":开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    };
    public static void main(String[] args) {
        // 启动三个线程去执行
        new Thread(runnable, "queue-7").start();
        new Thread(runnable, "queue-8").start();
        new Thread(runnable, "queue-9").start();
    }
}

topic绑定的路由关系

在这里插入图片描述

运行结果:

在这里插入图片描述

在这里插入图片描述

work模式(轮询和公平fair-能者多劳)

图解

在这里插入图片描述

轮询模式和公平模式的区别:

轮询模式:一个消费者一条,按均分配

公平分发:根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;按劳分配;

轮询模式

  1. 生产者
package rabbitmq.work.lunxun;

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

/**
 * @ClassName Producer
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/4 13:39
 */
public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("192.168.129.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            //===============================end topic模式==================================
            for (int i = 1; i <= 20; i++) {
                //消息的内容
                String msg = "学相伴:" + i;
                // 7: 发送消息给中间件rabbitmq-server
                // @params1: 交换机exchange
                // @params2: 队列名称/routingkey
                // @params3: 属性配置
                // @params4: 发送消息的内容
                channel.basicPublish("", "queue1", null, msg.getBytes());
            }
            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}
  1. 消费者1
package rabbitmq.work.lunxun;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @ClassName Consumer
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/4 13:39
 */
public class Consumer1 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("192.168.129.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work1");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
//            channel.queueDeclare("queue1", false, false, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            //每次从队列中读取一条数据
            finalChannel.basicQos(1);
            //自动应答改为true,这样就是轮询应答了
            finalChannel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(2000);
//                        finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work1-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

  1. 消费者2
package rabbitmq.work.lunxun;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @ClassName Consumer2
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/4 13:42
 */
public class Consumer2 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("192.168.129.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work2");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, true, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            //channel.basicQos(1);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicQos(1);
            finalChannel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(200);
//                        finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work2-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

运行结果:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

轮询模式的交换机是默认类型的,不需要特别指定。其自动应答机制必须为true,否则就成了公平分发机制了

在这里插入图片描述

在这里插入图片描述

公平fair(能者多劳)

  1. 生产者
package rabbitmq.work.fair;

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

/**
 * @ClassName Producer
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/4 13:39
 */
public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("192.168.129.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            //===============================end topic模式==================================
            for (int i = 1; i <= 20; i++) {
                //消息的内容
                String msg = "学相伴:" + i;
                // 7: 发送消息给中间件rabbitmq-server
                // @params1: 交换机exchange
                // @params2: 队列名称/routingkey
                // @params3: 属性配置
                // @params4: 发送消息的内容
                channel.basicPublish("", "queue2", null, msg.getBytes());
                Thread.sleep(1000);
            }
            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}
  1. 消费者1
package rabbitmq.work.fair;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @ClassName Consumer
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/4 13:39
 */
public class Consumer1 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("192.168.129.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work1");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
//            channel.queueDeclare("queue1", false, false, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            //每次从队列中读取一条数据
            finalChannel.basicQos(1);
            //自动应答改为false
            finalChannel.basicConsume("queue2", false, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(2000);
                        finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work1-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}
  1. 消费者2
package rabbitmq.work.fair;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @ClassName Consumer2
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/4 13:42
 */
public class Consumer2 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("192.168.129.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work2");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, true, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            //channel.basicQos(1);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicQos(1);
            finalChannel.basicConsume("queue2", false, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(200);
                        finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work2-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

运行结果:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

常用的消息中间件

  • activeMQ

  • rabbitMQ

  • kafka

  • rocketMQ

RabbitMQ的核心

在这里插入图片描述

AMQP模式

RabbitMQ使用场景

和springboot的整合

创建springboot和rabbitmq整合工程(fanout交换机模式)

在这里插入图片描述

创建生产者工程
  1. 创建springboot工程,引入rabbitmq的依赖

在这里插入图片描述

  1. yml文件中引入依赖
# 服务端口
server:
  port: 8080
# 配置rabbitmq服务
spring:
  rabbitmq:
    username: admin
    password: admin
    virtual-host: /
    host: 192.168.129.128
    port: 5672
  1. 完成交换机和队列的声明及绑定
@Configuration
public class RabbitMqConfiguration {
    //1.声明注册fanoutm模式的交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("fanout_order_exchange", true, false);
    }

    //2.声明队列sms.fanout.queue, email.fanout.queue, duanxin.fanout.queue
    @Bean
    public Queue smsQueue(){
        return new Queue("sms.fanout.queue", true);
    }

    @Bean
    public Queue emailQueue(){
        return new Queue("email.fanout.queue", true);
    }

    @Bean
    public Queue duanxinQueue(){
        return new Queue("duanxin.fanout.queue", true);
    }

    //3.声明队列和交换机的绑定
    @Bean
    public Binding smsBinding(){
        return BindingBuilder.bind(smsQueue()).to(fanoutExchange());
    }

    //3.声明队列和交换机的绑定
    @Bean
    public Binding emailBinding(){
        return BindingBuilder.bind(emailQueue()).to(fanoutExchange());
    }

    //3.声明队列和交换机的绑定
    @Bean
    public Binding duanxinBinding(){
        return BindingBuilder.bind(duanxinQueue()).to(fanoutExchange());
    }
}
  1. 发送消息
package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.UUID;

/**
 * @ClassName OrderService
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/5 16:18
 */
@Service
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 功能描述
     *
     * @param
     * @return
     * @Description //TODO
     * @Date
     * @Author
     */
    public void makeOrder(String userid, String productid, int num) {
        //1.根据商品id查询库存是否充足
        //2.保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生产成功:" + orderId);
        //3.通过MQ来完成消息的分发
        // 参数1:交换机  参数2:路由key/queue队列名称 参数3:消息内容
        String exchangeName = "fanout_order_exchange";
        String routingKey = "";
        rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
    }
}
  1. 测试消息发送
@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {

   @Autowired
   private OrderService orderService;

   @Test
   void contextLoads(){
      orderService.makeOrder("1","1",12);
   }
}
创建消费者工程
  1. 修改配置类
# 服务端口
server:
  port: 8081
# 配置rabbitmq服务
spring:
  rabbitmq:
    username: admin
    password: admin
    virtual-host: /
    host: 192.168.129.128
    port: 5672
  1. 绑定三个队列
@RabbitListener(queues = {"duanxin.fanout.queue"})
@Component
public class FanoutDuanxinConsumer {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("duanxin fanout---接收到了订单信息是:->" + message);
    }
}
@RabbitListener(queues = {"email.fanout.queue"})
@Component
public class FanoutEmailConsumer {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("email fanout---接收到了订单信息是:->" + message);
    }
}
@RabbitListener(queues = {"sms.fanout.queue"})
@Component
public class FanoutSMSConsumer {
    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("sms fanout---接收到了订单信息是:->" + message);
    }
}
  1. 测试
@SpringBootApplication
public class SpringbootOrderRabbitmqConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootOrderRabbitmqConsumerApplication.class, args);
    }
}

运行结果 (消费者和生产者都需要启动)

在这里插入图片描述

在这里插入图片描述

direct模式

生产者工程
  1. config类中的fanoutExchange改成directExchange,同时在交换机和队列绑定时后面加with(“[路由名称]”)
package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @ClassName RabbitMqConfiguration
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/5 16:31
 */
@Configuration
public class DirectRabbitMqConfiguration {
    //1.声明注册fanoutm模式的交换机
    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange("direct_order_exchange", true, false);
    }

    //2.声明队列sms.fanout.queue, email.fanout.queue, duanxin.fanout.queue
    @Bean
    public Queue smsQueue(){
        return new Queue("sms.direct.queue", true);
    }

    @Bean
    public Queue emailQueue(){
        return new Queue("email.direct.queue", true);
    }

    @Bean
    public Queue duanxinQueue(){
        return new Queue("duanxin.direct.queue", true);
    }

    //3.声明队列和交换机的绑定
    @Bean
    public Binding smsBinding(){
        return BindingBuilder.bind(smsQueue()).to(directExchange()).with("sms");
    }

    //3.声明队列和交换机的绑定
    @Bean
    public Binding emailBinding(){
        return BindingBuilder.bind(emailQueue()).to(directExchange()).with("email");
    }

    //3.声明队列和交换机的绑定
    @Bean
    public Binding duanxinBinding(){
        return BindingBuilder.bind(duanxinQueue()).to(directExchange()).with("duanxin");
    }
}
  1. 测试类
@Service
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 功能描述
     *
     * @param
     * @return
     * @Description //TODO
     * @Date
     * @Author
     */
    public void makeOrderDirect(String userid, String productid, int num) {
        //1.根据商品id查询库存是否充足
        //2.保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生产成功:" + orderId);
        //3.通过MQ来完成消息的分发
        // 参数1:交换机  参数2:路由key/queue队列名称 参数3:消息内容
        String exchangeName = "direct_order_exchange";
        String routingKey = "sms";
        String routingKey2 = "email";
        rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
        rabbitTemplate.convertAndSend(exchangeName, routingKey2, orderId);
    }
}
  1. 测试类
   @Test
   void contextLoads2(){
      orderService.makeOrderDirect("1","1",12);
   }
消费者工程

注:可以把交换机和队列的声明及绑定关系放到消费者工程中去,这样先启动消费者工程时就可以绑定交换机和队列关系

@RabbitListener(queues = {"duanxin.direct.queue"})
@Component
public class DirectDuanxinConsumer {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("duanxin direct---接收到了订单信息是:->" + message);
    }
}
@RabbitListener(queues = {"email.direct.queue"})
@Component
public class DirectEmailConsumer {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("email direct---接收到了订单信息是:->" + message);
    }
}
@RabbitListener(queues = {"sms.direct.queue"})
@Component
public class DirectSMSConsumer {
    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("sms direct---接收到了订单信息是:->" + message);
    }
}
  1. 测试结果(启动springboot的main方法):
  • 页面绑定关系成立

在这里插入图片描述

  • 消费者可以监听到消息

在这里插入图片描述

topics模式

生产者工程
package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.UUID;

/**
 * @ClassName OrderService
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/5 16:18
 */
@Service
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void makeOrderTopic(String userid, String productid, int num){
        //1.根据商品id查询库存是否充足
        //2.保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生产成功:" + orderId);
        //3.通过MQ来完成消息的分发
        // 参数1:交换机  参数2:路由key/queue队列名称 参数3:消息内容
        String exchangeName = "topic_order_exchange";
        //#.duanxin.#
        //com.#
        //*.email.#
        String routingKey = "com.duanxin";
        rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
    }
}
   @Test
   void contextLoads3(){
      orderService.makeOrderTopic("1","1",12);
   }
消费者工程
  1. 用注解的方式监听队列和绑定队列和交换机的关系
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.topic;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @ClassName TopicDuanxinConsumer
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/6 9:05
 */
@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "duanxin.topic.queue", durable = "true", autoDelete = "false"),
        exchange = @Exchange(value = "topic_order_exchange", type = ExchangeTypes.TOPIC),
        key = "#.duanxin.#"
))
public class TopicDuanxinConsumer {
    @RabbitHandler
    public void receiveMessage(String message) {
        System.out.println("duanxin topic---接收到了订单信息是:->" + message);
    }
}
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.topic;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @ClassName TopicDuanxinConsumer
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/6 9:05
 */
@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "email.topic.queue", durable = "true", autoDelete = "false"),
        exchange = @Exchange(value = "topic_order_exchange", type = ExchangeTypes.TOPIC),
        key = "*.email.#"
))
public class TopicEmailConsumer {
    @RabbitHandler
    public void receiveMessage(String message) {
        System.out.println("email topic---接收到了订单信息是:->" + message);
    }
}
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.topic;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @ClassName TopicDuanxinConsumer
 * @Descriptin TODO
 * @Author QiuYiping
 * @Date 2022/4/6 9:05
 */
@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "sms.topic.queue", durable = "true", autoDelete = "false"),
        exchange = @Exchange(value = "topic_order_exchange", type = ExchangeTypes.TOPIC),
        key = "com.#"
))
public class TopicSmsConsumer {
    @RabbitHandler
    public void receiveMessage(String message) {
        System.out.println("sms topic---接收到了订单信息是:->" + message);
    }
}

运行结果:

  • 交换机和队列相互绑定

在这里插入图片描述

  • sms和短信收到消息

在这里插入图片描述

ttl设置过期时间

设置队列的过期时间

  1. 消费者工程绑定交换机和队列,并设置队列的消息ttl
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

/**
 * @ClassName TTLRabbitMqConfiguration
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/6 10:34
 */
@Configuration
public class TTLRabbitMqConfiguration {
    //1.声明注册fanout模式的交换机
    @Bean
    public DirectExchange ttldirectExchange() {
        return new DirectExchange("ttl_direct_order_exchange", true, false);
    }

    //2.声明ttl队列并设置队列过期时间
    @Bean
    public Queue directTtlQueue() {
        HashMap<String, Object> args = new HashMap<>();
        args.put("x-message-ttl", 5000); //这里一定是int类型
        return new Queue("ttl.direct.queue", true,false ,false ,args);
    }

    //3.声明队列和交换机的绑定
    @Bean
    public Binding ttlBinding() {
        return BindingBuilder.bind(directTtlQueue()).to(ttldirectExchange()).with("ttl");
    }
}
  1. 生产者工程
package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.UUID;

/**
 * @ClassName OrderService
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/5 16:18
 */
@Service
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void makeOrderTtl(String userid, String productid, int num){
        //1.根据商品id查询库存是否充足
        //2.保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生产成功:" + orderId);
        //3.通过MQ来完成消息的分发
        // 参数1:交换机  参数2:路由key/queue队列名称 参数3:消息内容
        String exchangeName = "ttl_direct_order_exchange";
        //#.duanxin.#
        //com.#
        //*.email.#
        String routingKey = "ttl";
        rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
    }
}
  1. 生产者测试
   @Test
   void contextLoads4(){
      orderService.makeOrderTtl("1","1",12);
   }
  1. 运行结果:
  • 生成了ttl交换机和队列

在这里插入图片描述

  • 设置队列的过期时间可以在页面上操作

在这里插入图片描述

  • 启动生产者工程5秒后这个消息会自动消失

在这里插入图片描述

在这里插入图片描述

单独设置消息的过期时间

1.消费者工程

@Configuration
public class TTLRabbitMqConfiguration {
    //1.声明注册direct模式的交换机
    @Bean
    public DirectExchange ttldirectExchange() {
        return new DirectExchange("ttl_direct_order_exchange", true, false);
    }

    //2.声明ttl队列
    @Bean
    public Queue directTtlMessageQueue() {
        return new Queue("ttl.message.queue", true);
    }

    //3.声明队列和交换机的绑定
    @Bean
    public Binding ttlMessageBinding() {
        return BindingBuilder.bind(directTtlMessageQueue()).to(ttldirectExchange()).with("ttlmessage");
    }
}

2.生产者工程

public void makeOrderTtlMessage(String userid, String productid, int num){
        //1.根据商品id查询库存是否充足
        //2.保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生产成功:" + orderId);
        //3.通过MQ来完成消息的分发
        // 参数1:交换机  参数2:路由key/queue队列名称 参数3:消息内容
        String exchangeName = "ttl_direct_order_exchange";
        String routingKey = "ttlmessage";

        //给消息设置过期时间
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration("5000");
                message.getMessageProperties().setContentEncoding("UTF-8");
                return message;
            }
        };
        //给发送的这条消息绑定过期时间的后置处理器
        rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId, messagePostProcessor);
    }
  1. 运行结果:
  • 队列中的这条消息在5秒后自动消失

在这里插入图片描述

在这里插入图片描述

:如果消息队列和此条消息同时设置了过期时间,则以最小的过期时间为准

死信队列

死信队列是当消息在一个队列因为下列原因:

  • 消息被拒绝(basic.reject或basic.nack)并且requeue=false

  • 消息TTL过期

  • 队列达到最大长度(队列满了,数据无法添加到mq中)

    变成了 “死信队列” 后被重新投递(publish)到另一个Exchange,然后重新消费。说白了就是没有被消费的消息换个地方重新被消费

建立方式:建立一个普通的direct队列,取名为死信队列,然后把死信队列和设置了过期时间的队列产生关联,这样一旦消息队列过期了就会自动转到死信队列中

1.生产者建立死信队列和交换机的绑定关系

package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

/**
 * @ClassName DeadRabbitMqConfiguration
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/6 12:15
 */
@Configuration
public class DeadRabbitMqConfiguration {
    //1.声明注册direct模式的交换机
    @Bean
    public DirectExchange deadExchange() {
        return new DirectExchange("dead_direct_order_exchange", true, false);
    }

    //2.声明死信队列
    @Bean
    public Queue deadQueue() {
        return new Queue("dead.direct.queue", true);
    }

    //3.声明队列和交换机的绑定
    @Bean
    public Binding deadBindings() {
        return BindingBuilder.bind(deadQueue()).to(deadExchange()).with("dead");
    }
}
  1. 把设置了过期时间的消息队列和死信队列建立关联
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

/**
 * @ClassName TTLRabbitMqConfiguration
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/6 10:34
 */
@Configuration
public class TTLRabbitMqConfiguration {
    //1.声明注册fanout模式的交换机
    @Bean
    public DirectExchange ttldirectExchange() {
        return new DirectExchange("ttl_direct_order_exchange", true, false);
    }

    //2.声明ttl队列并设置队列过期时间
    @Bean
    public Queue directTtlQueue() {
        HashMap<String, Object> args = new HashMap<>();
        args.put("x-message-ttl", 5000);
        //此处是关键,设置死信队列和过期队列的关联
        args.put("x-dead-letter-exchange", "dead_direct_order_exchange");
        args.put("x-dead-letter-routing-key", "dead");
        return new Queue("ttl.direct.queue", true,false ,false ,args);
    }

    //3.声明队列和交换机的绑定
    @Bean
    public Binding ttlBinding() {
        return BindingBuilder.bind(directTtlQueue()).to(ttldirectExchange()).with("ttl");
    }
}
  1. 运行结果:
  • 5秒后未消费自动转到死信队列中

在这里插入图片描述

在这里插入图片描述

  • 页面上可以设置和获取这两个参数

在这里插入图片描述

内存和磁盘的阈值控制

内存阈值控制

命令:

rabbitmqctl set_vm_memory_high_watermark <fraction> [0.4为常规默认值]
rabbitmqctl set_vm_memory_high_watermark absolute 50MB

其含义是内存超过这个值(如50MB),则内存会爆红,且所有连接connection为阻塞状态,不能发送消息,需要调整内存

在这里插入图片描述

在这里插入图片描述

磁盘阈值控制

命令:

rabbitmqctl set_disk_free_limit  <disk_limit> [100GB]
rabbitmqctl set_disk_free_limit memory_limit  <fraction>
disk_limit:固定单位 KB MB GB
fraction :是相对阈值,建议范围在:1.0~2.0之间。(相对于内存)

如设置为100GB后会爆红,其含义为磁盘所剩容量低于这个值(100GB)则会报警(因目前只有15GB可用,所以会爆红),所有连接阻塞

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

恢复为正常值后可以连接

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

内存换页

即在内存达到一个比例时自动把其中的消息存盘到磁盘中,用磁盘换取内存空间

默认情况下,内存到达的阈值是50%时就会换页处理。
也就是说,在默认情况下该内存的阈值是0.4的情况下,当内存超过0.4*0.5=0.2时,会进行换页动作。

比如有1000MB内存,当内存的使用率达到了400MB,已经达到了极限,但是因为配置的换页内存0.5,这个时候会在达到极限400mb之前,会把内存中的200MB进行转移到磁盘中。从而达到稳健的运行。

可以通过设置 vm_memory_high_watermark_paging_ratio 来进行调整

vm_memory_high_watermark.relative = 0.4
vm_memory_high_watermark_paging_ratio = 0.7(设置小于1的值,默认为0.5)

集群

https://www.kuangstudy.com/zl/rabbitmq#1367869499746869249

分布式事务

在这里插入图片描述

在这里插入图片描述

系统与系统之间的分布式事务问题

在这里插入图片描述

案例展示

订单服务(order-service)
  1. 实体类Order
public class Order {
    private String orderId;
    private String userId;
    private String orderContent;
}
  1. controller
@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    public OrderService orderService;

    @PostMapping("/create")
    public String create(@RequestBody Order order) throws Exception {
        orderService.createOrder(order);
        return "success";
    }
}
  1. service
@Service
public class OrderService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Order orderInfo) throws Exception {
        //1.订单信息--插入订单系统,订单数据库事务
        saveOrder(orderInfo);
        String result = dispatchHttpApi(orderInfo.getOrderId() + "");
        if (!"success".equals(result)) {
            throw new Exception("订单创建失败,原因是运单接口调用失败!");
        }
    }

    private String dispatchHttpApi(String orderId) {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        // 链接超时>3秒
        factory.setConnectTimeout(3000);
        // 处理超时>2秒
        factory.setReadTimeout(2000);
        // 发送http请求
        String url = "http://localhost:9000/dispatch/order?orderId=" + orderId;
        RestTemplate restTemplate = new RestTemplate(factory); //异常
        String result = restTemplate.getForObject(url, String.class);
        return result;
    }

    public void saveOrder(Order order) throws Exception {
        String sqlString = "insert into ksd_order(order_id,user_id,order_content,create_time)values(?,?,?,?)";

        Timestamp curTime = new Timestamp(new Date().getTime());
        int count = jdbcTemplate.update(sqlString, order.getOrderId(), order.getUserId(), order.getOrderContent(), curTime);
        if (count != 1) {
            throw new Exception("订单创建失败,原因[数据库操作失败]");
        }
    }
}
  1. 配置类(application.yml)
server:
  port: 9001

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/kuangstudy_order?useUnicode=true&characterEncoding=utf-8
    username: qyp
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  rabbitmq:
    username: admin
    password: admin
    virtual-host: /
    host: 192.168.179.128
    port: 5672
    listener:
      simple:
        acknowledge-mode: manual
        retry:
          enabled: true
          max-attempts: 10
          initial-interval: 2000ms
#logging:
#  level:
#    root: debug
  1. sql文件
-- Table structure for ksd_dispatcher
-- ----------------------------
DROP TABLE IF EXISTS `ksd_dispatcher`;
CREATE TABLE `ksd_dispatcher`  (
  `dispatch_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `order_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` tinyint(1) NULL DEFAULT NULL,
  `user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `order_content` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `create_time` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`dispatch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
运单服务(dispatcher-service)
  1. controller类
@RestController
@RequestMapping("/dispatch")
public class DispatchController {
    @Autowired
    public DispatchService dispatchService;

    @RequestMapping("/order")
    public String lock(String orderId) throws Exception {
        if(orderId.equals("1000001")){
            Thread.sleep(3000L);
        }
        dispatchService.dispatch(orderId);
        return "success";
    }
}
  1. service类
@Service
public class DispatchService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void dispatch(String orderId) throws Exception {
        //定义保存sql
        String sqlString = "insert into ksd_dispatcher(order_id,dispatch_id,status,user_id,order_content,create_time)" +
                "values (?,?,?,?,?)";
        //添加运动记录
        Timestamp timeStamp = new Timestamp(new Date().getTime());
        int count = jdbcTemplate.update(sqlString, orderId, UUID.randomUUID().toString(), 0, "yp" + new Random().nextInt(100) + 1, "木子酱买了一包方便面",timeStamp);
        if (count != 1) {
            throw new Exception("订单创建失败,原因[数据库操作失败]");
        }
    }
}
  1. 配置类
server:
  port: 9000

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/kuangstudy_dispatcher?useUnicode=true&characterEncoding=utf-8
    username: qyp
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  rabbitmq:
    username: admin
    password: admin
    virtual-host: /
    host: 192.168.179.128
    port: 5672
    listener:
      simple:
        acknowledge-mode: manual
        retry:
          enabled: true
          max-attempts: 10
          initial-interval: 2000ms
#logging:
#  level:
#    root: debug
  1. sql文件
-- Table structure for ksd_order
-- ----------------------------
DROP TABLE IF EXISTS `ksd_order`;
CREATE TABLE `ksd_order`  (
  `order_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `order_content` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `create_time` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
正常场景测试成功(两个表都能够成功插入数据)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

异常场景测试(order表读取超时,发生回退,dispatch表超时后继续处理,数据可以成功插入,出现数据库不一致)

在这里插入图片描述

order表没有插入数据

在这里插入图片描述

dispatch表插入该条数据

在这里插入图片描述

可靠生产(分布式事务解决方案)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

订单工程:transaction-rabbitmq-order

操作原理如下:

  1. 写入订单信息到订单数据库,同时写入相关数据到订单回执数据库,此时回执数据的status为0
  2. 发送order信息到rabbitmq中(关联是orderId)
  3. 监听rabbitmq的回执信息,一旦成功收到该信息,则通过orderId把刚才的回执数据库status改为1
  4. 即代表该订单信息已经成功发送到rabbitmq,且rabbitmq服务可以正常运行
@Service
public class MQOrderService {
    @Autowired
    private OrderDataBaseService orderDataBaseService;
    @Autowired
    private OrderMQService orderMQService;

    public void createOrder(Order order) throws Exception {
        orderDataBaseService.saveOrder(order);
        orderMQService.sendMessage(order);
    }
}
@Service
public class OrderMQService {
    @Autowired
    public RabbitTemplate rabbitTemplate;
    @Autowired
    public JdbcTemplate jdbcTemplate;

    //@PostConstruct注解好多人认为是spring提供的,其实是java自己的注解
  /*  Java中该注解的说明:@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
    通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序:
    Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)*/
    @PostConstruct
    public void regCallback() {
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {

            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("cause:" + cause);
                String orderId = correlationData.getId();
                //如果ack为true代表消息已经收到
                if (!ack) {
                    System.out.println("MQ队列应答失败,orderId是:" + orderId);
                    return;
                }

                try {
                    String updatesql = "update ksd_order_message set status = 1 where order_id = ?";
                    int count = jdbcTemplate.update(updatesql, orderId);
                    if (count == 1) {
                        System.out.println("本地消息状态修改成功,消息成功投递到消息队列中...");
                    }
                } catch (Exception e) {
                    System.out.println("本地消息状态修改失败,出现异常:" + e.getMessage());
                }
            }
        });
    }

    public void sendMessage(Order order) {
        //通过MQ发送信息
        rabbitTemplate.convertAndSend("order_fanout_exchange", "", JSONObject.toJSONString(order),
               new CorrelationData(order.getOrderId()));
    }
}
package com.rabbitmq.service;

import com.rabbitmq.pojo.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.sql.Timestamp;
import java.util.Date;
import java.util.UUID;

/**
 * @ClassName OrderDataBaseService
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/7 18:24
 */
@Service
public class OrderDataBaseService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void saveOrder(Order order) throws Exception {
        String sqlString = "insert into ksd_order(order_id,user_id,order_content,create_time)values(?,?,?,?)";

        Timestamp curTime = new Timestamp(new Date().getTime());
        int count = jdbcTemplate.update(sqlString, order.getOrderId(), order.getUserId(), order.getOrderContent(), curTime);
        if (count != 1) {
            throw new Exception("订单创建失败,原因[数据库操作失败]");
        }
        saveLocalMessage(order);
    }

    /**
     * 保存信息到本地
     * @Description //TODO
     * @param
     * @return
     * @Date
     * @Author
     */
    private void saveLocalMessage(Order order) throws Exception {
        String sqlString = "insert into ksd_order_message(order_id, order_content,status,unique_id)values(?,?,?,?)";
        int count = jdbcTemplate.update(sqlString, order.getOrderId(), order.getOrderContent(), 0, UUID.randomUUID().toString());
        if (count != 1) {
            throw new Exception("订单状态表创建失败,原因[数据库操作失败]");
        }
    }
}

测试类:

@SpringBootTest
class TransactionRabbitmqOrderApplicationTests {
    @Autowired
    private MQOrderService mqOrderService;

    @Test
    public void orderCreatedMQ() throws Exception {
        //订单生成
        String orderId = "1000001";
        Order order = new Order();
        order.setOrderId(orderId);
        order.setUserId("1");
        order.setOrderContent("买了一个方便面");
        mqOrderService.createOrder(order);
        System.out.println("订单创建成功");
        Thread.sleep(2000);
    }
}

配置类:(需要开启publisher-confirm-type)

server:
  port: 9001

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/kuangstudy_order?useUnicode=true&characterEncoding=utf-8
    username: qyp
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  rabbitmq:
    username: admin
    password: admin
    virtual-host: /
    host: 192.168.179.128
    port: 5672
    listener:
      simple:
        acknowledge-mode: manual
        retry:
          enabled: true
          max-attempts: 10
          initial-interval: 2000ms
    publisher-confirm-type: correlated

运行结果:

  • 数据库订单回执表的status变成1,即成功投递到rabbitmq

在这里插入图片描述

在这里插入图片描述

  • rabbitmq交换机的order.queue队列上有一条消息

在这里插入图片描述

可靠消费

派单工程:transaction-rabbitmq-dispatcher

rabbitmq消费方式

@Service
public class OrderMqConsumer {
    @Autowired
    private DispatchService dispatchService;

    private int count = 1;

    //解决消息重试的几种方案
    //1.控制重发的次数(重试次数到了在自动ack下容易造成消息的丢失或者转移到死信队列)
    //2.try+catch+手动ack
    //3.try+catch+手动ack+死信队列处理+人工干预
    @RabbitListener(queues = {"order.queue"})
    public void messageconsumer(String ordermsg, Channel channel,
                                CorrelationData correlationData,
                                @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
        System.out.println("收到MQ的消息是:" + ordermsg + ",count=" + count++);
        //把ordermsg反序列化成order对象
        Order order = JSONObject.parseObject(ordermsg, Order.class);
        //获取订单id
        String orderId = order.getOrderId();
        System.out.println(1/0);
        //派单处理
        dispatchService.dispatch(orderId);
    }
}

发送运单

@Service
public class DispatchService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void dispatch(String orderId) throws Exception {
        //定义保存sql
        String sqlString = "insert into ksd_dispatcher(order_id,dispatch_id,status,user_id,order_content,create_time)" +
                "values (?,?,?,?,?,?)";
        //添加运动记录
        Timestamp timeStamp = new Timestamp(new Date().getTime());
        int count = jdbcTemplate.update(sqlString, orderId, UUID.randomUUID().toString(), 0, "yp" + new Random().nextInt(100) + 1, "木子酱买了一包方便面",timeStamp);
        if (count != 1) {
            throw new Exception("订单创建失败,原因[数据库操作失败]");
        }
    }
}

配置文件:

server:
  port: 9000

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/kuangstudy_dispatcher?useUnicode=true&characterEncoding=utf-8
    username: qyp
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  rabbitmq:
    username: admin
    password: admin
    virtual-host: /
    host: 192.168.179.128
    port: 5672
    listener:
      simple:
        acknowledge-mode: manual
        retry:
          enabled: true  #开启重试
          max-attempts: 3  #最大重试次数
          initial-interval: 2000ms  #重试间隔时间

死信队列的配置类

package com.rabbitmq.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

/**
 * @ClassName MqConfig
 * @Description TODO
 * @Author QiuYiping
 * @Date 2022/4/8 13:16
 */
@Configuration
public class MqConfig {

    //1.声明注册fanout模式的交换机
    @Bean
    public FanoutExchange deadExchange() {
        return new FanoutExchange("dead_order_exchange", true, false);
    }

    //2.声明死信队列
    @Bean
    public Queue deadQueue() {
        return new Queue("dead.order.queue", true);
    }

    //3.声明队列和交换机的绑定
    @Bean
    public Binding deadBindings() {
        return BindingBuilder.bind(deadQueue()).to(deadExchange());
    }

    //1.声明注册fanout模式的交换机
    @Bean
    public FanoutExchange orderExchange() {
        return new FanoutExchange("order_fanout_exchange", true, false);
    }

    //2.声明fanout队列
    @Bean
    public Queue orderQueue() {
        HashMap<String, Object> args = new HashMap<>();
        //此处是关键,设置死信队列和过期队列的关联
        args.put("x-dead-letter-exchange", "dead_order_exchange");
        return new Queue("order.queue", true,false ,false ,args);
    }

    //3.声明队列和交换机的绑定
    @Bean
    public Binding orderBindings() {
        return BindingBuilder.bind(orderQueue()).to(orderExchange());
    }
}

问题:

在这里插入图片描述

在这里插入图片描述

此处出现异常会造成消息无法消费,不停重试造成死循环,消耗服务器性能

方法一:application.yml中配置重试次数,重试次数完了直接结束。该条信息依然存在未丢失

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-08aidsBH-1649410211687)(image-20220408151650296.png)]
在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WCwInhMi-1649410211688)(image-20220408151933061.png)]
在这里插入图片描述

方法二:try+catch+手动ack(出现异常不重试,即使配置了重试次数也无效)

注意:该条消息因出现异常没有重试而丢失

        try {
            System.out.println("收到MQ的消息是:" + ordermsg + ",count=" + count++);
            //把ordermsg反序列化成order对象
            Order order = JSONObject.parseObject(ordermsg, Order.class);
            //获取订单id
            String orderId = order.getOrderId();
            System.out.println(1 / 0); //出现异常
            //派单处理
            dispatchService.dispatch(orderId);
            channel.basicAck(tag, false);
        } catch (Exception e) {
            // 如果出现异常
            // 重发一次后,丢失还是日记,存库需要根据自己的业务场景去决定
            // 参数1:消息的tag 参数2:false 多条处理 参数3:requeue 是否重发
            // true 则该条信息需要重发,会造成死循环,且配置项中的重试次数会失效
            // false 则该条信息不再重发,容易造成消息的丢失,可以采用死信队列接收
            channel.basicNack(tag, false, false);
        }
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FiEAg6dy-1649410211690)(image-20220408152557426.png)]
在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aLWFs4DV-1649410211691)(image-20220408152618058.png)]
在这里插入图片描述

方法三:try+catch+手动ack+死信队列处理+人工干预

上面出现异常未重试造成消息丢失,可以把出现异常的信息放到死信队列中,在死信队列中重新处理,如果死信队列中再出现异常,则采用人工干预

    //把队列和死信队列进行绑定
    @Bean
    public Queue orderQueue() {
        HashMap<String, Object> args = new HashMap<>();
        //此处是关键,设置死信队列和过期队列的关联
        args.put("x-dead-letter-exchange", "dead_order_exchange");
        return new Queue("order.queue", true,false ,false ,args);
    }
@Service
public class DeadMqConsumer {
    @Autowired
    private DispatchService dispatchService;

    private int count = 1;

    //解决消息重试的几种方案
    //1.控制重发的次数(重试次数到了在自动ack下容易造成消息的丢失或者转移到死信队列)
    //2.try+catch+手动ack
    //3.try+catch+手动ack+死信队列处理+人工干预
    @RabbitListener(queues = {"dead.order.queue"})
    public void messageconsumer(String ordermsg, Channel channel,
                                CorrelationData correlationData,
                                @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
        try {
            System.out.println("死信队列收到MQ的消息是:" + ordermsg + ",count=" + count++);
            //把ordermsg反序列化成order对象
            Order order = JSONObject.parseObject(ordermsg, Order.class);
            //获取订单id
            String orderId = order.getOrderId();
            //派单处理
            dispatchService.dispatch(orderId);
            channel.basicAck(tag, false);
        } catch (Exception e) {
            // 如果出现异常
            // 重发一次后,丢失还是日记,存库需要根据自己的业务场景去决定
            // 参数1:消息的tag 参数2:false 多条处理 参数3:requeue 是否重发
            // true 则该条信息需要重发,会造成死循环,且配置项中的重试次数会失效
            // false 则该条信息不再重发,容易造成消息的丢失,可以采用死信队列接收
            System.out.println("人工干预");
            System.out.println("发短信预警");
            System.out.println("同时把消息转移到别的DB存储");
            channel.basicNack(tag, false, false);
        }
    }
}

两阶段提交法(2PC)

待续

补偿事务TCC

待续

常见面试题

为什么rabbitMQ是基于channel去处理而不是基于连接?

可以存在没有交换机的队列吗?

不可能,虽然没有指定交换机但是会存在一个默认的交换机

图片面试

在这里插入图片描述

学习中间件的方法

在这里插入图片描述

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值