MQ消息中间件
MQ(Message Queue)译为消息队列,是应用程序与应用程序之间通信的一种方式。
为什么使用消息队列,适用于那些场景?
消息队列的3大作用:1.发送异步消息 2.解耦 3.削峰
-
(服务器A)通过发送异步消息给消息队列并交给消息接受方(服务器B),让消息接收方完成异步消息中需要做的操作。
消息接受方需要完成的操作,一般是耗费时间过长而不需要同步处理的操作
上图中的‘同步处理’位置若是有10个操作,并且全部采用同步处理的方式(即没有发送异步消息)。程序的结束需要等待10个同步操作结束,等待时间过长。
MQ的出现给我们提供了改进方式,例如,服务器A只需要完成10个操作里最重要的某一个或几个,其他操作交给服务器B。
怎么让服务器B帮我们执行接下来的异步操作呢?
答:让服务器B监听消息队列,收到服务器A发送过来的消息,就可以帮助服务器A完成剩下的操作。 -
MQ在微服务架构中充当了一个‘中介’的角色,通过MQ的机制,使得服务器A与服务器B进行交互,共同完成来着客服端的操作。
用交互的方式共同完成业务,会将应用程序解耦,更方便维护和更新业务。
例如: 在一个单体架构中,对一个业务增添功能,需要在业务逻辑的代码段中增添功能,还需要重新发布系统。
在微服务架构中,只需要让服务器将消息发送出去,让另外的服务器帮助完成新功能。 -
在实际应用场景中,数据库或许每秒只能处理1000次请求,但是应用程序使用的高峰期时,一秒可能有5000次请求,就需要使用到MQ来解决高负载的问题。
如何削峰呢?
把5000次请求放到队列里,数据库每次能处理多少,就拿走多少。如果能处理1000个,就先拿走1000个请求处理。
MQ的实现方式
-
MQ主要有两种实现方式:一是AMQP,二是JMS
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
JMS即Java消息服务(JavaMessage Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
-
AMQP和JMS的区别
- JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
- JMS限定了使用java语言来实现,AMQP只是协议,不限定语言符合协议即可。
- JMS中只有两种消息模式,AMQP中有多种消息模式。
RabbitMQ
基于AMQP协议,用erlang语言实现的消息队列。
- RabbitMQ中间件的使用需要物理机安装此服务。
- 为了节省安装时间,此处将用docker方式拉取RabbitMQ镜像
通过docker安装RabbitMQ服务
-
录取RabbitMQ镜像
docker pull rabbitmq:3.6
-
查看RabbitMQ镜像ID
docker ps -a
-
后台运行RabbitMQ容器,并映射端口
docker run -itd -p 5672:5672 -p 15672:15672 --name RabbitMQ 9df8
-
使用浏览器看是否能进入RabbitMQ的web界面以此来测试是否配置成功。
此处是在虚拟机中使用docker并做好端口映射来安装Rabbit服务,我的虚拟机IP地址是192.168.31.81
RabbitMQ的默认用户名和密码都是guest
Rabbit相关概念
如上图,可以看到导航条中出现Channels Exchange Queues等信息,在RabbitMQ的核心概念中远不止这些内容
概念 | 说明 |
---|---|
Connection | 顾名思义,就是网络连接 |
Channels | 信道,多路复用连接中的一条独立的双向数据流通道。发布和接受消息都要使用到。 |
Exchange | 接收消息,并根据路由键将消息保存到匹配的队列上。交换机有4个类型: direct(默认), fanout, topic, 和headers |
Broker | 消息中间件的服务节点;一般情况下可以将一个RabbitMQ Broker看作一台RabbitMQ 服务器。 |
Queue | 消息队列,用来保存消息直到发送给消费者。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。 |
Producer | 一个向交换机发布消息的客户端应用程序。 |
Consumer | 一个从消息队列里请求消息的客户端程序。 |
RabbitMQ的简单实现
RaabitMQ提供了5种模式,分别是:简单模式,work模式,publish/Subscribe(发布与订阅)模式,Routing(路由)模式,Topics(主题)模式。
简单模式
- 简单模式就是一个生产者和一个消费者以及一个队列的情况
- RabbitMQ的简单模式的实现统统使用
amqp-client
依赖中的工具。 - 导入依赖坐标
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
- 需要手动连接到RabbitMQ服务器上,编写一个连接类。
package com.example.Utils;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class ConnectionUtil {
public static Connection getConnetion() throws Exception{
ConnectionFactory connectionFactory = new ConnectionFactory();
//配置ip地址
connectionFactory.setHost("192.168.31.81");
//配置端口
connectionFactory.setPort(5672);
//配置虚拟主机
connectionFactory.setVirtualHost("/zyr");
//配置账号密码
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
Connection connection = connectionFactory.newConnection();
return connection;
}
}
- 编写消息的生产者
package com.example.mq;
import com.example.Utils.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import java.nio.charset.StandardCharsets;
public class simpleMqProduceor {
static final String QUEUE_NAME = "simple_queue";
public static void main(String[] args) throws Exception {
//通过工具类获取连接
Connection connetion = ConnectionUtil.getConnetion();
//创建信道
Channel channel = connetion.createChannel();
//通过信道声明队列
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//定义消息内容
String message = "hello zyr !";
//第一个参数是交换机名称,默认的交换机就是""
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
//发送完毕关闭信道和断开连接
channel.close();
connetion.close();
}
}
此处可能出现报错,原因是在RabbitMQ中还没创建"/zyr"的虚拟目录,同时也没有给guest账号访问此虚拟目录的权限。
- 在web端界面中的queue中检查队列的情况
- 编写消费者
package com.example.mq;
import com.example.Utils.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class simpleMqConsumer {
public static void main(String[] args) throws Exception{
Connection connetion = ConnectionUtil.getConnetion();
//创建信道
Channel channel = connetion.createChannel();
//通过信道声明队列
channel.queueDeclare(simpleMqProduceor.QUEUE_NAME,true,false,false,null);
//创建消费者并设置消息处理--消息的处理通过重写方法实现
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException, UnsupportedEncodingException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("接收到的消息为:" + new String(body, "utf-8"));
}
};
//监听消息
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(simpleMqProduceor.QUEUE_NAME, true, consumer);
}
}
work模式
work模式中存在的情况是一个生产者,一个队列,多个消费者。在这种模式下消费者对队列中的请求是一种竞争状态
- 生产者的编写和简单模式中一样,不同的是,我们需要发送多个消息对队列中来进行实验。
package com.example.workMq;
import com.example.Utils.ConnectionUtil;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class workMqProduceor {
static final String QUEUE_WORK = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connetion = ConnectionUtil.getConnetion();
Channel channel = connetion.createChannel();
channel.queueDeclare(QUEUE_WORK,true,false,false,null);
for(int i = 0; i<10; i++){
String message = "hello zyr " + i;
channel.basicPublish("",QUEUE_WORK,null,message.getBytes());
}
channel.close();
connetion.close();
}
}
- 编写2个消费进行实验
package com.example.workMq;
import com.example.Utils.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class workMqConsumer {
public static void main(String[] args) throws Exception {
Connection connetion = ConnectionUtil.getConnetion();
Channel channel = connetion.createChannel();
channel.queueDeclare(workMqProduceor.QUEUE_WORK,true,false,false,null);
//设置一次只接受一个消息
channel.basicQos(1);
//声明接受消息的consumer对象
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者1-接收到的消息为:" + new String(body, "utf-8"));
Thread.sleep(1000);
//确认消息
channel.basicAck(envelope.getDeliveryTag(), false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
channel.basicConsume(workMqProduceor.QUEUE_WORK,false,defaultConsumer);
}
}
package com.example.workMq;
import com.example.Utils.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class workMqConsumerTwo {
public static void main(String[] args) throws Exception {
Connection connetion = ConnectionUtil.getConnetion();
Channel channel = connetion.createChannel();
channel.queueDeclare(workMqProduceor.QUEUE_WORK,true,false,false,null);
//设置一次只接受一个消息
channel.basicQos(1);
//声明接受消息的consumer对象
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者2-接收到的消息为:" + new String(body, "utf-8"));
Thread.sleep(1000);
//确认消息
channel.basicAck(envelope.getDeliveryTag(), false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
channel.basicConsume(workMqProduceor.QUEUE_WORK,false,defaultConsumer);
}
}
-
work模式下的结果
-
通过实验结果也可以看出来work模式下,多个消费者对于队列中的消息是在一个竞争的状态
可以留意到在上述代码中设置监听的第二个参数用了false,代表当消息被接受后,不会自动的向队列返回一个’确认收到的消息’。在work模式的代码中,使用手动发送’确认收到的消息’给队列的方式,channel.basicAck(envelope.getDeliveryTag(), false);
注意:只有队列收到了这个’确认收到的消息’才会在队列中将这个被消费了的消息进行删除。
订阅模式
在简单模式和work模式中,存在的角色都是只有队列,生产者,消费者。但是从订阅模式开始,就多了一种角色—交换机(Exchange)。
-
每个消费者监听一个队列
-
交换机只会接收转发消息,不会保存消息。
-
发布订阅模式就是将交换机设置成了广播模式!!
问:以何种方式转发?
答:交换机可以设置各种类型,例如:- Fanout:广播,将消息交给所有绑定到交换机的队列
- Direct:定向,把消息交给符合指定routing key 的队列
- Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
-
生产者的编写
package com.example.publishMq;
import com.example.Utils.ConnectionUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
public class publishMqProduceor {
//交换机名称
static final String FANOUT_EXCHAGE = "fanout_exchange";
//队列名称
static final String FANOUT_QUEUE_1 = "fanout_queue_1";
//队列名称
static final String FANOUT_QUEUE_2 = "fanout_queue_2";
public static void main(String[] args) throws Exception {
Connection connetion = ConnectionUtil.getConnetion();
Channel channel = connetion.createChannel();
//声明交换机,对齐设置名称和类型,类型为广播模式
channel.exchangeDeclare(FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT);
channel.queueDeclare(FANOUT_QUEUE_1,true,false,false,null);
channel.queueDeclare(FANOUT_QUEUE_2,true,false,false,null);
//将交换机和队列进行绑定
channel.queueBind(FANOUT_QUEUE_1, FANOUT_EXCHAGE, "");
channel.queueBind(FANOUT_QUEUE_2, FANOUT_EXCHAGE, "");
String message = "hello zyr ";
channel.basicPublish(FANOUT_EXCHAGE,"",null,message.getBytes());
channel.close();
connetion.close();
}
}
- 消费者的编写
package com.example.publishMq;
import com.example.Utils.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class publishMqConsumer {
public static void main(String[] args) throws Exception {
Connection connetion = ConnectionUtil.getConnetion();
Channel channel = connetion.createChannel();
//channel.exchangeDeclare(publishMqProduceor.FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT);
channel.queueDeclare(publishMqProduceor.FANOUT_QUEUE_1,true,false,false,null);
//将队列和交换机绑定
//channel.queueBind(publishMqProduceor.FANOUT_QUEUE_1,publishMqProduceor.FANOUT_EXCHAGE,"");
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者1-接收到的消息为:" + new String(body, "utf-8"));
}
};
channel.basicConsume(publishMqProduceor.FANOUT_QUEUE_1, true, consumer);
}
}
package com.example.publishMq;
import com.example.Utils.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class publishMqConsumerTwo {
public static void main(String[] args) throws Exception {
Connection connetion = ConnectionUtil.getConnetion();
Channel channel = connetion.createChannel();
//channel.exchangeDeclare(publishMqProduceor.FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT);
channel.queueDeclare(publishMqProduceor.FANOUT_QUEUE_2,true,false,false,null);
//将队列和交换机绑定
//channel.queueBind(publishMqProduceor.FANOUT_QUEUE_2,publishMqProduceor.FANOUT_EXCHAGE,"");
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者2-接收到的消息为:" + new String(body, "utf-8"));
}
};
channel.basicConsume(publishMqProduceor.FANOUT_QUEUE_2, true, consumer);
}
}
- 订阅与发布模式实验结果
在发布订阅模式的简单实验中可以看到,生产者通过
channel.basicPublish
方法中第一个参数指定交换机(其中第二个参数是路由键,发布订阅模式中交换机是广播形式,不用指定。在简单和work模式中这个参数填队列名称),向交换机发送了一条信息,但是与此交换机绑定的队列都能收到此消息。
路由模式
路由模式中存在的角色和发布订阅模式中一致,不同的是设置的交换机类型不在是广播模式,意味发送到交换机的消息不会被转发到所有与交换机绑定的队列上。
-
发送到交换机上的消息,需要携带一个路由键
-
路由键主要是用来让交换机给我们匹配符合的队列
-
为了让消息和队列匹配,在进行交换机和队列的绑定时,也需要给队列设置一个路由键。
-
消费者接收消息时也需要在绑定交换机和队列时候,指定路由键
-
编写生产者
package com.example.directMq;
import com.example.Utils.ConnectionUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
public class directMqProduceor {
//交换机名称
static final String DIRECT_EXCHAGE = "direct_exchange";
//队列名称
static final String DIRECT_QUEUE_ZYR = "direct_queue_zyr";
//队列名称
static final String DIRECT_QUEUE_HELLO = "direct_queue_HELLO";
public static void main(String[] args) throws Exception {
Connection connetion = ConnectionUtil.getConnetion();
Channel channel = connetion.createChannel();
//声明交换机,并指明起类型为路由
channel.exchangeDeclare(DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);
//声明队列
channel.queueDeclare(DIRECT_QUEUE_ZYR,true,false,false,null);
channel.queueDeclare(DIRECT_QUEUE_HELLO,true,false,false,null);
//将交换机和队列进行绑定,并且指明绑定的路由键
channel.queueBind(DIRECT_QUEUE_ZYR,DIRECT_EXCHAGE,"zyr");
channel.queueBind(DIRECT_QUEUE_HELLO,DIRECT_EXCHAGE,"hello");
String message = "direct zyr";
//发送消息,并指明消息携带的路由键
channel.basicPublish(DIRECT_EXCHAGE,"zyr",null,message.getBytes());
message = "direct hello ";
channel.basicPublish(DIRECT_EXCHAGE,"hello",null,message.getBytes());
channel.close();
connetion.close();
}
}
- 编写消费者
package com.example.directMq;
import com.example.Utils.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class directMqConsumer {
public static void main(String[] args) throws Exception {
Connection connetion = ConnectionUtil.getConnetion();
Channel channel = connetion.createChannel();
//channel.exchangeDeclare(directMqProduceor.DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);
channel.queueDeclare(directMqProduceor.DIRECT_QUEUE_ZYR,true,false,false,null);
//绑定交换机和队列,并且指明路由键
//channel.queueBind(directMqProduceor.DIRECT_QUEUE_ZYR,directMqProduceor.DIRECT_EXCHAGE,"zyr");
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者1-接收到的消息为:" + new String(body, "utf-8"));
}
};
channel.basicConsume(directMqProduceor.DIRECT_QUEUE_ZYR,true,consumer);
}
}
package com.example.directMq;
import com.example.Utils.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class directMqConsumerTwo {
public static void main(String[] args) throws Exception {
Connection connetion = ConnectionUtil.getConnetion();
Channel channel = connetion.createChannel();
//channel.exchangeDeclare(directMqProduceor.DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);
channel.queueDeclare(directMqProduceor.DIRECT_QUEUE_HELLO,true,false,false,null);
//channel.queueBind(directMqProduceor.DIRECT_QUEUE_HELLO,directMqProduceor.DIRECT_EXCHAGE,"hello");
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException, UnsupportedEncodingException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者2-接收到的消息为:" + new String(body, "utf-8"));
}
};
channel.basicConsume(directMqProduceor.DIRECT_QUEUE_HELLO,true,consumer);
}
}
在路由模式的代码实现中,在生产者中声明路由类型的交换机,并声明2个队列,将交换机和队列进行绑定时,就开始和发布订阅模式不同----
channel.queueBind
方法中的第三个参数传入了一个路由键。同时发送消息传参时,也传入了路由键。
通配符模式
通配符模式是将队列和交换机绑定上的通配符和消息发送时携带的路由键进行匹配。
- #:能匹配一个或者多个词
- *:只能匹配一个词
- 生产者编写
package com.example.topicMq;
import com.example.Utils.ConnectionUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
public class topicMqProduceor {
//交换机名称
static final String TOPIC_EXCHAGE = "topic_exchange";
//队列名称
static final String TOPIC_QUEUE_1 = "topic_queue_1";
public static void main(String[] args) throws Exception {
Connection connetion = ConnectionUtil.getConnetion();
Channel channel = connetion.createChannel();
channel.exchangeDeclare(TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC);
//声明要发送的消息
String message = "hello topic zyr queue 1";
channel.basicPublish(TOPIC_EXCHAGE,"hello.zyr",null,message.getBytes());
channel.close();
connetion.close();
}
}
- 消费者的编写
package com.example.topicMq;
import com.example.Utils.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class topicMqConsumer {
public static void main(String[] args) throws Exception {
Connection connetion = ConnectionUtil.getConnetion();
Channel channel = connetion.createChannel();
channel.exchangeDeclare(topicMqProduceor.TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC);
channel.queueDeclare(topicMqProduceor.TOPIC_QUEUE_1,true,false,false,null);
channel.queueBind(topicMqProduceor.TOPIC_QUEUE_1,topicMqProduceor.TOPIC_EXCHAGE,"hello.#");
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者-接收到的消息为:" + new String(body, "utf-8"));
}
};
channel.basicConsume(topicMqProduceor.TOPIC_QUEUE_1,true,consumer);
}
}
-
先启动消费者,再启动生产者的实现结果
-
当先启动生产者时,可以看到MQ服务中已经出现了交换机,但是交换机信息中并没有绑定信息。
-----------------------------------------------------------------------------------------交换机出现了,那消息呢?
当消息发送到没有绑定队列的交换机上时,消息将被丢弃。 -
启动消费者之后,消费者一直处于监听状态,同时可以看到在MQ服务中出现消费者定义的队列。
-
再一次启动生产者,就得当上述刚刚出现的实验结果。
-
此后,结束消费者的运行状态,重复两次执行生产者,可以在MQ服务中看到队列中有2个消息。可以看到生产者的消息,没有丢失,而是成功保存到了队列中。
经过先启动上述在通配符模式中的测试,有以下几点:
- 交换机没有绑定队列时,消息将会丢失
- 交换机和队列的绑定,会直接在MQ服务中绑定上,不用重复在消费者和生产者之间进行绑定。