文章目录
RabbitMQ消息队列
RabbitMQ作为一个消息代理,主要负责接收、存储和转发消息,它提供了可靠的消息机制和灵活的消息路由,并支持消息集群和分布式部署,常用于应用解耦,耗时任务队列,流量削锋等场景。
1. 简单队列实现
引入jar文件
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.1</version>
</dependency>
创建生产者
package com.hz.rabbitmq;
import com.hz.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
/**
* @ClassName Provider
* @Description 生产者
* @Author Administrator
* @Date 2024/12/24 15:26
*/
public class Provider {
private static final String QUEUE_NAME = "rabbitmq";
public static void main(String[] args) throws Exception{
//获取连接
Connection connection = ConnectionUtils.getConnection();
//获取通道
Channel channel = connection.createChannel();
//声明队列 (参数1:队列名称,参数2:是否持久化,参数3:是否独占,参数4:是否自动删除,参数5:其他参数)
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//发送消息
String msg = "hello rabbitMQ";
//发送消息,参数1:交换机名称,参数2:队列名称,参数3:消息的其他属性(PERSISTENT_TEXT_PLAIN:数据持久化),参数4:消息内容
channel.basicPublish("",QUEUE_NAME, null,msg.getBytes());
System.out.println("发送消息成功");
channel.close();
connection.close();
}
}
创建消费者
package com.hz.rabbitmq;
import com.hz.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @ClassName Consumer01
* @Description 消费者01
* @Author Administrator
* @Date 2024/12/24 15:30
*/
public class Consumer01 {
private static final String QUEUE_NAME = "rabbitmq";
public static void main(String[] args) throws Exception
{
// 获取连接
Connection connection = ConnectionUtils.getConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 创建消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override // 消息到达,触发这个方法
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("消费者01接收到消息:" + msg);
}
};
// 监听队列 参数二:是否自动确认
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
2. Work模式(工作队列)
一个生产者对应多个消费者,但是只能有一个消费者获得消息!!!
生产者
发送 50 条数据
//声明队列 (参数1:队列名称,参数2:是否持久化,参数3:是否独占,参数4:是否自动删除,参数5:其他参数)
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
for (int i = 0; i < 50; i++){
//发送消息
String msg = "hello rabbitMQ===="+i;
//发送消息,参数1:交换机名称,参数2:队列名称,参数3:消息的其他属性(PERSISTENT_TEXT_PLAIN:数据持久化),参数4:消息内容
channel.basicPublish("",QUEUE_NAME, null,msg.getBytes());
}
创建消费者01,02
结果:轮询分发(Round-robin)
- 消费者 1 与消费者 2 处理的数据条数一样。
- 消费者 1 处理偶数
- 消费者 2 处理奇数
2.1 实现公平分发
生产者
同一时刻服务器只会发一条消息给消费者1 限制发送给消费者不得超过一条消息
//获取通道
Channel channel = connection.createChannel();
//保证消息发送的可靠性,一次发送一个消息
channel.basicQos(1);
消费者01休眠1000ms,02休眠3000ms
package com.hz.rabbitmq;
import com.hz.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @ClassName Consumer01
* @Description 消费者01
* @Author Administrator
* @Date 2024/12/24 15:30
*/
public class Consumer01 {
private static final String QUEUE_NAME = "rabbitmq";
public static void main(String[] args) throws Exception
{
// 获取连接
Connection connection = ConnectionUtils.getConnection();
// 获取通道
Channel channel = connection.createChannel();
channel.basicQos(1);
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 创建消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override // 消息到达,触发这个方法
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("消费者01接收到消息:" + msg);
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}finally {
// 手动确认消息
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
// 监听队列 参数二:是否自动确认
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
2.2 消息应答与持久化
消息应答
// 监听队列 参数二:是否自动确认false/true
channel.basicConsume(QUEUE_NAME, false, consumer);
True:自动确认
只要消息从队列中获取,无论消费者获取到消息后是否成功消费,都认为是消息已经成功消费。一旦rabbitmq 将消息分发给消费者,就会从内存中删除。(会丢失数据消息)
False:手动确认
消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态。如果有一个消费者挂掉,就会交付给其他消费者。手动告诉 rabbitmq 消息处理完成后,rabbitmq 删除内存中的消息。
// 手动确认消息
channel.basicAck(envelope.getDeliveryTag(), false);
使用Nack让消息回到队列
// 手动拒绝 参数 1:消息的编号 2:是否批量处理 3:false 丢弃,true 放回队列
channel.basicNack(envelope.getDeliveryTag(), false,true);
消费者
try {
Thread.sleep(1000);
}catch (InterruptedException e){
// 手动拒绝 参数 1:消息的编号 2:是否批量处理 3:false 丢弃,true 放回队列
channel.basicNack(envelope.getDeliveryTag(), false,true);
e.printStackTrace();
}finally {
// 手动确认消息
channel.basicAck(envelope.getDeliveryTag(), false);
}
如果 rabbitmq 挂了,我们的消息任然会丢失!
持久化
生产者
//保证消息发送的可靠性,一次发送一个消息
channel.basicQos(1);
//声明队列 (参数1:队列名称,参数2:是否持久化,参数3:是否独占,参数4:是否自动删除,参数5:其他参数)
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
for (int i = 0; i < 50; i++){
//发送消息
String msg = "hello rabbitMQ===="+i;
//发送消息,参数1:交换机名称,参数2:队列名称,参数3:消息的其他属性(PERSISTENT_TEXT_PLAIN:数据持久化),参数4:消息内容
channel.basicPublish("",QUEUE_NAME,MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes() );
}
声明队列要保持一致
3. 订阅模式
一个生产者将消息首先发送到交换器,交换器绑定多个队列,然后被监听该队列的消费者所接收并消费。
一个生产者多个消费者,每个消费者都有自己的队列,生产者没有吧消息直接发送到队列,而是发送到了交换机,每个队列都绑定到交换机,生产者发送的消息,经过交换机,到达队列,实现,一个消息被多个消费者获取的目的。
生产者
package com.hz.rabbitmq.dingyue;
import com.hz.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
/**
* @ClassName Provider 订阅者
* @Description 生产者
* @Author Administrator
* @Date 2024/12/25 10:37
*/
public class Provider {
// 交换机名称
private static final String EXCHANGE_NAME = "exchange_test";
public static void main(String[] args) throws Exception{
// 获取连接 和通道
Channel channel = ConnectionUtils.getConnection().createChannel();
// 声明交换机 fanout: 广播模式,不处理路由键
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//消息内容
String msg = "Hello World!";
// 发送消息
channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
System.out.println("发送消息成功");
// 关闭通道和连接
channel.close();
ConnectionUtils.getConnection().close();
}
}
消息发送到没有队列绑定的交换机时,消息将丢失,因为,交换机没有存储消息的能力,消息只能存在在队列中。
消费者
package com.hz.rabbitmq.dingyue;
import com.hz.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
/**
* @ClassName Consumer01
* @Description 消费者01
* @Author Administrator
* @Date 2024/12/25 10:44
*/
public class Consumer01 {
// 队列名称
private static final String QUEUE_NAME = "queue_test01";
// 交换机名称
private static final String EXCHANGE_NAME = "exchange_test";
public static void main(String[] args) throws Exception {
// 获取连接和通道
Channel channel = ConnectionUtils.getConnection().createChannel();
// 允许预取数量
channel.basicQos(1);
// 创建声明队列 (参数1:队列名称,参数2:是否持久化,参数3:是否独占,参数4:是否自动删除,参数5:额外参数)
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 绑定队列到交换机 (参数1:队列名称,参数2:交换机名称,参数3:路由键)
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("消费者01接收到消息:" + msg);
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(QUEUE_NAME,false,consumer);
}
}
//消费者02
// 队列名称
private static final String QUEUE_NAME = "queue_test02";
结果:生产者发送消息,两个消费者同时获得并输出。
4. 路由模式
生产者
package com.hz.rabbitmq.luyou;
import com.hz.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
* @ClassName Provider
* @Description 生产者
* @Author Administrator
* @Date 2024/12/25 11:03
*/
public class Provider {
// 交换机名称
private static final String EXCHANGE_NAME = "exchange_routing";
public static void main(String[] args) throws Exception{
// 获取连接
Connection connection = ConnectionUtils.getConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明交换机 direct:处理路由键
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 消息内容
String message = "Hello direct!11";
// 发送消息 info: 普通消息 warning: 警告消息 error: 错误消息
channel.basicPublish(EXCHANGE_NAME,"info",null,message.getBytes());
// 关闭通道和连接
channel.close();
connection.close();
}
}
消费者
package com.hz.rabbitmq.luyou;
import com.hz.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @ClassName Consumer02
* @Description 消费者02
* @Author Administrator
* @Date 2024/12/25 11:07
*/
public class Consumer02 {
private static final String QUEUE_NAME = "queue_routing02";
private static final String EXCHANGE_NAME = "exchange_routing";
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicQos(1);//限流,一次只接受一个消息
//绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "info");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "error");
//创建消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("消费者02接收到消息:" + msg);
//手动确认
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//监听队列
channel.basicConsume(QUEUE_NAME,false,consumer);
}
}
//消费者01
//绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "error");
结果:根据key值进行处理
5. 主题模式
#
匹配一个或多个
*
匹配一个
生产者
package com.hz.rabbitmq.zhuti;
import com.hz.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
* @ClassName Provider
* @Description 生产者
* @Author Administrator
* @Date 2024/12/25 11:26
*/
public class Provider {
// 交换机名称
private static final String EXCHANGE_NAME = "exchange_topic";
public static void main(String[] args) throws Exception{
// 获取连接
Connection connection = ConnectionUtils.getConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明交换机 topic: 主题模式
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 消息内容
String message = "Hello topic!";
// 发送消息
channel.basicPublish(EXCHANGE_NAME,"key.3.1",null,message.getBytes());
System.out.println("发送消息成功");
// 关闭通道和连接
channel.close();
connection.close();
}
}
消费者
package com.hz.rabbitmq.zhuti;
import com.hz.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @ClassName Consumer01
* @Description 消费者01
* @Author Administrator
* @Date 2024/12/25 11:07
*/
public class Consumer01 {
private static final String QUEUE_NAME = "queue_topic01";
private static final String EXCHANGE_NAME = "exchange_topic";
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicQos(1);//限流,一次只接受一个消息
//绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key.*");
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("消费者01接收到消息:" + msg);
//手动确认
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//监听队列
channel.basicConsume(QUEUE_NAME,false,consumer);
}
}
//消费者02
//绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key.3.*");
结果:根据通配符进行处理
6. 消息确认机制
- AMQP 协议中实现了事务机制
channel.txSelect()声明启动事务模式;
channel.txCommit()提交事务;
channel.txRollback()回滚事务;
生产者
Connection conn = ConnectionUtils.getConnection();
Channel ch = conn.createChannel();
ch.queueDeclare(QUEUE_NAME,false,false,false,null);
String str = "hello rabbit";
ch.txSelect();//开启事务
try {
ch.basicPublish("",QUEUE_NAME,null,str.getBytes());
//加入错误代码后事务回滚
int i = 1/0;
ch.txCommit();//提交事务
} catch (IOException e) {
e.printStackTrace();
ch.txRollback();//回滚事务
} finally {
ch.close();
conn.close();
}
模式缺点:降低系统吞吐量
- Confirm 模式
方式一:channel.waitForConfirms()普通发送方确认模式;
方式二:channel.waitForConfirmsOrDie()批量确认模式;
方式三:channel.addConfirmListener()异步监听发送方确认模式;
普通发送方确认模式
Connection conn = ConnectionUtils.getConnection();
Channel ch = conn.createChannel();
ch.queueDeclare(QUEUE_NAME,false,false,false,null);
String str = "hello rabbit1";
ch.confirmSelect();//开启消息确认模式
ch.basicPublish("",QUEUE_NAME,null,str.getBytes());
//加入错误代码后事务回滚
int i = 1/0;
if(ch.waitForConfirms())
{
System.out.println("消息确认发送");
}
ch.close();
conn.close();
批量确认模式
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 开启发送方确认模式
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
String message = "hello rabbit2";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
}
channel.waitForConfirmsOrDie(); //直到所有信息都发布,只要有一个未确认就会
IOException
System.out.println("全部执行完成");
异步监听发送方确认模式
// 开启发送方确认模式
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
String message = "holle wzy "+i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
}
//异步监听确认和未确认的消息
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("未确认消息,标识:" + deliveryTag);
}
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println(String.format("已确认消息,标识:%d,多个消息:%b",
deliveryTag, multiple));
}
});
异步模式的优点,就是执行效率高,不需要等待消息执行完,只需要监听消息即可。
deliveryTag
:如果是多条,这个就是最后一条消息的 tag
Multiple
:是否多条
7. Spring Boot连接Rabbit MQ
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/orders?allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 1234
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
listener:
simple:
prefetch: 1 # 每次只处理一个消息
acknowledge-mode: manual # 手动确认
7.1 简单队列模式
生产者
@RestController
@RequestMapping("/index")
public class IndexController {
@Autowired
RabbitTemplate rabbitTemplate;
@RequestMapping("/send")
public String send(){
//发送消息
rabbitTemplate.convertAndSend("hello","hello rabbitmq");
return "success";
}
}
消费者
@Component
public class Consumer {
@Autowired
BuyMapper buyMapper;
@Autowired
ProductMapper productMapper;
@RabbitHandler
@RabbitListener(queuesToDeclare =@Queue ("hello"))
public void receive(String msg) {
System.out.println("Received: " + msg);
}
}
7.2 工作队列
生产者
@RequestMapping("/work")
public String work(){
for (int i = 0; i < 50; i++){
//发送消息
rabbitTemplate.convertAndSend("work","hello rabbitmq"+i);
}
return "success";
}
消费者
@RabbitHandler
@RabbitListener(queuesToDeclare =@Queue ("work"))
public void receive2(Message message, Channel channel) {
String msg = null;
try {
msg = new String(message.getBody(), "UTF-8");
System.out.println("Received2: " + msg);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
try {
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
} catch (IOException ioException) {
ioException.printStackTrace();
}
}finally {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@RabbitHandler
@RabbitListener(queuesToDeclare =@Queue ("work"))
public void receive1(Message message, Channel channel) {
String msg = null;
try {
msg = new String(message.getBody(), "UTF-8");
System.out.println("Received1: " + msg);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
try {
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
} catch (IOException ioException) {
ioException.printStackTrace();
}
}finally {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//实现公平分发在yml中配置prefetch: 1 # 每次只处理一个消息
7.3 传输对象
生产者
@RequestMapping("/buy")
public String send2(){
Buy buy = new Buy(4,1);
//发送消息
rabbitTemplate.convertAndSend("buy",buy);
return "success";
}
消费者
@RabbitHandler
@RabbitListener(queues = "buy")
public void receive3(Message message, Channel channel) {
//获取对象
Buy buy = (Buy) SerializationUtils.deserialize(message.getBody());
buyMapper.insert(buy);//插入购买记录
Product product = new Product();
product.setId(buy.getGoodsId());
product.setStock(productMapper.selectById(buy.getGoodsId()).getStock()-1);
productMapper.updateById(product);//更新库存
//手动反馈
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
System.out.println("用户"+buy.getuId()+"购买商品"+buy.getGoodsId()+"成功");
} catch (IOException e) {
e.printStackTrace();
}
}
启动时创建队列
@Configuration
public class RabbitMQConfig {
//创建队列
@Bean
public Queue queue(){
return new Queue("buy");
}
}