一、概述
1.1 什么是MQ?
MQ全称为Message Queue,即消息队列,MQ 我们可以理解为消息队列,队列我们可以理解为管道,以管道的方式做消息传递。
开发中使用消息队列的应用场景:
(1)任务的异步处理
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理,提高了应用程序的响应时间。
(2)应用程序解耦合
MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。
1.2 什么是AMQP?
AMQP是一套公开的消息队列协议,最早在2003年被提出,它旨在从协议层定义消息通信数据的标准格式,为的就是解决MQ市场上协议不统一的问题。
官方地址:http://www.amqp.org/
1.3 为什么使用RabbitMQ?
RabbitMQ是由erlang语言开发,基于AMQP协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。比如说商城系统的秒杀功能,当用户进行结算时候,系统会提示我们稍等的文字提醒。那么在稍等的过程中,系统在做什么事情呢?其实,稍等过程就是在排队过程。就好像我们超市购物买单时候一样,我们也是要排队买单,一个一个轮流买单,这样可以避免出现插队所导致出现混乱的情况。
RabbitMQ的优势:
1)使得简单,功能强大;
2)基于AMQP协议;
3)社区活跃,文档完善;
4)高并发性能好,这主要得益于Erlang语言;
5)Spring Boot默认已集成RabbitMQ;
RabbitMQ官方地址:http://www.rabbitmq.com/
二、RabbitMQ入门
2.1 RabbitMQ的组成要素
RabbitMQ的基本机构图:
RabbitMQ各个组成部分:
- Broker:息队列服务进程,此进程包括两个部分:Exchange和Queue;
- Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑;
- Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费方;
- Producer:消息生产者,即生产方客户端,生产方客户端将消息发送到MQ;
- Consumer:消息消费者,即消费方客户端,接收MQ转发的消息;
2.2 消息发布接收流程
RabbitMQ的消息传递是通过TCP协议来实现的。
发送消息:
1)生产者和Broker建立TCP连接;
2)生产者和Broker建立通道;
3)生产者通过通道消息发送给Broker,由Exchange将消息进行转发;
4)Exchange将消息转发到指定的Queue;
接收消息:
1)消费者和Broker建立TCP连接;
2)消费者和Broker建立通道;
3)消费者监听指定的Queue;
4)当有消息到达Queue时Broker默认将消息推送给消费者;
5)消费者接收到消息;
2.3 下载安装
2.3.1 安装ErLang
RabbitMQ由Erlang语言编写。因此,安装RabbitMQ之前需要先安装ErLang,并保持版本匹配。
本项目使用Erlang/OTP 22.1 版本和RabbitMQ3.8.2版本。
ErLang下载地址:http://erlang.org/download/otp_win64_22.1.exe
下载并安装成功后配置erlang环境变量:
最后在path环境变量中添加%ERLANG_HOME%\bin;
2.3.2 安装RabbitMQ
第一步:下载安装RabbitMQ;
RabbitMQ的下载地址:http://www.rabbitmq.com/download.html
下载安装成功后会自动创建RabbitMQ服务并且启动。
第二步:安装rabbitMQ的管理插件;
rabbitmq-plugins.bat enable rabbitmq_management
第三步:在浏览器上访问 http://localhost:15672/,然后输入用户名(guest)和密码(guest)登录。
2.4 第一个RabbitMQ示例
2.4.1 创建父工程
第一步:新建一个SpringBoot工程作为父工程;
第二步:加入amqp-client依赖;
下面是父工程pom文件的完成配置:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mqtest</groupId>
<artifactId>mq-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<name>mq-parent</name>
<properties>
<java.properties>1.8</java.properties>
</properties>
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
</project>
2.4.2 创建生产者子模块
发布消息的基本步骤:
1)创建ConnectionFactory实例;
2)获取Connection实例;
3)创建Channel通道;
4)声明消息队列,队列名称为test;
5)调用Channel实例的basicPublish方法向队列发送消息;
package com.mqtest;
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.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
public class Producer {
static String queueName = "test";
static boolean isPersist = true; // 是否持久化
static boolean isMonoploy = false; // 是否独占此连接,false代表不独占
static boolean isAutoClear = false; // 当队列不再使用时,是否自动删除此队列
static Map<String, Object> params = new HashMap<String, Object>(); // 队列参数
public static void main(String[] args) {
Connection conn = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");
// 创建Connection
conn = factory.newConnection();
// 创建Channel
channel = conn.createChannel();
// 声明队列
channel.queueDeclare(queueName, isPersist, isMonoploy, isAutoClear, params);
// 准备发送的消息
String msg = "hello mq";
// 发布消息到默认交换机
channel.basicPublish("", queueName, null, msg.getBytes());
System.out.println("发布成功!");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (channel != null) {
try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2.4.3 创建消费者子模块
消费消息的基本步骤:
1)创建ConnectionFactory实例;
2)获取Connection实例;
3)创建Channel通道;
4)声明消息队列,队列名称为test;
5)监听队列;
package com.xuecheng.mqtest;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class Consumer {
static String queueName = "test";
static boolean isPersist = true; // 是否持久化
static boolean isMonoploy = false; // 是否独占此连接,false代表不独占
static boolean isAutoClear = false; // 当队列不再使用时,是否自动删除此队列
static Map<String, Object> params = new HashMap<String, Object>(); // 队列参数
// 是否自动恢复,true代表消费者接收到消息后自动向mq发送确认信息,
// mq接收到确认消息后会删除消息
static boolean isAutoReply = true;
public static void main(String[] args) {
Connection conn = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
// 创建Connection
conn = factory.newConnection();
// 创建Channel
channel = conn.createChannel();
// 声明队列
channel.queueDeclare(queueName, isPersist, isMonoploy, isAutoClear, params);
// 定义消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("消息内容:" + msg);
}
};
// 监听队列
channel.basicConsume(queueName, isAutoReply, consumer);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (channel != null) {
try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2.4.4 测试
第一步:启动RabbitMQ服务;
第二步:运行生产者模块;
第三步:运行消费者模块;
三、工作模式
RabbitMQ支持以下几种工作模式:
- work queues
- publish / subscribe
- routing
- topics
- header
- rpc
3.1 work queues模式
work queues工作模式相对于上面入门示例,多了一个消费端,两个消费端共同消费同一个队列中的消息。
work queues模式的特点:
1)一条消息只会被一个消费者接收;
2)rabbitmq采用轮询方式将消息平均发送给每一个消费者;
3)消费者在处理完一条消息后,才能够继续处理下一条消息;
3.2 publish / subscribe模式
publish / subscribe模式的特点:
1)每个消费者监听自己的队列;
2)产者将消息发给交换机,再由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息;
3.2.1 生产者
使用publish / subscribe模式发布消息的基本步骤:
1)创建ConnectionFactory实例;
2)获取Connection实例;
3)创建channel通道;
4)通过channel对象的exchangeDeclare方法声明交换机;
5)通过channel对象的queueDeclare方法声明队列;
6)通过channel对象的queueBind方法,将队列绑定到交换机上;
7)通过channel对象的basicPublish方法发布消息;
package com.xuecheng.mqtest;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/*
发布订阅模式
1.生产者需要指定routingkey
2.需要声明交换机
3.声明两个队列,并且绑定到此交换机上,绑定时不需要指定routingkey
*/
public class Producer02 {
static String queue_inform_email = "queue_inform_email"; // 发邮件的队列名称
static String queue_inform_sms = "queue_inform_sms"; // 发短信的队列名称
static String exchange_fanout_inform = "exchange_fanout_inform"; // 交换机名称
static boolean isPersist = true; // 是否持久化
static boolean isMonoploy = false; // 是否独占此连接,false代表不独占
static boolean isAutoClear = false; // 当队列不再使用时,是否自动删除此队列
static Map<String, Object> params = new HashMap<String, Object>(); // 队列参数
static String routingKey = "";
static String emptyQueueName = "";
static AMQP.BasicProperties properties; // 消息属性
public static void main(String[] args) {
Connection conn = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");
// 创建Connection
conn = factory.newConnection();
// 创建Channel
channel = conn.createChannel();
// 声明交换机
// 第二个参数代表交换机类型,FANOUT代表广播类型
channel.exchangeDeclare(exchange_fanout_inform, BuiltinExchangeType.FANOUT);
// 声明队列
channel.queueDeclare(queue_inform_email, isPersist, isMonoploy, isAutoClear, params);
channel.queueDeclare(queue_inform_sms, isPersist, isMonoploy, isAutoClear, params);
// 绑定队列到交换机上
channel.queueBind(queue_inform_email, exchange_fanout_inform, routingKey);
channel.queueBind(queue_inform_sms, exchange_fanout_inform, routingKey);
// 准备发送的消息
for (int i = 0; i < 10; i++) {
String msg = "hello mq_" + i;
// 发布消息到指定交换机
channel.basicPublish(exchange_fanout_inform
, emptyQueueName, properties, msg.getBytes());
}
System.out.println("发布成功!");
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
if (channel != null) {
try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.2.2 消费者
使用publish / subscribe模式消费消息的基本步骤:
1)创建ConnectionFactory实例;
2)获取Connection实例;
3)创建channel通道;
4)通过channel对象的exchangeDeclare方法声明交换机;
5)通过channel对象的queueDeclare方法声明队列;
6)通过channel对象的queueBind方法,将队列绑定到交换机上;
7)通过channel对象的basicConsume方法监听队列;
下面代码定义一个消费者,用于监听queue_inform_email队列的消息。
package com.xuecheng.mqtest;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/*
发布订阅模式
*/
public class Consumer02_email {
static String queue_inform_email = "queue_inform_email"; // 发邮件的队列名称
static String exchange_fanout_inform = "exchange_fanout_inform"; // 交换机名称
static boolean isPersist = true; // 是否持久化
static boolean isMonoploy = false; // 是否独占此连接,false代表不独占
static boolean isAutoClear = false; // 当队列不再使用时,是否自动删除此队列
static Map<String, Object> params = new HashMap<String, Object>(); // 队列参数
static String routingKey = "";
static AMQP.BasicProperties properties; // 消息属性
// 是否自动恢复,true代表消费者接收到消息后自动向mq发送确认信息,
// mq接收到确认消息后会删除消息
static boolean isAutoReply = true;
public static void main(String[] args) {
Connection conn = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");
// 创建Connection
conn = factory.newConnection();
// 创建Channel
channel = conn.createChannel();
// 声明交换机
channel.exchangeDeclare(exchange_fanout_inform, BuiltinExchangeType.FANOUT);
// 声明队列
channel.queueDeclare(queue_inform_email, isPersist, isMonoploy, isAutoClear, params);
// 将队列绑定到交换机上
channel.queueBind(queue_inform_email, exchange_fanout_inform, routingKey);
// 定义消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
// 交换机
String exchange = envelope.getExchange();
// 路由Key
String routingKey = envelope.getRoutingKey();
// 消息ID
long deliveryTag = envelope.getDeliveryTag();
// 消息内容
String msg = new String(body, "utf-8");
System.out.println("接收到的邮件:" + msg);
}
};
// 监听队列
channel.basicConsume(queue_inform_email, isAutoReply, consumer);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:消费者不需要关闭通道和连接,因为消费者需要不断监听通道。
同样地,依据上面示例定义另外一个消费者监听queue_inform_sms队列消息。
package com.xuecheng.mqtest;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/*
发布订阅模式
*/
public class Consumer02_sms {
static String queue_inform_sms = "queue_inform_sms"; // 发短信的队列名称
static String exchange_fanout_inform = "exchange_fanout_inform"; // 交换机名称
static boolean isPersist = true; // 是否持久化
static boolean isMonoploy = false; // 是否独占此连接,false代表不独占
static boolean isAutoClear = false; // 当队列不再使用时,是否自动删除此队列
static Map<String, Object> params = new HashMap<String, Object>(); // 队列参数
static String routingKey = "";
static AMQP.BasicProperties properties; // 消息属性
// 是否自动恢复,true代表消费者接收到消息后自动向mq发送确认信息,
// mq接收到确认消息后会删除消息
static boolean isAutoReply = true;
public static void main(String[] args) {
Connection conn = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");
// 创建Connection
conn = factory.newConnection();
// 创建Channel
channel = conn.createChannel();
// 声明交换机
channel.exchangeDeclare(exchange_fanout_inform, BuiltinExchangeType.FANOUT);
// 声明队列
channel.queueDeclare(queue_inform_sms, isPersist, isMonoploy, isAutoClear, params);
// 将队列绑定到交换机上
channel.queueBind(queue_inform_sms, exchange_fanout_inform, routingKey);
// 定义消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
// 交换机
String exchange = envelope.getExchange();
// 路由Key
// String routingKey = envelope.getRoutingKey();
// 消息ID
long deliveryTag = envelope.getDeliveryTag();
// 消息内容
String msg = new String(body, "utf-8");
System.out.println("接收到的短信:" + msg);
}
};
// 监听队列
channel.basicConsume(queue_inform_sms, isAutoReply, consumer);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.3 Routing模式
Routing模式的特点:
1)每个消费者监听自己的队列,并且设置routingkey;
2)生产者将消息发给交换机,由交换机根据routingkey来转发消息到指定的队列;
3.3.1 生产者
与发布/订阅模式不同的是:
1)声明交换机不是使用BuiltinExchangeType.FANOUT类型,而是使用BuiltinExchangeType.DIRECT类型;
channel.exchangeDeclare(exchange_fanout_inform, BuiltinExchangeType.DIRECT);
2)将队列绑定到交换机时需要指定routingKey参数;
channel.queueBind(queue_inform_email, exchange_fanout_inform, queue_inform_email);
channel.queueBind(queue_inform_sms, exchange_fanout_inform, queue_inform_sms);
完整代码:
package com.xuecheng.mqtest;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/*
routing路由模式
*/
public class Producer03 {
static String queue_inform_email = "queue_inform_email"; // 发邮件的队列名称
static String queue_inform_sms = "queue_inform_sms"; // 发短信的队列名称
static String exchange_fanout_inform = "exchange_fanout_inform"; // 交换机名称
static boolean isPersist = true; // 是否持久化
static boolean isMonoploy = false; // 是否独占此连接,false代表不独占
static boolean isAutoClear = false; // 当队列不再使用时,是否自动删除此队列
static Map<String, Object> params = new HashMap<String, Object>(); // 队列参数
static String emptyQueueName = "";
static AMQP.BasicProperties properties; // 消息属性
public static void main(String[] args) {
Connection conn = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");
// 创建Connection
conn = factory.newConnection();
// 创建Channel
channel = conn.createChannel();
// 声明交换机
// 第二个参数代表交换机类型,DIRECT代表交换机使用路由模式
channel.exchangeDeclare(exchange_fanout_inform, BuiltinExchangeType.DIRECT);
// 声明队列
channel.queueDeclare(queue_inform_email, isPersist, isMonoploy, isAutoClear, params);
channel.queueDeclare(queue_inform_sms, isPersist, isMonoploy, isAutoClear, params);
// 绑定队列到交换机上,routingKey与队列名称相同
channel.queueBind(queue_inform_email, exchange_fanout_inform, queue_inform_email);
channel.queueBind(queue_inform_sms, exchange_fanout_inform, queue_inform_sms);
// 发送消息到邮件队列
for (int i = 0; i < 5; i++) {
String msg = "hello mq_" + i;
// 发布消息到指定交换机
channel.basicPublish(exchange_fanout_inform
, queue_inform_email, properties, msg.getBytes());
}
// 发送消息到sms队列
for (int i = 0; i < 5; i++) {
String msg = "hello mq_" + i;
// 发布消息到指定交换机
channel.basicPublish(exchange_fanout_inform
, queue_inform_sms, properties, msg.getBytes());
}
System.out.println("发布成功!");
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
if (channel != null) {
try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.3.2 消费者
下面是监听queue_inform_email队列的消费者:
package com.xuecheng.mqtest;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/*
发布订阅模式
*/
public class Consumer03_email {
static String queue_inform_email = "queue_inform_email"; // 发邮件的队列名称
static String exchange_fanout_inform = "exchange_fanout_inform"; // 交换机名称
static boolean isPersist = true; // 是否持久化
static boolean isMonoploy = false; // 是否独占此连接,false代表不独占
static boolean isAutoClear = false; // 当队列不再使用时,是否自动删除此队列
static Map<String, Object> params = new HashMap<String, Object>(); // 队列参数
// 是否自动恢复,true代表消费者接收到消息后自动向mq发送确认信息,
// mq接收到确认消息后会删除消息
static boolean isAutoReply = true;
public static void main(String[] args) {
Connection conn = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");
// 创建Connection
conn = factory.newConnection();
// 创建Channel
channel = conn.createChannel();
// 声明交换机
channel.exchangeDeclare(exchange_fanout_inform, BuiltinExchangeType.DIRECT);
// 声明队列
channel.queueDeclare(queue_inform_email, isPersist, isMonoploy, isAutoClear, params);
// 将队列绑定到交换机上
channel.queueBind(queue_inform_email, exchange_fanout_inform, queue_inform_email);
// 定义消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
// 消息内容
String msg = new String(body, "utf-8");
System.out.println("接收到的邮件:" + msg);
}
};
// 监听队列
channel.basicConsume(queue_inform_email, isAutoReply, consumer);
} catch (Exception e) {
e.printStackTrace();
}
}
}
下面是监听queue_inform_sms队列的消费者:
package com.xuecheng.mqtest;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/*
发布订阅模式
*/
public class Consumer03_sms {
static String queue_inform_sms = "queue_inform_sms"; // 发邮件的队列名称
static String exchange_fanout_inform = "exchange_fanout_inform"; // 交换机名称
static boolean isPersist = true; // 是否持久化
static boolean isMonoploy = false; // 是否独占此连接,false代表不独占
static boolean isAutoClear = false; // 当队列不再使用时,是否自动删除此队列
static Map<String, Object> params = new HashMap<String, Object>(); // 队列参数
// 是否自动恢复,true代表消费者接收到消息后自动向mq发送确认信息,
// mq接收到确认消息后会删除消息
static boolean isAutoReply = true;
public static void main(String[] args) {
Connection conn = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");
// 创建Connection
conn = factory.newConnection();
// 创建Channel
channel = conn.createChannel();
// 声明交换机
channel.exchangeDeclare(exchange_fanout_inform, BuiltinExchangeType.DIRECT);
// 声明队列
channel.queueDeclare(queue_inform_sms, isPersist, isMonoploy, isAutoClear, params);
// 将队列绑定到交换机上
channel.queueBind(queue_inform_sms, exchange_fanout_inform, queue_inform_sms);
// 定义消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
// 消息内容
String msg = new String(body, "utf-8");
System.out.println("接收到的sms:" + msg);
}
};
// 监听队列
channel.basicConsume(queue_inform_sms, isAutoReply, consumer);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.4 Topics模式
Topics模式的特点:
1)每个消费者监听自己的队列,并且设置带统配符的routingkey;
2)生产者将消息发给broker,由交换机根据routingkey来转发消息到指定的队列;
统配符规则:中间以“.”分隔,符号#可以匹配多个词,符号*可以匹配一个词语。比如:inform.#.email.# 代表发送邮件,inform.#.sms.# 代表发送sms, inform.sms.email 代表发送邮件和sms。
相对于前面两种模式而言,Topic模式的功能更加强大,它也可以实现Routing、publish/subscirbe模式的功能。
3.5 header模式
header模式与routing不同的地方在于,header模式取消routingkey,使用header中的key/value(键值对)匹配队列。
声明交换机的代码改为:
channel.exchangeDeclare(exchange_fanout_inform, BuiltinExchangeType.HEADER);
队列和交换机绑定的代码改为:
Map<String, Object> headers_email = new Hashtable<String, Object>();
headers_email.put("inform_type", "email");
Map<String, Object> headers_sms = new Hashtable<String, Object>();
headers_sms.put("inform_type", "sms");
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_HEADERS_INFORM,"",headers_email);
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_HEADERS_INFORM,"",headers_sms);
发送消息的代码改为:
String message = "email inform to user" + i;
Map<String,Object> headers = new Hashtable<String, Object>();
headers.put("inform_type", "email");//匹配email通知消费者绑定的header
//headers.put("inform_type", "sms");//匹配sms通知消费者绑定的header
AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties.Builder();
properties.headers(headers);
//Email通知
channel.basicPublish(EXCHANGE_HEADERS_INFORM, "", properties.build(), message.getBytes());
消费者代码改为:
channel.exchangeDeclare(EXCHANGE_HEADERS_INFORM,BuiltinExchangeType.HEADERS);
Map<String, Object> headers_email = new Hashtable<String, Object>();
headers_email.put("inform_email", "email");
//交换机和队列绑定
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_HEADERS_INFORM,"",headers_email);
3.6 rpc模式
RPC即客户端远程调用服务端的方法 ,使用MQ可以实现RPC的异步调用,基于Direct交换机实现。
rpc模式的基本工作流程:
1)客户端即是生产者就是消费者,向RPC请求队列发送RPC调用消息,同时监听RPC响应队列;
2)服务端监听RPC请求队列的消息,收到消息后执行服务端的方法,得到方法返回的结果;
3)服务端将RPC方法的结果发送到RPC响应队列;
4)客户端监听RPC响应队列,接收到RPC调用结果;
四、SpringBoot整合RabbitMQ
4.1 生产者
第一步:在mq-parent工程下新建springmq-producer子模块;
第二步:修改pom文件,加入相关依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
第三步:在application.yml文件中配置rabbitmq服务;
server:
port: 44000
spring:
application:
name: test-rabbitmq-producer
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtualHost: /
第四步:新建RabbitConfig配置类,然后在该类中配置交换机、队列,以及交换机和队列的绑定;
package com.xuecheng.test.rabbitmq.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
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(EXCHANGE_TOPICS_INFORM)
public Exchange exchangeTopicsForm() {
// durable(true)表示当消息队列重启后,交换机仍然存在
return ExchangeBuilder.topicExchange(EXCHANGE_TOPICS_INFORM).durable(true).build();
}
// 配置sms消息队列
@Bean(QUEUE_INFORM_SMS)
public Queue queueInformSms() {
Queue queue = new Queue(QUEUE_INFORM_SMS);
return queue;
}
// 配置email消息队列
@Bean(QUEUE_INFORM_EMAIL)
public Queue queueInformEmail() {
Queue queue = new Queue(QUEUE_INFORM_EMAIL);
return queue;
}
// 将sms队列绑定到交换机上
@Bean
public Binding bindingQueueInformSms(@Qualifier(QUEUE_INFORM_SMS) Queue queue,
@Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("inform.#.sms.#").noargs();
}
// 将email队列绑定到交换机上
@Bean
public Binding bindingQueueInformEmail(@Qualifier(QUEUE_INFORM_EMAIL) Queue queue,
@Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("inform.#.email.#").noargs();
}
}
第五步:创建SpringBoot启动类;
@SpringBootApplication
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class,args);
}
}
第六步:创建测试类,将RabbitTemplate注入到测试类中;
package com.xuecheng.test.rabbitmq;
import com.xuecheng.test.rabbitmq.config.RabbitConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest
@RunWith(SpringRunner.class)
public class ProducerTest {
@Autowired
RabbitTemplate rabbitTemplate;
}
第七步:编写测试方法,向RabbitMQ发送消息;
@Test
public void testSendByTopics() {
for (int i = 0; i < 5; i++) {
String message = "hello_" + i;
rabbitTemplate.convertAndSend(RabbitConfig.EXCHANGE_TOPICS_INFORM,
"inform.sms.email", message);
}
System.out.println("发布成功!");
}
4.2 消费者
第一步:在mq-parent工程下新建springmq-consumer子模块;
第二步:修改pom文件,加入相关依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
第三步:新建RabbitConfig类,该类存储了队列信息;
package com.xuecheng.test.rabbitmq.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class RabbitConfig {
public static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
public static final String QUEUE_INFORM_SMS = "queue_inform_sms";
}
第四步:新建ReceiveHandler类,在该类中定义两个方法,然后使用@RabbitListener注解指定要监听的队列名称;
package com.xuecheng.test.rabbitmq.handler;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.xuecheng.test.rabbitmq.config.RabbitConfig;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class ReceiveHandler {
@RabbitListener(queues = {RabbitConfig.QUEUE_INFORM_EMAIL})
public void receive_email(String msg, Message message, Channel channel) {
System.out.println("sms -> " + msg);
}
@RabbitListener(queues = {RabbitConfig.QUEUE_INFORM_SMS})
public void receive_sms(String msg, Message message, Channel channel) {
System.out.println("email -> " + msg);
}
}
第五步:新建测试类;
package com.xuecheng.test.rabbitmq;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}