RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例

文章目录

  • 快速入门案例
    • 实现步骤
      • 生产者代码 Producer
      • 消费者代码 Consumer
  • AMQP
    • AMQP生产者流转过程
  • RabbitMQ组件和架构
    • 核心组成部分
    • RabbitMQ整体架构
    • RabbitMQ运行流程
  • 交换机模式理解
    • 简单模式Simple
    • 发布订阅模式Fanout
      • web操作
        • 1. 创建交换机
        • 2. 创建队列
        • 3. 绑定交换机与队列
        • 4. 发送消息
      • 编码测试
        • 生产者Producer
        • 消费者
    • 路由模式Direct
    • 主题模式Topic
      • 添加队列绑定路由
    • Header模式
      • 创建交换机
      • 创建队列
      • 绑定队列
    • 用编码的方式来定义交换机、队列、绑定交换机队列
      • 测试
    • 工作队列模式Work
      • 工作队列模式 - 轮询Round-Robin
        • 编写生产者Producer
        • 编写消费者FWork1、FWork2
      • 工作队列模式 - 公平分发
        • 编写消费者FWork1、FWork2
  • 使用场景
    • 解耦、削峰、异步
    • 高内聚、低耦合
  • RabbitMQ整合SpringBoot
    • simple简单模式
      • 生产者代码
        • 1. 定义配置类
        • 2. 业务类发送消息
        • 3. 测试调用发送方法
      • 消费者代码
    • Direct模式 -- 基于配置类的绑定
      • 生产者代码
        • 消息队列配置类DirectRabbitmqCfg
        • 业务调用处理
        • 测试
      • 消费者代码
    • 主题模式Topic -- 基于注解的绑定
      • 消费者代码
      • 生产者
        • 业务代码与消息发送
        • 测试代码

快速入门案例

Producer生产者  ->  MQ  ->  消费者 

RabbitMQ Tutorials:
https://www.rabbitmq.com/getstarted.html

RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第1张图片RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第2张图片

实现步骤

  1. 构建Maven工程,导入rabbitMQ的依赖
  2. 启动rabbitmq-server服务
  3. 定义生产者、消费者,观察消息在rabbitmq-server服务中的过程

生产者代码 Producer

package com.hx.demo.simple;

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

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

/**
 * @author Huathy
 * @date 2023-03-21 22:01
 * @description 简单模式Simple
 * 所有中间件技术都是基于TCP/IP协议基础上,构建新型的协议规范,只不过rabbitmq遵循的是amqp
 * IP port
 */
public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 创建链接工程
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("admin123");
        factory.setVirtualHost("/");
        // 2. 创建链接connection
        Connection connection = null;
        Channel channel = null;
        try {
            connection = factory.newConnection("生产者");
            // 3. 创建交换机、队列、绑定关系、路由key、发送消息、接收消息
            channel = connection.createChannel();
            // 4. 准备消息内容
            String queueName = "queue1";
            /**
             * 队列名称
             * 是否持久化:durable=false 所谓持久化消息是否存盘。那么非持久化的消息是否存盘?会存盘,但重启会丢失。
             * 是否具有排他性:是否独占独立。
             * 是否自动删除:随着最后一个消费者消费完毕,是否把队列删除
             * 额外参数:携带附属参数
             */
//            channel.queueDeclare(queueName, false, false, false, null);
            // 生成一个持久化的队列
            channel.queueDeclare("queue2", true, false, false, null);
            // 5. 发送消息给队列queue
            String msg = "Hello Huathy";
            // 参数: 交换机 、 队列(路由key)、消息的控制状态、消息主题
            channel.basicPublish("", queueName, null, msg.getBytes());
            System.out.println("消息发送完成......");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭连接
            if (channel != null && channel.isOpen()) {
                channel.close();
            }
            // 7. 关闭通道
            if (connection != null && connection.isOpen()) {
                connection.close();
            }
        }
    }
}

消费者代码 Consumer

package com.hx.demo.simple;

import com.rabbitmq.client.*;

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

/**
 * @author Huathy
 * @date 2023-03-21 22:52
 * @description
 */
