RabbitMQ基础篇

参考资料:黑马RabbitMQ

RabbitMQ基础篇

  • 一、 消息队列概述
    • 1. 什么是消息中间件
    • 2. 为什么使用消息中间件
    • 3. MQ常用应用场景
      • 1. 任务异步处理
      • 2. 应用程序解耦合
      • 3. 削峰填谷
    • 4. AMQP 和 JMS
      • 1. AMQP
      • 2. JMS
      • 3. AMQP 和 JMS 的区别
    • 5. 常见的MQ产品
    • 6. RabbitMQ
  • 二、安装及配置RabbitMQ
  • 三、RabbitMQ五种工作模式
    • 入门项目搭建
      • 1. 创建项目
      • 2. 导入依赖
      • 3. 编写获取连接工具类
    • 1. 基本消息模型(简单模式)
      • 1. 官网图解
      • 2. 处理流程
      • 3. 代码编写
        • 1. 生产者
        • 2. 消费者
    • 2. Work工作模式
        • 1. 官网图解
        • 2. 处理流程
        • 3. 应用
        • 4. 代码编写
          • 1. 生产者
          • 2. 两个消费者
    • 3. Publish/Subscribe(发布/订阅模式,也称 广播模式)
      • 1. 官网图解
      • 2. 处理流程
      • 3. 代码编写
        • 1. 生产者
        • 2. 两个消费者
    • 4. Routing(路由模式)
      • 1. 官网图解
      • 2. 处理流程
      • 3. 代码编写
        • 1. 生产者
        • 1. 两个消费者
    • 5. topic(通配符模式)
      • 1. 官网图解
      • 2. 处理流程
      • 3. 代码编写
        • 1. 生产者
      • 2. 两个消费者
    • 6. 工作模式总结
      • 1. 分类
    • 2. 非路由模式和路由模式的区别
  • 四、SpringBoot整合RabbitMQ
    • 1. 项目搭建
    • 2. 编写生产者代码
    • 3. 编写消费者代码
    • 4. 总结

一、 消息队列概述

1. 什么是消息中间件

​ MQ全称为Message Queue,消息队列是应用程序和应用程序之间的通信方法。消息队列是一种异步的服务间通信方式,适用于无服务器和微服务架构。消息在被处理和删除之前一直存储在队列上。每条消息仅可被一位用户处理一次。消息队列可被用于分离重量级处理、缓冲或批处理工作以及缓解高峰期工作负载。是一种典型的生产者-消费者模型。

2. 为什么使用消息中间件

​ 在项目中,可将一些无需即时返回且耗时的操作提取出来,进行异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高系统吞吐量

3. MQ常用应用场景

1. 任务异步处理

​ 将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。

2. 应用程序解耦合

​ MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。

3. 削峰填谷

​ 如订单系统,在下单的时候就会往数据库写数据。但是数据库只能支撑每秒1000左右的并发写入,并发量再高就容易宕机。低峰期的时候并发也就100多个,但是在高峰期时候,并发量会突然激增到5000以上,这个时候数据库肯定卡死了。此时,我们可以采用MQ来进行削峰填谷,具体就是:
​ 由MQ来将消息存储起来,服务器根据自己的处理能力慢慢处理,这样,服务器就不会因为突发的高并发访问而挂掉了。削峰:高并发时期的处理请求被保存,没有立即执行。填谷:高并发后,服务器慢慢处理MQ保存的请求。

  • 使用MQ前

RabbitMQ基础篇_第1张图片

  • 使用MQ后

RabbitMQ基础篇_第2张图片
削峰填谷图解
RabbitMQ基础篇_第3张图片

4. AMQP 和 JMS

​ MQ是消息通信的模型;实现MQ的大致有两种主流方式:AMQP、JMS。

1. AMQP

​ AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。

2. JMS

