RabbitMQ初步了解

前言

以前常听别人说消息队列,MQ,AMQP,也不知道它们是什么,直到现在接触到了RabbitMQ,才有了一个初步的了解,这里记录一下基本使用。

1、MQ

有必要先了解一下什么是消息队列?

MQ全称Message Queue,中文称为消息队列。消息队列(MQ),是一种应用程序对应用程序的通信方法。应用程序通过写和检索出入列队的针对应用程序的数据(消息)来通信,而无需专用连接来链接它们。

消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。

BM WebSphere MQ 支持两种不同的应用程序编程接口:Java 消息服务(JMS)和消息队列接口(MQI)。在 IBM WebSphere MQ 服务器上,JMS 绑定方式被映射到 MQI。应用程序直接与其本地队列管理器通过使用 MQI 进行对话,MQI 是一组要求队列管理器提供服务的调用。MQI 的引人之处是它只提供 13 次调用。这意味着对于应用程序编程员它是一种非常易于使用的接口,因为大部分艰苦工作都将透明完成的。

重点的一句话是:MQ是一种应用程序对应用程序的通信方法。

2、AMQP

了解什么是MQ之后再来了解一下AMQP:

AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。
AMQP是高级消息队列协议。

(1)为什么使用AMQP

我们的目标是实现一种在全行业广泛使用的标准消息中间件技术,以便降低企业和系统集成的开销,并且向大众提供工业级的集成服务。

我们的宗旨是通过AMQP,让消息中间件的能力最终被网络本身所具有,并且通过消息中间件的广泛使用发展出一系列有用的应用程序。

(2)AMQP模型

我们需要明确的定义服务器的语义,因为所有服务器实现都应该保持这些语义的一致性,否则就无法进行互操作。

因此AMQP模型描述了一套模块化的组件以及这些组件之间进行连接的标准规则。
在服务器中,三个主要功能模块连接成一个处理链完成预期的功能:

exchange接收发布应用程序发送的消息,并根据一定的规则将这些消息路由到"消息队列"。

message queue存储消息,直到这些消息被消费者安全处理完为止。

binding定义了exchange和message queue之间的关联,提供路由规则。

使用这个模型我们可以很容易的模拟出存储转发队列和主题订阅这些典型的消息中间件概念。

一个AMQP服务器类似于邮件服务器,exchange类似于消息传输代理(email里的概念),message queue类似于邮箱。Binding定义了每一个传输代理中的消息路由表,发布者将消息发给特定的传输代理,然后传输代理将这些消息路由到邮箱中,消费者从这些邮箱中取出消息。

在以前的中间件系统的应用场景中,发布者直接将消息发送给邮箱或者邮件列表。

区别就在于用户可以控制message queue与exchage的连接规则,这可以做很多有趣的事情,比如定义一条规则:“将所有包含这样这样的消息头的消息都复制一份再发送到消息队列中”。

小结: AMQP是一套公开的消息队列协议,最早在2003年被提出,它旨在从协议层定义消息通信数据的标准格式,为的就是解决MQ市场上协议不统一的问题。

3、JMS

Java中相关的便是JMS:

JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。

JMS是一种与厂商无关的 API,用来访问收发系统消息,它类似于JDBC(Java Database Connectivity)。这里,JDBC 是可以用来访问许多不同关系数据库的 API,而 JMS 则提供同样与厂商无关的访问方法,以访问消息收发服务。许多厂商都支持 JMS,包括 IBM 的 MQSeries、BEA的 Weblogic JMS service和 Progress 的 SonicMQ。 JMS 使您能够通过消息收发服务(有时称为消息中介程序或路由器)从一个 JMS 客户机向另一个 JMS客户机发送消息。消息是 JMS 中的一种类型对象,由两部分组成:报头和消息主体。报头由路由信息以及有关该消息的元数据组成。消息主体则携带着应用程序的数据或有效负载。根据有效负载的类型来划分,可以将消息分为几种类型,它们分别携带:简单文本(TextMessage)、可序列化的对象 (ObjectMessage)、属性集合 (MapMessage)、字节流 (BytesMessage)、原始值流 (StreamMessage),还有无有效负载的消息 (Message)。

小结:JMS是java提供的一套消息服务API标准,其目的是为所有的java应用程序提供统一的消息通信的标准,类似java的jdbc,只要遵循jms标准的应用程序之间都可以进行消息通信。

那么AMQP和JMS的区别是什么呢?

JMS是java语言专属的消息服务标准,它是在api层定义标准,并且只能用于java应用;而AMQP是在协议层定义的标准,是跨语言的 。

4、RabbitMQ

了解以上几个概念之后,可以开始了解RabbitMQ了。

(1)RabbitMQ介绍

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

(2)开发中MQ的应用场景

先来了解MQ的使用场景:

  • 任务异步处理
    将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
  • 应用程序解耦合
    MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。

(3)市场上的MQ产品

ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ、Redis。

(4)为什么使用RabbitMQ

市场上的MQ产品那么多,为什么使用RabbitMQ呢?来看看它的优点:

  • 使得简单,功能强大。
  • 基于AMQP协议。
  • 社区活跃,文档完善。
  • 高并发性能好,这主要得益于Erlang语言。
  • Spring Boot默认已集成RabbitMQ。

(5)工作原理