public class Consumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 创建链接工程
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("admin123");
        factory.setVirtualHost("/");
        // 2. 创建链接connection
        Connection connection = null;
        Channel channel = null;
        try {
            connection = factory.newConnection("生产者");
            // 3. 创建交换机、队列、绑定关系、路由key、发送消息、接收消息
            channel = connection.createChannel();
            // 4. 准备消息内容
            String queueName = "queue1";
            channel.basicConsume(queueName, true, (consumerTag, delivery) -> {
                System.out.println("收到消息:" + new String(delivery.getBody(), "UTF-8"));
            }, consumerTag -> {
                System.out.println("接收失败了...");
            });

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭连接
            if (channel != null && channel.isOpen()) {
                channel.close();
            }
            // 7. 关闭通道
            if (connection != null && connection.isOpen()) {
                connection.close();
            }
        }
    }
}

AMQP

Advanced Message Queuing Protocol高级消息队列协议。是应用层协议的一个开放标准,为面向消息的中间件设计。

AMQP生产者流转过程

RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第3张图片

RabbitMQ 为什么是基于通道channel而不是基于连接connection处理?
连接是短链接,经过三四握手四次挥手,消耗时间。开关连接消耗性能。
而通过长连接,创建多个信道,提高性能。

如何保证消费者的可靠消费?利用ASK机制和手动ASK。

RabbitMQ组件和架构

核心组成部分

消息投递到交换机再放到队列中
RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第4张图片核心概念:
Server : 又称Broker ,接受客户端的连接,实现AMQP实体服务。 安装rabbitmq-server
Connection : 连接,应用程序与Broker的网络连接 TCP/IP/三次握手和四次挥手
Channel:网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道,客户端可以建立对各Channel,每个Channel代表一个会话任务。
Message :消息: 服务与应用程序之间传送的数据,由Properties和body组成,Properties可是对消息进行修饰,比如消息的优先级,延迟等高级特性,Body则就是消息体的内容。
Virtual Host: 虚拟地址,用于进行逻辑隔离,最上层的消息路由,一个虚拟主机理由可以有若干个Exhange和Queueu,同一个虚拟主机里面不能有相同名字的Exchange
Exchange: 交换机,接受消息,根据路由键发送消息到绑定的队列。(不具备消息存储的能力)
Bindings : Exchange和Queue之间的虚拟连接,binding中可以保护多个routing key.
Routing key : 是一个路由规则,虚拟机可以用它来确定如何路由一个特定消息
Queue : 队列: 也成为Message Queue,消息队列,保存消息并将它们转发给消费者.

可以存在没有交换机的队列么?不可以。虽然没有指定,但是一定会存在一个默认的交换机。

RabbitMQ整体架构

RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第5张图片

RabbitMQ运行流程

RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第6张图片

交换机模式理解

简单模式Simple

VirtualPort类比为磁盘目录:隔离区分目的
Name:队列名称
Feature:D表示持久化,Args表示参数

遵循AMQP协议,任何队列都必须绑定交换机。如果没有指定交换机,那么就会绑定默认的交换机。

发布订阅模式Fanout

应用场景:注册发送邮件、短信等可以广播来实现消息同步
Fanout模式指定路由key是没有意义的
RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第7张图片

web操作

1. 创建交换机

RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第8张图片

2. 创建队列

RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第9张图片

3. 绑定交换机与队列

RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第10张图片RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第11张图片

4. 发送消息

RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第12张图片
RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第13张图片

编码测试

生产者Producer

public class Producer {
    private static String EXCHANGE_NAME = "fanout-exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 创建链接工程
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("admin123");
        factory.setVirtualHost("/");
        // 2. 创建链接connection
        Connection connection = null;
        Channel channel = null;
        try {
            connection = factory.newConnection("生产者");
            // 3. 创建交换机、队列、绑定关系、路由key、发送消息、接收消息
            channel = connection.createChannel();
            // 4. 准备消息内容
            String queueName = "fanout1";
            /**
             * 队列名称
             * 是否持久化:durable=false 所谓持久化消息是否存盘。那么非持久化的消息是否存盘?会存盘,但重启会丢失。
             * 注意如果新建的队列是持久化的这里就要是true,否则报错
             * 是否具有排他性:是否独占独立。
             * 是否自动删除:随着最后一个消费者消费完毕,是否把队列删除
             * 额外参数:携带附属参数
             */
            channel.queueDeclare(queueName, true, false, false, null);
            // 5. 发送消息给队列queue
            String msg = "Hello Huathy";
            // 参数: 交换机 、 队列(路由key)、消息的控制状态、消息主题
            // 这种模式下写不写路由key都无效,但是不可以为null
            channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
            System.out.println("消息发送完成......");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭连接
            if (channel != null && channel.isOpen()) {
                channel.close();
            }
            // 7. 关闭通道
            if (connection != null && connection.isOpen()) {
                connection.close();
            }
        }
    }
}