​ JMS即Java消息服务(JavaMessage Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。

3. AMQP 和 JMS 的区别

  • JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
  • JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
  • JMS规定了两种消息模式;而AMQP的消息模式更加丰富

5. 常见的MQ产品

目前市场上常见的消息队列产品如下:

  • ActiveMQ:基于JMS
  • ZeroMQ:基于C语言开发
  • RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好
  • RocketMQ:基于JMS,阿里巴巴产品
  • Kafka:类似MQ的产品;分布式消息系统,高吞吐量

6. RabbitMQ

​ RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。

RabbitMQ官方地址

RabbitMQ提供了6种模式:

  • 基本消息模型
  • work工作模式
  • publish/subscribe(发布/订阅模式,也称 广播模式)
  • routing(路由模式)
  • Topics(通配符模式)
  • RPC远程调用模式(暂不作记录)

官网对应模式介绍

二、安装及配置RabbitMQ

参考博客

三、RabbitMQ五种工作模式

入门项目搭建

1. 创建项目

​ 分别创建两个项目,一个作为生产者,一个作为消费者。项目结构如下:

RabbitMQ基础篇_第4张图片

2. 导入依赖

在两个项目中分别导入以下依赖

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

3. 编写获取连接工具类

两个工程都需要这个工具类,复制一份即可

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

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


/**
 * @author 叶子
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.producer.util
 * @Data 2020/11/19 星期四 14:49
 */
public class ConnectionUtil {
     
    /**
     * RabbitMQ主机地址
     */
    private static final String HOSt = "Linux主机地址";
    /**
     * 端口
     */
    private static final Integer PORT = 5672;
    /**
     * 虚拟机
     * 一个RabbitMQ服务可以设置多个虚拟机
     */
    private static final String VIRTUALHOST = "/test";
    /**
     * 用户名
     */
    private static final String USERNAME = "叶子";
    /**
     * 密码
     */
    private static final String PASSWORD = "yezi";


    /**
     * 创建连接
     * @return
     * @throws Exception
     */
    public static Connection getConnection(){
     
        ConnectionFactory factory = new ConnectionFactory();

        factory.setHost(HOSt);
        factory.setPort(PORT);
        factory.setVirtualHost(VIRTUALHOST);
        factory.setUsername(USERNAME);
        factory.setPassword(PASSWORD);
        Connection connection = null;
        try {
     
            connection = factory.newConnection();
        } catch (IOException e) {
     
            e.printStackTrace();
        } catch (TimeoutException e) {
     
            e.printStackTrace();
        }

        return connection;
    }
}

注意导包是否正确!!!

1. 基本消息模型(简单模式)

1. 官网图解

RabbitMQ基础篇_第5张图片

  • P :生产者
  • 红格:消息队列
  • C :消费者

2. 处理流程

  1. 生产者向消息队列发送消息
  2. 消费者通过监听指定消息队列获取消息

3. 代码编写

1. 生产者

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import icu.yezi.rabbitmq.producer.util.ConnectionUtil;


/**
 * 演示基本消息模型
 * 案例:
 *      叶子给A用户发了一条私聊消息
 *
 *  基本消息模型:
 *      组成:
 *          1. 一个生产者
 *          2. 一个消费者
 *          3. 一个消息队列
 *      生产者 和 消费者 通过消息队列进行消息处理
 *      这个模型实际上使用了默认的交换机
 *
 * 这里使用的是部署在阿里云上的RabbitMQ服务,所以记得开放使用到的端口!!!
 *  启动这个类后可以在RabbitMQ控制面板上看到一个新的消息队列
 *
 * @author 叶子
 * @Description 基本消息模型-生产者
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.producer.messageModel.basic
 * @Data 2020/11/19 星期四 15:02
 */
public class BasicProducer {
     
    private static final String QUEUE_NAME = "basic_test";
    private static final String MESSAGE = "Hello,RabbitMQ!";

    public static void main(String[] args) throws Exception {
     
        // ① 获取连接
        Connection conn = ConnectionUtil.getConnection();
        // ② 创建连接通道,只有使用通道才能完成消息相关操作
        Channel channel = conn.createChannel();
        /**
         * ③ 声明消息队列,假如没有这个消息队列,就会自动创建
         * queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments)
         * 参数说明:
         *      1. queue 队列名称
         *      2. durable 是否持久化(关闭后消息不丢失)
         *      3. exclusive 是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列则自动删除,如果将此参数设置true可用于临时队列的创建
         *      4. autoDelete 是否自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除)
         *      5. arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间
         */
        channel.queueDeclare(QUEUE_NAME,false,false,true,null);

        /**
         * ④ 向指定消息队列中发送消息
         * basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
         * 参数说明:
         *      1. exchange 指定交换机名称,如果不指定将使用mq的默认交换机(需要设置为"")
         *      2. routingKey 指定路由key,交换机根据路由key来将消息转发到指定的队列,如果使用默认交换机,routingKey设置为队列的名称
         *      3. mandatory
         *      4. immediate
         *      5. props 消息属性
         *      6. body 要发送的消息
         *
         * 注意:
         *  1. 这里使用的是basicPublish方法的重载形式,只需要参数 1 2 5 6
         *  2. 使用默认交换机,所以交换机名称设置为 ""
         *  3. 使用默认交换机,所以路由key设置为队列名称
         */
        channel.basicPublish("",QUEUE_NAME,null,MESSAGE.getBytes());

        // ⑤ 关闭连接
        channel.close();
        conn.close();
    }
}

2. 消费者

import com.rabbitmq.client.*;
import icu.yezi.rabbitmq.consumer.util.ConnectionUtil;

import java.io.IOException;

/**
 * 启动这个类后,可以在RabbitMQ控制台的Channels项下看到一个新的Channel
 *
 * @author 叶子
 * @Description 基本消息模型-消费者
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.consumer.messageModel.basic
 * @Data 2020/11/19 星期四 15:51
 */
public class BasicConsumer {
     
    private static final String QUEUE_NAME = "basic_test";

    public static void main(String[] args) throws Exception {
     
        // ① 获取连接
        Connection conn = ConnectionUtil.getConnection();

        // ② 创建连接通道,没有通道就无法进行消息处理
        Channel channel = conn.createChannel();

        /**
         * ③ 声明消息队列
         *      假如没有指定消息队列,就会自动创建
         *      如果生产者已经创建了指定队列,就可以不在此处进行声明
         * 此处不再进行声明
         *
         * 注意:
         *  如果在生产者中声明了队列,而在消费者中没有声明队列
         *  那么,不要先启动消费者,否则会因为不存在指定队列而抛出异常
         */

        // ④ 重写处理消息的方法(消费方法)
        Consumer consumer = new DefaultConsumer(channel){
     
            /**
             * 消费方法
             * @param consumerTag 消费者标签,用来标识消费者
             * @param envelope 信封 封装了DeliveryTag、exchange(交换机)、RoutingKey(路由key) 三个信息
             * @param properties 消息属性,这里生产者没有进行设置,所以很多为null
             * @param body 接收的消息
             * @throws IOException
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
     
                System.out.println("consumerTag--->"+consumerTag);
                System.out.println("DeliveryTag--->"+envelope.getDeliveryTag());
                // 交换机
                System.out.println("exchange------>"+envelope.getExchange());
                //路由key
                System.out.println("RoutingKey---->"+envelope.getRoutingKey());
                System.out.println("properties---->"+properties);
                System.out.println("接受的消息是--->"+new String(body));
            }
        };

        /**
         * ⑤ 设置监听队列
         * 参数明细:
         * 1、queue 队列名称
         * 2、autoAck 自动回复,当消费者接收到消息后要告诉mq消息已接收,如果将此参数设置为tru表示会自动回复mq,如果设置为false要通过编程实现回复
         * 3、callback,消费方法,当消费者接收到消息要执行的方法
         */
        channel.basicConsume(QUEUE_NAME,true,consumer);

        /**
         * 注意:
         *  消费者要一直监听生产者的动作,所以在此不要关闭连接!
         *  不要关闭连接!
         */
    }
}

2. Work工作模式

1. 官网图解

RabbitMQ基础篇_第6张图片

2. 处理流程

  1. 生产者向消息队列发送消息
  2. 多个消费者通过监听同一个指定的消息队列获取消息

注意:这些消费者之间是竞争关系!

3. 应用

可以通过创建多个消费者的方式提高消息处理效率。

4. 代码编写

1. 生产者
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import icu.yezi.rabbitmq.producer.util.ConnectionUtil;

/**
 * 演示Work工作模式
 *  案例:
 *      叶子在游戏上发布了一系列任务,任务完成数量越多,奖励越多
 *
 * Work工作模式
 *  组成:一个生产者 + 多个消费者 + 一个消息队列
 *  多个消费者共同竞争一个消息队列中待处理的消息,提高了处理效率
 *
 * @author 叶子
 * @Description Work模式-生产者
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.producer.messageModel.work
 * @Data 2020/11/19 星期四 17:24
 */
public class WorkProducer {
     
    private static final String QUEUE_NAME = "work_test";

    public static void main(String[] args) throws Exception {
     
        // ① 获取连接
        Connection conn = ConnectionUtil.getConnection();
        // ② 创建通道
        Channel channel = conn.createChannel();
        // ③ 声明消息队列
        channel.queueDeclare(QUEUE_NAME,false,false,true,null);
        // ④ 发送多条消息
        for (int i = 1; i <= 20; i++) {
     
            String message = "第" + i + "条消息";
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
        }

        // ⑤ 关闭连接
        channel.close();
        conn.close();
    }
}
2. 两个消费者

①号消费者

import com.rabbitmq.client.*;
import icu.yezi.rabbitmq.consumer.util.ConnectionUtil;

import java.io.IOException;

/**
 * @author 叶子
 * @Description Work模式-①号消费者
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.consumer.messageModel.work
 * @Data 2020/11/19 星期四 17:34
 */
public class WorkConsumer1 {
     
    private static final String QUEUE_NAME = "work_test";

    public static void main(String[] args) throws Exception {
     
        // ① 获取连接
        Connection conn = ConnectionUtil.getConnection();
        // ② 创建通道
        Channel channel = conn.createChannel();
        // ③ 声明消息队列

        // ④ 重写消费消息的方法
        Consumer consumer = new DefaultConsumer(channel){
     
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
     
                System.out.println("一号消费者 处理了" + new String(body));
            }
        };
        // ⑤ 设置监听队列
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }
}

②号消费者

import com.rabbitmq.client.*;
import icu.yezi.rabbitmq.consumer.util.ConnectionUtil;

import java.io.IOException;

/**
 * @author 叶子
 * @Description Work模式-②号消费者
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.consumer.messageModel.work
 * @Data 2020/11/19 星期四 17:39
 */
public class WorkConsumer2 {
     
    private static final String QUEUE_NAME = "work_test";

    public static void main(String[] args) throws Exception {
     
        // ① 获取连接
        Connection conn = ConnectionUtil.getConnection();
        // ② 创建通道
        Channel channel = conn.createChannel();
        // ③ 声明消息队列

        // ④ 重写消费消息的方法
        Consumer consumer = new DefaultConsumer(channel){
     
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
     
                System.out.println("二号消费者 处理了" + new String(body));
            }
        };
        // ⑤ 设置监听队列
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }
}

