RabbitMQ
MQ官网速递:https://www.rabbitmq.com/getstarted.html
MQ的优势
- 应用解耦:提高系统的容错性和可维护性;
- 异步提速:提升用户体验和系统吞吐量;
- 削峰填谷:提高系统稳定性。
MQ的劣势:
- 系统可用性降低:如果MQ服务器挂了,则关联业务都会受影响,需要保证MQ的高可用;
- 系统复杂度提高:各服务之间需要通过MQ进行异步调用,因此需要保证消息不丢失等情况。
常见的MQ产品
AMQP:即Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端、中间件不同产品,不同的开发语言等条件的限制。2006年AMQP规范发布,类比HTTP。
RabbitMQ的基础架构图
Broker:接收和分发消息的应用,RabbitMQ Server就是Message Broker。
Virtual Host:出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中的namespace概念。当多个不同的用户使用同一个RabbitMQ Server提供的服务时,可以划分出多个vHost,每个用户在自己的vHost创建exchange/queue等。
Connection:publisher(producer)/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)和fanout(multicast)。
Queue:消息最终被送到队列等待consumer消费。
Binding:exchange和queue之间的虚拟连接,binding中可以包含routing key,binding信息被保存到exchange中的查询表里,用于管理message分发。
RabbitMQ的消息确认机制
confirm:表示生产者把消息发送到broker时的状态,后续会出现两种情况:ack(表示broker已经接收数据)和nack(表示broker拒收消息,原因可能是队列已满、限流、IO异常等……)。
return:表示消息被broker正常接收(ack)后,但broker没有对应的队列,消息被退回给生产者。
这两种状态只代表生产者和broker之间的消息发送情况,与消费者是否接收、确认消息无关。
RabbitMQ的高级特性
- 消息端限流:消费端限制每次读取消息的数量。
- TTL:Time To Live,表示存活时间/过期时间。当消息到了过期时间后还没有被消费,消息就会被自动清除。RabbitMQ可以设置消息的过期时间,也可以给整个队列设置过期时间。
- 死信队列:DLX(Dead Letter Exchange),也就是死信交换机。死信就是存活时间到期还没有被消费、要被清除的消息,这类消息可以被重新发送到另一个交换机,这个交换机就是DLX。消息成为死信的几种情况:队列消息长度上限、消费者拒绝消费消息并且不重回队列(重发)、原队列存在消息过期设置且消息超时未被消费。
- 延时队列:消息进入队列后不会被立即消费,只有到达指定时间后才会被消费,比如需求下单后15分钟未支付就取消订单回滚库存或新用户注册7天后发信息问候就可以通过延时队列来实现。RabbitMQ没有提供延时队列的功能,但是可以通过TTL+死信队列组合使用实现延时队列的效果。
- 消息可靠性投递
- Consumer ACK
消息可靠性的要求
- 持久化
- exchange持久化
- queue持久化
- message持久化
- 生产者确认confirm
- 消费者确认ack
- broker高可用
消息积压的可能原因
- 消费者宕机积压
- 消费者消费能力不足积压
- 发送者发送流量太大
消息积压的解决方案
- 上线更多的消费者,提升消费能力;
- 上线专门的队列,把消息记录进数据库,再慢慢处理。
消息幂等性保障:幂等性指一次或多次请求某一个资源,对资源本身应具有同样的结果。在MQ中指消费多条相同的消息,结果都应该一致。可以通过乐观锁来保障幂等性。
RabbitMQ提供了6种工作模式:简单模式、work queues(队列模式)、publish/subscribe(发布与订阅模式)、routing(路由模式)、topics(主题模式)、RPC(远程调用模式,远程调用不太算MQ)。
代码实现简单模式通信
引入依赖包
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.4.3</version>
</dependency>
生产者
package com.example.rabbitmq.helloworld;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* 简单模式 发布者
*
* @author YangWanYi
* @version 1.0
* @date 2022-12-09 14:49
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
// 设置主机信息
connectionFactory.setHost("192.168.0.42");
/*
端口
5672:RabbitMQ的通讯端口
25672:RabbitMQ的节点间的CLI通讯端口
15672:RabbitMQ HTTP_API的端口,管理员用户才能访问,用于管理RabbitMQ,需要启动Management插件。
1883、8883:MQTT插件启动时的端口。
61613、61614:STOMP客户端插件启用的时候的端口。
15674、15675:基于webscoket的STOMP端口和MOTT端口。
*/
connectionFactory.setPort(5672);
// 这里最好使用建VirtualHost的用户登陆 否则可能找不到这个VirtualHost
connectionFactory.setUsername("ywy");
connectionFactory.setPassword("ywy");
connectionFactory.setVirtualHost("/myHost");
// 获取TCP长连接
Connection connection = connectionFactory.newConnection();
// 创建通信通道 相当于TCP中的虚拟连接
Channel channel = connection.createChannel();
/*
如果队列名不存在 会自动创建
P1:队列名称ID
P2:是否持久化,false表示不持久化数据,MQ停掉数据就会丢失。
P3:是否队列私有化,false表示所有消费者都可以访问,true表示只有第一次拥有该队列的消费者才能一直使用。
P4:是否自动删除,false表示连接停掉后不自动删除这个队列。
P5:其他参数
*/
final String QUEUE_NAME = "helloworld";
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 定义需要发送的消息
String msg = "超多钱!";
/*
发送消息
P1:交换机 简单模式不需要指定交换机
P2:队列名称
P3:其他参数
P4:要发送的消息
*/
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes(StandardCharsets.UTF_8));
// 关闭连接
channel.close();
connection.close();
System.out.println("恭喜!消息发送成功!");
}
}
消费者
package com.example.rabbitmq.helloworld;
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 YangWanYi
* @version 1.0
* @date 2022-12-09 15:11
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
// 设置主机信息
connectionFactory.setHost("192.168.0.42");
connectionFactory.setPort(5672);
// 这里最好使用建VirtualHost的用户登陆 否则可能找不到这个VirtualHost
connectionFactory.setUsername("ywy");
connectionFactory.setPassword("ywy");
connectionFactory.setVirtualHost("/myHost");
// 获取TCP长连接
Connection connection = connectionFactory.newConnection();
// 创建通信通道 相当于TCP中的虚拟连接
Channel channel = connection.createChannel();
/*
如果队列名不存在 会自动创建
P1:队列名称ID
P2:是否持久化,false表示不持久化数据,MQ停掉数据就会丢失。
P3:是否队列私有化,false表示所有消费者都可以访问,true表示只有第一次拥有该队列的消费者才能一直使用。
P4:是否自动删除,false表示连接停掉后不自动删除这个队列。
P5:其他参数
*/
final String QUEUE_NAME = "helloworld";
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
/*
从MQ服务器中获取数据
P1:队列名
P2:是否自动确认收到消息,false表示需要手动编码确认消息(MQ推荐手动)。
P3:DefaultConsumer的实现类对象 做消息处理
*/
channel.basicConsume(QUEUE_NAME, false, new Receiver(channel));
}
}
消息处理类
package com.example.rabbitmq.helloworld;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
/**
* 消费对象实现类 做消息处理
*
* @author YangWanYi
* @version 1.0
* @date 2022-12-09 15:16
*/
public class Receiver extends DefaultConsumer {
private Channel channel;
/**
* 重写构造函数 Channel通道对象需要从外层传入,在handleDelivery中需要用到
*
* @param channel
*/
public Recevier(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body);
System.out.println("消费者接收到的消息:" + msg);
System.out.println("消费者接收到的消息ID:" + envelope.getDeliveryTag());
/*
签收消息
P1:消息ID
P2:false表示只确认签收当前的消息 true表示签收该消费者所有未签收的消息
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
代码实现队列模式通信
Rabbit工具类
package com.example.rabbitmq.helloworld;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* Rabbit工具类
*
* @author YangWanYi
* @version 1.0
* @date 2022-12-09 16:41
*/
public class RabbitUtil {
private static ConnectionFactory connectionFactory = new ConnectionFactory();
static {
// 设置主机信息
connectionFactory.setHost("192.168.0.42");
/*
端口
5672:RabbitMQ的通讯端口
25672:RabbitMQ的节点间的CLI通讯端口
15672:RabbitMQ HTTP_API的端口,管理员用户才能访问,用于管理RabbitMQ,需要启动Management插件。
1883、8883:MQTT插件启动时的端口。
61613、61614:STOMP客户端插件启用的时候的端口。
15674、15675:基于webscoket的STOMP端口和MOTT端口。
*/
connectionFactory.setPort(5672);
// 这里最好使用建VirtualHost的用户登陆 否则可能找不到这个VirtualHost
connectionFactory.setUsername("ywy");
connectionFactory.setPassword("ywy");
connectionFactory.setVirtualHost("myHost");
}
/**
* 获取TCP连接
*
* @return com.rabbitmq.client.Connection
* @author YangWanYi
* @date 2022/12/9 16:33
*/
public static Connection getConnection() {
Connection connection = null;
try {
connection = connectionFactory.newConnection();
return connection;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
RabbitMQ 常量类
package com.example.rabbitmq.helloworld;
/**
* Rabbit常量类
*
* @author YangWanYi
* @version 1.0
* @date 2022-12-09 16:41
*/
public class RabbitConstant {
public static final String QUEUE_HELLO_WORLD = "helloworld";
public static final String EXCHANGE_NEWS = "news";
public static final String EXCHANGE_DIRECT = "routingDirect";
public static final String EXCHANGE_TOPIC = "topicExchange";
public static final String QUEUE_PRO_SUB_1 = "proSub1";
public static final String QUEUE_PRO_SUB_2 = "proSub2";
public static final String QUEUE_ROUTING_1 = "routing1";
public static final String QUEUE_ROUTING_2 = "routing2";
}
队列模式 生产者
package com.example.rabbitmq.workqueue;
import com.alibaba.fastjson.JSONObject;
import com.example.rabbitmq.helloworld.RabbitConstant;
import com.example.rabbitmq.helloworld.RabbitUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* 队列模式 生产者
*
* @author YangWanYi
* @version 1.0
* @date 2022-12-09 16:41
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
// 获取TCP长连接
Connection connection = RabbitUtil.getConnection();
// 创建通信通道 相当于TCP中的虚拟连接
Channel channel = connection.createChannel();
/*
如果队列名不存在 会自动创建
P1:队列名称ID
P2:是否持久化,false表示不持久化数据,MQ停掉数据就会丢失。
P3:是否队列私有化,false表示所有消费者都可以访问,true表示只有第一次拥有该队列的消费者才能一直使用。
P4:是否自动删除,false表示连接停掉后不自动删除这个队列。
P5:其他参数
*/
channel.queueDeclare(RabbitConstant.QUEUE_HELLO_WORLD, false, false, false, null);
int times = 100;
for (int i = 1; i < times; i++) {
// 封装要发送的消息
MessageVo messageVo = new MessageVo("骑手您好!", "平安路" + i + "号", "您有一个新的订单待配送!");
String msg = JSONObject.toJSONString(messageVo);
/*
发送消息
P1:交换机 队列模式不需要指定交换机
P2:队列名称
P3:其他参数
P4:要发送的消息
*/
channel.basicPublish("", RabbitConstant.QUEUE_HELLO_WORLD, null, msg.getBytes(StandardCharsets.UTF_8));
}
System.out.println("队列模式,消息发送成功!");
channel.close();
connection.close();
}
}
队列模式 -消费者 消费者代码一样 可以多写几个一起消费来模拟队列消费
package com.example.rabbitmq.workqueue;
import com.example.rabbitmq.helloworld.RabbitConstant;
import com.example.rabbitmq.helloworld.RabbitUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 队列模式 消费者1号
* 队列模式的多个消费者都是共同消费的同一个队列的消息
* @author YangWanYi
* @version 1.0
* @date 2022/12/10 12:46
*/
public class Consumer1 {
public static void main(String[] args) throws IOException {
// 获取TCP长连接
Connection connection = RabbitUtil.getConnection();
// 创建通信通道 相当于TCP中的虚拟连接
final Channel channel = connection.createChannel();
/*
如果队列名不存在 会自动创建
P1:队列名称ID
P2:是否持久化,false表示不持久化数据,MQ停掉数据就会丢失。
P3:是否队列私有化,false表示所有消费者都可以访问,true表示只有第一次拥有该队列的消费者才能一直使用。
P4:是否自动删除,false表示连接停掉后不自动删除这个队列。
P5:其他参数
*/
channel.queueDeclare(RabbitConstant.QUEUE_HELLO_WORLD, false, false, false, null);
/*
如果不写basicQos(1),MQ会自动把所有的消息平均发给所有的消费者。
写了,MQ不再对消费者一次性发送多个消息,而是消费者处理完一个消息后(确认后),再从队列里获取一个新的。
*/
channel.basicQos(1);
/*
从MQ服务器中获取数据
P1:队列名
P2:是否自动确认收到消息,false表示需要手动编码确认消息(MQ推荐手动)。
P3:DefaultConsumer的实现类对象 做消息处理
*/
channel.basicConsume(RabbitConstant.QUEUE_HELLO_WORLD, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 处理消息
String msg = new String(body);
System.out.println("消费者1号收到消息:" + msg);
/*
签收消息
P1:消息ID
P2:false表示只确认签收当前的消息 true表示签收该消费者所有未签收的消息
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
代码实现发布订阅模式通信
发布订阅模式 生产者
package com.example.rabbitmq.pubsub;
import com.example.rabbitmq.helloworld.RabbitConstant;
import com.example.rabbitmq.helloworld.RabbitUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
/**
* 发布订阅模式 生产者
*
* @author YangWanYi
* @version 1.0
* @date 2022-12-10 13:28
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
// 获取TCP长连接
Connection connection = RabbitUtil.getConnection();
// 获取输入信息
String next = new Scanner(System.in).next();
// 创建通信通道 相当于TCP中的虚拟连接
Channel channel = connection.createChannel();
/*
发送消息
P1:交换机名称
P2:队列名称 发布订阅模式在发布消息时不需要指定队列名,在消费端指定队列名即可
P3:其他参数
P4:要发送的消息
*/
channel.basicPublish(RabbitConstant.EXCHANGE_NEWS, "", null, next.getBytes(StandardCharsets.UTF_8));
// 关闭连接
channel.close();
connection.close();
}
}
发布订阅模式 消费者1号 不同的消费者同一个交换机绑定不同的队列名,生产者会通过交换机给已绑定的不同的队列发送同样的消息。
package com.example.rabbitmq.pubsub;
import com.example.rabbitmq.helloworld.RabbitConstant;
import com.example.rabbitmq.helloworld.RabbitUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 发布订阅模式 消费者1号
* 发布订阅模式的多个消费者都分别消费自己绑定队列的消息
*
* @author YangWanYi
* @version 1.0
* @date 2022-12-10 13:37
*/
public class Consumer1 {
public static void main(String[] args) throws IOException {
// 获取TCP长连接
Connection connection = RabbitUtil.getConnection();
// 创建通信通道 相当于TCP中的虚拟连接
final Channel channel = connection.createChannel();
/*
声明队列
P1:队列名称 如果队列名不存在 会自动创建
P2:是否持久化,false表示不持久化数据,MQ停掉数据就会丢失。
P3:是否队列私有化,false表示所有消费者都可以访问,true表示只有第一次拥有该队列的消费者才能一直使用。
P4:是否自动删除,false表示连接停掉后不自动删除这个队列。
P5:其他参数
*/
channel.queueDeclare(RabbitConstant.QUEUE_PRO_SUB_1, false, false, false, null);
/*
绑定交换机
P1:队列名
P2:交换机名 必须先创建好,否则会报错
P3:路由key 发布订阅模式还用不到这个参数
*/
channel.queueBind(RabbitConstant.QUEUE_PRO_SUB_1, RabbitConstant.EXCHANGE_NEWS, "");
/*
如果不写basicQos(1),MQ会自动把所有的消息平均发给所有的消费者。
写了,MQ不再对消费者一次性发送多个消息,而是消费者处理完一个消息后(确认后),再从队列里获取一个新的。
*/
channel.basicQos(1);
/*
从MQ服务器中获取数据
P1:队列名
P2:是否自动确认收到消息,false表示需要手动编码确认消息(MQ推荐手动)。
P3:DefaultConsumer的实现类对象 做消息处理
*/
channel.basicConsume(RabbitConstant.QUEUE_PRO_SUB_1, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 处理消息
String msg = new String(body);
System.out.println("发布订阅模式消费者1号收到消息:" + msg);
/*
签收消息
P1:消息ID
P2:false表示只确认签收当前的消息 true表示签收该消费者所有未签收的消息
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
发布订阅模式 消费者2号 不同的消费者同一个交换机绑定不同的队列名,生产者会通过交换机给已绑定的不同的队列发送同样的消息。
package com.example.rabbitmq.pubsub;
import com.example.rabbitmq.helloworld.RabbitConstant;
import com.example.rabbitmq.helloworld.RabbitUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 发布订阅模式 消费者2号
* 发布订阅模式的多个消费者都分别消费自己绑定队列的消息
*
* @author YangWanYi
* @version 1.0
* @date 2022-12-10 13:37
*/
public class Consumer2 {
public static void main(String[] args) throws IOException {
// 获取TCP长连接
Connection connection = RabbitUtil.getConnection();
// 创建通信通道 相当于TCP中的虚拟连接
final Channel channel = connection.createChannel();
/*
声明队列
P1:队列名称 如果队列名不存在 会自动创建
P2:是否持久化,false表示不持久化数据,MQ停掉数据就会丢失。
P3:是否队列私有化,false表示所有消费者都可以访问,true表示只有第一次拥有该队列的消费者才能一直使用。
P4:是否自动删除,false表示连接停掉后不自动删除这个队列。
P5:其他参数
*/
channel.queueDeclare(RabbitConstant.QUEUE_PRO_SUB_2, false, false, false, null);
/*
绑定交换机
P1:队列名
P2:交换机名 必须先创建好,否则会报错。发布订阅模式的交换机类型是fanout(广播)。
P3:路由key 发布订阅模式还用不到这个参数
*/
channel.queueBind(RabbitConstant.QUEUE_PRO_SUB_2, RabbitConstant.EXCHANGE_NEWS, "");
/*
如果不写basicQos(1),MQ会自动把所有的消息平均发给所有的消费者。
写了,MQ不再对消费者一次性发送多个消息,而是消费者处理完一个消息后(确认后),再从队列里获取一个新的。
*/
channel.basicQos(1);
/*
从MQ服务器中获取数据
P1:队列名
P2:是否自动确认收到消息,false表示需要手动编码确认消息(MQ推荐手动)。
P3:DefaultConsumer的实现类对象 做消息处理
*/
channel.basicConsume(RabbitConstant.QUEUE_PRO_SUB_2, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 处理消息
String msg = new String(body);
System.out.println("发布订阅模式消费者2号收到消息:" + msg);
/*
签收消息
P1:消息ID
P2:false表示只确认签收当前的消息 true表示签收该消费者所有未签收的消息
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
代码实现路由模式通信
路由模式 生产者 发送消息时需要指定routing key。
package com.example.rabbitmq.routing;
import com.example.rabbitmq.helloworld.RabbitConstant;
import com.example.rabbitmq.helloworld.RabbitUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 路由模式 生产者
*
* @author YangWanYi
* @version 1.0
* @date 2022/12/10 21:43
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
// 模拟要发送的消息 key:routing-key value:消息详情
Map<String, String> messages = new HashMap<>(10);
int times = 10;
for (int i = 0; i < times; i++) {
messages.put("routing-key" + (i + 1), "消息详情" + (i + 1));
}
// 获取TCP长连接
Connection connection = RabbitUtil.getConnection();
// 创建通信通道 相当于TCP中的虚拟连接
Channel channel = connection.createChannel();
Iterator<Map.Entry<String, String>> iterator = messages.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
/*
发送消息
P1:交换机名称
P2:routing key
P3:其他参数
P4:要发送的消息
*/
channel.basicPublish(RabbitConstant.EXCHANGE_DIRECT, entry.getKey(), null, entry.getValue().getBytes(StandardCharsets.UTF_8));
}
// 关闭连接
channel.close();
connection.close();
}
}
路由模式 消费者1号
package com.example.rabbitmq.routing;
import com.example.rabbitmq.helloworld.RabbitConstant;
import com.example.rabbitmq.helloworld.RabbitUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 路由模式 消费者1号
* 路由模式消费自己指定routing key的消息
*
* @author YangWanYi
* @version 1.0
* @date 2022/12/10 21:43
*/
public class Consumer1 {
public static void main(String[] args) throws IOException {
// 获取TCP长连接
Connection connection = RabbitUtil.getConnection();
// 创建通信通道 相当于TCP中的虚拟连接
final Channel channel = connection.createChannel();
/*
声明队列
P1:队列名称 如果队列名不存在 会自动创建
P2:是否持久化,false表示不持久化数据,MQ停掉数据就会丢失。
P3:是否队列私有化,false表示所有消费者都可以访问,true表示只有第一次拥有该队列的消费者才能一直使用。
P4:是否自动删除,false表示连接停掉后不自动删除这个队列。
P5:其他参数
*/
channel.queueDeclare(RabbitConstant.QUEUE_ROUTING_1, false, false, false, null);
/*
绑定交换机
P1:队列名
P2:交换机名 必须先创建好,否则会报错。路由模式的交换机类型是direct(point-to-point)。
P3:路由key 发布订阅模式还用不到这个参数
*/
channel.queueBind(RabbitConstant.QUEUE_ROUTING_1, RabbitConstant.EXCHANGE_DIRECT, "routing-key5");
channel.queueBind(RabbitConstant.QUEUE_ROUTING_1, RabbitConstant.EXCHANGE_DIRECT, "routing-key6");
channel.queueBind(RabbitConstant.QUEUE_ROUTING_1, RabbitConstant.EXCHANGE_DIRECT, "routing-key7");
/*
如果不写basicQos(1),MQ会自动把所有的消息平均发给所有的消费者。
写了,MQ不再对消费者一次性发送多个消息,而是消费者处理完一个消息后(确认后),再从队列里获取一个新的。
*/
channel.basicQos(1);
/*
从MQ服务器中获取数据
P1:队列名
P2:是否自动确认收到消息,false表示需要手动编码确认消息(MQ推荐手动)。
P3:DefaultConsumer的实现类对象 做消息处理
*/
channel.basicConsume(RabbitConstant.QUEUE_ROUTING_1, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 处理消息
String msg = new String(body);
System.out.println("路由模式消费者1号收到消息:" + msg);
/*
签收消息
P1:消息ID
P2:false表示只确认签收当前的消息 true表示签收该消费者所有未签收的消息
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
路由模式 消费者2号
package com.example.rabbitmq.routing;
import com.example.rabbitmq.helloworld.RabbitConstant;
import com.example.rabbitmq.helloworld.RabbitUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 路由模式 消费者2号
* 路由模式消费自己指定routing key的消息
*
* @author YangWanYi
* @version 1.0
* @date 2022/12/10 21:43
*/
public class Consumer2 {
public static void main(String[] args) throws IOException {
// 获取TCP长连接
Connection connection = RabbitUtil.getConnection();
// 创建通信通道 相当于TCP中的虚拟连接
final Channel channel = connection.createChannel();
/*
声明队列
P1:队列名称 如果队列名不存在 会自动创建
P2:是否持久化,false表示不持久化数据,MQ停掉数据就会丢失。
P3:是否队列私有化,false表示所有消费者都可以访问,true表示只有第一次拥有该队列的消费者才能一直使用。
P4:是否自动删除,false表示连接停掉后不自动删除这个队列。
P5:其他参数
*/
channel.queueDeclare(RabbitConstant.QUEUE_ROUTING_2, false, false, false, null);
/*
绑定交换机
P1:队列名
P2:交换机名 必须先创建好,否则会报错。路由模式的交换机类型是direct(point-to-point)。
P3:路由key 发布订阅模式还用不到这个参数
*/
channel.queueBind(RabbitConstant.QUEUE_ROUTING_2, RabbitConstant.EXCHANGE_DIRECT, "routing-key1");
channel.queueBind(RabbitConstant.QUEUE_ROUTING_2, RabbitConstant.EXCHANGE_DIRECT, "routing-key2");
channel.queueBind(RabbitConstant.QUEUE_ROUTING_2, RabbitConstant.EXCHANGE_DIRECT, "routing-key3");
/*
如果不写basicQos(1),MQ会自动把所有的消息平均发给所有的消费者。
写了,MQ不再对消费者一次性发送多个消息,而是消费者处理完一个消息后(确认后),再从队列里获取一个新的。
*/
channel.basicQos(1);
/*
从MQ服务器中获取数据
P1:队列名
P2:是否自动确认收到消息,false表示需要手动编码确认消息(MQ推荐手动)。
P3:DefaultConsumer的实现类对象 做消息处理
*/
channel.basicConsume(RabbitConstant.QUEUE_ROUTING_2, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 处理消息
String msg = new String(body);
System.out.println("路由模式消费者2号收到消息:" + msg);
/*
签收消息
P1:消息ID
P2:false表示只确认签收当前的消息 true表示签收该消费者所有未签收的消息
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
代码实现主题模式通信
主题模式 生产者
package com.example.rabbitmq.topic;
import com.example.rabbitmq.helloworld.RabbitConstant;
import com.example.rabbitmq.helloworld.RabbitUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 主题模式 生产者
* 主题模式的交换机类型是topic
* @author YangWanYi
* @version 1.0
* @date 2022/12/10 22:29
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
// 模拟要发送的消息 key:routing-key value:消息详情
Map<String, String> messages = new HashMap<>(10);
int times = 10;
for (int i = 0; i < times; i++) {
messages.put("routing.key." + (i + 1), "消息详情" + (i + 1));
}
// 获取TCP长连接
Connection connection = RabbitUtil.getConnection();
// 创建通信通道 相当于TCP中的虚拟连接
Channel channel = connection.createChannel();
Iterator<Map.Entry<String, String>> iterator = messages.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
/*
发送消息
P1:交换机名称
P2:routing key
P3:其他参数
P4:要发送的消息
*/
channel.basicPublish(RabbitConstant.EXCHANGE_TOPIC, entry.getKey(), null, entry.getValue().getBytes(StandardCharsets.UTF_8));
}
// 关闭连接
channel.close();
connection.close();
}
}
主题模式 消费者1号
package com.example.rabbitmq.topic;
import com.example.rabbitmq.helloworld.RabbitConstant;
import com.example.rabbitmq.helloworld.RabbitUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 主题模式 消费者1号
* topic模式与routing模式都可以根据routing key把消息路由到不同的队列,
* 只是topic类型的交换机可以让队列在绑定routing key的时候使用通配符。
* 通配符规则:
* # 匹配一个或多个词;
* * 匹配一个词。
* 比如text.#能匹配text.abc或text.abc.123,text.*只能匹配text.abc。
*
* @author YangWanYi
* @version 1.0
* @date 2022/12/10 22:29
*/
public class Consumer1 {
public static void main(String[] args) throws IOException {
// 获取TCP长连接
Connection connection = RabbitUtil.getConnection();
// 创建通信通道 相当于TCP中的虚拟连接
final Channel channel = connection.createChannel();
/*
声明队列
P1:队列名称 如果队列名不存在 会自动创建
P2:是否持久化,false表示不持久化数据,MQ停掉数据就会丢失。
P3:是否队列私有化,false表示所有消费者都可以访问,true表示只有第一次拥有该队列的消费者才能一直使用。
P4:是否自动删除,false表示连接停掉后不自动删除这个队列。
P5:其他参数
*/
channel.queueDeclare(RabbitConstant.QUEUE_ROUTING_1, false, false, false, null);
/*
绑定交换机
P1:队列名
P2:交换机名 必须先创建好,否则会报错。主题模式的交换机类型是topics
P3:路由key 发布订阅模式还用不到这个参数
*/
channel.queueBind(RabbitConstant.QUEUE_ROUTING_1, RabbitConstant.EXCHANGE_TOPIC, "routing.#");
/*
如果不写basicQos(1),MQ会自动把所有的消息平均发给所有的消费者。
写了,MQ不再对消费者一次性发送多个消息,而是消费者处理完一个消息后(确认后),再从队列里获取一个新的。
*/
channel.basicQos(1);
/*
从MQ服务器中获取数据
P1:队列名
P2:是否自动确认收到消息,false表示需要手动编码确认消息(MQ推荐手动)。
P3:DefaultConsumer的实现类对象 做消息处理
*/
channel.basicConsume(RabbitConstant.QUEUE_ROUTING_1, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 处理消息
String msg = new String(body);
System.out.println("主题模式消费者1号收到消息:" + msg);
/*
签收消息
P1:消息ID
P2:false表示只确认签收当前的消息 true表示签收该消费者所有未签收的消息
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
主题模式 消费者2号
package com.example.rabbitmq.topic;
import com.example.rabbitmq.helloworld.RabbitConstant;
import com.example.rabbitmq.helloworld.RabbitUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 主题模式 消费者2号
* topic模式与routing模式都可以根据routing key把消息路由到不同的队列,
* 只是topic类型的交换机可以让队列在绑定routing key的时候使用通配符。
* 通配符规则:
* # 匹配一个或多个词;
* * 匹配一个词。
* 比如text.#能匹配text.abc或text.abc.123,text.*只能匹配text.abc。
*
* @author YangWanYi
* @version 1.0
* @date 2022/12/10 22:29
*/
public class Consumer2 {
public static void main(String[] args) throws IOException {
// 获取TCP长连接
Connection connection = RabbitUtil.getConnection();
// 创建通信通道 相当于TCP中的虚拟连接
final Channel channel = connection.createChannel();
/*
声明队列
P1:队列名称 如果队列名不存在 会自动创建
P2:是否持久化,false表示不持久化数据,MQ停掉数据就会丢失。
P3:是否队列私有化,false表示所有消费者都可以访问,true表示只有第一次拥有该队列的消费者才能一直使用。
P4:是否自动删除,false表示连接停掉后不自动删除这个队列。
P5:其他参数
*/
channel.queueDeclare(RabbitConstant.QUEUE_ROUTING_2, false, false, false, null);
/*
绑定交换机
P1:队列名
P2:交换机名 必须先创建好,否则会报错。路由模式的交换机类型是direct(point-to-point)。
P3:路由key 发布订阅模式还用不到这个参数
*/
channel.queueBind(RabbitConstant.QUEUE_ROUTING_2, RabbitConstant.EXCHANGE_TOPIC, "*.*.1");
/*
如果不写basicQos(1),MQ会自动把所有的消息平均发给所有的消费者。
写了,MQ不再对消费者一次性发送多个消息,而是消费者处理完一个消息后(确认后),再从队列里获取一个新的。
*/
channel.basicQos(1);
/*
从MQ服务器中获取数据
P1:队列名
P2:是否自动确认收到消息,false表示需要手动编码确认消息(MQ推荐手动)。
P3:DefaultConsumer的实现类对象 做消息处理
*/
channel.basicConsume(RabbitConstant.QUEUE_ROUTING_2, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 处理消息
String msg = new String(body);
System.out.println("主题模式消费者2号收到消息:" + msg);
/*
签收消息
P1:消息ID
P2:false表示只确认签收当前的消息 true表示签收该消费者所有未签收的消息
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
Spring整合RabbitMQ
生产者客户端
POM依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
配置文件 rabbitmq.properties
rabbitmq.host=192.168.0.42
rabbitmq.port=5672
rabbitmq.username=ywy
rabbitmq.password=ywy
rabbitmq.virtual-host=myHost
配置文件 spring-rabbitmq-producer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<!--定义管理交换机、队列 有这行配置,下边配置的交换机和队列都会自动创建。-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义持久化队列,不存在则自动创建;不绑定到交换机则绑定到默认交换机
默认交换机类型为direct,名字为:"",路由键为队列的名称
-->
<!--
id:bean的名称
name:queue的名称
auto-declare:自动创建
auto-delete:自动删除。 最后一个消费者和该队列断开连接后,自动删除队列
durable:是否持久化
-->
<rabbit:queue id="spring_queue" name="spring_queue" auto-declare="true"/>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~广播;所有队列都能收到消息~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>
<!--定义广播类型交换机;并绑定上述两个队列-->
<rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="spring_fanout_queue_1" />
<rabbit:binding queue="spring_fanout_queue_2"/>
</rabbit:bindings>
</rabbit:fanout-exchange>
<!-- 定义队列-->
<rabbit:queue id="spring_direct_queue" name="spring_direct_queue" auto-declare="true"/>
<!--
定义 Routing 路由模式 交互机
-->
<rabbit:direct-exchange name="spring_direct_exchange" >
<rabbit:bindings>
<!--direct 类型的交换机绑定队列 key :路由key queue:队列名称-->
<rabbit:binding queue="spring_direct_queue" key="info"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~通配符;*匹配一个单词,#匹配多个单词 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_star" name="spring_topic_queue_star" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well" name="spring_topic_queue_well" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well2" name="spring_topic_queue_well2" auto-declare="true"/>
<!--
声明 topic 类型的交换机
-->
<rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding pattern="ywy.*" queue="spring_topic_queue_star"/>
<rabbit:binding pattern="ywy.#" queue="spring_topic_queue_well"/>
<rabbit:binding pattern="test.#" queue="spring_topic_queue_well2"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>
消息发送测试类
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* 发送消息测试
*
* @author YangWanYi
* @version 1.0
* @date 2022-12-10 23:49
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class SendMsgTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testHelloWorld() {
// 简单模式 发消息
rabbitTemplate.convertAndSend("spring_queue", "hello world spring....");
}
@Test
public void testFanout() {
// 队列模式 发消息
rabbitTemplate.convertAndSend("spring_fanout_exchange", "", "spring fanout....");
}
@Test
public void testDirect() {
// 路由模式 发消息
rabbitTemplate.convertAndSend("spring_direct_exchange", "info", "spring Direct....");
}
@Test
public void testTopics() {
// 主题模式 发消息
rabbitTemplate.convertAndSend("spring_topic_exchange", "baiqi.hehe.haha", "spring topic....");
}
}
消费者客户端
POM依赖【同上】
配置文件 rabbitmq.properties【同上】
配置文件 spring-rabbitmq-consumer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<bean id="springQueueListener" class="com.ywy.rabbitmq.listener.SpringQueueListener"/>
<bean id="fanoutListener1" class="com.ywy.rabbitmq.listener.FanoutListener"/>
<!-- <bean id="fanoutListener2" class="com.ywy.rabbitmq.listener.FanoutListener2"/>
<bean id="topicListenerStar" class="com.ywy.rabbitmq.listener.TopicListenerStar"/>
<bean id="topicListenerWell" class="com.ywy.rabbitmq.listener.TopicListenerWell"/>
<bean id="topicListenerWell2" class="com.ywy.rabbitmq.listener.TopicListenerWell2"/>
-->
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="springQueueListener" queue-names="spring_queue"/>
<rabbit:listener ref="fanoutListener1" queue-names="spring_fanout_queue_1"/>
<!--<rabbit:listener ref="fanoutListener2" queue-names="spring_fanout_queue_2"/>
<rabbit:listener ref="topicListenerStar" queue-names="spring_topic_queue_star"/>
<rabbit:listener ref="topicListenerWell" queue-names="spring_topic_queue_well"/>
<rabbit:listener ref="topicListenerWell2" queue-names="spring_topic_queue_well2"/>-->
</rabbit:listener-container>
</beans>
接收消息测试类
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 消费端测试
*
* @author YangWanYi
* @version 1.0
* @date 2022-12-11 00:32
*/
public class ConsumerTest {
public static void main(String[] args) {
// 初始化IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring-rabbitmq-consumer.xml");
}
}
简单模式 消息监听类 其他模式一样,只需要实现监听接口MessageListener即可。
package com.ywy.rabbitmq.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
/**
* 简单模式消费者客户端监听器
*
* @author YangWanYi
* @version 1.0
* @date 2022-12-11 00:28
*/
public class SpringQueueListener implements MessageListener {
@Override
public void onMessage(Message message) {
System.out.println("简单模式消费者客户端收到消息:" + new String(message.getBody()));
}
}
SpringBoot整合RabbitMQ
生产者客户端
YML文件配置
# 配置RabbitMQ的基本信息
spring:
rabbitmq:
host: 192.168.0.42
port: 5672
username: ywy
password: ywy
virtual-host: myHost
POM依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
RabbitMQ配置文件
package com.example.rabbitmq.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RabbitMQ配置文件
*
* @author YangWanYi
* @version 1.0
* @date 2022-12-11 14:57
*/
@Configuration
public class RabbitMqConfig {
/**
* 定义交换机名称
*/
public static final String EXCHANGE_NAME = "boot_topic_exchange";
/**
* 定义队列名称
*/
public static final String QUEUE_NAME = "boot_queue";
/**
* 声明交换机
*
* @return org.springframework.amqp.core.Exchange
* @author YangWanYi
* @date 2022/12/11 15:04
*/
@Bean
public Exchange declareExchange() {
// P1:交换机名称 P2:是否持久化
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
/**
* 声明队列
*
* @return org.springframework.amqp.core.Queue
* @author YangWanYi
* @date 2022/12/11 15:07
*/
@Bean
public Queue declareQueue() {
return QueueBuilder.durable(QUEUE_NAME).build();
}
/**
* 绑定队列与交换机
*
* @param queue 队列
* @param exchange 交换机
* @return org.springframework.amqp.core.Binding
* @author YangWanYi
* @date 2022/12/11 15:13
*/
@Bean
public Binding bindQueueAndExchange(@Qualifier("declareQueue") Queue queue, @Qualifier("declareExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();
}
}
测试消息发送
package com.example.springbootrabbitmqproducer;
import com.example.rabbitmq.config.RabbitMqConfig;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringbootRabbitmqProducerApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void sendMSg() {
this.rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE_NAME,"boot.money","超多钱超多钱……");
}
}
消费者客户端
YML文件配置【同上】
POM依赖【同上】
消息监听类
package com.example.rabbitmq.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* RabbitMQ监听器
*
* @author YangWanYi
* @version 1.0
* @date 2022-12-11 15:54
*/
@Component
public class RabbitMqListener {
/**
* 监听消息
*
* @param msg 收到的消息
* @return void
* @author YangWanYi
* @date 2022/12/11 15:56
*/
@RabbitListener(queues = "boot_queue")
public void listenQueue(Message msg) {
System.out.println("springboot消费端收到消息:" + msg);
}
}