消费者

public class Consumer {
    private static Runnable run = new Runnable() {
        @SneakyThrows
        @Override
        public void run() {
            // 1. 创建链接工程
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("127.0.0.1");
            factory.setPort(5672);
            factory.setUsername("admin");
            factory.setPassword("admin123");
            factory.setVirtualHost("/");
            // 2. 创建链接connection
            Connection connection = null;
            Channel channel = null;
            try {
                connection = factory.newConnection("生产者");
                // 3. 创建交换机、队列、绑定关系、路由key、发送消息、接收消息
                channel = connection.createChannel();
                // 4. 准备消息内容
                String queueName = Thread.currentThread().getName();
                channel.basicConsume(queueName, true, (consumerTag, delivery) -> {
                    System.out.println(queueName + "收到消息:" + new String(delivery.getBody(), "UTF-8"));
                }, consumerTag -> {
                    System.out.println("接收失败了...");
                });

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 6. 关闭连接
                if (channel != null && channel.isOpen()) {
                    channel.close();
                }
                // 7. 关闭通道
                if (connection != null && connection.isOpen()) {
                    connection.close();
                }
            }
        }
    };

    public static void main(String[] args) throws IOException, TimeoutException {
        new Thread(run, "fanout1").start();
        new Thread(run, "fanout2").start();
    }
}

路由模式Direct

在发布订阅模式的基础上增加路由key来做一个选择。 只有对应key的队列才能收到消息。
RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第14张图片
RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第15张图片

主题模式Topic

支持模糊匹配的路由key。对key做模糊匹配。匹配上的key才能收到消息。
#:0个或多个
*:有且只能有1个
RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第16张图片

添加队列绑定路由

RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第17张图片

Header模式

参数模式可以根据参数来进行过滤。

创建交换机

RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第18张图片

创建队列

RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第19张图片

绑定队列

RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第20张图片

用编码的方式来定义交换机、队列、绑定交换机队列

public class Producer {
    final static String EXCHANGE_NAME = "direct-msg-exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 创建链接工程
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("admin123");
        factory.setVirtualHost("/");
        // 2. 创建链接connection
        Connection connection = null;
        Channel channel = null;
        try {
            connection = factory.newConnection("生产者");
            // 3. 创建交换机、队列、绑定关系、路由key、发送消息、接收消息
            channel = connection.createChannel();
            // 4. 准备消息内容
            /**
             * 队列名称
             * 是否持久化:durable=false 所谓持久化消息是否存盘。那么非持久化的消息是否存盘?会存盘,但重启会丢失。
             * 是否具有排他性:是否独占独立。
             * 是否自动删除:随着最后一个消费者消费完毕,是否把队列删除
             * 额外参数:携带附属参数
             */
            // 定义一个交换机
            channel.exchangeDeclare(EXCHANGE_NAME, "direct", true);
            // 生成一个持久化的队列
            String queue = EXCHANGE_NAME.concat("-1");
            channel.queueDeclare(queue, true, false, false, null);
            channel.queueBind(queue, EXCHANGE_NAME, "order");
            // 5. 发送消息给队列queue
            String msg = "Hello Huathy";
            // 参数: 交换机 、 队列(路由key)、消息的控制状态、消息主题
            channel.basicPublish(EXCHANGE_NAME, "order", null, msg.getBytes());
            System.out.println("消息发送完成......");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭连接
            if (channel != null && channel.isOpen()) {
                channel.close();
            }
            // 7. 关闭通道
            if (connection != null && connection.isOpen()) {
                connection.close();
            }
        }
    }
}

测试

public class Consumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 创建链接工程
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("admin123");
        factory.setVirtualHost("/");
        // 2. 创建链接connection
        Connection connection = null;
        Channel channel = null;
        try {
            connection = factory.newConnection("生产者");
            // 3. 创建交换机、队列、绑定关系、路由key、发送消息、接收消息
            channel = connection.createChannel();
            // 4. 准备消息内容
            String queueName = Producer.EXCHANGE_NAME.concat("-1");
            channel.basicConsume(queueName, true, (consumerTag, delivery) -> {
                System.out.println("收到消息:" + new String(delivery.getBody(), "UTF-8"));
            }, consumerTag -> {
                System.out.println("接收失败了...");
            });

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭连接
            if (channel != null && channel.isOpen()) {
                channel.close();
            }
            // 7. 关闭通道
            if (connection != null && connection.isOpen()) {
                connection.close();
            }
        }
    }
}

工作队列模式Work