3. Publish/Subscribe(发布/订阅模式,也称 广播模式)

1. 官网图解

RabbitMQ基础篇_第7张图片

2. 处理流程

  1. 创建交换机 + 声明队列

  2. 交换机绑定队列 (泛式绑定,没有指定路由key)

  3. 生产者发送消息给交换机,交换机将消息转发给所有绑定的消息队列

  4. 消费者通过监听消息队列获取消息

    ​ 因为在这里消息会被同等转发给所有消息队列,不存在差异(即部分消息队列可以收到消息,部分收不到),类似于广播,所以被称为广播/订阅模式

3. 代码编写

1. 生产者

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import icu.yezi.rabbitmq.producer.util.ConnectionUtil;

/**
 * 演示发布/订阅模式(广播模式)
 *  案例:
 *      叶子的个人博客更新时,所有订阅用户都会收到推送
 * 发布/订阅 模式
 * 组成:
 *      1. 一个交换机,类型为 fanout
 *      2. 多个消息队列 ,和交换机进行绑定(routingKey 设置为 "")
 *      3. 多个消费者
 * 信息发送给交换机,交换机将信息转发给所有绑定的消息队列,所有 "订阅" 绑定消息队列的消费者都能接收到消息
 * 注意:
 *      1. 这里的 消息队列和交换机 之间属于任意绑定模式,不存在路由分发,所以所有绑定的消息队列可以接收到相同消息
 *      2. 路由模式采用路由分发的方式实现:不同的信息交给不同的消息队列
 *
 * 尚不了解的问题:
 *      1. 订阅者处理消息是否有先后顺序,能否同时处理同一个消息?
 * @author 叶子
 * @Description 发布/订阅模式 - 生产者
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.producer.messageModel.publish_subscribe
 * @Data 2020/11/19 星期四 18:23
 */