先来看RabbitMQ的基本结构:
在这里插入图片描述
组成部分说明:

  • Producer :消息生产者,即生产方客户端,生产方客户端将消息发送到MQ。
  • Consumer :消息消费者,即消费方客户端,接收MQ转发的消息。
  • Broker :消息队列服务进程,此进程包括两个部分:Exchange和Queue。
  • Exchange :消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
  • Queue :消息队列,存储消息的队列,消息到达队列并转发给指定的消费方。

消息发布接收流程如下:

消息发布

  • 生产者和Broker建立TCP连接。
  • 生产者和Broker建立通道。
  • 生产者通过通道消息发送给Broker,由Exchange将消息进行转发。
  • Exchange将消息转发到指定的Queue(队列)。

接收消息

  • 消费者和Broker建立TCP连接。
  • 消费者和Broker建立通道。
  • 消费者监听指定的Queue(队列)。
  • 当有消息到达Queue时Broker默认将消息推送给消费者。
  • 消费者接收到消息。

(6)下载和安装

下面进行RabbitMQ的下载和安装。

RabbitMQ由Erlang语言开发,Erlang语言用于并发及分布式系统的开发,在电信领域应用广泛,OTP(Open Telecom Platform)作为Erlang语言的一部分,包含了很多基于Erlang开发的中间件及工具库,安装RabbitMQ需要先安装Erlang/OTP,并保持版本匹配,去erlang官网下载好与系统匹配的安装包:
在这里插入图片描述
我这是以前下载的,下载好后点击安装,安装完毕后需要配置环境变量:

在这里插入图片描述
Path路径指定到安装包的bin目录下:
在这里插入图片描述
配置完环境变量之后测试一下是否正确,cmd打开命令,输入erl:
在这里插入图片描述
出现以上界面那么erlang就安装成功了,接下来可以安装RabbitMQ了:
在这里插入图片描述
我这也是以前下载的,点击exe程序进行安装,安装之后查看服务里面:
在这里插入图片描述
RabbitMQ安装成功之后自动添加到了服务里面。

(7)启动和测试

启动RabbitMQ的服务,然后浏览器输入:http://localhost:15672。
启动成功之后可能会有延迟,如果浏览器访问不了,等一会儿再访问:
在这里插入图片描述
这是访问成功的页面,输入账号密码都是guest,然后点击Login,登录成功之后的页面:
在这里插入图片描述
Connection中会显示所有连接:
在这里插入图片描述
暂时未有连接。

channels中会显示连接的通道:
在这里插入图片描述
exchanges里面会显示所有交换机,以下是RabbitMQ自带的:
在这里插入图片描述
Queues里面显示所有创建的队列:
在这里插入图片描述
暂时没有。

admin是用户相关信息:
在这里插入图片描述
创建两个maven工程来模拟生产者和消费者:

生产者:rabbitmq-producer。
消费者:rabbitmq-consumer。

先来看生产者工程:

  • pom依赖
		<!-- amqp依赖 -->
		<dependency>
			<groupId>com.rabbitmq</groupId>
			<artifactId>amqp-client</artifactId>
			<version>5.7.3</version>
		</dependency>

		<!-- logging日志包 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-logging</artifactId>
			<version>2.3.7.RELEASE</version>
		</dependency>

		<!-- 测试包 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<version>2.3.1.RELEASE</version>
			<scope>test</scope>
		</dependency>
  • 测试

我直接在测试包下写了:
在这里插入图片描述
创建新类Producer01,具体代码如下:

package com.ycz.rabbitmq.producer;

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

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

/*
 * 消息生产者
 */
public class Producer01 {
    
    //定义队列
    private static final String QUEUE = "helloworld";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置主机、端口、账号、密码、虚拟主机
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");
        //定义连接和通道
        Connection connection = null;
        Channel channel = null;
        try {
            //通过工厂对象建立新连接
            connection = connectionFactory.newConnection();
            //基于连接创建会话通道
            //生产者和mq服务所有通信都在channel通道中完成
            channel = connection.createChannel();
            //声明队列,如果mq中没有该队列,则创建
            //第一个参数是队列名,第二个参数是否持久化,第三个参数队列是否独占此连接
            //第四个参数队列不再使用时是否删除,第五个参数是队列参数
            channel.queueDeclare(QUEUE,true,false,false,null);
            String mess = "hello,程序员!";//要发送的消息
            //消息发布
            //第一个参数表示交换机Exchange的名称,如果未指定,则使用默认的Exchange
            //第二个参数表示消息的路由key,是用于Exchange将消息转发到指定的消息队列
            //第三个参数是消息包含的属性,第四个参数是消息体
            channel.basicPublish("", QUEUE, null, mess.getBytes());
            System.out.println("发送的消息是:" + mess);
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭通道和连接
            if(channel != null) {
                channel.close();
            }
            if(connection != null) {
                connection.close();
            }
        }
    }

}

执行这个Java程序,控制台输出:
在这里插入图片描述
然后查看RabbitMQ:
在这里插入图片描述
创建了一个新的队列helloworld,点击查看Message:
在这里插入图片描述

可以看到是有1条消息存入的,消息内容也可以查看。

然后看交换机:
在这里插入图片描述
可以看到第一个AMQP默认的交换机有消息流入,那么这条消息通过交换机路由到了指定队列中,暂时存储在队列中,等待被接收,生产者发送完消息之后会自动关闭掉通道和连接。接下来在消费者工程中接收这条消息。

