1、消息中间件概述
1.1、什么是消息中间件
MQ全称 Message Queue ,消息队列是应用程序与应用程序之间的通讯媒介,多用于分布式系统之间的通信。
下图为进行远程通信的两种方式:直接远程调用和通过消息中间件 完成通信。
-
为什么要使用MQ
在项目中,有些业务操作不需要即时返回并且处理耗时,可以把这些操作提取出来进行异步处理,而这种异步处理的方式可以大大的节省服务器的请求响应时间,从而提高系统的吞吐量。
-
MQ的常见使用场景
-
任务异步处理
将不需要同步处理且耗时的操作有消息队列通知消息接收方进行异步处理,提高了应用程序的响应时间。
-
应用程序解耦合
MQ就相当于一个中介,生产方不直接和消费方进行交互,而是通过MQ来进行交互,对程序进行解耦合
比如订单系统要远程调用库存系统,支付系统,物流系统,这样会耦合,修改参数时麻烦。使用消息队列后,订单系统给消息队列MQ发送一条消息就算成功了。
-
削峰填谷
如订单系统,在下单的时候就会向数据库中写入数据,但是数据库只能支撑每秒1000次的并发写入,并发量再高就容易出现宕机。在低峰期并发量也就1000可以满足,但在高峰期时并发量可能会突增到5000以上,这个时候数据库肯定会卡死。
这个时候引入MQ来保存消息,系统就可以按照自己的消费能力类获取消息进行消费,比如每秒1000条数据,这样就不会卡死数据库了。但使用MQ之后就会限制消费消息的速度为1000,这样一来,在高峰起的时候,产生的大量数据势必会被积压到MQ中,高峰就被“削”掉了,因为消息的积压,在高峰期过后的一段时间内,消费消息的速度还会维持在1000QPS,直到积压的消息被消费完,这就叫做“填谷”。
-
1.2、消息队列产品比较
ActiveMQ目前的社区非常不活跃,逐渐式微,这里就不多介绍。
综上所述:
MQ的选型首先要基于本身的业务场景,对于目前整个行业的使用情况来说:
现在越来越多的公司会去用RocketMQ,因为其时Java开发的,大公司基础架构研发能力较强,可以进行本地化的功能完善那个,比如滴滴,其就是在RokcketMQ的基础上进行改造,当然,如果能够满足当前需求,就无需改造了。
对于中小型公司来说,技术研发能力较为一般,技术挑战不是特别高,用RabbitMQ是个不错的选择。由于RabbitMQ是erlang语言开发的,这就阻止了大量的Java工程师去深入研究和掌控它,对公司而言,几乎是不可控的状态,但由于RabbitMQ是开源的,并且社区活跃度高,比较稳定的支持,所以,对于一般性的规模RabbitMQ是足以胜任的,只是进行改造的难度太大,所以推荐中小型的公司使用。
如果是大数据领域的实时计算,日志采集等场景,那么Kafaka绝对是首选。
2、RabbitMQ的安装及配置
RabbaitMQ是由erlang语言开发的,所以需要安装erlang语言开发环境:otp_win64_20.2.exe
链接: https://pan.baidu.com/s/14cUa5wW6r9eDWUGRpzRAng
提取码: iba3
安装RabbitMQ:rabbitmq-server-3.7.4.exe
链接: https://pan.baidu.com/s/1TZrKduqjnlcPBrttWZDIPA
提取码: t2iq
2.1、先后安装otp_win64_20.2.exe,rabbitmq-server-3.7.4.exe 傻瓜式安装即可;
2.2、进入rabbitMQ的安装目录的sbin目录,再上方路径框输入cmd,回车
2.3、输入下面命令回车,开启管理
rabbitmq-plugins enable rabbitmq_management
2.4、重启服务,双击rabbitmq-server.bat,打开浏览器,地址栏输入: http://127.0.0.1:15672/
Username和Password 都为 guest,登录
3、RabbitMQ使用
RabbitMQ提供了6中模式:简单模式,work模式,Publish/Subscribe发布与订阅模式,Routing路由模式,Topics主题模式,RPC远程调用模式(远程调用,不太算MQ;暂不介绍);
3.1、简单模式
3.1.1、创建工程,添加依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
3.1.2、抽取连接工具类
/**
* @ClassName: ConnectionUtil
* @Description: 抽取Connection 连接工具类
* @Author: DiTian
* @Date: 2022/3/12 16:14
*/
public class ConnectionUtil {
public static Connection getConnection() throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址:默认 localhost
connectionFactory.setHost("localhost");
//连接端口号:默认5672
connectionFactory.setPort(5672);
//虚拟主机名称:默认 /
//connectionFactory.setVirtualHost("");
//连接用户名:默认 guest
//connectionFactory.setUsername("");
//连接密码:默认 guest
//connectionFactory.setPassword("");
//创建链接
return connectionFactory.newConnection();
}
}
3.1.3、编写消息生产者
/**
* @ClassName: Producer
* @Description: 消息生产者
* @Author: DiTian
* @Date: 2022/3/11 9:09
*/
public class Producer {
static final String QUEUE_NAME = "simple_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//创建链接
Connection connection = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
//创建(声明)队列
/* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次链接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//要发送的信息
String message = "nice to meet you !";
/*
*参数1:交换机名称,如果没有指定,则使用默认的Default Exchange
*参数2:路由key,简单模式可以传递队列名称
*参数3:消息其它属性
*参数4:消息内容
*/
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("信息发送成功:"+message);
//关闭资源
channel.close();
connection.close();
}
}
3.1.4、编写消息消费之
/**
* @ClassName: Consumer
* @Description: 消息消费者 简单模式一个消息生产者一个消息消费者
* @Author: DiTian
* @Date: 2022/3/12 16:20
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
//创建(声明)队列
/* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次链接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(Producer.QUEUE_NAME,true,false,false,null);
//创建消费者,并设置消息处理
DefaultConsumer consumer = new DefaultConsumer(channel){
/**
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws UnsupportedEncodingException {
//路由 key
System.out.println("路由key为:"+envelope.getRoutingKey());
//交换机 exchange
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(Producer.QUEUE_NAME,false,consumer);
}
}
3.1.5、小结
简单模式:
3.2、Work queues工作队列模式
Work Queues与简单模式相比,可以有多个消费端共同消费同一个消息队列中的消息
应用场景: 对于任务较重或者任务较多的情况使用工作队列可以提高任务的处理速度
3.2.1、生产者
/**
* @ClassName: Producer
* @Description: 消息生产者 工作队列模式:一个消息生产者,多个消息消费者
* @Author: DiTian
* @Date: 2022/3/13 21:17
*/
public class Producer {
static final String QUEUE_WORK = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//建立连接 connection
Connection connection = ConnectionUtil.getConnection();
//建立信道 channel
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_WORK,true,false,false,null);
for (int i = 1; i <= 20; i++) {
//创建并发布消息
String message = "work模式--"+i;
channel.basicPublish("",QUEUE_WORK,null,message.getBytes());
System.out.println("已发送消息--"+i);
}
//关闭资源
channel.close();
connection.close();
}
}
3.2.2、消费者1
/**
* @ClassName: Consumer1
* @Description:
* @Author: DiTian
* @Date: 2022/3/13 21:24
*/
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建信道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(Producer.QUEUE_WORK,true,false,false,null);
//一次只能接受并处理一个消息
channel.basicQos(1);
//创建消费者,并设置消息处理
DefaultConsumer consumer = new DefaultConsumer(channel){
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[]body) throws UnsupportedEncodingException {
try {
//路由 key
System.out.println("路由key为:"+envelope.getRoutingKey());
//交换机 exchange
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 | IOException e) {
e.printStackTrace();
}
}
};
//监听消息
channel.basicConsume(Producer.QUEUE_WORK,false,consumer);
}
}
3.2.3、消费者2
/**
* @ClassName: Consumer2
* @Description:
* @Author: DiTian
* @Date: 2022/3/13 21:24
*/
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建信道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(Producer.QUEUE_WORK,true,false,false,null);
//一次只能接受并处理一个消息
channel.basicQos(1);
//创建消费者,并设置消息处理
DefaultConsumer consumer = new DefaultConsumer(channel){
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[]body) throws UnsupportedEncodingException {
try {
//路由 key
System.out.println("路由key为:"+envelope.getRoutingKey());
//交换机 exchange
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 | IOException e) {
e.printStackTrace();
}
}
};
//监听消息
channel.basicConsume(Producer.QUEUE_WORK,false,consumer);
}
}
3.2.4、小结
多个消费者对消息队列中的同一信息是竞争的关系
3.3、Publish/Subscribe发布与订阅模式
前面两种模式只有三个角色,而在发布订阅模式中多了一个exchange交换机角色,过程:
- P:生产者,把消息发送给X交换机,而不是直接发送到队列中
- C:消费者,消息的接收者,会一直等待消息的到来
- Queue:消息队列,接收并缓存消息
- Exchange:交换机,一方面接收生产的发送的消息,另一方面处理消息,把消息发送到特定队列或者所有队列,或者将消息丢弃。具体如何操作,取决于Exchange的类型,Exchange常见的3种类型:
- Fanout:广播,将消息交给绑定到交换机的所有队列
- Direct:定向,把消息交给符合指定routing key的队列
- Topic:通配符,把消息交给符合routing pattern(路由模式)的队列
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
3.3.1、生产者
/**
* @ClassName: Producer
* @Description: Publisher/Subscriber 发布订阅模式
* @Author: DiTian
* @Date: 2022/3/14 9:48
*/
public class Producer {
//交换机名称
static final String FANOUT_EXCHANGE = "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 IOException, TimeoutException {
//创建链接
Connection connection = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
/**
* 声明交换机
* 参数1:交换机名称
* 参数2:交换机类型
*/
channel.exchangeDeclare(FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT);
/**
* 声明队列
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用时自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(FANOUT__QUEUE_1,true,false,false,null);
channel.queueDeclare(FANOUT__QUEUE_2,true,false,false,null);
//队列绑定交换机
channel.queueBind(FANOUT__QUEUE_1,FANOUT_EXCHANGE,"");
channel.queueBind(FANOUT__QUEUE_2,FANOUT_EXCHANGE,"");
for (int i = 1; i < 10; i++) {
//创建并发送消息
String message = "Publisher/Subscriber 模式---"+i;
/**
* 参数1:交换机名称
* 参数2:路由key
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish(FANOUT_EXCHANGE,"",null,message.getBytes());
System.out.println("已发送消息:"+message);
}
//关闭资源
channel.close();
connection.close();
}
}
3.3.2、消费者1
/**
* @ClassName: Consumer1
* @Description:
* @Author: DiTian
* @Date: 2022/3/14 10:40
*/
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT);
//声明队列
channel.queueDeclare(Producer.FANOUT__QUEUE_1,true,false,false,null);
//队列绑定交换机
channel.queueBind(Producer.FANOUT__QUEUE_1,Producer.FANOUT_EXCHANGE,"");
//创建消费者并设置消息处理
DefaultConsumer consumer = new DefaultConsumer(channel){
public void handleDelivery(String consumerTag, Envelope envelope,AMQP.BasicProperties properties,byte[]body) throws UnsupportedEncodingException {
//路由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(Producer.FANOUT__QUEUE_1,true,consumer);
}
}
3.3.3、消费者2
/**
* @ClassName: Consumer2
* @Description:
* @Author: DiTian
* @Date: 2022/3/14 10:40
*/
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT);
//声明队列
channel.queueDeclare(Producer.FANOUT__QUEUE_2, true, false, false, null);
//队列绑定交换机
channel.queueBind(Producer.FANOUT__QUEUE_2, Producer.FANOUT_EXCHANGE, "");
//创建消费者并设置消息处理
DefaultConsumer consumer = new DefaultConsumer(channel) {
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws 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(Producer.FANOUT__QUEUE_2, true, consumer);
}
}
3.3.4、小结
启动所有消费者,然后使用生产者发送消息;在每个消费者对应的控制台可以查看到生产者发送的所有消息;到达广播的效果。
在执行完测试代码后,其实到RabbitMQ的管理后台找到Exchanges选项卡,点击 fanout_exchange 的交换机,可以查看到绑定的队列:fanout-queue-1,fanout-queue-2
交换机绑定队列,绑定之后一个消息可以被转发存储到多个队列,也使得一个消息可以被多个消费者消费
3.4、Routing路由模式
路由模式特点:
- 队列与交换机的绑定,不是任意的绑定,而是指定一个RoutingKey(路由key)
- 消息的发送方在向Exchange交换机发送消息时,也必须指定消息的RoutingKey
- Exchange交换机不再把消息转发给所有绑定的队列,而是根据消息的RoutingKey去匹配队列的RoutingKey,进行消息的转发
下图代表了不同级别的日志怎么处理,比如C1是文件,C2是控制台
图解:
- P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
- X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列
- C1:消费者,其所在队列指定了需要routing key 为 error 的消息
- C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息
3.4.1、生产者
/**
* @ClassName: Producer
* @Description: 路由模式的交换机类型
* @Author: DiTian
* @Date: 2022/3/14 20:29
*/
public class Producer {
static final String DIRECT_EXCHANGE = "direct_exchange";
static final String DIRECT_QUEUE_INSERT = "direct_queue_insert";
static final String DIRECT_QUEUQ_UPDATE = "direct_queue_update";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
/**
*声明交换机
* 参数1:交换机名称
* 参数2:交换机类型 fanout,topic,direct,headers
*/
channel.exchangeDeclare(DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT);
/**
* 声明队列
* 参数1:队列名称
* 参数2:是否持久化
* 参数3:是否独占
* 参数4:不使用时是否删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(DIRECT_QUEUE_INSERT,true,false,false,null);
channel.queueDeclare(DIRECT_QUEUQ_UPDATE,true,false,false,null);
//队列绑定交换机
channel.queueBind(DIRECT_QUEUE_INSERT,DIRECT_EXCHANGE,"insert");
channel.queueBind(DIRECT_QUEUQ_UPDATE,DIRECT_EXCHANGE,"update");
//发送消息
String message = "新增商品--路由模式:routing key 为 insert";
channel.basicPublish(DIRECT_EXCHANGE,"insert",null,message.getBytes());
System.out.println("已发送消息:"+message);
//发送消息
message = "修改商品--路由模式:routing key 为 update";
channel.basicPublish(DIRECT_EXCHANGE,"update",null,message.getBytes());
System.out.println("已发送消息:"+message);
//关闭资源
channel.close();
connection.close();
}
}
3.4.2、消费者1
/**
* @ClassName: Consumer1
* @Description:
* @Author: DiTian
* @Date: 2022/3/14 20:42
*/
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建链接
Connection connection = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT);
//声明队列
channel.queueDeclare(Producer.DIRECT_QUEUE_INSERT,true,false,false,null);
//队列绑定交换机
channel.queueBind(Producer.DIRECT_QUEUE_INSERT,Producer.DIRECT_EXCHANGE,"insert");
//创建消费者并设置消息处理
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("消费者1-接收到的消息为:" + new String(body, "utf-8"));
}
};
//监听消息
channel.basicConsume(Producer.DIRECT_QUEUE_INSERT,true,consumer);
}
}
3.4.3、消费者2
/**
* @ClassName: Consumer1
* @Description:
* @Author: DiTian
* @Date: 2022/3/14 20:42
*/
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建链接
Connection connection = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT);
//声明队列
channel.queueDeclare(Producer.DIRECT_QUEUQ_UPDATE,true,false,false,null);
//队列绑定交换机
channel.queueBind(Producer.DIRECT_QUEUQ_UPDATE,Producer.DIRECT_EXCHANGE,"update");
//创建消费者并设置消息处理
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("消费者1-接收到的消息为:" + new String(body, "utf-8"));
}
};
//监听消息
channel.basicConsume(Producer.DIRECT_QUEUQ_UPDATE,true,consumer);
}
}
3.4.4、小结
启动所有消费者,然后使用生产者发送消息;在消费者对应的控制台可以查看到生产者发送对应routing key对应队列的消息;到达按照需要接收的效果。
在执行完测试代码后,其实到RabbitMQ的管理后台找到Exchanges选项卡,点击 direct_exchange 的交换机,可以查看到如下的绑定:direct-queue-insert:insert,direct-queue-update:update
Routing模式要求队列在绑定交换机时要指定routing key,消息会转发到符合routing key的队列。
3.5、Topics通配符模式
Topic类型与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!
Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
通配符规则:
#:匹配一个或多个词
*:匹配不多不少恰好1个词
举例:
item.#:能够匹配item.insert.abc 或者 item.insert
item.*:只能匹配item.insert
3.5.1、生产者
/**
* @ClassName: Producer
* @Description: 通配符topic的交换机类型:topic
* @Author: DiTian
* @Date: 2022/3/14 20:57
*/
public class Producer {
static final String TOPIC_EXCHANGE= "topic_exchange";
static final String TOPIC_QUEUE_1 = "topic_queue_1";
static final String TOPIC_QUEUE_2 = "topic_queue_2";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
/**
* 声明交换机
* 参数1:交换机名称
* 参数2:交换机类型
*/
channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
//发送消息
String message = "新增了商品--Topic模式:routing key 为 item_insert";
channel.basicPublish(TOPIC_EXCHANGE,"item.insert",null,message.getBytes());
System.out.println("已发送消息:"+message);
//发送消息
message = "更新了商品--Topic模式:routing key 为 item_update";
channel.basicPublish(TOPIC_EXCHANGE,"item.update",null,message.getBytes());
System.out.println("已发送消息:"+message);
//发送消息
message = "删除了商品--Topic模式:routing key 为 item_delete";
channel.basicPublish(TOPIC_EXCHANGE,"item.delete",null,message.getBytes());
System.out.println("已发送消息:"+message);
//关闭资源
channel.close();
connection.close();
}
}
3.5.2、消费者1
/**
* @ClassName: Consumer1
* @Description:
* @Author: DiTian
* @Date: 2022/3/14 21:08
*/
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
//声明队列
channel.queueDeclare(Producer.TOPIC_QUEUE_1,true,false,false,null);
//队列绑定交换机
channel.queueBind(Producer.TOPIC_QUEUE_1,Producer.TOPIC_EXCHANGE,"item.insert");
channel.queueBind(Producer.TOPIC_QUEUE_1,Producer.TOPIC_EXCHANGE,"item.delete");
//创建消费者处理消息
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("消费者1-接收到的消息为:" + new String(body, "utf-8"));
}
};
//监听消息
channel.basicConsume(Producer.TOPIC_QUEUE_1,true,consumer);
}
}
3.5.3、消费者2
/**
* @ClassName: Consumer1
* @Description:
* @Author: DiTian
* @Date: 2022/3/14 21:08
*/
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
//声明队列
channel.queueDeclare(Producer.TOPIC_QUEUE_2,true,false,false,null);
//队列绑定交换机
channel.queueBind(Producer.TOPIC_QUEUE_2,Producer.TOPIC_EXCHANGE,"item.*");
//创建消费者处理消息
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("消费者1-接收到的消息为:" + new String(body, "utf-8"));
}
};
//监听消息
channel.basicConsume(Producer.TOPIC_QUEUE_2,true,consumer);
}
}
3.5.4、小结
Topic主题模式可以实现 Publish/Subscribe发布与订阅模式 和 Routing路由模式 的功能;只是Topic在配置routing key 的时候可以使用通配符,显得更加灵活。
3.6、模式总结
RabbitMQ工作模式:
1、简单模式 HelloWorld
一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)
2、工作队列模式 Work Queue
一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)
3、发布订阅模式 Publish/subscribe
需要设置类型为fanout的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列
4、路由模式 Routing
需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列
5、通配符模式 Topic
需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列
4、SpringBoot整合RabbitMQ
在spring boot项目中只需要引入对应的amqp启动器依赖即可,方便的使用RabbitTemplate发送消息,使用注解接收消息。
生产者工程:
- application.yml文件配置RabbitMQ相关信息;
- 在生产者工程中编写配置类,用于创建交换机和队列,并进行绑定
- 注入RabbitTemplate对象,通过RabbitTemplate对象发送消息到交换机
消费者工程:
- application.yml文件配置RabbitMQ相关信息
- 创建消息处理类,用于接收队列中的消息并进行处理
4.1、搭建生产者工程
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
编写配置
spring:
rabbitmq:
host: localhost
port: 5672
RabbitMQ配置类
/**
* @ClassName: RabbitMQConfig
* @Description:
* @Author: DiTian
* @Date: 2022/3/15 12:15
*/
@Configuration
public class RabbitMQConfig {
//交换机名称
public static final String ITEM_TOPIC_EXCHANGE = "item_topic_exchange";
//队列名称
public static final String ITEM_QUEUE = "item_queue";
//声明交换机
@Bean("itemTopicExchange")
public Exchange topicExchange(){
return ExchangeBuilder.topicExchange(ITEM_TOPIC_EXCHANGE).durable(true).build();
}
//声明队列
@Bean("itemQueue")
public Queue itemQueue(){
return QueueBuilder.durable(ITEM_QUEUE).build();
}
//队列绑定交换机
@Bean
public Binding itemQueueExchange(@Qualifier("itemQueue") Queue queue,@Qualifier("itemTopicExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("item.*").noargs();
}
}
启动类
@SpringBootApplication
public class SpringbootRabbitmqProducerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRabbitmqProducerApplication.class, args);
}
}
测试类
@RunWith(SpringRunner.class)
@SpringBootTest
class SpringbootRabbitmqProducerApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void test() {
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE, "item.insert", "商品新增,routing key 为item.insert");
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE, "item.update", "商品修改,routing key 为item.update");
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE, "item.delete", "商品删除,routing key 为item.delete");
}
}
4.2、搭建消费者工程
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
编写配置
spring:
rabbitmq:
host: localhost
port: 5672
消息监听处理类
/**
* @ClassName: MyListener
* @Description: 消息监听处理类
* @Author: DiTian
* @Date: 2022/3/15 12:28
*/
@Component
public class MyListener {
/**
* 监听某个队列
* @param message 接收到的消息
*/
@RabbitListener(queues = "item_queue")
public void myListener1(String message){
System.out.println("消费者接收到消息:"+message);
}
}
启动类
@SpringBootApplication
public class SpringbootRabbitmqConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRabbitmqConsumerApplication.class, args);
}
}
4.3、测试
首先启动消费者工程监听消息,启动生产的者工程的测试类SpringbootRabbitmqProducerApplicationTests,查看消费者工程控制台是否接收到消息,也可以 打开控制管理面板 http://127.0.0.1:15672/ 查看Exchange和Queues的绑定关系
ete");
}
}
### 4.2、搭建消费者工程
添加依赖
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
编写配置
spring:
rabbitmq:
host: localhost
port: 5672
消息监听处理类
/**
* @ClassName: MyListener
* @Description: 消息监听处理类
* @Author: DiTian
* @Date: 2022/3/15 12:28
*/
@Component
public class MyListener {
/**
* 监听某个队列
* @param message 接收到的消息
*/
@RabbitListener(queues = "item_queue")
public void myListener1(String message){
System.out.println("消费者接收到消息:"+message);
}
}
启动类
@SpringBootApplication
public class SpringbootRabbitmqConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRabbitmqConsumerApplication.class, args);
}
}
4.3、测试
首先启动消费者工程监听消息,启动生产的者工程的测试类SpringbootRabbitmqProducerApplicationTests,查看消费者工程控制台是否接收到消息,也可以 打开控制管理面板 http://127.0.0.1:15672/ 查看Exchange和Queues的绑定关系