public class PublishProducer {
     
    private static final String EXCHANGE_NAME = "exchange_fanout";
    private static final String QUEUE_NAME_1 = "fanout_test_1";
    private static final String QUEUE_NAME_2 = "fanout_test_2";
    private static final String MESSAGE = "叶子博客更新了!";

    public static void main(String[] args) throws Exception {
     
        // ① 获取连接
        Connection conn = ConnectionUtil.getConnection();

        // ② 创建通道
        Channel channel = conn.createChannel();

        /**
         * ③ 声明exchange(交换机),指定类型为fanout
         * exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map arguments)
         * 参数说明:
         *      1. exchange 交换机名称
         *      2. type 交换机类型
         *      3. durable 是否持久化
         *      4. autoDelete 是否自动删除
         *      5. internal
         *      6. arguments 额外指定参数
         */
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

        // ④ 声明队列
        channel.queueDeclare(QUEUE_NAME_1,false,false,true,null);
        channel.queueDeclare(QUEUE_NAME_2,false,false,true,null);

        /**
         * ⑤ 队列绑定交换机
         * queueBind(String queue, String exchange, String routingKey, Map arguments)
         * 参数说明:
         *      1. queue 队列名称
         *      2. exchange 交换机名称
         *      3. routingKey 路由key
         *          如果交换机类型为: fanout ,则routingKey设置为 ""
         *      4. arguments 参数
         */
        channel.queueBind(QUEUE_NAME_1,EXCHANGE_NAME,"");
        channel.queueBind(QUEUE_NAME_2,EXCHANGE_NAME,"");

        /**
         *  发布消息到exchange
         * 这里交换机会将
         */
        channel.basicPublish(EXCHANGE_NAME,"",null,MESSAGE.getBytes());
        // ⑥ 关闭连接
        channel.close();
        conn.close();
    }
}