当有多个消费者时,我们的消息会被哪个消费者消费呢?又应该如何均衡消费者消费消息的多少?
主要有俩种消费方式:

  1. 轮询分发:一个消费者消费一条,绝对平均分配
  2. 公平分发:根据消费者的消费能力进行公平分发,处理快的处理多,处理慢的处理少。相对平均按劳分配。

工作队列模式 - 轮询Round-Robin

该模式接收消息是当有多个消费者接入时,消息的分配模式是一个消费者分配一条,直到消息消费完成。

编写生产者Producer

public class Producer {
    private static String EXCHANGE_NAME = "";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 创建链接工程
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("admin123");
        factory.setVirtualHost("/");
        // 2. 创建链接connection
        Connection connection = null;
        Channel channel = null;
        try {
            connection = factory.newConnection("生产者");
            // 3. 创建交换机、队列、绑定关系、路由key、发送消息、接收消息
            channel = connection.createChannel();
            // 4. 准备消息内容
            String queueName = "fanout1";
            /**
             * 队列名称
             * 是否持久化:durable=false 所谓持久化消息是否存盘。那么非持久化的消息是否存盘?会存盘,但重启会丢失。
             * 注意如果新建的队列是持久化的这里就要是true,否则报错
             * 是否具有排他性:是否独占独立。
             * 是否自动删除:随着最后一个消费者消费完毕,是否把队列删除
             * 额外参数:携带附属参数
             */
            channel.queueDeclare(queueName, true, false, false, null);
            // 5. 发送消息给队列queue
            for (int i = 0; i < 20; i++) {
                String msg = i + " Huathy" + SimpleDateFormat.getDateTimeInstance().format(new Date());
                // 参数: 交换机 、 队列(路由key)、消息的控制状态、消息主题
                channel.basicPublish(EXCHANGE_NAME, "work1", null, msg.getBytes());
                System.out.println(i + "消息发送完成......");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭连接
            if (channel != null && channel.isOpen()) {
                channel.close();
            }
            // 7. 关闭通道
            if (connection != null && connection.isOpen()) {
                connection.close();
            }
        }
    }
}

编写消费者FWork1、FWork2

这里节省篇幅只展示1个的示例

public class Work1 {

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 创建链接工程
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("admin123");
        factory.setVirtualHost("/");
        // 2. 创建链接connection
        Connection connection = null;
        Channel channel = null;
        try {
            connection = factory.newConnection("work1的消费者1");
            // 3. 创建交换机、队列、绑定关系、路由key、发送消息、接收消息
            channel = connection.createChannel();
            // 4. 准备接收消息内容
            System.out.println("work1的消费者01 开始接收消息:");
            channel.basicConsume("work1", true, (consumerTag, delivery) -> {
                System.out.println("work1的消费者01" + "收到消息:" + new String(delivery.getBody(), "UTF-8"));
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, consumerTag -> {
                System.out.println("接收失败了...");
            });
            System.in.read();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭连接
            if (channel != null && channel.isOpen()) {
                channel.close();
            }
            // 7. 关闭通道
            if (connection != null && connection.isOpen()) {
                connection.close();
            }
        }
    }
}

工作队列模式 - 公平分发

需要基于上面的轮询分发模式进行修改:

finalChannel.basicQos(1);
channel.basicConsume("work1", false, (consumerTag, delivery)->{});
finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);

编写消费者FWork1、FWork2

这里节省篇幅只展示1个的示例

public class FWork1 {

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 创建链接工程
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("admin123");
        factory.setVirtualHost("/");
        // 2. 创建链接connection
        Connection connection = null;
        Channel channel = null;
        try {
            connection = factory.newConnection("work1的消费者1");
            // 3. 创建交换机、队列、绑定关系、路由key、发送消息、接收消息
            channel = connection.createChannel();
            // 4. 准备接收消息内容
            System.out.println("work1的消费者01 开始接收消息:");
            Channel finalChannel = channel;
            // 公平分发还必须将指标qos定义出来,每次的指标为1获取1条。需要根据当前服务器内存、CPU来设置大小
            finalChannel.basicQos(3);
            channel.basicConsume("work1", false, (consumerTag, delivery) -> {
                System.out.println("work1的消费者01" + "收到消息:" + new String(delivery.getBody(), "UTF-8"));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
            }, consumerTag -> {
                System.out.println("接收失败了...");
            });
            System.in.read();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭连接
            if (channel != null && channel.isOpen()) {
                channel.close();
            }
            // 7. 关闭通道
            if (connection != null && connection.isOpen()) {
                connection.close();
            }
        }
    }
}