消费者工程:

  • pom依赖
		<!-- amqp依赖 -->
		<dependency>
			<groupId>com.rabbitmq</groupId>
			<artifactId>amqp-client</artifactId>
			<version>5.7.3</version>
		</dependency>

		<!-- logging日志包 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-logging</artifactId>
			<version>2.3.7.RELEASE</version>
		</dependency>

		<!-- 测试包 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<version>2.3.1.RELEASE</version>
			<scope>test</scope>
		</dependency>
  • 测试
    直接在测试包中写:
    在这里插入图片描述
    创建Consumer01类,具体代码如下:
package com.ycz.rabbitmq.consumer;

import java.io.IOException;

import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

/*
 * 消费者接收消息
 */
public class Consumer01 {
    
    //定义要监听的队列
    private static final String QUEUE = "helloworld";
    
    public static void main(String[] args) throws Exception {
        //工厂对象
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置主机和端口
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        //创建连接
        Connection connection = connectionFactory.newConnection();
        //创建通道
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE, true, false, false, null);
        //定义消费的方法
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {

            /*
                             * 覆盖此方法
                             * 第一个参数为消费者的标签,在channel.basicConsume()去指定
                             * 第二个参数为消息包的内容,可从中获取id消息routingkey,交换机,消息和重传标志
                             * 第三个参数为属性
                             * 第四个参数为消息体
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, 
                    BasicProperties properties, byte[] body)
                    throws IOException {
                //获取交换机
                String exchange = envelope.getExchange();
                //获取路由Key
                String routingKey = envelope.getRoutingKey();
                //获取消息ID
                long deliverTag = envelope.getDeliveryTag();
                //获取消息内容
                String mess = new String(body,"utf-8");
                System.out.println("接收到的消息为:" + mess);
            }
            
            
        };
        
        //监听队列
        //第一个参数为队列名称
        //第二个参数为是否自动回复,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动回复
        //第三个参数为消费消息的方法,消费者接收到消息后调用此方法
        channel.basicConsume(QUEUE, true,defaultConsumer);
    }

}

运行这个Java程序,控制台如下:
在这里插入图片描述
接收到了队列中的消息。再来拦RabbitMQ界面:
在这里插入图片描述

在这里插入图片描述
此时消费者通过通道连接上了。再看看Queue中的Message:
在这里插入图片描述
消息被消费了。运行这个程序后,会自动从RabbitMQ的队列中接收生产者发送的消息,且这个连接不会关闭。

简单总结一下流程:

发送端流程

  • 创建连接
  • 创建通道
  • 声明队列
  • 发送消息

接收端流程

  • 创建连接
  • 创建通道
  • 声明队列
  • 监听队列
  • 接收消息
  • ack回复

以上就完成了一个简单的发送消息和接收消息的过程,下面来更深一步的了解一下RabbitMQ。

(8)RabbitMQ的工作模式

RabbitMQ有以下几种工作模式 :

  • Work queues
  • Publish/Subscribe
  • Routing
  • Topics
  • Header
  • RPC

接下来介绍一下每一种工作模式。

a、Work queues

先来看图示:
在这里插入图片描述
work queues与上面的测试程序相比,多了一个消费端,即两个消费端共同消费同一个队列中的消息。

应用场景:对于 任务过重或任务较多情况使用工作队列可以提高任务处理的速度。

测试:
在这里插入图片描述
将这个程序启动两次,然后启动生产者工程3次,观察控制台,第一个消费者控制台:
在这里插入图片描述
第二个消费者控制台:
在这里插入图片描述
可以发现第一条消息发送给了第一个消费者,第二条消息发送给了第二个消费者,第三条消息又发送给了第一个消费者,这样轮循着发送。

结论:

  • 一条消息只会被一个消费者接收。
  • rabbit采用轮询的方式将消息是平均发送给消费者。
  • 消费者在处理完某条消息后,才会收到下一条消息。

b、Publish/subscribe

图示:
在这里插入图片描述
这种称为发布/订阅模式,特点如下:

  • 每个消费者监听自己的队列。
  • 生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。

下面用代码进行测试,现有场景如下:
当用户充值成功或转账完成系统通知用户,通知方式有短信、邮件多种方法 。

生产者:

创建一个新的类Producer02_publish,具体内容如下:

package com.ycz.rabbitmq.producer;

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

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

/*
 * 发布订阅模式
 */
public class Producer02_publish {
    
    //邮件队列
    private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
    
    //短信队列
    private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
    
    //交换机
    private static final String EXCHANGE_FANOUT_INFORM = "exchange_fanout_inform";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");
        Connection connection = null;
        Channel channel = null;
        try {
            connection = connectionFactory.newConnection();
            channel = connection.createChannel();
            //声明队列,这里要声明两个
            channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
            channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
            //声明交换机
            //第一个参数是交换机名称,第二个参数是交换机类型
            //交换机类型:fanout、topic、direct、header,这里使用fanout
            channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
            //队列绑定到交换机
            channel.queueBind(QUEUE_INFORM_EMAIL, EXCHANGE_FANOUT_INFORM, "");
            channel.queueBind(QUEUE_INFORM_SMS, EXCHANGE_FANOUT_INFORM, "");
            //发送消息,这里用一个for循环来实现
            for(int i=1;i<=5;i++) {
                String message = "第" + i +"条信息!";
                //向交换机发送消息
                channel.basicPublish(EXCHANGE_FANOUT_INFORM, "", null, message.getBytes("utf-8"));
                System.out.println("发送的消息是:' " + message + "'");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭连接和通道
            if(channel != null) {
                channel.close();
            }
            if(connection != null) {
                connection.close();
            }
        }
    }

}

执行这个程序,控制台输出:
在这里插入图片描述
消费者:

消费者有2个,邮件消费者和短信消费者,邮件消费者代码如下:

package com.ycz.rabbitmq.consumer;

import java.io.IOException;

import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

/*
 * 邮件消费者
 */
public class Consumer02_subscribe_email {
    
    //监听的邮件队列
    private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
    
    //交换机
    private static final String EXCHANGE_FANOUT_INFORM = "exchange_fanout_inform";
    
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //声明邮件队列
        channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
        //声明交换机
        channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
        //队列绑定交换机
        channel.queueBind(QUEUE_INFORM_EMAIL, EXCHANGE_FANOUT_INFORM, "");
        //定义消费的方法
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, 
                    BasicProperties properties, byte[] body)
                    throws IOException {
                //获取消息内容
                String mess = new String(body,"utf-8");
                System.out.println("接收到的消息为:" + mess);
            }
            
            
        };
        //监听队列
        channel.basicConsume(QUEUE_INFORM_EMAIL, true,defaultConsumer);
    }

}

