一、RabbitMQ
1.1 RabbitMQ介绍
MQ全称为Message Queue,即消息队列, RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。RabbitMQ官方地址:http://www.rabbitmq.com/
-
开发中消息队列通常有如下应用场景:
- 1 、任务异步处理。
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。 - 2 、应用程序解耦合
MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。
- 1 、任务异步处理。
-
市场上还有哪些消息队列?
ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ、Redis。
-
为什么使用RabbitMQ呢?
- 1 、使得简单,功能强大。
- 2 、基于AMQP协议。
- 3 、社区活跃,文档完善。
- 4 、高并发性能好,这主要得益于Erlang语言。
- 5 、Spring Boot默认已集成RabbitMQ
1.2 AMQP
总结:AMQP是一套公开的消息队列协议,最早在 2003 年被提出,它旨在从协议层定义消息通信数据的标准格式,为的就是解决MQ市场上协议不统一的问题。RabbitMQ就是遵循AMQP标准协议开发的MQ服务。
1.3 JMS
总结:JMS是java提供的一套消息服务API标准,其目的是为所有的java应用程序提供统一的消息通信的标准,类似java的jdbc,只要遵循jms标准的应用程序之间都可以进行消息通信。它和AMQP有什么 不同,jms是java语言专属的消息服务标准,它是在api层定义标准,并且只能用于java应用;而AMQP是在协议层定义的标准,是跨语言的 。
1.4 RabbitMQ工作原理
RabbitMQ的基本结构:
组成部分说明如下:
Broker
:消息队列服务进程,此进程包括两个部分:Exchange和Queue。Exchange
:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。Queue
:消息队列,存储消息的队列,消息到达队列并转发给指定的消费方。Producer
:消息生产者,即生产方客户端,生产方客户端将消息发送到MQ。Consumer
:消息消费者,即消费方客户端,接收MQ转发的消息。
消息发布接收流程:
- - - - -发送消息 - - - - -
1 、生产者和Broker建立TCP连接。
2 、生产者和Broker建立通道。
3 、生产者通过通道消息发送给Broker,由Exchange将消息进行转发。
4 、Exchange将消息转发到指定的Queue(队列)
- - - - - 接收消息 - - - -
1 、消费者和Broker建立TCP连接
2 、消费者和Broker建立通道
3 、消费者监听指定的Queue(队列)
4 、当有消息到达Queue时Broker默认将消息推送给消费者。
5 、消费者接收到消息。
二、RabbitMQ安装使用
2.1 下载安装
RabbitMQ由Erlang语言开发,Erlang语言用于并发及分布式系统的开发,在电信领域应用广泛,OTP(Open Telecom Platform)作为Erlang语言的一部分,包含了很多基于Erlang开发的中间件及工具库,安装RabbitMQ需要安装Erlang/OTP,并保持版本匹配,如下图:
RabbitMQ的下载地址:http://www.rabbitmq.com/download.html
本项目使用Erlang/OTP 20.3版本和RabbitMQ3.7.3版本。
-
1 ) 下载erlang
地址如下:http://erlang.org/download/otp_win64_20.3.exe
erlang安装完成需要配置erlang环境变量:ERLANG_HOME=D:\Program Files\erl9.3
在path中添加:PATH=%ERLANG_HOME%\bin;
-
2 )安装RabbitMQ
https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.7.3
`
2.2 启动
安装成功后会自动创建RabbitMQ服务并且启动。
-
安装管理插件
管理员身份运行rabbitmq-plugins.bat enable rabbitmq_management
-
启动服务
rabbitmq-service.bat install 安装服务
rabbitmq-service.bat stop 停止服务
rabbitmq-service.bat start 启动服务
rabbitmq-service.bat restart 重启服务 -
启动成功 登录RabbitMQ
进入浏览器,输入:
http://localhost:15672
(初始账号和密码:guest/guest)
2.3 注意事项
1 、 安装erlang和r abbitMQ以管理员身份运行。
2 、 当卸载重新安装时会出现RabbitMQ服务注册失败,此时需要进入注册表清理erlang,搜索RabbitMQ、E rlSrv,将对应的项全部删除。
2.4 RabbitMQ的使用
2.4.1搭建环境
创建maven工程、创建生产者工程和消费者工程,分别加入RabbitMQ java client的依赖。
- rabbitmq-producer:生产者工程
- rabbitmq-consumer:消费者工程
<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>
2.4.2 生产者
import com.rabbitmq.client.*;
import java.io.IOException;
public class HelloConsumer {
private static final String HELLO_QUEUE = "hello_queue";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1.创建Connectionfactory对象,用于生产Connection对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
// 2.生产Connection对象
connection = connectionFactory.newConnection();
// 3.通过连接对象创建通道
channel = connection.createChannel();
// 4.声明一个队列
/**
* queue – 队列名称
* durable – true代表mq服务重启后,队列依然存在
* exclusive - true代表当前连接独占该队列,连接关闭,队列就消失了
* autoDelete - true队列不再使用时,直接删除此队列
* argments - 扩展参数
*/
channel.queueDeclare(HELLO_QUEUE, true, false, false, null);
// 5.读取消息
channel.basicConsume(HELLO_QUEUE, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("入门案例:消费端接收到消息:" + new String(body));
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.4.3 消费者
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 轮询
*/
public class HelloProducer {
private static final String HELLO_QUEUE = "hello_queue";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
//1.创建ConnectionFactory对象,用于生产Connection对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
//2.生产Connection对象
connection = connectionFactory.newConnection();
//3.通过连接对象创建通道
channel = connection.createChannel();
//4.声明一个队列
/**
* queue – 队列名称
* durable – true代表mq服务重启后,队列依然存在
* exclusive - true代表当前连接独占该队列,连接关闭,队列就消失了
* autoDelete - true队列不再使用时,直接删除此队列
* argments - 扩展参数
*/
channel.queueDeclare(HELLO_QUEUE,true,false,false,null);
//5.定义信息,发送到mq消息队列中
String msg = "hello RabbitMQ Producer";
//6.发送信息
/**
* exchange – 交换机
* routingKey – 路由Key
* props – 消息属性
* body – 消息内容
*/
channel.basicPublish("",HELLO_QUEUE,null,msg.getBytes());
System.out.println("入门案例:上传者发送信息队列 ===> " + msg);
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if(channel != null){
channel.close();
}
if(connection != null){
connection.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
2.4.4 总结
-
1 、发送端操作流程
1 )创建连接
2 )创建通道
3 )声明队列
4 )发送消息 -
2 、接收端
1 )创建连接
2 )创建通道
3 )声明队列
4 )监听队列
5 )接收消息
6 )ack回复
三、工作模式
3.1 Work queues
work queues与入门程序相比,多了一个消费端,两个消费端共同消费同一个队列中的消息。
-
应用场景:
对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
-
测试:
1 、使用入门程序,启动多个消费者。
2 、生产者发送多个消息。 -
结果:
1 、一条消息只会被一个消费者接收;
2 、rabbit采用轮询的方式将消息是平均发送给消费者的;
3 、消费者在处理完某条消息后,才会收到下一条消息。
3.2 Publish/subscribe
3.2.1 工作模式
发布订阅模式:
1 、每个消费者监听自己的队列。
2 、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息
3.2.2 代码实现
- 1 、生产者
声明Exchange_fanout_inform交换机。
声明两个队列并且绑定到此交换机,绑定时不需要指定routingkey
发送消息时不需要指定routingkey
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
/**
* publish工作模式 ===> 生产者
* 发布订阅
*/
public class PublishProducer {
private static final String PUBLISH_EXCHANGE = "publish_exchange";
private static final String PUBLISH_QUEUE_EMAIL = "publish_queue_email";
private static final String PUBLISH_QUEUE_SMS = "publish_queue_sms";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1.创建Connectionfactory对象,用于生产Connection对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
// 2.生产Connection对象
connection = connectionFactory.newConnection();
// 3.通过连接对象创建通道
channel = connection.createChannel();
// 4.声明交换机
channel.exchangeDeclare(PUBLISH_EXCHANGE, BuiltinExchangeType.FANOUT);
// 5.声明队列
/**
* queue – 队列名称
* durable – true代表mq服务重启后,队列依然存在
* exclusive - true代表当前连接独占该队列,连接关闭,队列就消失了
* autoDelete - true队列不再使用时,直接删除此队列
* argments - 扩展参数
*/
channel.queueDeclare(PUBLISH_QUEUE_EMAIL, true, false, false, null);
channel.queueDeclare(PUBLISH_QUEUE_SMS, true, false, false, null);
// 6.交换机绑定队列
/**
* queue – 队列名称
* exchange – 交换机名称
* routingKey – 路由Key
*/
channel.queueBind(PUBLISH_QUEUE_EMAIL, PUBLISH_EXCHANGE, "");
channel.queueBind(PUBLISH_QUEUE_SMS, PUBLISH_EXCHANGE, "");
// 7.发送消息
String msg = "publish msg producer";
channel.basicPublish(PUBLISH_EXCHANGE, "", null, msg.getBytes());
System.out.println("publish案例:生产者发送消息:" + msg);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (channel != null) {
channel.close();
}
if (connection != null) {
connection.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
- 2、消费者
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* publish-email工作模式 ===> 消费者
*/
public class PublishConsumerEmail {
private static final String PUBLISH_QUEUE_EMAIL = "publish_queue_email";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1.创建Connectionfactory对象,用于生产Connection对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
// 2.生产Connection对象
connection = connectionFactory.newConnection();
// 3.通过连接对象创建通道
channel = connection.createChannel();
// 5.声明队列
channel.queueDeclare(PUBLISH_QUEUE_EMAIL, true, false, false, null);
channel.basicConsume(PUBLISH_QUEUE_EMAIL, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("publish案例:消费端email接收到消息:" + new String(body));
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* publish-SMS工作模式 ===> 消费者
*/
public class PublishConsumerSMS {
private static final String PUBLISH_QUEUE_SMS = "publish_queue_sms";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1.创建Connectionfactory对象,用于生产Connection对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
// 2.生产Connection对象
connection = connectionFactory.newConnection();
// 3.通过连接对象创建通道
channel = connection.createChannel();
// 5.声明队列
channel.queueDeclare(PUBLISH_QUEUE_SMS,true,false,false,null);
channel.basicConsume(PUBLISH_QUEUE_SMS,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("publish案例:消费端sms接收到消息:"+new String(body));
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.2.3 思考
publish/subscribe与work queues有什么区别:
区别:
1 )work queues不用定义交换机,而publish/subscribe需要定义交换机。
2 )publish/subscribe的生产方是面向交换机发送消息,work queues的生产方是面向队列发送消息(底层使用默认交换机)。
3 )publish/subscribe需要设置队列和交换机的绑定,work queues不需要设置,实质上work queues会将队列绑定到默认的交换机 。相同点:
1、两者实现的发布/订阅的效果是一样的,多个消费端监听同一个队列不会重复消费消息。
2 、实质工作用什么 publish/subscribe还是work queues。
建议使用 publish/subscribe,发布订阅模式比工作队列模式更强大,并且发布订阅模式可以指定自己专用的交换机。
3.3 Routing
3.3.1 工作模式
路由模式:
1 、每个消费者监听自己的队列,并且设置routingkey。
2 、生产者将消息发给交换机,由交换机根据routingkey来转发消息到指定的队列。
3.3.2 代码实现
- 1 、生产者
声明exchange_routing_inform交换机。
声明两个队列并且绑定到此交换机,绑定时需要指定routingkey
发送消息时需要指定routingkey
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
/**
* routing 路由模式 ===> 生产者
*/
public class RoutingProducer {
private static final String ROUTING_EXCHANGE = "routing_exchange";
private static final String ROUTING_QUEUE_EMAIL = "routing_queue_email";
private static final String ROUTING_QUEUE_SMS = "routing_queue_sms";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1.创建Connectionfactory对象,用于生产Connection对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
// 2.生产Connection对象
connection = connectionFactory.newConnection();
// 3.通过连接对象创建通道
channel = connection.createChannel();
// 4.声明交换机
channel.exchangeDeclare(ROUTING_EXCHANGE, BuiltinExchangeType.DIRECT);
// 5.声明队列
channel.queueDeclare(ROUTING_QUEUE_EMAIL,true,false,false,null);
channel.queueDeclare(ROUTING_QUEUE_SMS,true,false,false,null);
// 6.交换机绑定队列
channel.queueBind(ROUTING_QUEUE_EMAIL,ROUTING_EXCHANGE,ROUTING_QUEUE_EMAIL);
channel.queueBind(ROUTING_QUEUE_SMS,ROUTING_EXCHANGE,ROUTING_QUEUE_SMS);
// 7.发送消息
String emailMsg = "routing email msg producer";
channel.basicPublish(ROUTING_EXCHANGE,ROUTING_QUEUE_EMAIL,null,emailMsg.getBytes());
String smsMsg = "routing sms msg producer";
channel.basicPublish(ROUTING_EXCHANGE,ROUTING_QUEUE_SMS,null,smsMsg.getBytes());
System.out.println("routing案例:生产者发送消息:"+emailMsg+" "+smsMsg);
} catch (Exception e) {
e.printStackTrace();
} finally {
try{
if(channel != null){
channel.close();
}
if(connection != null){
connection.close();
}
} catch (Exception e){
e.printStackTrace();
}
}
}
}
- 消费者
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* routing-email工作模式 ===> 消费者
*/
public class RoutingConsumerEmail {
private static final String ROUTING_QUEUE_EMAIL = "routing_queue_email";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1.创建Connectionfactory对象,用于生产Connection对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
// 2.生产Connection对象
connection = connectionFactory.newConnection();
// 3.通过连接对象创建通道
channel = connection.createChannel();
// 5.声明队列
channel.queueDeclare(ROUTING_QUEUE_EMAIL,true,false,false,null);
channel.basicConsume(ROUTING_QUEUE_EMAIL,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("routing案例:消费端email接收到消息:"+new String(body));
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* routing-sms工作模式 ===> 消费者
*/
public class RoutingConsumerSMS {
private static final String ROUTING_QUEUE_SMS = "routing_queue_sms";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1.创建Connectionfactory对象,用于生产Connection对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
// 2.生产Connection对象
connection = connectionFactory.newConnection();
// 3.通过连接对象创建通道
channel = connection.createChannel();
// 5.声明队列
channel.queueDeclare(ROUTING_QUEUE_SMS,true,false,false,null);
channel.basicConsume(ROUTING_QUEUE_SMS,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("routing案例:消费端sms接收到消息:"+new String(body));
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.3.3 思考
Routing模式和Publish/subscibe有啥区别?
Routing模式要求队列在绑定交换机时要指定routingkey,消息会转发到符合routingkey的队列。
3.4 Topic
3.4.1 工作模式
路由模式:
1 、每个消费者监听自己的队列,并且设置带统配符的routingkey。
2 、生产者将消息发给broker,由交换机根据routingkey来转发消息到指定的队列。
3.4.2 代码实现
- 1 、生产者
声明交换机,指定topic类型:
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;
/**
* topics路由模式 ===> 生产者
*/
public class TopicsProducer {
private static final String TOPICS_EXCHANGE = "topics_exchange";
private static final String TOPICS_QUEUE_EMAIL = "topics_queue_email";
private static final String TOPICS_QUEUE_SMS = "topics_queue_sms";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1.创建Connectionfactory对象,用于生产Connection对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
// 2.生产Connection对象
connection = connectionFactory.newConnection();
// 3.通过连接对象创建通道
channel = connection.createChannel();
// 4.声明交换机
channel.exchangeDeclare(TOPICS_EXCHANGE, BuiltinExchangeType.TOPIC);
// 5.声明队列
channel.queueDeclare(TOPICS_QUEUE_EMAIL, true, false, false, null);
channel.queueDeclare(TOPICS_QUEUE_SMS, true, false, false, null);
// 6.交换机绑定队列
channel.queueBind(TOPICS_QUEUE_EMAIL, TOPICS_EXCHANGE, "inform.#.email.#");//可以接收inform.xxx.email.xxx的信息
channel.queueBind(TOPICS_QUEUE_SMS, TOPICS_EXCHANGE, "inform.#.sms.#");
// 7.发送消息
String emailMsg = "routing email msg producer";
channel.basicPublish(TOPICS_EXCHANGE, "inform.email", null, emailMsg.getBytes());
String smsMsg = "routing sms msg producer";
channel.basicPublish(TOPICS_EXCHANGE, "inform.sms", null, smsMsg.getBytes());
String msg = "都得有的消息 all";
channel.basicPublish(TOPICS_EXCHANGE, "inform.sms.email", null, msg.getBytes());
System.out.println("routing案例:生产者发送消息:" + emailMsg + " " + smsMsg + " " + msg);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (channel != null) {
channel.close();
}
if (connection != null) {
connection.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
- 2、消费端
队列绑定交换机指定通配符:
统配符规则:- 中间以“.”分隔。
- 符号#可以匹配多个词,符号*可以匹配一个词语。
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* topics-email工作模式 ===> 消费者
*/
public class TopicConsumerEmail {
private static final String TOPICS_QUEUE_EMAIL = "topics_queue_email";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1.创建Connectionfactory对象,用于生产Connection对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
// 2.生产Connection对象
connection = connectionFactory.newConnection();
// 3.通过连接对象创建通道
channel = connection.createChannel();
// 5.声明队列
channel.queueDeclare(TOPICS_QUEUE_EMAIL, true, false, false, null);
channel.basicConsume(TOPICS_QUEUE_EMAIL, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("topic案例:消费端email接收到消息:" + new String(body));
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* topics-sms工作模式 ===> 消费者
*/
public class TopicConsumerSMS {
private static final String TOPICS_QUEUE_SMS = "topics_queue_sms";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1.创建Connectionfactory对象,用于生产Connection对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
// 2.生产Connection对象
connection = connectionFactory.newConnection();
// 3.通过连接对象创建通道
channel = connection.createChannel();
// 5.声明队列
channel.queueDeclare(TOPICS_QUEUE_SMS, true, false, false, null);
channel.basicConsume(TOPICS_QUEUE_SMS, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("topic案例:消费端sms接收到消息:" + new String(body));
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.4.3 思考
本案例的需求使用Routing工作模式能否实现?
使用Routing模式也可以实现本案例,共设置三个 routingkey,分别是email、sms、all,email队列绑定email和all,sms队列绑定sms和all,这样就可以实现上边案例的功能,实现过程比topics复杂。
Topic模式更多加强大,它可以实现Routing、publish/subscirbe模式的功能。
3.5 Header
header模式与routing不同的地方在于,header模式取消routingkey,使用header中的 key/value(键值对)匹配队列。
-
案例:
根据用户的通知设置去通知用户,设置接收Email的用户只接收Email,设置接收sms的用户只接收sms,设置两种
通知类型都接收的则两种通知都有效。 -
1 )生产者
队列与交换机绑定的代码与之前不同,如下:
import com.rabbitmq.client.*;
import java.util.HashMap;
/**
* header工作描述 ===> 生产者
*/
public class HeaderProducer {
private static final String HEADER_EXCHANGE = "header_exchange";
private static final String HEADER_QUEUE_EMAIL = "header_queue_email";
private static final String HEADER_QUEUE_SMS = "header_queue_sms";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1.创建Connectionfactory对象,用于生产Connection对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
// 2.生产Connection对象
connection = connectionFactory.newConnection();
// 3.通过连接对象创建通道
channel = connection.createChannel();
// 4.声明交换机
channel.exchangeDeclare(HEADER_EXCHANGE, BuiltinExchangeType.HEADERS);
// 5.声明队列
channel.queueDeclare(HEADER_QUEUE_EMAIL,true,false,false,null);
channel.queueDeclare(HEADER_QUEUE_SMS,true,false,false,null);
// 6.交换机绑定队列
HashMap<String, Object> emailMap = new HashMap<>();
emailMap.put("inform_type","email");
channel.queueBind(HEADER_QUEUE_EMAIL,HEADER_EXCHANGE,"",emailMap);
HashMap<String, Object> smsMap = new HashMap<>();
smsMap.put("inform_type","sms");
channel.queueBind(HEADER_QUEUE_SMS,HEADER_EXCHANGE,"",smsMap);
// 7.发送消息
String emailMsg = "Header模式 email msg 你真棒";
AMQP.BasicProperties.Builder emailBuilder = new AMQP.BasicProperties.Builder();
emailBuilder.headers(emailMap);
channel.basicPublish(HEADER_EXCHANGE,"",emailBuilder.build(),emailMsg.getBytes());
String smsMsg = "Header模式 sms msg 你真棒";
AMQP.BasicProperties.Builder smsBuilder = new AMQP.BasicProperties.Builder();
smsBuilder.headers(smsMap);
channel.basicPublish(HEADER_EXCHANGE,"",smsBuilder.build(),smsMsg.getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
try{
if(channel != null){
channel.close();
}
if(connection != null){
connection.close();
}
} catch (Exception e){
e.printStackTrace();
}
}
}
}
- 2 )消费者
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Map;
//Header-email工作模式 ===> 消费者
public class HeaderConsumerEmail {
private static final String HEADER_QUEUE_EMAIL = "header_queue_email";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1.创建Connectionfactory对象,用于生产Connection对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
// 2.生产Connection对象
connection = connectionFactory.newConnection();
// 3.通过连接对象创建通道
channel = connection.createChannel();
// 5.声明队列
Map<String, Object> emailMap = new Hashtable<>();
emailMap.put("inform_type", "email");
channel.queueDeclare(HEADER_QUEUE_EMAIL, true, false, false, emailMap);
channel.basicConsume(HEADER_QUEUE_EMAIL, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("headers案例:消费端email接收到消息:" + new String(body));
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Map;
//Header-SMS工作模式 ===> 消费者
public class HeaderConsumerSMS {
private static final String HEADER_QUEUE_SMS = "header_queue_sms";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1.创建Connectionfactory对象,用于生产Connection对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
// 2.生产Connection对象
connection = connectionFactory.newConnection();
// 3.通过连接对象创建通道
channel = connection.createChannel();
// 5.声明队列
Map<String, Object> emailMap = new Hashtable<>();
emailMap.put("inform_type", "email");
channel.queueDeclare(HEADER_QUEUE_SMS, true, false, false, emailMap);
channel.basicConsume(HEADER_QUEUE_SMS, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("headers案例:消费端sms接收到消息:" + new String(body));
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.6 RPC
RPC即客户端远程调用服务端的方法 ,使用MQ可以实现RPC的异步调用,基于Direct交换机实现,流程如下:
- 1 、客户端即是生产者就是消费者,向RPC请求队列发送RPC调用消息,同时监听RPC响应队列。
- 2 、服务端监听RPC请求队列的消息,收到消息后执行服务端的方法,得到方法返回的结果
- 3 、服务端将RPC方法 的结果发送到RPC响应队列
- 4 、客户端(RPC调用方)监听RPC响应队列,接收到RPC调用结果。
四、SpringBoot整合RabbitMQ
4.1 搭建SpringBoot环境
选择基于Spring-Rabbit去操作RabbitMQ
使用spring-boot-starter-amqp会自动添加spring-rabbit依赖,如下:
<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>
4.2 配置
- 第一步、配置application.yml
配置连接rabbitmq的参数
server:
port: 8080
spring:
application:
name: test-springboot-mq
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
- 第二步、定义RabbitConfig类,配置Exchange、Queue、及绑定交换机。
本例配置Topic交换机。
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 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";
/**
*交换机配置
*ExchangeBuilder提供了fanout、direct、topic、header交换机类型的配置
*@return the exchange
*/
@Bean(EXCHANGE_TOPICS_INFORM)
public Exchange EXCHANGE_TOPICS_INFORM() {
//durable(true)持久化,消息队列重启后交换机仍然存在
return ExchangeBuilder.topicExchange(EXCHANGE_TOPICS_INFORM).durable(true).build();
}
//声明队列
@Bean(QUEUE_INFORM_SMS)
public Queue QUEUE_INFORM_SMS() {
Queue queue = new Queue(QUEUE_INFORM_SMS);
return queue;
}
//声明队列
@Bean(QUEUE_INFORM_EMAIL)
public Queue QUEUE_INFORM_EMAIL() {
Queue queue = new Queue(QUEUE_INFORM_EMAIL); return queue;
}
/** channel.queueBind(INFORM_QUEUE_SMS,"inform_exchange_topic","inform.#.sms.#");
*绑定队列到交换机 .
*
*@param queue the queue
*@param exchange the exchange
*@return the binding
*/
@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();
}
@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();
}
}
4.3 生产端
import com.lxw.test.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 Producer05_topics_springboot {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
public void testSendByTopics(){ for (int i=0;i<5;i++){
String message = "sms email inform to user"+i; rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM,"inform.sms.email",message); System.out.println("Send Message is:'" + message + "'");
}
}
}
4.4 消费端
4.5 测试
启动springboot后可直接在控制台看到队列中的消息,也可通过控制面板进行观察
每日一点点进步
不进则退