使用场景

解耦、削峰、异步

  1. 同步异步的问题(串行)
    串行方式:将订单信息写入数据库成功后,发送注册邮件,再发送注册短信,以上三个步骤完成以后返回给客户端。
    RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第21张图片
  2. 并行方式 异步线程池
    并行方式:将订单信息写入数据库成功后,发送注册邮件的同时发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间。

RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第22张图片
存在问题:耦合度高、需要自己写线程池维护成本高、出现了消息可能丢失需要自己来做补偿、需要自己实现保证消息的可靠性、高可用

  1. 异步 消息队列
    RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第23张图片好处:完全解耦用MQ建立桥接、有独立的线程池运行模型、出现消息丢失MQ也有持久化功能
    问题:如何保证消息可靠性、死信队列、消息转移?
    如果服务器无法承受,需要自己写高可用,HA镜像模型高可用。

高内聚、低耦合

方便业务扩充
RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第24张图片
RabbitMQ学习(二)入门案例与五种模式(简单模式、发布订阅模式、路由模式、主题模式、Header参数模式)及整合SpringBoot案例_第25张图片

RabbitMQ整合SpringBoot

simple简单模式

编写配置

server:
  port: 8889
spring:
  # 配置rabbitMQ服务
  rabbitmq:
    port: 5672
    username: admin
    password: admin123
    host: 127.0.0.1
    virtual-host: /

生产者代码

1. 定义配置类

@Configuration
public class RabbitmqCfg {
    public final static String EXCHANGE_NAME = "fanout_order_exchange";
    public final static String ROUTE_KEY = "";

    // 声明一个fanout模式的交换机
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(EXCHANGE_NAME, true, false);
    }

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

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

    // 完成绑定关系
    @Bean
    public Binding smsBinding() {
        return BindingBuilder.bind(smsQueue()).to(fanoutExchange());
    }

    @Bean
    public Binding emailBinding() {
        return BindingBuilder.bind(emailQueue()).to(fanoutExchange());
    }

}

2. 业务类发送消息

@Service
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public String makeOrder() {
        String orderId = SimpleDateFormat.getDateTimeInstance().format(new Date()).concat(" - ").concat(UUID.randomUUID().toString());
        System.out.println("Order保存逻辑处理");
        rabbitTemplate.convertAndSend(RabbitmqCfg.EXCHANGE_NAME, RabbitmqCfg.ROUTE_KEY, orderId);
        return "1";
    }
}

3. 测试调用发送方法

@SpringBootTest
class Rabbitmq1ApplicationTests {
    @Autowired
    OrderService orderService;

    @Test
    void orderTest() {
        String res = orderService.makeOrder();
        System.out.println("res = " + res);
    }
}

消费者代码

@RabbitListener(queues = {"email.fanout.queue"})
@Service
public class FanoutEmailConsumer {
    @RabbitHandler
    public void receiveMsg(String msg) {
        System.out.println("EMAIL FANOUT 接收到消息 --> " + msg);
    }
}
@Service
public class FanoutSmsConsumer {
    @RabbitHandler
    public void receiveMsg(String msg) {
        System.out.println("SMS FANOUT 接收到消息 --> " + msg);
    }
}

启动测试,查看控制台输出:

rabbitConnectionFactory#46039a21:0/SimpleConnection@73c31181 [delegate=amqp://admin@127.0.0.1:5672/, localPort= 3030]
SMS FANOUT 接收到消息 --> 2023-3-23 12:39:46 - bdecc460-3507-4f97-a104-b721dfbfabaa
EMAIL FANOUT 接收到消息 --> 2023-3-23 12:39:46 - bdecc460-3507-4f97-a104-b721dfbfabaa
EMAIL FANOUT 接收到消息 --> 2023-3-23 14:07:46 - 5f6e8940-4ecb-4d0b-8058-ae47844af5d7
SMS FANOUT 接收到消息 --> 2023-3-23 14:07:46 - 5f6e8940-4ecb-4d0b-8058-ae47844af5d7

Direct模式 – 基于配置类的绑定

生产者代码

消息队列配置类DirectRabbitmqCfg