执行这个程序,控制台输出:
在这里插入图片描述
短信消费者如下:

package com.ycz.rabbitmq.consumer;

import java.io.IOException;

import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

/*
 * 短信消费者
 */
public class Consumer02_subscribe_sms {
    
    //监听的短信队列
    private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
    
    //交换机
    private static final String EXCHANGE_FANOUT_INFORM = "exchange_fanout_inform";
    
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //声明邮件队列
        channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
        //声明交换机
        channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
        //队列绑定交换机
        channel.queueBind(QUEUE_INFORM_SMS, EXCHANGE_FANOUT_INFORM, "");
        //定义消费的方法
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, 
                    BasicProperties properties, byte[] body)
                    throws IOException {
                //获取消息内容
                String mess = new String(body,"utf-8");
                System.out.println("接收到的消息为:" + mess);
            }
            
            
        };
        //监听队列
        channel.basicConsume(QUEUE_INFORM_SMS, true,defaultConsumer);
    }

}

执行这个程序,控制台如下:
在这里插入图片描述
再看RabbitMQ界面:
在这里插入图片描述
创建了2个新的队列,再看交换机:
在这里插入图片描述
这是自己声明的交换机,如果不声明,那它默认使用第一个交换机。

下面总结比较一下publish/subscribe与work queues,看看它们的区别:

相同点:

两者实现的发布/订阅的效果是一样的,多个消费端监听同一个队列不会重复消费消息。

不同点:

  • work queues不用定义交换机,而publish/subscribe需要定义交换机。
  • publish/subscribe的生产方是面向交换机发送消息,work queues的生产方是面向队列发送消息(底层使用默认交换机)。
  • publish/subscribe需要设置队列和交换机的绑定,work queues不需要设置,实质上work queues会将队列绑定到默认的交换机 。

建议:

建议使用 publish/subscribe,发布订阅模式比工作队列模式更强大,并且发布订阅模式可以指定自己专用的交换机。

c、Routing

图示:
在这里插入图片描述
路由模式的特点:

  • 每个消费者监听自己的队列,并且设置routingkey。
  • 生产者将消息发给交换机,由交换机根据routingkey来转发消息到指定的队列。

下面用代码测试:

生产者:

package com.ycz.rabbitmq.producer;

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

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

/*
 * routing模式
 */
public class Producer03_routing {
    
    //邮件队列
    private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
    
    //短信队列
    private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
    
    //交换机
    private static final String EXCHANGE_ROUTING_INFORM = "exchange_routing_inform";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");
        Connection connection = null;
        Channel channel = null;
        try {
            connection = connectionFactory.newConnection();
            channel = connection.createChannel();
            //声明队列
            channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
            channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
            channel.queueDeclare("example", true, false, false, null);
            //声明交换机
            channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
            //队列绑定到交换机,一个队列可以设置多个routingKey,通过不同的routingKey绑定到交换机
            channel.queueBind(QUEUE_INFORM_EMAIL, EXCHANGE_ROUTING_INFORM, QUEUE_INFORM_EMAIL);
            channel.queueBind(QUEUE_INFORM_EMAIL, EXCHANGE_ROUTING_INFORM, "common info");
            channel.queueBind(QUEUE_INFORM_SMS, EXCHANGE_ROUTING_INFORM, QUEUE_INFORM_SMS);
            channel.queueBind(QUEUE_INFORM_SMS, EXCHANGE_ROUTING_INFORM, "common info");
            //发送邮件消息,只有邮件消费者才能收到
            for(int i=1;i<=5;i++) {
                String mess = "第" + i +"条email信息!";
                //向交换机发送消息
                channel.basicPublish(EXCHANGE_ROUTING_INFORM, QUEUE_INFORM_EMAIL, null, mess.getBytes());
                System.out.println("发送的消息是:' " + mess + "'");
            }
            //发送短信消息,只有短信消费者才能收到
            for(int i=1;i<=3;i++) {
                String mess = "第" + i +"条短信信息!";
                //向交换机发送消息
                channel.basicPublish(EXCHANGE_ROUTING_INFORM, QUEUE_INFORM_SMS, null, mess.getBytes());
                System.out.println("发送的消息是:' " + mess + "'");
            }
            //发送通用消息,邮件和短信消费者都能收到
            for(int i=1;i<=5;i++) {
                String mess = "第" + i +"条公共信息!";
                //向交换机发送消息
                channel.basicPublish(EXCHANGE_ROUTING_INFORM, "common info", null, mess.getBytes());
                System.out.println("发送的消息是:' " + mess + "'");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭连接和通道
            if(channel != null) {
                channel.close();
            }
            if(connection != null) {
                connection.close();
            }
        }
    }


}