2. 两个消费者

①号消费者

import com.rabbitmq.client.*;
import icu.yezi.rabbitmq.consumer.util.ConnectionUtil;

import java.io.IOException;

/**
 * @author 叶子
 * @Description 发布/订阅模式 - ①号消费者
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.consumer.messageModel.publish_subscribe
 * @Data 2020/11/19 星期四 18:53
 */
public class SubscribeConsumer1 {
     
    private static final String QUEUE_NAME_1 = "fanout_test_1";

    public static void main(String[] args) throws Exception {
     
        // ① 获取连接
        Connection conn = ConnectionUtil.getConnection();
        // ② 创建通道
        Channel channel = conn.createChannel();
        // ③ 声明消息队列

        // ④ 重写消费消息的方法
        Consumer consumer = new DefaultConsumer(channel){
     
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
     
                System.out.println("一号消费者 处理了" + new String(body));
            }
        };
        // ⑤ 设置监听队列
        channel.basicConsume(QUEUE_NAME_1,true,consumer);
    }
}

②号消费者

import com.rabbitmq.client.*;
import icu.yezi.rabbitmq.consumer.util.ConnectionUtil;

import java.io.IOException;

/**
 * @author 叶子
 * @Description 发布/订阅模式 - ②号消费者
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.consumer.messageModel.publish_subscribe
 * @Data 2020/11/19 星期四 18:53
 */
public class SubscribeConsumer2 {
     
    private static final String QUEUE_NAME_2 = "fanout_test_2";

    public static void main(String[] args) throws Exception {
     
        // ① 获取连接
        Connection conn = ConnectionUtil.getConnection();
        // ② 创建通道
        Channel channel = conn.createChannel();
        // ③ 声明消息队列

        // ④ 重写消费消息的方法
        Consumer consumer = new DefaultConsumer(channel){
     
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
     
                System.out.println("二号消费者 处理了" + new String(body));
            }
        };
        // ⑤ 设置监听队列
        channel.basicConsume(QUEUE_NAME_2,true,consumer);
    }
}

4. Routing(路由模式)

1. 官网图解

RabbitMQ基础篇_第8张图片

2. 处理流程

  1. 创建交换机 + 消息队列
  2. 交换机通过不同的 routingkey 绑定到不同的消息队列(路由key)
  3. 生产者将消息发送给交换机,指定 routingkey
  4. 交换机通过 routingkey 确定将消息发送给哪些消息队列
  5. 不同消费者通过监听不同消息队列可能收到不同的消息

核心:依据 routingkey 实现了消息的选择性发送

3. 代码编写

本案例实现原理图解

RabbitMQ基础篇_第9张图片

1. 生产者

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import icu.yezi.rabbitmq.producer.util.ConnectionUtil;

/**
 * 演示路由模式:
 *  案例:
 *      叶子开辟了个人的付费专栏,该专栏发布的博客只有VIP用户可以查看
 *      免费博客所有人都可以查看
 *
 * 路由模式:
 *  组成:
 *      1. 一个生产者
 *      2. 一个交换机
 *      3. 多个消息队列 (通过不同的routingKey绑定到交换机上)
 *      4. 多个消费者
 *  流程分析:
 *      1. 生产者发送消息时指定交换机+routingKey
 *      2. 交换机根据 routingKey 将消息发送到对应的消息队列
 *      3. 消费者接收绑定消息队列的消息
 *  核心:根据 routingKey 实现路由分发
 *      消息指定的 routingKey 和消息队列绑定的 routingKey 匹配时,该消息队列才能收到该消息
 *
 * @author 叶子
 * @Description 路由模式 - 生产者
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.producer.messageModel.routing
 * @Data 2020/11/19 星期四 19:28
 */
public class RoutingProducer {
     
    private static final String EXCHANGE_NAME = "exchange_direct";

    private static final String QUEUE_NAME_USER = "direct_user";
    private static final String QUEUE_NAME_USER_VIP = "direct_user_vip";

    private static final String ROUTING_KEY_USER = "user";
    private static final String ROUTING_KEY_USER_VIP = "user_vip";

    private static final String MESSAGE_USER = "叶子的免费博客!";
    private static final String MESSAGE_USER_VIP = "叶子的vip付费专栏博客!";