@Configuration
public class DirectRabbitmqCfg {
    public final static String EXCHANGE_NAME = "direct_order_exchange";
    // 声明一个direct模式的交换机
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange(EXCHANGE_NAME, true, false);
    }
    // 声明队列
    @Bean
    public Queue directSmsQueue() {
        return new Queue("sms.direct.queue", true);
    }
    @Bean
    public Queue directEmailQueue() {
        return new Queue("email.direct.queue", true);
    }
    // 完成绑定关系
    @Bean
    public Binding directSmsBinding() {
        return BindingBuilder.bind(directSmsQueue()).to(directExchange()).with("sms");
    }
    @Bean
    public Binding directEmailBinding() {
        return BindingBuilder.bind(directEmailQueue()).to(directExchange()).with("email");
    }
}

业务调用处理

@Service
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
public String makeOrderDirect() {
        String orderId = SimpleDateFormat.getDateTimeInstance().format(new Date()).concat(" - ").concat(UUID.randomUUID().toString());
        System.out.println("Order保存逻辑处理");
        rabbitTemplate.convertAndSend(DirectRabbitmqCfg.EXCHANGE_NAME, "sms", orderId);
        return "1";
    }
}

测试

@Test
void orderDirectMQTest(){
    String res = orderService.makeOrderDirect();
    System.out.println("res = " + res);
}

消费者代码

@RabbitListener(queues = {"email.direct.queue"})
@Service
public class DirectEmailConsumer {
    @RabbitHandler
    public void receiveMsg(String msg) {
        System.out.println("EMAIL direct 接收到消息 --> " + msg);
    }
}
@RabbitListener(queues = {"sms.direct.queue"})
@Service
public class DirectSmsConsumer {
    @RabbitHandler
    public void receiveMsg(String msg) {
        System.out.println("SMS direct 接收到消息 --> " + msg);
    }
}

启动测试输出:

2023-03-23 14:30:09.211  INFO 13160 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#33a55bd8:0/SimpleConnection@3f63a513 [delegate=amqp://admin@127.0.0.1:5672/, localPort= 3643]
SMS direct 接收到消息 --> 2023-3-23 14:29:54 - 9b5b8678-81d2-407a-b41b-783f4a5fe7df

问题:消费者和生产者都可以对消息交换机、队列、关系进行配置,那么在生产者配置好,还是消费者更好?
我们可以在web管理页面、生产者、消费者端进行交换机、队列、关系的配置。但是还是消费者来定义比较好,一般消费者是最先启动的服务,如果没有的话,消费者端会启动失败。

主题模式Topic – 基于注解的绑定

消费者代码

@Service
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "email.topic.queue",declare = "true",autoDelete = "false"),
        exchange = @Exchange(value = "topic-order-exchange",type = ExchangeTypes.TOPIC),
        key = "*.email.#"
))
public class TopicEmailConsumer {
    @RabbitHandler
    public void receiveMsg(String msg) {
        System.out.println("Email Topic 接收到消息 --> " + msg);
    }
}
@Service
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "sms.topic.queue",declare = "true",autoDelete = "false"),
        exchange = @Exchange(value = "topic-order-exchange",type = ExchangeTypes.TOPIC),
        key = "#.sms.#"
))
public class TopicSmsConsumer {
    @RabbitHandler
    public void receiveMsg(String msg) {
        System.out.println("SMS Topic 接收到消息 --> " + msg);
    }
}
@Service
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "tel.topic.queue",declare = "true",autoDelete = "false"),
        exchange = @Exchange(value = "topic-order-exchange",type = ExchangeTypes.TOPIC),
        key = "com.#"
))
public class TopicTelConsumer {
    @RabbitHandler
    public void receiveMsg(String msg) {
        System.out.println("TEL Topic 接收到消息 --> " + msg);
    }
}

生产者

业务代码与消息发送

public String makeOrderTopic(String routeKey) {
    String orderId = SimpleDateFormat.getDateTimeInstance().format(new Date()).concat(" - ").concat(UUID.randomUUID().toString());
    System.out.println("Order保存逻辑处理");
    rabbitTemplate.convertAndSend("topic-order-exchange", routeKey, orderId);
    return "1";
}

测试代码

/**
 * #.sms.#
 * *.email.#
 * tel  :  com.#
 */
@Test
void orderDirectMQTopic_1() {
    String res = orderService.makeOrderTopic("com.hi.email");
    System.out.println("res = " + res);
}

@Test
void orderDirectMQTopic_2() {
    String res = orderService.makeOrderTopic("com.sms");
    System.out.println("res = " + res);
}

@Test
void orderDirectMQTopic_3() {
    String res = orderService.makeOrderTopic("com.sms");
    System.out.println("res = " + res);
}

你可能感兴趣的:(RabbitMQ,java-rabbitmq,rabbitmq,学习)