执行这Java程序,控制台:
在这里插入图片描述
再看RabbitMQ界面:
在这里插入图片描述
队列还是刚才创建的。看队列的绑定,该队列绑定了2个routingKey:
在这里插入图片描述
短信队列也绑定了2个routingKey:
在这里插入图片描述
再看交换机:
在这里插入图片描述
交换机创建了一个新的。

在这里插入图片描述
交换机绑定到了4个队列,有3个不同的routingKey。

消费者:

邮件消费者代码如下:

package com.ycz.rabbitmq.consumer;

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

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

/*
 * 邮件消费者
 */
public class Consumer03_routing_email {
    
    //邮件队列
    private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
    
    //交换机
    private static final String EXCHANGE_ROUTING_INFORM = "exchange_routing_inform";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
        //声明交换机
        channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
        //队列绑定交换机
        channel.queueBind(QUEUE_INFORM_EMAIL, EXCHANGE_ROUTING_INFORM, QUEUE_INFORM_EMAIL);
        channel.queueBind(QUEUE_INFORM_EMAIL, EXCHANGE_ROUTING_INFORM, "common info");
        //定义消费的方法
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, 
                    BasicProperties properties, byte[] body)
                    throws IOException {
                //获取消息内容
                String mess = new String(body,"utf-8");
                System.out.println("接收到的消息为:" + mess);
            }
            
            
        };
        //监听队列
        channel.basicConsume(QUEUE_INFORM_EMAIL, true,defaultConsumer);
    }

}

运行这个Java程序,控制台如下:
在这里插入图片描述
邮件消费者接收到了邮件消息和公共消息。

短信消费者代如下:

package com.ycz.rabbitmq.consumer;

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

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

/*
 * 短息消费者
 */
public class Consumer03_routing_sms {
    
    //短信队列
    private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
    
    //交换机
    private static final String EXCHANGE_ROUTING_INFORM = "exchange_routing_inform";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
        //声明交换机
        channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
        //队列绑定交换机
        channel.queueBind(QUEUE_INFORM_SMS, EXCHANGE_ROUTING_INFORM, QUEUE_INFORM_SMS);
        channel.queueBind(QUEUE_INFORM_SMS, EXCHANGE_ROUTING_INFORM, "common info");
        //定义消费的方法
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, 
                    BasicProperties properties, byte[] body)
                    throws IOException {
                //获取消息内容
                String mess = new String(body,"utf-8");
                System.out.println("接收到的消息为:" + mess);
            }
            
            
        };
        //监听队列
        channel.basicConsume(QUEUE_INFORM_SMS, true,defaultConsumer);
    }

}

执行这个Java程序,控制台输出:
在这里插入图片描述
短信消费者接收到了短信消息和公共消息。

Routing和Publish/subscibe的区别:

Routing模式要求队列在绑定交换机时要指定routingkey,消息会转发到符合routingkey的队列。

d、Topics

图示:
在这里插入图片描述
Topics模式特点:

  • 每个消费者监听自己的队列,并且设置带统配符的routingkey。
  • 生产者将消息发给broker,由交换机根据routingkey来转发消息到指定的队列。

通配符规则:

中间以“.”分隔。符号#可以匹配多个词或空,符号*可以匹配一个词语。

下面用代码进行测试。

案例:根据用户的通知设置去通知用户,设置接收Email的用户只接收Email,设置接收sms的用户只接收sms,设置两种通知类型都接收的则两种通知都有效。

生产者:

生产者代码如下:

package com.ycz.rabbitmq.producer;

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

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

/*
 * 生产者
 */
public class Producer04_topics {
    
    //邮件队列
    private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
    
    //短信队列
    private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
    
    //交换机
    private static final String EXCHANGE_TOPICS_INFORM = "exchange_topics_inform";
    