    public static void main(String[] args) throws Exception {
     
        // ① 获取连接
        Connection conn = ConnectionUtil.getConnection();

        // ② 创建通道
        Channel channel = conn.createChannel();

        /**
         * ③ 声明exchange(交换机),指定类型为direct
         * exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map arguments)
         * 参数说明:
         *      1. exchange 交换机名称
         *      2. type 交换机类型
         *      3. durable 是否持久化
         *      4. autoDelete 是否自动删除
         *      5. internal
         *      6. arguments 额外指定参数
         */
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        // ④ 声明队列
        channel.queueDeclare(QUEUE_NAME_USER,false,false,true,null);
        channel.queueDeclare(QUEUE_NAME_USER_VIP,false,false,true,null);

        /**
         * ⑤ 队列绑定交换机
         * queueBind(String queue, String exchange, String routingKey, Map arguments)
         * 参数说明:
         *      1. queue 队列名称
         *      2. exchange 交换机名称
         *      3. routingKey 路由key
         *          如果交换机类型为: fanout ,则routingKey设置为 ""
         *      4. arguments 参数
         *
         * 注意:对这里的处理逻辑做下简单的解释
         *  MESSAGE_USER 信息发送给了 路由 ROUTING_KEY_USER,
         *  而路由 ROUTING_KEY_USER 同时绑定了一般用户和VIP用户两个消息队列,所以他们都能收到消息
         *  即实现:普通用户+VIP用户 都可以阅读 免费博客!
         *
         *  MESSAGE_USER_VIP 信息发送给了路由 ROUTING_KEY_USER_VIP,
         *  而路由 ROUTING_KEY_USER_VIP 只绑定了 VIP用户的消息队列
         *  所以实现了:只有VIP用户可以阅读付费专栏博客!
         */
        channel.queueBind(QUEUE_NAME_USER,EXCHANGE_NAME,ROUTING_KEY_USER);
        channel.queueBind(QUEUE_NAME_USER_VIP,EXCHANGE_NAME,ROUTING_KEY_USER);

        channel.queueBind(QUEUE_NAME_USER_VIP,EXCHANGE_NAME,ROUTING_KEY_USER_VIP);

        /**
         *  发布消息到exchange
         * 这里交换机会将
         */
        channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY_USER,null,MESSAGE_USER.getBytes());
        channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY_USER_VIP,null,MESSAGE_USER_VIP.getBytes());
        // ⑥ 关闭连接
        channel.close();
        conn.close();
    }

}

1. 两个消费者

①号消费者

import com.rabbitmq.client.*;
import icu.yezi.rabbitmq.consumer.util.ConnectionUtil;

import java.io.IOException;

/**
 * @author 叶子
 * @Description 路由模式 - ①号消费者(普通用户)
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.consumer.messageModel.routing
 * @Data 2020/11/19 星期四 19:39
 */
public class RoutingConsumerUser {
     
    private static final String QUEUE_NAME_USER = "direct_user";

    public static void main(String[] args) throws Exception {
     
        // ① 获取连接
        Connection conn = ConnectionUtil.getConnection();
        // ② 创建通道
        Channel channel = conn.createChannel();
        // ③ 声明消息队列

        // ④ 重写消费消息的方法
        Consumer consumer = new DefaultConsumer(channel){
     
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
     
                System.out.println("普通用户阅读了" + new String(body));
            }
        };
        // ⑤ 设置监听队列
        channel.basicConsume(QUEUE_NAME_USER,true,consumer);
    }
}

②号消费者

import com.rabbitmq.client.*;
import icu.yezi.rabbitmq.consumer.util.ConnectionUtil;

import java.io.IOException;

/**
 * @author 叶子
 * @Description 路由模式 - ②号消费者(VIP用户)
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.consumer.messageModel.routing
 * @Data 2020/11/19 星期四 19:39
 */
public class RoutingConsumerUserVip {
     
    private static final String QUEUE_NAME_USER_VIP = "direct_user_vip";

    public static void main(String[] args) throws Exception {
     
        // ① 获取连接
        Connection conn = ConnectionUtil.getConnection();
        // ② 创建通道
        Channel channel = conn.createChannel();
        // ③ 声明消息队列

        // ④ 重写消费消息的方法
        Consumer consumer = new DefaultConsumer(channel){
     
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
     
                System.out.println("VIP用户阅读了" + new String(body));
            }
        };
        // ⑤ 设置监听队列
        channel.basicConsume(QUEUE_NAME_USER_VIP,true,consumer);
    }
}

5. topic(通配符模式)

1. 官网图解

