RabbitMQ消息队列

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. 消费者 1 与消费者 2 处理的数据条数一样。
  2. 消费者 1 处理偶数
  3. 消费者 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");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值