    //routingKey,使用通配符
    private static final String ROUTINGKEY_EMAIL = "inform.#.email.#";
    private static final String ROUTINGKEY_SMS = "inform.#.sms.#";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");
        Connection connection = null;
        Channel channel = null;
        try {
            connection = connectionFactory.newConnection();
            channel = connection.createChannel();
            //声明队列
            channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
            channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
            //声明交换机
            channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
            //队列绑定交换机
            channel.queueBind(QUEUE_INFORM_EMAIL, EXCHANGE_TOPICS_INFORM, ROUTINGKEY_EMAIL);
            channel.queueBind(QUEUE_INFORM_SMS, EXCHANGE_TOPICS_INFORM, ROUTINGKEY_SMS);
            // 匹配email的发送消息
            for (int i = 1; i <= 5; i++) {
                String mess = "第" + i + "条email信息!";
                // 向交换机发送消息
                channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.email", null, mess.getBytes("utf-8"));
                System.out.println("发送的消息是:' " + mess + "'");
            }
            // 匹配短信的发送信息
            for (int i = 1; i <= 3; i++) {
                String mess = "第" + i + "条短信信息!";
                // 向交换机发送消息
                channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.sms", null, mess.getBytes("utf-8"));
                System.out.println("发送的消息是:' " + mess + "'");
            }
            // 同时匹配email和短信的发送消息
            for (int i = 1; i <= 5; i++) {
                String mess = "第" + i + "条公共信息!";
                // 向交换机发送消息
                channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.email.sms", null, mess.getBytes("utf-8"));
                System.out.println("发送的消息是:' " + mess + "'");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(channel != null) {
                channel.close();
            }
            if(connection != null) {
                connection.close();
            }
        }
    }

}

执行这个Java程序,控制台输出:
在这里插入图片描述
再来看RabbitMQ界面:
在这里插入图片描述
队列还是原来的。

队列的绑定:
在这里插入图片描述
增加了一个通配符的路由。

在这里插入图片描述
短信队列也是增加了一个通配符的路由。
在这里插入图片描述
交换机增加了一个。

消费者:

邮件消费者的代码如下:

package com.ycz.rabbitmq.consumer;

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

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

/*
 * 邮件消费者
 */
public class Consumer04_topics_email {
    
    //邮件队列
    private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
    
    // 交换机
    private static final String EXCHANGE_TOPICS_INFORM = "exchange_topics_inform";
    
    //routingKey
    private static final String ROUTINGKEY_EMAIL = "inform.#.email.#";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
        //声明交换机
        channel.exchangeDeclare(QUEUE_INFORM_EMAIL, BuiltinExchangeType.TOPIC);
        //队列绑定到交换机
        channel.queueBind(QUEUE_INFORM_EMAIL, EXCHANGE_TOPICS_INFORM, ROUTINGKEY_EMAIL);
        //定义消费的方法
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, 
                    BasicProperties properties, byte[] body)
                    throws IOException {
                //获取消息内容
                String mess = new String(body,"utf-8");
                System.out.println("接收到的消息为:" + mess);
            }
            
            
        };
        //监听队列
        channel.basicConsume(QUEUE_INFORM_EMAIL, true,defaultConsumer);
    }
    

}

执行这个Java程序,控制台:
在这里插入图片描述
邮件消费者接收到了邮件消息和匹配到的公共消息。

短信消费者代码如下:

package com.ycz.rabbitmq.consumer;

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

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

/*
 * 短息消费者
 */
public class Consumer04_topics_sms {
    
    //短信队列
    private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
    
    // 交换机
    private static final String EXCHANGE_TOPICS_INFORM = "exchange_topics_inform";
    
    //routingKey
    private static final String ROUTINGKEY_SMS = "inform.#.sms.#";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
        //声明交换机
        channel.exchangeDeclare(QUEUE_INFORM_SMS, BuiltinExchangeType.TOPIC);
        //队列绑定到交换机
        channel.queueBind(QUEUE_INFORM_SMS, EXCHANGE_TOPICS_INFORM, ROUTINGKEY_SMS);
        //定义消费的方法
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, 
                    BasicProperties properties, byte[] body)
                    throws IOException {
                //获取消息内容
                String mess = new String(body,"utf-8");
                System.out.println("接收到的消息为:" + mess);
            }
            
            
        };
        //监听队列
        channel.basicConsume(QUEUE_INFORM_SMS, true,defaultConsumer);
    }
    

}

执行这个Java程序,控制台:
在这里插入图片描述
短信消费者接收到了短信消息和匹配到的公共消息。

小结:

使用Routing模式也可以实现本案例,共设置三个 routingkey,分别是email、sms、all,email队列绑定email和all,sms队列绑定sms和all,这样就可以实现上边案例的功能,实现过程比topics复杂。

Topic模式更多加强大,它可以实现Routing、publish/subscirbe模式的功能。

e、Header 模式

header模式与routing不同的地方在于,header模式取消routingkey,使用header中的 key/value(键值对)匹配队列。

案例:根据用户的通知设置去通知用户,设置接收Email的用户只接收Email,设置接收sms的用户只接收sms,设置两种通知类型都接收的则两种通知都有效。

生产者代码如下:

package com.ycz.rabbitmq.producer;

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

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

/*
 * 生产者
 */
public class Producer05_headers {
    
    //邮件队列
    private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
    
    //短信队列
    private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
    