RabbitMQ基础篇_第10张图片

2. 处理流程

topic的处理流程和routing的处理流程其实差不多,只是topic的 routingkey 使用了更强大、更灵活的通配符

  • * :恰好匹配一个单词
  • # :匹配一个或多个单词

例如:*.user 可以匹配 admin.user 但不能匹配 vip.admin.user。 而 #.user 就可以匹配。

  1. 创建交换机 + 消息队列
  2. 交换机通过不同的 routingkey 绑定到不同的消息队列(路由key)
  3. 生产者将消息发送给交换机,指定 routingkey
  4. 交换机通过 routingkey 确定将消息发送给哪些消息队列
  5. 不同消费者通过监听不同消息队列可能收到不同的消息

3. 代码编写

1. 生产者

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import icu.yezi.rabbitmq.producer.util.ConnectionUtil;

/**
 * 演示通配符模式
 * 实现routing模式的案例
 *
 * 其实通配符和路由模式都是基于路由分发实现的,只是通配符模式引入了两个通配符
 * 使用起来更加方便,功能更强大
 *
 * @author 叶子
 * @Description 通配符匹配模式 - 生产者
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.producer.messageModel.topic
 * @Data 2020/11/19 星期四 20:12
 */
public class TopicProducer {
     
    private static final String EXCHANGE_NAME = "topic.direct";

    private static final String QUEUE_NAME_USER = "topic.user";
    private static final String QUEUE_NAME_USER_VIP = "topic.user.vip";

    private static final String ROUTING_KEY_USER = "*.user";    //可以匹配到普通用户和VIP用户
    private static final String ROUTING_KEY_USER_VIP = "#.vip"; //只能匹配到VIP用户

    private static final String MESSAGE_USER = "叶子的免费博客!";
    private static final String MESSAGE_USER_VIP = "叶子的vip付费专栏博客!";

    public static void main(String[] args) throws Exception {
     
        // ① 获取连接
        Connection conn = ConnectionUtil.getConnection();

        // ② 创建通道
        Channel channel = conn.createChannel();

        // ③ 声明exchange(交换机),指定类型为topic
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

        // ④ 声明队列
        channel.queueDeclare(QUEUE_NAME_USER,false,false,true,null);
        channel.queueDeclare(QUEUE_NAME_USER_VIP,false,false,true,null);

        /**
         * ⑤ 队列绑定交换机
         *
         * 注意:对这里的处理逻辑做下简单的解释
         *  MESSAGE_USER 信息发送给了 路由 ROUTING_KEY_USER,
         *  而路由 ROUTING_KEY_USER 同时绑定了一般用户和VIP用户两个消息队列,所以他们都能收到消息
         *  即实现:普通用户+VIP用户 都可以阅读 免费博客!
         *
         *  MESSAGE_USER_VIP 信息发送给了路由 ROUTING_KEY_USER_VIP,
         *  而路由 ROUTING_KEY_USER_VIP 只绑定了 VIP用户的消息队列
         *  所以实现了:只有VIP用户可以阅读付费专栏博客!
         *
         *  * 恰好匹配一个单词
         *  # 匹配一个及以上的单词
         */
        channel.queueBind(QUEUE_NAME_USER,EXCHANGE_NAME,ROUTING_KEY_USER);
        channel.queueBind(QUEUE_NAME_USER_VIP,EXCHANGE_NAME,ROUTING_KEY_USER);

        channel.queueBind(QUEUE_NAME_USER_VIP,EXCHANGE_NAME,ROUTING_KEY_USER_VIP);

        /**
         *  发布消息到exchange
         * 这里交换机会将
         */
        channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY_USER,null,MESSAGE_USER.getBytes());
        channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY_USER_VIP,null,MESSAGE_USER_VIP.getBytes());
        // ⑥ 关闭连接
        channel.close();
        conn.close();
    }
}

2. 两个消费者

①号消费者

import com.rabbitmq.client.*;
import icu.yezi.rabbitmq.consumer.util.ConnectionUtil;

import java.io.IOException;

/**
 * @author 叶子
 * @Description 通配符匹配模式 - ①号消费者(普通用户)
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.consumer.messageModel.routing
 * @Data 2020/11/19 星期四 19:39
 */
public class TopicConsumerUser {
     
    private static final String QUEUE_NAME_USER = "topic.user";

    public static void main(String[] args) throws Exception {
     
        // ① 获取连接
        Connection conn = ConnectionUtil.getConnection();
        // ② 创建通道
        Channel channel = conn.createChannel();
        // ③ 声明消息队列

        // ④ 重写消费消息的方法
        Consumer consumer = new DefaultConsumer(channel){
     
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
     
                System.out.println("普通用户阅读了" + new String(body));
            }
        };
        // ⑤ 设置监听队列
        channel.basicConsume(QUEUE_NAME_USER,true,consumer);
    }
}

