MQ全称Message Queue (消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。
用户从订单系统向库存系统、支付系统等这一链路发送消息,如果库存系统出现错误,可能也会导致订单系统出错,从而给用户带来不好的体验。
并且如果要在该下单链路上添加新的服务,为了完成服务间的通信,只能修改订单系统的代码。
如果在订单系统和后面的系统中间加一个消息中间件,情况就会好很多。
假设库存系统出错了,这并不会影响到订单系统,因为中间有一个Mq隔离,有时库存系统出错时暂时的,等一会儿库存系统被维护好了,可以继续从Mq中重新取出消息进行操作。
并且如果需要新增一个服务,也不需要更改订单系统的代码。
Mq使得应用间解耦提升容错性和可维护性
在黑马点评中就用到了异步提速,不过没有使用Mq,而是使用的java阻塞队列(后面改成了redis的消息中间件)
原始得订单系统是同步进行的,需要各个服务都完成之后才向用户返回信息
使用Mq后可以在订单系统将消息发送到Mq后直接向用户返回信息,后面的库存系统和支付系统可以慢慢的从Mq中取出消息去执行
提升用户体验和系统吞吐量(单位时间内处理请求的数目)。
既然MQ有优势也有劣势,那么使用MQ需要满足什么条件呢?
①产者不需要从消费者处获得反馈。引入消息队列之前的直接调用,其接口的返回值应该为空,这才让明明下层的动作还没做,.上层却当成动作做完了继续往后走,即所谓异步成为了可能。
②容许短暂的不一致性。
③确实是用了有效果。即解耦、提速、削峰这些方面的收益,超过加入MQ,管理MQ这些成本。
目前业界有很多的MQ产品,例如RabbitMQ、RocketMQ、ActiveMQ、Kafka、 ZeroMQ、MetaMq等,也有直接使用Redis充当消息队列的案例 (黑马点评就是) ,而这些消息队列产品,各有侧重,在实际选型时,需要结合自身需求及MQ产品特征,综合考虑。
AMQP,即Advanced Message Queuing Protosol (高级消息队列协议) , 是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。2006年, AMQP规范发布。类比HTTP。
AMQP协议流程图
2007年,Rabbit 技术公司基于AMQP标准开发的RabbitMQ 1.0发布。RabbitMQ 采用Erlang语言开发。Erlang语言由Ericson设计,专为开发高并发和分布式系统的一种语言,在电信领域使用广泛。
①中间这一块是服务端,生产者和消费者会与服务段建立connect连接,频繁的建立connect连接会影响性能消耗资源,所以在一个connect连接中有很多channel,可以通过channel建立连接。
②服务端的内部有很多的虚拟机(virtual host)
③虚拟机中有交换机和队列,交换机通过一些规则和队列绑定
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种工作模式: ①简单模式、②work queues、③Routing路由模式、④Topics 主题模式、⑤Publish/Subscribe 发布与订阅模式、⑥RPC远程调用模式(远程调用,不太算MQ;暂不作介绍)。
官网对应模式介绍: https://www.rabbitmq.com/getstarted.html
自行在网上找教程
简单模式:
使用一个入门程序了解生产者和消费者的代码编写:
步骤:
①创建工程(生产者,消费者)
②分别添加依赖
③编写生产者发送消息
④编写消费者接收消息
生产者代码:
工厂参数要和rabbitmq部署时严格一致,以及和rabbitmq management网页中设置的参数一致
package ratbbitMq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author Watching
* * @date 2023/7/12
* * Describe:消息队列生产者
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置工厂参数
connectionFactory.setHost("1.12.244.105");//ip地址,默认localhost
connectionFactory.setVirtualHost("itcast");//虚拟机名称 默认 /
connectionFactory.setPort(5673);//端口号 默认值 5672
connectionFactory.setUsername("heima");//用户名 默认 guest
connectionFactory.setPassword("heima");//密码 默认 guest
//3.创建connection连接
Connection connection = connectionFactory.newConnection();
//4.创建channel连接
Channel channel = connection.createChannel();
//5.创建队列
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments)
queue:队列名称
durable:是否持久化,重启后是否存在
exclusive:是否独占,只能有一个交换机与其绑定,当connection关闭时,是否删除队列
autoDelete:是否自动删除,当没有consumer的时候,是否删除队列
arguments:参数
*/
channel.queueDeclare("hello_world", true, false, false, null);
//6.发送消息
/*
basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
exchange:交换机名称,简单模式使用默认的交换机
routingKey:路由名称,简单模式下为queue名称
props:配置信息
body:消息
*/
channel.basicPublish("","hello_world",null,"你好".getBytes());
channel.close();
connection.close();
}
}
消费者代码:
消费者代码和生产者代码创建连接工厂、连接、channel的部分相同,需要注意的是,使用channel消费消息时需要实现一个回调函数。
package rabbitMq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author Watching
* * @date 2023/7/12
* * Describe:
* 消费者
*/
public class consumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("1.12.244.105");
connectionFactory.setVirtualHost("itcast");//虚拟机名称 默认 /
connectionFactory.setPort(5673);//端口号
connectionFactory.setUsername("heima");//用户名 默认 guest
connectionFactory.setPassword("heima");//密码 默认 guest
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
queue:队列名称
autoAck:是否自动确认消息
callback:回调函数
*/
channel.basicConsume("hello_world", true, new Consumer() {
@Override
public void handleConsumeOk(String s) {
}
@Override
public void handleCancelOk(String s) {
}
@Override
public void handleCancel(String s) throws IOException {
}
@Override
public void handleShutdownSignal(String s, ShutdownSignalException e) {
}
@Override
public void handleRecoverOk(String s) {
}
/*
s:标识
envelope:获取一些信息,交换机,路由Key等
basicProperties:参数
bytes:消息
*/
@Override
public void handleDelivery(String s, Envelope envelope, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
System.out.println("consumerTag:" + s);
System.out.println("envelope_exchange:" + envelope.getExchange());
System.out.println("envelope_RoutingKey:" + envelope.getRoutingKey());
System.out.println("properties:" + basicProperties);
System.out.println("body:" + new String(bytes));
}
});
}
}
Work Queues:与入门程序的简单模式相比,多了一个或一些消费端 ,多个消费端共同消费同一一个队列中的消息。同一条消息只能被一个消费者获取。
应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
生产者代码:
工作队列的生产者代码与简单模式的生产者代码区别不大,为了展示多条消息被多个消费者消费,这里使用循环发送了10条消息
package ratbbitMq.produce_work_queues;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author Watching
* * @date 2023/7/13
* * Describe:工作队列模式 生产者
*/
public class producer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("1.12.244.105");
connectionFactory.setVirtualHost("itcast");
connectionFactory.setUsername("heima");
connectionFactory.setPassword("heima");
connectionFactory.setPort(5673);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("work_queue", true, false, false, null);
for (int i = 0; i < 10; i++) {
String s = i + "测试工作队列";
channel.basicPublish("", "work_queue", null, s.getBytes());
}
channel.close();
connection.close();
}
}
消费者代码
package rabbitMq.simple;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author Watching
* * @date 2023/7/12
* * Describe:
* 消费者
*/
public class consumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("1.12.244.105");
connectionFactory.setVirtualHost("itcast");//虚拟机名称 默认 /
connectionFactory.setPort(5673);//端口号
connectionFactory.setUsername("heima");//用户名 默认 guest
connectionFactory.setPassword("heima");//密码 默认 guest
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
queue:队列名称
autoAck:是否自动确认消息
如果autoAck参数设置为false,即消费者不自动确认消息,那么在消费者接收到消息后,消息将保留在队列中,直到消费者显式地确认消息为止。
如果消费者不确认消息,并且没有进行任何处理,那么该消息将一直留在队列中,不会被移除
callback:回调函数
*/
channel.basicConsume("hello_world", true, new Consumer() {
@Override
public void handleConsumeOk(String s) {
}
@Override
public void handleCancelOk(String s) {
}
@Override
public void handleCancel(String s) throws IOException {
}
@Override
public void handleShutdownSignal(String s, ShutdownSignalException e) {
}
@Override
public void handleRecoverOk(String s) {
}
/*
s:标识
envelope:获取一些信息,交换机,路由Key等
basicProperties:参数
bytes:消息
*/
@Override
public void handleDelivery(String s, Envelope envelope, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
System.out.println("consumerTag:" + s);
System.out.println("envelope_exchange:" + envelope.getExchange());
System.out.println("envelope_RoutingKey:" + envelope.getRoutingKey());
System.out.println("properties:" + basicProperties);
System.out.println("body:" + new String(bytes));
}
});
}
}
小结:
①在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系。
②Work Queues对于任务过重或(任务较多情况使用工作队列可以提高任务处理的速度。例如:短信服务部署多个,只需要有一个节点成功发送即可。只要有一个服务器获取到短信并发送就行
在订阅模型中,多了一个Exchange角色,而且过程略有变化:
生产者代码:
Pubsub模式的生产者代码和工作队列的区别:
Pubsub模式多了一个交换机,我们需要创建交换机并且将交换机与创建的队列进行绑定。代码细节请看注释
package ratbbitMq.produce_Pubsub;
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;
/**
* @author Watching
* * @date 2023/7/17
* * Describe:
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置连接参数
connectionFactory.setVirtualHost("itcast");
connectionFactory.setHost("1.12.244.105");
connectionFactory.setUsername("heima");
connectionFactory.setPassword("heima");
connectionFactory.setPort(5673);
//3.创建连接
Connection connection = connectionFactory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建队列
String queueName1 = "test_fanout_queue1";
String queueName2 = "test_fanout_queue2";
channel.queueDeclare(queueName1,true,false,false,null);
channel.queueDeclare(queueName2,true,false,false,null);
//6.创建交换机
/*
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map arguments)
exchange:交换机名称
type:交换机类型
fanout:广播
direct:定向
topic:通配符
headers:参数匹配
durable:是否持久化
aotuDelete:没有任何与之关联的队列或交换器时,会被自动删除
internal:指定交换器是否是内部的。如果设置为true,则表示该交换器仅可用于内部使用,不能被客户端用来发布消息。一般用于实现一些特殊的交换器交互模式。
arguments:一组可选的附加参数,用于设置交换器的特定属性。这些参数是键值对的形式,允许用户自定义交换器的行为。
*/
String exchangeName = "test_fanout";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true);
//7.绑定队列和交换机
/*
queueBind(String queue, String exchange, String routingKey)
queue:队列名称
exchange:交换机名称
routingKey:路由键,绑定规则:
如果交换机类型为fanout,则路由键设置为""
*/
channel.queueBind(queueName1,exchangeName,"");
channel.queueBind(queueName2,exchangeName,"");
//8.发送消息
channel.basicPublish(exchangeName,"",null,"测试Pubsub——fanout".getBytes());
//9.释放资源
channel.close();
connection.close();
}
}
消费者代码:
消费者代码和工作队列模式的代码没有区别
package rabbitMq.produce_Pubsub;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author Watching
* * @date 2023/7/17
* * Describe:
*/
public class Consumer_Pubsub1 {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("1.12.244.105");
connectionFactory.setVirtualHost("itcast");
connectionFactory.setUsername("heima");
connectionFactory.setPassword("heima");
connectionFactory.setPort(5673);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.basicConsume("test_fanout_queue1",true, new com.rabbitmq.client.Consumer() {
@Override
public void handleConsumeOk(String s) {
}
@Override
public void handleCancelOk(String s) {
}
@Override
public void handleCancel(String s) throws IOException {
}
@Override
public void handleShutdownSignal(String s, ShutdownSignalException e) {
}
@Override
public void handleRecoverOk(String s) {
}
@Override
public void handleDelivery(String s, Envelope envelope, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
// System.out.println("consumerTag:" + s);
// System.out.println("envelope_exchange:" + envelope.getExchange());
// System.out.println("envelope_RoutingKey:" + envelope.getRoutingKey());
// System.out.println("properties:" + basicProperties);
System.out.println("body:" + new String(bytes));
}
});
}
}
Pubsub工作模式和工作队列的工作模式的区别在于:
Pubsub多了一个交换机(非默认交换机),交换机将消息发送到与之绑定的每个队列,每个队列都会有同样的消息,不同的消费者再分别从不同的队列中获取相同的消息。
工作队列只有一个消息队列,没有交换机(非默认交换机),消息会被发送到一个队列中,不同的消费者从同一个队列中获取不同的消息。
模式说明:
package ratbbitMq.produce_routing;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author Watching
* * @date 2023/7/17
* * Describe:
*/
public class producer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置工厂参数
connectionFactory.setPort(5673);
connectionFactory.setUsername("heima");
connectionFactory.setPassword("heima");
connectionFactory.setHost("1.12.244.105");
connectionFactory.setVirtualHost("itcast");
//3.创建连接对象
Connection connection = connectionFactory.newConnection();
//4.创建channel
Channel channel = connection.createChannel();
//5.创建队列
channel.queueDeclare("test_routing1", true, false, false, null);
channel.queueDeclare("test_routing2", true, false, false, null);
//6.创建交换机
channel.exchangeDeclare("routing", BuiltinExchangeType.DIRECT);
//7.绑定交换机和队列并设置RoutingKey
channel.queueBind("test_routing1","routing","error");
channel.queueBind("test_routing2","routing","warning");
channel.queueBind("test_routing2","routing","info");
//8.发送消息,并在发送消息的时候指定RoutingKey
channel.basicPublish("routing","warning",null,"xxx访问了xxx方法 【warning】".getBytes());
channel.basicPublish("routing","error",null,"xxx访问了xxx方法 【error】".getBytes());
//9.释放资源
channel.close();
connection.close();
}
}
消费者代码:
消费者代码与前面三种模式无区别
package rabbitMq.consumer_routing;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author Watching
* * @date 2023/7/17
* * Describe:
*/
public class Consumer_routing1 {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("1.12.244.105");
connectionFactory.setVirtualHost("itcast");
connectionFactory.setUsername("heima");
connectionFactory.setPassword("heima");
connectionFactory.setPort(5673);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.basicConsume("test_routing1",true, new Consumer() {
@Override
public void handleConsumeOk(String s) {
}
@Override
public void handleCancelOk(String s) {
}
@Override
public void handleCancel(String s) throws IOException {
}
@Override
public void handleShutdownSignal(String s, ShutdownSignalException e) {
}
@Override
public void handleRecoverOk(String s) {
}
@Override
public void handleDelivery(String s, Envelope envelope, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
// System.out.println("consumerTag:" + s);
// System.out.println("envelope_exchange:" + envelope.getExchange());
// System.out.println("envelope_RoutingKey:" + envelope.getRoutingKey());
// System.out.println("properties:" + basicProperties);
System.out.println("body:" + new String(bytes));
}
});
}
}
在 RabbitMQ 的 Topic 模式中,通配符用于匹配特定的 Routing Key。Topic 模式支持两种通配符:星号(*)和井号(#)。
星号(*)通配符:
(星号)可以匹配一个单词,单词是由点号(.)分隔的字符串。
例如,“*.apple” 可以匹配 “red.apple”、“green.apple”,但不能匹配 “apple” 或 “red.green.apple”。
井号(#)通配符:
#(井号)可以匹配零个或多个单词,单词是由点号(.)分隔的字符串。
例如,“fruit.#” 可以匹配 “fruit.apple”、“fruit.orange”、“fruit.red.apple”,以及类似的任何其他组合。
注意事项:
发布消息到 “fruits_exchange”,Routing Key 为 “green.apple”,此消息将被发送到队列 A 。
发布消息到 “fruits_exchange”,Routing Key 为 “fruit.orange”,此消息将仅被发送到队列 B,因为它只匹配了队列 B 的绑定键。
通过使用不同的通配符规则,Topic 模式允许更灵活和精确地路由消息到不同的队列,以满足复杂的消息路由需求。
举例:
下面我们将使用Topic工作模式实现下面的要求
生产者代码:
Topic工作模式的生产者代码与Routing路由模式的生产者代码的区别是,Topic的代码在绑定队列和交换机时,需要指定带有通配符的Routingkey
,并且在定义交换机时不要忘了将交换机的类型设置为topic.
package ratbbitMq.produce_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;
/**
* @author Watching
* * @date 2023/7/18
* * Describe:
*/
public class producer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置工厂参数
connectionFactory.setPort(5673);
connectionFactory.setUsername("heima");
connectionFactory.setPassword("heima");
connectionFactory.setHost("1.12.244.105");
connectionFactory.setVirtualHost("itcast");
//3.创建连接对象
Connection connection = connectionFactory.newConnection();
//4.创建channel
Channel channel = connection.createChannel();
//5.创建队列
channel.queueDeclare("test_Queue_Topic1", true, false, false, null);
channel.queueDeclare("test_Queue_Topic2", true, false, false, null);
channel.queueDeclare("test_Queue_Topic3", true, false, false, null);
channel.queueDeclare("test_Queue_Topic4", true, false, false, null);
//6.创建交换机
channel.exchangeDeclare("Topic_exchange", BuiltinExchangeType.TOPIC);
//7.绑定交换机和队列并设置RoutingKey
channel.queueBind("test_Queue_Topic1","Topic_exchange","usa.#");
channel.queueBind("test_Queue_Topic2","Topic_exchange","#.news");
channel.queueBind("test_Queue_Topic3","Topic_exchange","#.weather");
channel.queueBind("test_Queue_Topic4","Topic_exchange","europe.#");
//8.发送消息,并在发送消息的时候指定RoutingKey
channel.basicPublish("Topic_exchange","usa.news",null,"美国新闻【usa.news】".getBytes());
channel.basicPublish("Topic_exchange","usa.weather",null,"美国天气【usa.weather】".getBytes());
channel.basicPublish("Topic_exchange","europe.news",null,"欧洲新闻【europe.news】".getBytes());
channel.basicPublish("Topic_exchange","europe.weather",null,"欧洲天气【europe.weather】".getBytes());
//9.释放资源
channel.close();
connection.close();
}
}
消费者代码:
在这个例子中我创建了四个消费者,消费者的代码都相同,只是在绑定队列时要指定获取消息的队列名称。
package rabbitMq.consumer_Topic;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author Watching
* * @date 2023/7/17
* * Describe:
*/
public class Consumer_topic1 {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("1.12.244.105");
connectionFactory.setVirtualHost("itcast");
connectionFactory.setUsername("heima");
connectionFactory.setPassword("heima");
connectionFactory.setPort(5673);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.basicConsume("test_Queue_Topic1",true, new Consumer() {
@Override
public void handleConsumeOk(String s) {
}
@Override
public void handleCancelOk(String s) {
}
@Override
public void handleCancel(String s) throws IOException {
}
@Override
public void handleShutdownSignal(String s, ShutdownSignalException e) {
}
@Override
public void handleRecoverOk(String s) {
}
@Override
public void handleDelivery(String s, Envelope envelope, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
// System.out.println("consumerTag:" + s);
// System.out.println("envelope_exchange:" + envelope.getExchange());
// System.out.println("envelope_RoutingKey:" + envelope.getRoutingKey());
// System.out.println("properties:" + basicProperties);
System.out.println("body:" + new String(bytes));
}
});
}
}
运行结果:
小结:
Topic主题模式可以实现Pub/Sub发布与订阅模式和Routing路由模式的功能,只是Topic 在配置routing key的时候可以使用通配符,显得更加灵活。
在实际的项目中,我们是不可能直接使用原生的代码进行开发的,springboot的整合会帮助我们简化开发。
下面是springboot整合rabbitMQ的流程步骤:
生产者:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
spring:
rabbitmq:
password: heima
username: heima
port: 5673
virtual-host: itcast
host: 1.12.244.105
package com.rabbitmq.springboot_mqproducer.rabbitMQconfig;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Watching
* * @date 2023/7/18
* * Describe:
*/
@Configuration
public class MQConfig {
public static final String EXCHANGE_NAME = "boot_topic_exchange";
public static final String QUEUE_NAME = "boot_topic_queue";
/*
创建交换机
*/
@Bean("topic_exchange_bean")
public Exchange bootExchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
/*
创建队列
*/
@Bean("topic_queue_bean")
public Queue bootQueue() {
return QueueBuilder.durable(QUEUE_NAME).build();
}
/*
绑定交换机
*/
@Bean
public Binding bootBinding(@Qualifier("topic_exchange_bean") Exchange exchange,@Qualifier("topic_queue_bean") Queue queue){
return BindingBuilder.bind(queue).to(exchange).with("test.*").noargs();
}
}
package com.rabbitmq.springboot_mqproducer;
import com.rabbitmq.springboot_mqproducer.rabbitMQconfig.MQConfig;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
class SpringbootMqProducerApplicationTests {
@Resource
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
rabbitTemplate.convertAndSend(MQConfig.EXCHANGE_NAME,"test.hello","测试springboot整合交换机");
}
}
消费者:
消费者前两步代码和生产者相同,不需要再编写定义交换机、队列、绑定关系的配置类,最后一步需要使用
@RabbitListener 注解进行监听。
代码:
package com.rabbit.springboot_mqconsumer;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author Watching
* * @date 2023/7/19
* * Describe:
*/
@Component
public class RabbitMQListener {
@RabbitListener(queues = {"boot_topic_queue"})//填写队列名称,可以以字符串数组的方式监听多个队列
public void listener(Message message){
System.out.println(message);
}
}
小结:
①SpringBoot提供了快速整合RabbitMQ的方式
②基本信息再ym|中配置,队列交互机以及绑定关系在配置类中使用Bean的方式配置
③生产端直接注入 RabbitTemplate 完成消息发送
④消费端直接使用 @RabbitListener 完成消息接收