    // 交换机
    private static final String EXCHANGE_HEADER_INFORM = "exchange_header_inform";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");
        Connection connection = null;
        Channel channel = null;
        try {
            connection = connectionFactory.newConnection();
            channel = connection.createChannel();
            //声明队列
            channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
            channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
            // 声明交换机
            channel.exchangeDeclare(EXCHANGE_HEADER_INFORM, BuiltinExchangeType.HEADERS);
            //声明map
            Map<String,Object> headers_email = new HashMap<>();
            headers_email.put("inform_type","email");
            Map<String,Object> headers_sms = new HashMap<>();
            headers_sms.put("inform_type","sms");
            // 交换机绑定队列
            channel.queueBind(QUEUE_INFORM_EMAIL, EXCHANGE_HEADER_INFORM, "",headers_email);
            channel.queueBind(QUEUE_INFORM_SMS, EXCHANGE_HEADER_INFORM, "",headers_sms);
            //发送邮件消息
            for(int i=1;i<=5;i++) {
                String mess = "第" + i + "条email信息!";
                Map<String,Object> headers = new HashMap<>();
                headers.put("inform_type", "email");
                //Properties对象
                AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties().builder();
                //将map放进properties对象中
                properties.headers(headers);
                //向交换机发送消息
                channel.basicPublish(EXCHANGE_HEADER_INFORM, "", properties.build(), mess.getBytes("utf-8"));
                System.out.println("发送的消息是:' " + mess + "'");
            }
            //发送短息消息
            for(int i=1;i<=5;i++) {
                String mess = "第" + i + "条短信消息!";
                Map<String,Object> headers = new HashMap<>();
                headers.put("inform_type", "sms");
                //Properties对象
                AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties().builder();
                //将map放进properties对象中
                properties.headers(headers);
                //向交换机发送消息
                channel.basicPublish(EXCHANGE_HEADER_INFORM, "", properties.build(), mess.getBytes("utf-8"));
                System.out.println("发送的消息是:' " + mess + "'");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(channel!=null) {
                channel.close();
            }
            if(connection!=null) {
                connection.close();
            }
        }
    }

}

执行这个Java程序,控制台:
在这里插入图片描述
再来看RabbitMQ界面:
在这里插入图片描述
多了一个绑定,短信队列也一样:
在这里插入图片描述
交换机新增了一个:
在这里插入图片描述
消费者:

邮件消费者代码如下:

package com.ycz.rabbitmq.consumer;

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

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

/*
 * 邮件消费者
 */
public class Consumer05_headers_email {
    
    // 邮件队列
    private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
    
    // 交换机
    private static final String EXCHANGE_HEADER_INFORM = "exchange_header_inform";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
        // 声明交换机
        channel.exchangeDeclare(EXCHANGE_HEADER_INFORM, BuiltinExchangeType.HEADERS);
        Map<String,Object> headers_email = new HashMap<>();
        headers_email.put("inform_type", "email");
        // 交换机绑定到队列
        channel.queueBind(QUEUE_INFORM_EMAIL, EXCHANGE_HEADER_INFORM, "",headers_email);
        //定义消费的方法
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, 
                    BasicProperties properties, byte[] body)
                    throws IOException {
                //获取消息内容
                String mess = new String(body,"utf-8");
                System.out.println("接收到的消息为:" + mess);
            }
            
            
        };
        //监听队列
        channel.basicConsume(QUEUE_INFORM_EMAIL, true,defaultConsumer);
    }

}

执行这个Java程序,控制台:
在这里插入图片描述
短信消费者代码如下:

package com.ycz.rabbitmq.consumer;

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

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

/*
 * 短信消费者
 */
public class Consumer05_headers_sms {
    
    // 邮件队列
    private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
    
    // 交换机
    private static final String EXCHANGE_HEADER_INFORM = "exchange_header_inform";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
        // 声明交换机
        channel.exchangeDeclare(EXCHANGE_HEADER_INFORM, BuiltinExchangeType.HEADERS);
        Map<String,Object> headers_email = new HashMap<>();
        headers_email.put("inform_type", "email");
        // 交换机绑定到队列
        channel.queueBind(QUEUE_INFORM_SMS, EXCHANGE_HEADER_INFORM, "",headers_email);
        //定义消费的方法
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, 
                    BasicProperties properties, byte[] body)
                    throws IOException {
                //获取消息内容
                String mess = new String(body,"utf-8");
                System.out.println("接收到的消息为:" + mess);
            }
            
            
        };
        //监听队列
        channel.basicConsume(QUEUE_INFORM_SMS, true,defaultConsumer);
    }

}

执行这个Java程序,控制台:
在这里插入图片描述
f、RPC

图示:
在这里插入图片描述
RPC即客户端远程调用服务端的方法 ,使用MQ可以实现RPC的异步调用,基于Direct交换机实现,流程如下:

  • 客户端即是生产者就是消费者,向RPC请求队列发送RPC调用消息,同时监听RPC响应队列。
  • 服务端监听RPC请求队列的消息,收到消息后执行服务端的方法,得到方法返回的结果。
  • 服务端将RPC方法 的结果发送到RPC响应队列。
  • 客户端(RPC调用方)监听RPC响应队列,接收到RPC调用结果。

(9)Spring Boot整合RabbitMQ

生产者:

  • pom依赖
		<!-- 
		<dependency>
			<groupId>com.rabbitmq</groupId>
			<artifactId>amqp-client</artifactId>
			<version>5.7.3</version>
		</dependency>
		-->

        <!-- Spring boot整合amqp依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
			<version>2.3.1.RELEASE</version>
		</dependency>

替换一个依赖就可以。

  • yml配置
server:
  port: 40000
  
spring:
  application: 
    name: rabbitmq-producer
  ##rabbitmq配置
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtualHost: /
  • 启动类
package com.ycz.rabbitmq.producer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
//扫描本项目下的所有包
@ComponentScan(basePackages = {"com.ycz.rabbitmq.producer"})
public class RabbitApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(RabbitApplication.class,args);
    }

}
  • RabbitMQ配置类
package com.ycz.rabbitmq.producer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/*
 * rabbitmq配置类
 */