②号消费者

import com.rabbitmq.client.*;
import icu.yezi.rabbitmq.consumer.util.ConnectionUtil;

import java.io.IOException;

/**
 * @author 叶子
 * @Description 通配符匹配模式 - ②号消费者(VIP用户)
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.rabbitmq.consumer.messageModel.routing
 * @Data 2020/11/19 星期四 19:39
 */
public class TopicConsumerUserVip {
     
    private static final String QUEUE_NAME_USER_VIP = "topic.user.vip";

    public static void main(String[] args) throws Exception {
     
        // ① 获取连接
        Connection conn = ConnectionUtil.getConnection();
        // ② 创建通道
        Channel channel = conn.createChannel();
        // ③ 声明消息队列

        // ④ 重写消费消息的方法
        Consumer consumer = new DefaultConsumer(channel){
     
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
     
                System.out.println("VIP用户阅读了" + new String(body));
            }
        };
        // ⑤ 设置监听队列
        channel.basicConsume(QUEUE_NAME_USER_VIP,true,consumer);
    }
}

6. 工作模式总结

1. 分类

其实,依据前面的介绍,我们可以将5种工作模式大致分为两类:

  • 非路由模式

    1. 基本消息模型
    2. work工作模式
  • 路由模式

    1. Publish/subscribe
    2. routing
    3. topic

2. 非路由模式和路由模式的区别

  1. 非路由模式不使用交换机,直接向消息队列发送消息(其实内部使用了默认的交换机)。而路由模式使用交换机,信息发送给交换机,由交换机负责转发给消息队列
  2. 非路由模式会将消息发给所有绑定的监听队列,而路由模式会由交换机根据rongtingkey来确定将消息发送给哪个消息队列。即:就消息发送而言路由模式具有选择差异性(Publish/subscribe除外,它使用了默认的rongtingkey,也是将消息推送给所有绑定的消息队列)
  3. 路由模式由于使用了rongtingkey分发模式而更加灵活

四、SpringBoot整合RabbitMQ

1. 项目搭建

分别创建两个Spring Boot项目,一个作为生产者,一个作为消费者
RabbitMQ基础篇_第11张图片

导入依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-amqpartifactId>
dependency>

2. 编写生产者代码

yml文件配置

spring:
  rabbitmq:
    host: 主机IP地址
    port: 5672
    username: 叶子
    password: 密码
    virtual-host: /test

RabbitMQ配置类

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

/**
 * @author 叶子
 * @Description RabbitMQ - 生产者配置类
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.producer.conig
 * @Data 2020/11/20 星期五 16:07
 */
@Configuration
public class RabbitMQConfig {
     
    public static final String EXCHANGE = "boot.exchange";
    private static final String QUEUE = "boot.queue";

    @Bean(name = EXCHANGE)
    public Exchange exchange(){
     
        return ExchangeBuilder.topicExchange(EXCHANGE).durable(false).autoDelete().build();
    }

    @Bean(name = QUEUE)
    public Queue queue(){
     
        return QueueBuilder.durable(QUEUE).build();
    }

    @Bean
    public Binding binding(@Qualifier(EXCHANGE) Exchange exchange, @Qualifier(QUEUE) Queue queue){
     
        return BindingBuilder.bind(queue).to(exchange).with("vip.*").noargs();
    }

}

在测试类中进行测试

@SpringBootTest
class ProducerApplicationTests {
     
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    void contextLoads() {
     
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE,"vip.user","Hello,SpringBoot and RabbitMQ!");
    }

}

3. 编写消费者代码

yml文件配置和生产者一样

编写消息队列监听类

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * @author 叶子
 * @Description RabbitMQ - 消息队列监听类
 * @DevelopmentTools IntelliJ IDEA
 * @PackageName icu.yezi.consumer.listener
 * @Data 2020/11/20 星期五 16:20
 */
@Component
public class RabbitMQListener {
     

    @RabbitListener(queues = "boot.queue")
    public void listenerQueue(Message message){
     
        System.out.println(new String(message.getBody()));
    }
}

注意:消费者可以不添加配置类

4. 总结

  • 生产者
  1. 配置yml文件
  2. 编写配置类
  3. 编写测试类
  • 消费者
  1. 配置yml文件
  2. 编写消息队列监听类

入门学习笔记,如有错误,请于评论区指出,非常感谢!

你可能感兴趣的:(#,RabbitMQ,rabbitmq,spring,java)