什么是RabbitMQ
1.1 MQ概述
MQ全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。
⚫ MQ,消息队列,存储消息的中间件
⚫ 分布式系统通信两种方式:直接远程调用 和 借助第三方 完成间接通信
⚫ 发送方称为生产者,接收方称为消费者
1.2 MQ 的优势和劣势
- 优势
- 应用解耦:提供了程序的可扩展性 系统的耦合性越高,容错性就越低,可维护性就越低。
- 异步提速:提高了系统的性能 提升用户体验和系统吞吐量(单位时间内处理请求的数目)。
- 削峰填谷:提升了系统的稳定性
-
使用了 MQ 之后,限制消费消息的速度为1000,这样一来,高峰期产生的数据势必会被积压在 MQ 中,高峰 就被“削”掉了,但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000,直 到消费完积压的消息,这就叫做“填谷”。 使用MQ后,可以提高系统稳定性。
- 劣势
- 增加了系统维护成本
- 系统可用性降低(忽略)
1.3 常见的 MQ 产品
Pulsar 最新流行(可以了解一下这个MQ产品)
1.4 RabbitMQ 简介
AMQP,即 Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议
的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中
间件不同产品,不同的开发语言等条件的限制。2006年,AMQP 规范发布。类比HTTP。
2007年,Rabbit 技术公司基于 AMQP 标准开发的 RabbitMQ 1.0 发布。RabbitMQ 采用 Erlang 语言开发。
Erlang 语言由 Ericson 设计,专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。
RabbitMQ 基础架构如下图:
RabbitMQ 中的相关概念:
⚫ Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker
⚫ Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等
⚫ Connection:publisher/consumer 和 broker 之间的 TCP 连接
⚫ Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection极大减少了操作系统建立 TCP connection 的开销
⚫ Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
⚫ Queue:消息最终被送到这里等待 consumer 取走
⚫ Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据
RabbitMQ 提供了 6 种工作模式
RabbitMQ 提供了 6 种工作模式:简单模式、work queues、Publish/Subscribe 发布与订阅模式、Routing
路由模式、Topics 主题模式、RPC 远程调用模式(远程调用,不太算 MQ;暂不作介绍)。
官网对应模式介绍:https://www.rabbitmq.com/getstarted.html
RabbitMQ的安装
⚫ RabbitMQ 官方地址:http://www.rabbitmq.com/
docker安装RabbitMQ
#指定版本,该版本包含了web控制页面
docker pull rabbitmq:management
#方式一:默认guest 用户,密码也是 guest
docker run -d --hostname my-rabbit --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq:management
#方式二:设置用户名和密码
docker run -d --hostname my-rabbit --name rabbit -e RABBITMQ_DEFAULT_USER=root -e RABBITMQ_DEFAULT_PASS=root -p 15672:15672 -p 5672:5672 rabbitmq:management
RabbitMQ的6种工作模式代码演示(代码注释有详细解释)
模式一,HelloWorld 简单模式
需求:使用简单模式完成消息传递
步骤:
① 创建工程(生成者、消费者)
② 分别添加依赖
③ 编写生产者发送消息
④ 编写消费者接收消息
导包
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.9.0</version>
</dependency>
编写消费者发送消息
package com.fs.provider;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
提供方发送一个消息去RabbitMQ,等待消费者去消费这个消息
执行成功后,登录http://192.168.93.132:15672 去查看发现有一个叫hello_world队列
*/
public class HelloWorldProvider {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置5大参数(必须设置)
connectionFactory.setHost("192.168.93.132");// ip 默认值 localhost
connectionFactory.setPort(5672); //端口 RabbitMQ代码操作默认端口 5672
connectionFactory.setVirtualHost("/fs");// 虚拟机 默认值/ 这个是我自己创建的Virtual Hosts 每个业务用不同的虚拟机,隔离
connectionFactory.setUsername("xiaofu"); // 用户名 默认 guest
connectionFactory.setPassword("xiaofu"); //密码 默认 guest
//获取对应的连接
Connection connection = connectionFactory.newConnection();
//创建 channel 使用NIO同步非阻塞的方式通信
Channel channel = connection.createChannel();
//创建队列 Queue
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1. queue:队列名称(自己定义,且全局唯一)
2. durable:是否持久化,当mq重启之后,还在.
3. exclusive:有下面2个意思
* 是否独占。只能有一个消费者监听这队列
* 当Connection关闭时,是否删除队列
4. autoDelete:是否自动删除。当没有Consumer 消费端时,自动删除掉
5. arguments:参数。
*/
//如果没有一个名字叫hello_world的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("hello_world",true,false,false,null);
//制作发送的消息
String message = "HelloWorld~~~RabbitMQ~~~";
/*
basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
参数:
1. exchange:交换机名称。简单模式下交换机会使用默认的 "" 就是这个(AMQP default)
2. routingKey:路由名称 简单模式下默认就是队列名称 消费端的的 Routing key 完全一致,才会接收到消息
3. props:配置信息
4. body:发送消息数据 字节数组
*/
//发送消息到队列
channel.basicPublish("","hello_world",null,message.getBytes());
//释放资源,这里若不释放,程序不会停止
channel.close();
connection.close();
}
}
编写消费者接收消息
package com.fs.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
消息消费者
当我们消息提供者发生一条消息到RabbitMQ的Queue队列中,消费者一监听到我们的Queue队列中有一条消息未被消费,就会立马取出来消费这条消息
*/
public class HelloWorldConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数
connectionFactory.setHost("192.168.93.132");// ip 默认值 localhost
connectionFactory.setPort(5672); //端口 RabbitMQ代码操作默认端口 5672
connectionFactory.setVirtualHost("/fs");// 虚拟机 默认值/ 这个是我自己创建的Virtual Hosts
connectionFactory.setUsername("xiaofu"); // 用户名 默认 quest
connectionFactory.setPassword("xiaofu"); //密码 默认 quest
//获取对应的连接
Connection connection = connectionFactory.newConnection();
//创建 channel
Channel channel = connection.createChannel();
//创建队列 Queue 由于提供方创建了,我们这里就不需要创建了
// channel.queueDeclare("hello_world",true,false,false,null);
//接收消息 消费消息
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识,消息的唯一ID
2. envelope:获取一些信息,交换机,路由key... 消息来自哪里
3. properties:配置信息 就是我们发生消息传递的参数,我们这个HelloWorld案列传递的是null
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);
System.out.println("body:"+new String(body));
}
};
//基本模式
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象(就是上面创建的对象,用于消息的处理消费)
*/
channel.basicConsume("hello_world",true,defaultConsumer);
//关闭资源? 不要
}
}
测试结果
先执行提供者
在执行消费者
模式二,work queues
-
在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系。
-
Work Queues 对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。例如:短信服务部署多个,只需要有一个节点成功发送即可。
提供方代码编写
package com.fs.provider;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Work queues 工作队列模式:
* 与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。
*
* 比如发送10个消息给队列.有2个消费端共同消费这个队列,那么就会平均去消费这个队列中的消息,不会出现同时
* 消费同一个消息.
*
* 在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系。
*/
public class Producer_WorkQueues {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数
connectionFactory.setHost("192.168.93.132");// ip 默认值 localhost
connectionFactory.setPort(5672); //端口 RabbitMQ代码操作默认端口 5672
connectionFactory.setVirtualHost("/fs");// 虚拟机 默认值/ 这个是我自己创建的Virtual Hosts
connectionFactory.setUsername("xiaofu"); // 用户名 默认 quest
connectionFactory.setPassword("xiaofu"); //密码 默认 quest
//获取对应的连接
Connection connection = connectionFactory.newConnection();
//创建 channel
Channel channel = connection.createChannel();
//5. 创建队列Queue
channel.queueDeclare("work_queues",true,false,false,null);
//发送10次消息,来让2个消费者同时消费一个队列
for (int i = 1; i <= 10; i++) {
String body = i+"hello rabbitmq~~~";
//6. 发送消息
channel.basicPublish("","work_queues",null,body.getBytes());
}
//7.释放资源
channel.close();
connection.close();
}
}
消费方代码编写
消费1
package com.fs.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
Queue队列存在多个消费者时, 消费者获取队列中的消费时采用默认的轮询方式
*/
public class Consumer_WorkQueues1 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数
connectionFactory.setHost("192.168.93.132");// ip 默认值 localhost
connectionFactory.setPort(5672); //端口 RabbitMQ代码操作默认端口 5672
connectionFactory.setVirtualHost("/fs");// 虚拟机 默认值/ 这个是我自己创建的Virtual Hosts
connectionFactory.setUsername("xiaofu"); // 用户名 默认 quest
connectionFactory.setPassword("xiaofu"); //密码 默认 quest
//获取对应的连接
Connection connection = connectionFactory.newConnection();
//创建 channel
Channel channel = connection.createChannel();
//5. 创建队列Queue
//如果没有一个名字叫hello_world的队列,则会创建该队列,如果有则不会创建,我们消费者已经创建了
// channel.queueDeclare("work_queues",true,false,false,null);
// 消费回调对象
Consumer consumer = new DefaultConsumer(channel){
//回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
}
};
//消费,传递消费对象
channel.basicConsume("work_queues",true,consumer);
//关闭资源?不要
}
}
消费2
package com.fs.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_WorkQueues2 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数
connectionFactory.setHost("192.168.93.132");// ip 默认值 localhost
connectionFactory.setPort(5672); //端口 RabbitMQ代码操作默认端口 5672
connectionFactory.setVirtualHost("/fs");// 虚拟机 默认值/ 这个是我自己创建的Virtual Hosts
connectionFactory.setUsername("xiaofu"); // 用户名 默认 quest
connectionFactory.setPassword("xiaofu"); //密码 默认 quest
//获取对应的连接
Connection connection = connectionFactory.newConnection();
//创建 channel
Channel channel = connection.createChannel();
//如果没有一个名字叫hello_world的队列,则会创建该队列,如果有则不会创建
// channel.queueDeclare("work_queues",true,false,false,null);
// 消费回调对象
Consumer consumer = new DefaultConsumer(channel){
//回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
}
};
//消费,传递消费对象
channel.basicConsume("work_queues",true,consumer);
//关闭资源?不要
}
}
测试
测试先运行消费方接受消息,然后运行提供方发送消息,就会发现2个消费方轮询的消费了提供方发送的消息
模式三,Publish/Subscribe 发布与订阅模式
在订阅模型中,多了一个 Exchange 角色,而且过程略有变化:
⚫ P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
⚫ C:消费者,消息的接收者,会一直等待消息到来
⚫ Queue:消息队列,接收消息、缓存消息
⚫ Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:
➢ Fanout:广播,将消息交给所有绑定到交换机的队列
➢ Direct:定向,把消息交给符合指定routing key 的队列
➢ Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与 Exchange 绑定,或者没有符合路由规则的队列,那么消息会丢失!
Fanout:广播
编写提供方代码
package com.fs.provider;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Pub/Sub 订阅模式
* Fanout:广播,将消息交给所有绑定到交换机的队列
*/
public class Producer_PubSub {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数
connectionFactory.setHost("192.168.93.132");// ip 默认值 localhost
connectionFactory.setPort(5672); //端口 RabbitMQ代码操作默认端口 5672
connectionFactory.setVirtualHost("/fs");// 虚拟机 默认值/ 这个是我自己创建的Virtual Hosts
connectionFactory.setUsername("xiaofu"); // 用户名 默认 quest
connectionFactory.setPassword("xiaofu"); //密码 默认 quest
//获取对应的连接
Connection connection = connectionFactory.newConnection();
//创建 channel
Channel channel = connection.createChannel();
/*
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
参数:
1. exchange:交换机名称
2. type:交换机类型 点进源码查看枚举如下
DIRECT("direct"),:定向
FANOUT("fanout"),:扇形(广播),发送消息到每一个与之绑定队列。
TOPIC("topic"),通配符的方式
HEADERS("headers");参数匹配
3. durable:是否持久化
4. autoDelete:自动删除,该交换机没有队列的时候就自动删除
5. internal:内部使用。 一般false
6. arguments:参数
*/
//定义交换机名称
String exchangeName = "test_fanout";
//5. 创建交换机,指定交换机类型为FANOUT Fanout:广播,将消息交给所有绑定到交换机的队列
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT, true, false, false, null);
//6. 自定义队列名称
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
//创建队列
channel.queueDeclare(queue1Name, true, false, false, null);
channel.queueDeclare(queue2Name, true, false, false, null);
//7. 绑定队列和交换机
/*
queueBind(String queue, String exchange, String routingKey)
参数:
1. queue:队列名称
2. exchange:交换机名称
3. routingKey:路由键,路由规则
如果交换机的类型为fanout ,routingKey设置为"" 因为为广播的方式,所以不用设置routingKey路由规则
*/
channel.queueBind(queue1Name, exchangeName, "");
channel.queueBind(queue2Name, exchangeName, "");
//自定义的消息
String body = "日志信息:张三调用了findAll方法...日志级别:info...";
//8. 发送消息
channel.basicPublish(exchangeName, "", null, body.getBytes());
//9. 释放资源
channel.close();
connection.close();
}
}
编写消费房代码
消费1
package com.fs.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_PubSub1 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数
connectionFactory.setHost("192.168.93.132");// ip 默认值 localhost
connectionFactory.setPort(5672); //端口 RabbitMQ代码操作默认端口 5672
connectionFactory.setVirtualHost("/fs");// 虚拟机 默认值/ 这个是我自己创建的Virtual Hosts
connectionFactory.setUsername("xiaofu"); // 用户名 默认 quest
connectionFactory.setPassword("xiaofu"); //密码 默认 quest
//获取对应的连接
Connection connection = connectionFactory.newConnection();
//创建 channel
Channel channel = connection.createChannel();
//队列1 的名称 与提供方的队列名一致
String queue1Name = "test_fanout_queue1";
//String queue2Name = "test_fanout_queue2";
// 回调对象 接收消息
Consumer consumer = new DefaultConsumer(channel){
//回调方法,消费消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
//模拟消费队列1 的任务是将日志打印到控制台
System.out.println("将日志信息打印到控制台.....");
}
};
//消费,使用队列1,传递回调对象
channel.basicConsume(queue1Name,true,consumer);
//关闭资源?不要
}
}
消费2
package com.fs.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_PubSub2 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数
connectionFactory.setHost("192.168.93.132");// ip 默认值 localhost
connectionFactory.setPort(5672); //端口 RabbitMQ代码操作默认端口 5672
connectionFactory.setVirtualHost("/fs");// 虚拟机 默认值/ 这个是我自己创建的Virtual Hosts
connectionFactory.setUsername("xiaofu"); // 用户名 默认 quest
connectionFactory.setPassword("xiaofu"); //密码 默认 quest
//获取对应的连接
Connection connection = connectionFactory.newConnection();
//创建 channel
Channel channel = connection.createChannel();
//String queue1Name = "test_fanout_queue1";
//队列2 的名称 与提供方的队列名一致
String queue2Name = "test_fanout_queue2";
// 回调对象 接收消息
Consumer consumer = new DefaultConsumer(channel){
//回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
//模拟消费队列2 的任务是将消息保存到数据库
System.out.println("将日志信息保存数据库.....");
}
};
//消费,使用队列2
channel.basicConsume(queue2Name,true,consumer);
//关闭资源?不要
}
}
测试运行
先执行提供方发送消息,后执行2个消费方消费消息
模式四,Routing路由模式 Direct
Routing 模式要求队列在绑定交换机时要指定 routing key,消息会转发到符合 routing key 的队列。
提供端代码编写
package com.fs.provider;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Pub/Sub 订阅模式
* Direct:定向,把消息交给符合指定routing key 的队列
*/
public class Producer_Routing {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数
connectionFactory.setHost("192.168.93.132");// ip 默认值 localhost
connectionFactory.setPort(5672); //端口 RabbitMQ代码操作默认端口 5672
connectionFactory.setVirtualHost("/fs");// 虚拟机 默认值/ 这个是我自己创建的Virtual Hosts
connectionFactory.setUsername("xiaofu"); // 用户名 默认 quest
connectionFactory.setPassword("xiaofu"); //密码 默认 quest
//获取对应的连接
Connection connection = connectionFactory.newConnection();
//创建 channel
Channel channel = connection.createChannel();
//自定义交换机名称
String exchangeName = "test_direct";
//5. 创建交换机,指定交换机类型 Direct:定向,把消息交给符合指定routing key 的队列
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT, true, false, false, null);
//6. 创建队列
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
channel.queueDeclare(queue1Name, true, false, false, null);
channel.queueDeclare(queue2Name, true, false, false, null);
//7. 绑定队列和交换机
/*
queueBind(String queue, String exchange, String routingKey)
参数:
1. queue:队列名称
2. exchange:交换机名称
3. routingKey:路由键,绑定规则 路由规则
因为这次指定的交换机为DIRECT类型,所以我们需要指定路由键
*/
//队列1绑定 error 的routingKey
channel.queueBind(queue1Name, exchangeName, "error");
//队列2绑定 info error warning 这三个routingKey
channel.queueBind(queue2Name, exchangeName, "info");
channel.queueBind(queue2Name, exchangeName, "error");
channel.queueBind(queue2Name, exchangeName, "warning");
String body = "日志信息:张三调用了delete方法...出错误了。。。日志级别:error...";
String body2 = "日志信息:张三调用了findAll方法.日志级别:info...";
//8. 发送消息
channel.basicPublish(exchangeName, "info", null, body2.getBytes());
channel.basicPublish(exchangeName, "error", null, body.getBytes());
//9. 释放资源
channel.close();
connection.close();
}
}
消费端代码编写
消费1
package com.fs.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Routing1 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数
connectionFactory.setHost("192.168.93.132");// ip 默认值 localhost
connectionFactory.setPort(5672); //端口 RabbitMQ代码操作默认端口 5672
connectionFactory.setVirtualHost("/fs");// 虚拟机 默认值/ 这个是我自己创建的Virtual Hosts
connectionFactory.setUsername("xiaofu"); // 用户名 默认 quest
connectionFactory.setPassword("xiaofu"); //密码 默认 quest
//获取对应的连接
Connection connection = connectionFactory.newConnection();
//创建 channel
Channel channel = connection.createChannel();
String queue1Name = "test_direct_queue1";
//String queue2Name = "test_direct_queue2";
// 回调对象 接收消息
Consumer consumer = new DefaultConsumer(channel){
//回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息打印到控制台.....");
}
};
//消费,传递队列与回调对象
channel.basicConsume(queue1Name,true,consumer);
//关闭资源?不要
}
}
消费2
package com.fs.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Routing2 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数
connectionFactory.setHost("192.168.93.132");// ip 默认值 localhost
connectionFactory.setPort(5672); //端口 RabbitMQ代码操作默认端口 5672
connectionFactory.setVirtualHost("/fs");// 虚拟机 默认值/ 这个是我自己创建的Virtual Hosts
connectionFactory.setUsername("xiaofu"); // 用户名 默认 quest
connectionFactory.setPassword("xiaofu"); //密码 默认 quest
//获取对应的连接
Connection connection = connectionFactory.newConnection();
//创建 channel
Channel channel = connection.createChannel();
//String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
// 回调对象 接收消息
Consumer consumer = new DefaultConsumer(channel){
//回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
//模拟调用了业务层的操作
System.out.println("将日志信息存储到数据库.....");
}
};
//消费
channel.basicConsume(queue2Name,true,consumer);
//关闭资源?不要
}
}
测试运行
先执行提供方发送消息,后执行2个消费方消费消息
模式五,Topics 主题模式 通配符模式
⚫ Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型Exchange 可以让队列在绑定 Routing key 的时候使用通配符!
⚫ Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
⚫ 通配符规则:# 匹配一个或多个词,* 匹配不多不少恰好1个词,例如:item.# 能够匹配 item.insert.abc或者 item.insert,item.* 只能匹配 item.insert
提供端代码编写
package com.fs.provider;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Pub/Sub 发布订阅者模式
* <p>
* Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
*/
public class Producer_Topics {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数
connectionFactory.setHost("192.168.93.132");// ip 默认值 localhost
connectionFactory.setPort(5672); //端口 RabbitMQ代码操作默认端口 5672
connectionFactory.setVirtualHost("/fs");// 虚拟机 默认值/ 这个是我自己创建的Virtual Hosts
connectionFactory.setUsername("xiaofu"); // 用户名 默认 quest
connectionFactory.setPassword("xiaofu"); //密码 默认 quest
//获取对应的连接
Connection connection = connectionFactory.newConnection();
//创建 channel
Channel channel = connection.createChannel();
//自定义交换机名称
String exchangeName = "test_topic";
//5. 创建交换机,指定交换机类型 Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC, true, false, false, null);
//6. 创建队列
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
channel.queueDeclare(queue1Name, true, false, false, null);
channel.queueDeclare(queue2Name, true, false, false, null);
//7. 绑定队列和交换机
/*
queueBind(String queue, String exchange, String routingKey)
参数:
1. queue:队列名称
2. exchange:交换机名称
3. routingKey:路由键,绑定规则
因为是TOPIC 规则 通配符
*/
//= 系统的名称.日志级别
//通配符规则:# 匹配0个或多个词,* 匹配不多不少恰好1个词
channel.queueBind(queue1Name, exchangeName, "#.error");
channel.queueBind(queue1Name, exchangeName, "order.*");
channel.queueBind(queue2Name, exchangeName, "*.*");
//定义消息
String body = "日志信息:张三调用了findAll方法...日志级别:info...";
String body2 = "日志信息:张三调用了delete方法.执行错误..日志级别:error...";
//8. 发送消息
//这个消息只有队列2能收到,因为只满足 *.*
channel.basicPublish(exchangeName, "goods.find", null, body.getBytes());
//这个消息队列1和队列2 都能收到并消费,因为满足通配符
channel.basicPublish(exchangeName, "goods.error", null, body2.getBytes());
//9. 释放资源
channel.close();
connection.close();
}
}
消费端代码编写
消费1
package com.fs.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Topic1 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数
connectionFactory.setHost("192.168.93.132");// ip 默认值 localhost
connectionFactory.setPort(5672); //端口 RabbitMQ代码操作默认端口 5672
connectionFactory.setVirtualHost("/fs");// 虚拟机 默认值/ 这个是我自己创建的Virtual Hosts
connectionFactory.setUsername("xiaofu"); // 用户名 默认 quest
connectionFactory.setPassword("xiaofu"); //密码 默认 quest
//获取对应的连接
Connection connection = connectionFactory.newConnection();
//创建 channel
Channel channel = connection.createChannel();
String queue1Name = "test_topic_queue1";
//String queue2Name = "test_topic_queue2";
// 回调对象 接收消息
Consumer consumer = new DefaultConsumer(channel){
//回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息存入数据库.......");
}
};
//消费
channel.basicConsume(queue1Name,true,consumer);
//关闭资源?不要
}
}
消费2
package com.fs.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Topic2 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数
connectionFactory.setHost("192.168.93.132");// ip 默认值 localhost
connectionFactory.setPort(5672); //端口 RabbitMQ代码操作默认端口 5672
connectionFactory.setVirtualHost("/fs");// 虚拟机 默认值/ 这个是我自己创建的Virtual Hosts
connectionFactory.setUsername("xiaofu"); // 用户名 默认 quest
connectionFactory.setPassword("xiaofu"); //密码 默认 quest
//获取对应的连接
Connection connection = connectionFactory.newConnection();
//创建 channel
Channel channel = connection.createChannel();
//String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
//回调对象
Consumer consumer = new DefaultConsumer(channel){
//回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息打印控制台.......");
}
};
//消费
channel.basicConsume(queue2Name,true,consumer);
//关闭资源?不要
}
}
测试运行
工作模式总结
-
简单模式 HelloWorld
一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)。 -
工作队列模式 Work Queue
一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)。 -
发布订阅模式 Publish/subscribe
需要设置类型为 fanout 的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消
息发送到绑定的队列。 -
路由模式 Routing
需要设置类型为 direct 的交换机,交换机和队列进行绑定,并且指定 routing key,当发送消息到交换机
后,交换机会根据 routing key 将消息发送到对应的队列。 -
通配符模式 Topic
需要设置类型为 topic 的交换机,交换机和队列进行绑定,并且指定通配符方式的 routing key,当发送
消息到交换机后,交换机会根据 routing key 将消息发送到对应的队列。
Spring 整合 RabbitMQ
⚫ 使用 Spring 整合 RabbitMQ 将组件全部使用配置方式实现,简化编码
⚫ Spring 提供 RabbitTemplate 简化发送消息 API
⚫ 使用监听机制简化消费者编码
fs-spring-rabbitMQ-provider 消息提供方项目
pom.xml
<?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>
<artifactId>fs-rabbitMQ-study</artifactId>
<groupId>com.fs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>fs-spring-rabbitMQ-provider</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.amqp/spring-rabbit -->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.8.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
rabbitmq.properties
rabbitmq.host=192.168.93.132
rabbitmq.port=5672
rabbitmq.username=xiaofu
rabbitmq.password=xiaofu
rabbitmq.virtual-host=/fs
spring-rabbitmq-producer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义持久化队列,不存在则自动创建;不绑定到交换机则绑定到默认交换机
默认交换机类型为direct,名字为:"",路由键为队列的名称
-->
<!--
id:bean的名称
name:queue的名称
auto-declare:自动创建
auto-delete:自动删除。 最后一个消费者和该队列断开连接后,自动删除队列
exclusive:是否独占
durable:是否持久化
-->
<rabbit:queue id="spring_queue" name="spring_queue" auto-declare="true"/>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~广播;所有队列都能收到消息~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>
<!--定义广播类型交换机;并绑定上述两个队列-->
<rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="spring_fanout_queue_1" />
<rabbit:binding queue="spring_fanout_queue_2"/>
</rabbit:bindings>
</rabbit:fanout-exchange>
<!--<rabbit:direct-exchange name="aa" >
<rabbit:bindings>
<!–direct 类型的交换机绑定队列 key :路由key queue:队列名称–>
<rabbit:binding queue="spring_queue" key="xxx"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>-->
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~通配符;*匹配一个单词,#匹配多个单词 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_star" name="spring_topic_queue_star" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well" name="spring_topic_queue_well" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well2" name="spring_topic_queue_well2" auto-declare="true"/>
<rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding pattern="fs.*" queue="spring_topic_queue_star"/>
<rabbit:binding pattern="fs.#" queue="spring_topic_queue_well"/>
<rabbit:binding pattern="xf.#" queue="spring_topic_queue_well2"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>
ProducerTest 提供消息发送测试类
package com.fs;
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.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class ProducerTest {
//1.注入 RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testHelloWorld(){
//2.发送消息
rabbitTemplate.convertAndSend("spring_queue","hello world spring....");
}
/**
* 发送fanout消息
*/
@Test
public void testFanout(){
//2.发送消息
//因为是Fanout类型的交换机,所以发生的时候要指定交换机名称,指定""路由规则,发送的消息
rabbitTemplate.convertAndSend("spring_fanout_exchange","","spring fanout....");
}
/**
* 发送topic消息
*/
@Test
public void testTopics(){
//2.发送消息
//因为是Topics类型的交换机,所以发生的时候要指定交换机名称,指定通配符路由规则,发送的消息
rabbitTemplate.convertAndSend("spring_topic_exchange","fs.hehe.haha","spring topic....");
}
}
fs-spring-rabbitMQ-consumer 消息消费方项目
pom.xml
<?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>
<artifactId>fs-rabbitMQ-study</artifactId>
<groupId>com.fs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>fs-spring-rabbitMQ-consumer</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.amqp/spring-rabbit -->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.8.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
rabbitmq.properties(与提供方一模一样)
spring-rabbitmq-consumer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<!-- 注入我们实现MessageListener接口的类-->
<bean id="springQueueListener" class="com.fs.rabbitmq.listener.SpringQueueListener"/>
<!--<bean id="fanoutListener1" class="com.fs.rabbitmq.listener.FanoutListener1"/>
<bean id="fanoutListener2" class="com.fs.rabbitmq.listener.FanoutListener2"/>
<bean id="topicListenerStar" class="com.fs.rabbitmq.listener.TopicListenerStar"/>
<bean id="topicListenerWell" class="com.fs.rabbitmq.listener.TopicListenerWell"/>
<bean id="topicListenerWell2" class="com.fs.rabbitmq.listener.TopicListenerWell2"/>
-->
<!-- 注入连接工厂,消费队列中的消息-->
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="springQueueListener" queue-names="spring_queue"/>
<!-- <rabbit:listener ref="fanoutListener1" queue-names="spring_fanout_queue_1"/>
<rabbit:listener ref="fanoutListener2" queue-names="spring_fanout_queue_2"/>
<rabbit:listener ref="topicListenerStar" queue-names="spring_topic_queue_star"/>
<rabbit:listener ref="topicListenerWell" queue-names="spring_topic_queue_well"/>
<rabbit:listener ref="topicListenerWell2" queue-names="spring_topic_queue_well2"/>-->
</rabbit:listener-container>
</beans>
SpringQueueListener 消息监听
package com.fs.rabbitmq.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
/*
创建一个类来实现MessageListener 消息监听的接口
重写onMessage方法
使用Message参数来消费
*/
public class SpringQueueListener implements MessageListener {
@Override
public void onMessage(Message message) {
//打印消息
System.out.println(new String(message.getBody()));
}
}
ConsumerTest spring容器启动测试
package com.fs;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class ConsumerTest {
@Test
public void test1(){
boolean flag = true;
//让代码不停止,就一直开启连接监听我们的队列中的消息
while (true){
}
}
}
测试Spring 整合 RabbitMQ
先执行ConsumerTest的test1方法,让那个消息监听器一直监听某个队列,有消息就消费
然后执行消息提供方的测试方法发送消息
Springboot 整合RabbitMQ
生产端
- 创建生产者SpringBoot工程
- 引入start,依赖坐标
org.springframework.boot
spring-boot-starter-amqp
- 编写yml配置,基本信息配置
- 定义交换机,队列以及绑定关系的配置类
- 注入RabbitTemplate,调用方法,完成消息发送
消费端
6. 创建消费者SpringBoot工程
7. 引入start,依赖坐标
org.springframework.boot
spring-boot-starter-amqp
8. 编写yml配置,基本信息配置
9. 定义监听类,使用@RabbitListener注解完成队列监听。
父pom.xml
<!-- spring boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.2.RELEASE</version>
<type>pom</type>
<!-- import 导入父工程的配置-->
<scope>import</scope>
</dependency>
fs-springboot-rabbitMQ-provider 消息提供端
pom.xml
<?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>
<artifactId>fs-rabbitMQ-study</artifactId>
<groupId>com.fs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>fs-springboot-rabbitMQ-provider</artifactId>
<dependencies>
<!-- spring-boot-starter-web spring-boot-starter-actuator绑定在一块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--2. rabbitmq-->
<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>
</dependencies>
</project>
application.yml
# 配置RabbitMQ的基本信息 ip 端口 username password 虚拟机..
spring:
rabbitmq:
host: 192.168.93.132 # ip
port: 5672
username: xiaofu
password: xiaofu
virtual-host: /springboot
ProducerApplication 主启动
package com.fs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class);
}
}
RabbitMQConfig 配置RabbitMQ类
package com.fs.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;
/*
java配置类
创建交换机与队列,绑定关系
链式编程,很方便
这些也可以在RabbitMQ的web管理界面去创建
*/
@Configuration
public class RabbitMQConfig {
//定义交换机名称与队列名称
public static final String EXCHANGE_NAME = "boot_topic_exchange";
public static final String QUEUE_NAME = "boot_queue";
//1.交换机Exchange
@Bean("bootExchange")
public Exchange bootExchange(){
//创建一个topic类型的交换机 ExchangeBuilder使用交换机构建对象
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
//2.Queue 队列
@Bean("bootQueue")
public Queue bootQueue(){
//QueueBuilder 使用队列构建对象
//返回一个队列,指定队列名称,也可以不使用参数 ,使用的参数withArgument,可以点进源码查看 x-message-ttl ttl 设置消息过期时间,5000 就是5秒过期
// return QueueBuilder.durable(QUEUE_NAME).withArgument("x-message-ttl",5000).build();
//不设置参数
return QueueBuilder.durable(QUEUE_NAME).build();
}
//3. 队列和交换机绑定关系 Binding
/*
1. 指定哪个队列
2. 指定哪个交换机
3. routing key
*/
@Bean
public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange") Exchange exchange){
//BindingBuilder 使用绑定构建对象
//返回bind绑定那个队列,to位于那个交换机,with在那个路由规则,noargs不指定参数
return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();
}
}
ProducerTest 消息发送测试方法
package com.fs;
import com.fs.rabbitmq.config.RabbitMQConfig;
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.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest
@RunWith(SpringRunner.class)
public class ProducerTest {
//1.注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSend(){
//发送消息,因为我们绑定的规则是 boot.# 所以发送"boot.haha" 消费方式能够接受到的
//第一个参数,交换机名称,二参数 routingKey 三参数 消息
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.haha","boot mq hello~~~");
}
}
fs-springboot-rabbitMQ-consumer 消息消费端
pom.xml
<?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>
<artifactId>fs-rabbitMQ-study</artifactId>
<groupId>com.fs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>fs-springboot-rabbitMQ-consumer</artifactId>
<dependencies>
<!-- spring-boot-starter-web spring-boot-starter-actuator绑定在一块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--2. rabbitmq-->
<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>
</dependencies>
</project>
application.yml
spring:
rabbitmq:
host: 192.168.93.132 #主机ip
port: 5672 #端口
username: xiaofu
password: xiaofu
virtual-host: /springboot
publisher-confirms: true
publisher-returns: true
listener:
simple:
acknowledge-mode: manual
ConsumerSpringbootApplication 主启动
package com.fs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConsumerSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerSpringbootApplication.class, args);
}
}
RabbimtMQListener 队列监听类
@RabbitListener(queues = {监听的队列,可以指定多个})
package com.fs.Queuelistener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/*
定义监听类
*/
@Component
public class RabbimtMQListener {
//@RabbitListener(queues = {监听的队列,可以指定多个})
//监听boot_queue 这个队列,有消息就消费
@RabbitListener(queues = "boot_queue")
public void ListenerQueue(Message message, Channel channel){
//System.out.println(message);
//打印消息
System.out.println(new String(message.getBody()));
}
}
测试运行
先点击消费端的主启动,把消费端启动起来,就会执行我们定义的监听器,然后监听队列,这个队列有消息发送就会被消费
然后在点击我们提供端的测试方法发送消息