@Configuration
public class RabbitMQConfig {
    
    //邮件队列
    public static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
    
    //短信队列
    public static final String QUEUE_INFORM_SMS = "queue_inform_sms";
    
    //交换机
    public static final String EXCHANGE_TOPICS_INFORM = "exchange_topics_inform";
    
    //注册邮件队列
    @Bean(QUEUE_INFORM_EMAIL)
    public Queue QUEUE_INFORM_EMAIL() {
        return new Queue(QUEUE_INFORM_EMAIL);
    }
    
    //注册短信队列
    @Bean(QUEUE_INFORM_SMS)
    public Queue QUEUE_INFORM_SMS() {
        return new Queue(QUEUE_INFORM_SMS);
    }
    
    //注册交换机
    @Bean(EXCHANGE_TOPICS_INFORM)
    public Exchange EXCHANGE_TOPICS_INFORM() {
      //durable(true)持久化,消息队列重启后交换机仍然存在
        return ExchangeBuilder.topicExchange(EXCHANGE_TOPICS_INFORM ).
                durable(true).build();
    }
    
    //邮件队列绑定交换机
    @Bean
    public Binding BINDING_QUEUE_INFORM_EMAIL(
            @Qualifier(QUEUE_INFORM_EMAIL) Queue queue,
            @Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).
                with("inform.#.email.#").noargs();
    }
    
    //短信队列绑定到交换机
    @Bean
    public Binding BINDING_QUEUE_INFORM_SMS(
            @Qualifier(QUEUE_INFORM_SMS) Queue queue,
            @Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).
                with("inform.#.sms.#").noargs();
    }
    
}

配置类的作用是向Spring容器中注册队列、交换机和绑定。

  • 测试类
package com.ycz.rabbitmq.producer;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.ycz.rabbitmq.producer.config.RabbitMQConfig;

@SpringBootTest
@RunWith(SpringRunner.class)
public class Producer_springboot {
    
    //注入RabbitTemplate模板
    @Autowired
    RabbitTemplate rabbitTemplate;
    
    //测试使用RabbitTemplate模板发送消息
    @Test
    public void testSendMess() {
        for(int i=1;i<=5;i++) {
            String mess = "给用户的第" + i + "条信息!";
            //发送
            rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_TOPICS_INFORM,
                    "inform.sms.email",mess);
            System.out.println("发送的消息是:" + mess);
        }
    }

}

在测试包下建立此类,用来向交换机发送消息。执行这个测试方法,控制台如下:
在这里插入图片描述
下面是消费者,这里就只配邮件消费者:

  • pom依赖
		<!-- amqp依赖 -->
		<!--  
		<dependency>
			<groupId>com.rabbitmq</groupId>
			<artifactId>amqp-client</artifactId>
			<version>5.7.3</version>
		</dependency>
		-->
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
			<version>2.3.1.RELEASE</version>
		</dependency>
  • yml配置
server:
  port: 40001
  
spring:
  application: 
    name: rabbitmq-consumer
  ##rabbitmq配置
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtualHost: /
  • 启动类
package com.ycz.rabbitmq.consumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
//扫描本工程的所有包
@ComponentScan(basePackages = {"com.ycz.rabbitmq.consumer"})
public class RabbitApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(RabbitApplication.class,args);
    }

}
  • RabbitMQ配置类
package com.ycz.rabbitmq.consumer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/*
 * 配置类
 */
@Configuration
public class RabbitMQConfig {
    
    //队列
    public final static String QUEUE_INFORM_EMAIL = "queue_inform_email";

    //交换机
    public final static String EXCHANGE_TOPICS_INFORM = "exchange_topics_inform";
    
    //注册交换机
    @Bean(EXCHANGE_TOPICS_INFORM)
    public Exchange EXCHANGE_TOPICS_INFORM() {
        return ExchangeBuilder.topicExchange(EXCHANGE_TOPICS_INFORM)
                .durable(true).build();
    }
    
    //注册邮件队列
    @Bean(QUEUE_INFORM_EMAIL)
    public Queue QUEUE_INFORM_EMAIL() {
        Queue queue = new Queue(QUEUE_INFORM_EMAIL);
        return queue;
    }
    
    
    //绑定邮件队列到交换机
    @Bean
    public Binding BINDING_QUEUE_INFORM_EMAIL(
            @Qualifier(QUEUE_INFORM_EMAIL) Queue queue,
            @Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("inform.#.email.#").noargs();
    }
    
}

我这里只注册邮件队列。

  • 监听类
package com.ycz.rabbitmq.consumer.mq;

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

import com.ycz.rabbitmq.consumer.config.RabbitMQConfig;

/*
 * 此类用来监听队列
 */
@Component
public class ReceiveHandler {
    
    //监听email队列
    @RabbitListener(queues = {RabbitMQConfig.QUEUE_INFORM_EMAIL})
    public void receiveEmail(String mess) {
        System.out.println("邮箱收到的消息为:" + mess);
    }

}

监听类是用来监听队列,并且接收消息的。

启动消费者工程,控制台如下:
在这里插入图片描述
收到了生产者发送的消息,那么SpringBoot整合RabbitMQ就成功了!

5、总结

对MQ有了一个大概的了解,能够使用RabbitMQ来发送和接收消息,了解它的几种模式的特点,能够将RabbitMQ整合到SpringBoot,以后会继续深入了解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值