分布式之RabbitMQ的使用(1)

文章目录

什么是RabbitMQ

消息队列(Message Queue,简称MQ),从字面意思上看,本质是个队列,FIFO先入先出,只不过队列中存放的内容是message而已。MQ框架非常之多,比较流行的有RabbitMq、ActiveMq、ZeroMq、kafka,以及阿里开源的RocketMQ。开发语言:Erlang -- 面向并发的编程语言

为什么会产生消息队列?有几个原因

不同进程(process)之间传递0消息时,两个进程之间耦合程度过高,改动一个进程,引发必须修改另一个进程,为了隔离这两个进程,在两进程间抽离出一层(一个模块),所有两进程之间传递的消息,都必须通过消息队列来传递,单独修改某一个进程,不会影响另一个;

不同进程(process)之间传递消息时,为了实现标准化,将消息的格式规范化了,并且,某一个进程接受的消息太多,一下子无法处理完,并且也有先后顺序,必须对收到的消息进行排队,因此诞生了事实上的消息队列。

有以下几个模式:

  1. 简单队列
  2. 工作队列
  3. 订阅模式
  4. 路由分发模式
  5. 主题模式

RabbitMQ安装

请看我之前分布式专栏的博客:RabbitMQ的安装

简单队列实现

生产者,队列,消费者

生产者生产消息,消费者拿到消息,RabbitMQ存储消息。

创建项目

创建项目

我们这里先使用指定的版本的包。

引入JAR

xml 复制代码
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.7.1</version>
</dependency>

初始化项目

删除java.com.hsh下的所有文件然后新增文件夹test01,test02,test03,utils

创建连接工具类

在utils文加下新建ConnectionUtils文件

java 复制代码
package com.hsh.utils;

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class ConnectionUtils {
    public static Connection getConnection() {
        try {
            Connection connection = null;
            // 定义一个连接工厂
            ConnectionFactory connectionFactory = new ConnectionFactory();
            // 设置连接地址
            connectionFactory.setHost("127.0.0.1");
            // 设置端口号
            connectionFactory.setPort(5672);
            // 设置虚拟主机(相当于数据库中的库)
            connectionFactory.setVirtualHost("/");
            // 设置用户名
            connectionFactory.setUsername("guest");
            // 设置密码
            connectionFactory.setPassword("guest");
            connection = connectionFactory.newConnection();
            
            return connection;

        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
}

创建生产者

代码编写

java 复制代码
package com.hsh.test01;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Producer01 {
    public static void main(String[] args) {
        System.out.println("生产者启动...");
        // 获得连接
        Connection connection = ConnectionUtils.getConnection();
        try {
            // 创建通道
            Channel channel = connection.createChannel();
            // 创建队列声明
            channel.queueDeclare("贝尔摩德", false, false, false, null);
            // 定义发送消息的数据
            String message = "秘密让女人更有魅力";
            // 发送消息
            channel.basicPublish("", "贝尔摩德", null, message.getBytes());

            System.out.println("生产者发送消息:" + message);
            channel.close();
            connection.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

打开命令行窗口查看

打开RabbitMQ的可视化界面,点击

设置是否排他:为 true 则设置队列为排他的。如果一个队列被声明为排 他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。这里需要注意 三点:排他队列是基于连接( Connection) 可见的,同 个连接的不同信道 (Channel) 是可以同时访问同一连接创建的排他队列; "首次"是指如果 个连接己经声明了 排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同:即使该队 列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除,这种队列 适用于一个客户端同时发送和读取消息的应用场景

创建消费者

我们需要对上面存到RabbitMQ的数据进行监听,如果RabbitMQ里面有数据就拿。而监听可以单独建一个类,也可写一个匿名内部类。

写法一:新建类

test01编写监听类

java 复制代码
package com.hsh.test01;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;

public class ListenerConsumer extends DefaultConsumer {
    public ListenerConsumer(Channel channel) {
        super(channel);
    }
    @Override
    public void handleDelivery(String consumerTag,
                               Envelope envelope,
                               AMQP.BasicProperties properties,
                               byte[] body) throws IOException {
        String message = new String(body, "UTF-8");
        System.out.println("消费者1:" + message);
    }
}

test01编写消费者

java 复制代码
package com.hsh.test01;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Consumer01 {
    public static void main(String[] args) {
        System.out.println("消费者启动...");

        try {
            // 获得连接
            Connection connection = ConnectionUtils.getConnection();
            // 创建通道
            Channel channel = connection.createChannel();
            // 连接队列
            channel.queueDeclare("贝尔摩德", false, false, false, null);
            // 监听 true自动反馈
            channel.basicConsume("贝尔摩德", true, new ListenerConsumer(channel));
            
        }catch (Exception e){
            e.printStackTrace();
        }

    }

}

写法二:使用匿名内部类

修改test01中的Consumer01

java 复制代码
package com.hsh.test01;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer01 {
    public static void main(String[] args) {
        System.out.println("消费者启动...");

        try {
            // 获得连接
            Connection connection = ConnectionUtils.getConnection();
            // 创建通道
            Channel channel = connection.createChannel();
            // 连接队列
            channel.queueDeclare("贝尔摩德", false, false, false, null);
            // 监听 true自动反馈
            // 使用匿名内部类
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String message = new String(body, "UTF-8");
                    System.out.println("消费者1:" + message);
                }
            };
            // 监听 true自动反馈
            channel.basicConsume("贝尔摩德", true, defaultConsumer);

        }catch (Exception e){
            e.printStackTrace();
        }

    }

}

两种写法的测试

我们先去删除队列中存入的值

先开消费者,在开启生产者

两种方法对比

第一种写法简单,不需要新建一个类,我比较喜欢第一种。

Work模式

一个生产者对应多个消费者,但是只能有一个消费者获得消息!!!

下面我们的生产者for循环打印20条消息,然后消费者进行使用。

创建生产者

test02中创建Producer02

java 复制代码
package com.hsh.test02;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Producer02 {
    public static void main(String[] args) {
        System.out.println("生产者启动...");
        // 获得连接
        Connection connection = ConnectionUtils.getConnection();
        try {
            // 创建通道
            Channel channel = connection.createChannel();
            // 创建队列声明
            channel.queueDeclare("毛利兰", false, false, false, null);

            for (int i = 0; i < 20; i++){
                // 定义发送消息的数据
                String message = "我并不讨厌等待,因为等待得越久,见到他的时候,我就越开心" + i;
                // 发送消息
                channel.basicPublish("", "毛利兰", null, message.getBytes());
                System.out.println("生产者发送消息:" + message);
                // 休眠
                Thread.sleep(200);
            }

            channel.close();
            connection.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

创建消费者

创建消费者01

java 复制代码
package com.hsh.test02;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer01 {
    public static void main(String[] args) {
        System.out.println("消费者启动...");

        try {
            // 获得连接
            Connection connection = ConnectionUtils.getConnection();
            // 创建通道
            Channel channel = connection.createChannel();
            // 连接队列
            channel.queueDeclare("毛利兰", false, false, false, null);
            // 监听 true自动反馈
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String message = new String(body, "UTF-8");
                    System.out.println("消费者1:" + message);
                }
            };
            // 
            channel.basicConsume("毛利兰", true, defaultConsumer);

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

创建消费者02

java 复制代码
package com.hsh.test02;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer02 {
    public static void main(String[] args) {
        System.out.println("消费者启动...");

        try {
            // 获得连接
            Connection connection = ConnectionUtils.getConnection();
            // 创建通道
            Channel channel = connection.createChannel();
            // 连接队列
            channel.queueDeclare("毛利兰", false, false, false, null);
            // 监听 true自动反馈
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String message = new String(body, "UTF-8");
                    System.out.println("消费者1:" + message);
                }
            };
            channel.basicConsume("毛利兰", true, defaultConsumer);

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

测试

先运行消费者,再运行生产者。

1.消费者1与消费者2处理的数据条数一样。

2.消费者1偶数

3.消费者2奇数

这种方式叫轮询分发(Round-robin)。

公平分发和手动/自动反馈

公平分发

现在有2个消费者,所有的奇数的消息都是繁忙的,而偶数则是轻松的。按照轮询的方式,奇数的任务交给了第一个消费者,所以一直在忙个不停。偶数的任务交给另一个消费者,则立即完成任务,然后闲得不行。而RabbitMQ则是不了解这些的。这是因为当消息进入队列,RabbitMQ就会分派消息。它不看消费者为应答的数目,只是盲目的将消息发给轮询指定的消费者。

我们可以给两个消费者分别加个延迟,模拟性能好的执行多一点,性能不好的执行少一点

修改消费者01

我们需要在消费者01加上如下

java 复制代码
try {
   Thread.sleep(100);
} catch (Exception e) {
   e.printStackTrace();
}

代码演示

java 复制代码
package com.hsh.test02;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer01 {
    public static void main(String[] args) {
        System.out.println("消费者启动...");

        try {
            // 获得连接
            Connection connection = ConnectionUtils.getConnection();
            // 创建通道
            Channel channel = connection.createChannel();
            // 连接队列
            channel.queueDeclare("毛利兰", false, false, false, null);
            // 监听 true自动反馈
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    String message = new String(body, "UTF-8");
                    System.out.println("消费者1:" + message);
                }
            };
            // 监听 true自动反馈
            channel.basicConsume("毛利兰", true, defaultConsumer);

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

修改消费者02

我们需要在消费者02加上如下

java 复制代码
try {
   Thread.sleep(500);
} catch (Exception e) {
   e.printStackTrace();
}

代码演示

java 复制代码
package com.hsh.test02;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer02 {
    public static void main(String[] args) {
        System.out.println("消费者启动...");

        try {
            // 获得连接
            Connection connection = ConnectionUtils.getConnection();
            // 创建通道
            Channel channel = connection.createChannel();
            // 连接队列
            channel.queueDeclare("毛利兰", false, false, false, null);
            // 监听 true自动反馈
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    try {
                        Thread.sleep(500);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    String message = new String(body, "UTF-8");
                    System.out.println("消费者1:" + message);
                }
            };
            channel.basicConsume("毛利兰", true, defaultConsumer);

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

运行结果有问题

并没预期效果,这是因为没有设置手动反馈和限制接收消息个数。

手动/自动反馈

但是我们演示上面的按性能好坏出来不同量的代码编写。我们需要让程序手动反馈否则看不出效果。那么什么是手动反馈,什么是自动反馈。就是我们之前写过的代码中的channel.basicConsume("毛利兰", true, defaultConsumer);

java 复制代码
package com.hsh.test02;

public class Consumer01 {
    public static void main(String[] args) {
		// ....
        try {
            
            //...
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){//....
            };
            // 这里true就是自动反馈
            channel.basicConsume("毛利兰", true, defaultConsumer);

        }catch (Exception e){
           
        }
    }
}

true是自动反馈(NACK),false是手动反馈(ACK)

我举个例子说明自动反馈

比如A是消费者01,B是消费者02,。

自动反馈

自动反馈是接收很多消息后再去进行处理。此时A,B就会争先恐后的拿数据,就会出现对半接收的情况。就不会出现性能好的执行多一点,性能不好的执行少一点的情况。也就是B很菜,但是一直拿,不管我能不能跑的完,很贪心,B拿多了,它还处理的慢。典型的贪多嚼不烂。

手动反馈

只有当我处理完当前的消息后才去拿新的消息。也就是能者多劳。此时就能出现性能好的执行多一点,性能不好的执行少一点的情况。除此之外我们还需要设置每次只允许通道中只有一条消息。

手动/自动反馈代码实现

修改消费者

我们需要在消费者加上channel.basicQos(1);

java 复制代码
/*
    同一时刻服务器只会发一条消息给消费者
    限制发送给消费者不得超过一条消息
 */
channel.basicQos(1);

// 接收消息后返回处理完毕
try {
    String message = new String(body, "UTF-8");
    System.out.println(envelope.getDeliveryTag()+"消费者1:" + message);
} catch (Exception e) {
    e.printStackTrace();
} finally {
    // 手动反馈
    // 第一个参数:envelope.getDeliveryTag() 当前消息的编号 我在上面的输出打印了可以看看
    // 第二个参数:false单挑消息应答,true批量应答
    channel.basicAck(envelope.getDeliveryTag(), false);
}

// 改为手动反馈
channel.basicConsume("毛利兰", false, defaultConsumer);

消费者01代码演示

java 复制代码
package com.hsh.test02;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.io.UnsupportedEncodingException;


public class Consumer01 {
    public static void main(String[] args) {
        System.out.println("消费者启动...");

        try {
            // 获得连接
            Connection connection = ConnectionUtils.getConnection();
            // 创建通道
            Channel channel = connection.createChannel();
            channel.basicQos(1);
            // 连接队列
            channel.queueDeclare("毛利兰", false, false, false, null);
            // 监听 true自动反馈
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    // 这里需要包起来,无论报不保存,都需要执行 channel.basicAck(envelope.getDeliveryTag(), false);
                    try {
                        String message = new String(body, "UTF-8");
                        System.out.println(envelope.getDeliveryTag()+"消费者1:" + message);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        // 手动反馈
                        // 第一个参数:envelope.getDeliveryTag() 当前消息的编号 我在上面的输出打印了可以看看
                        // 第二个参数:false单挑消息应答,true批量应答
                        channel.basicAck(envelope.getDeliveryTag(), false);
                    }

                }
            };
            channel.basicConsume("毛利兰", false, defaultConsumer);

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

消费者02代码演示

java 复制代码
package com.hsh.test02;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;


public class Consumer02 {
    public static void main(String[] args) {
        System.out.println("消费者启动...");

        try {
            // 获得连接
            Connection connection = ConnectionUtils.getConnection();
            // 创建通道
            Channel channel = connection.createChannel();
            channel.basicQos(1);
            // 连接队列
            channel.queueDeclare("毛利兰", false, false, false, null);
            // 监听 true自动反馈
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    try {
                        Thread.sleep(500);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    // 这里需要包起来,无论报不保存,都需要执行 channel.basicAck(envelope.getDeliveryTag(), false);
                    try {
                        String message = new String(body, "UTF-8");
                        System.out.println(envelope.getDeliveryTag()+"消费者2:" + message);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        // 手动反馈
                        // 第一个参数:envelope.getDeliveryTag() 当前消息的编号 我在上面的输出打印了可以看看
                        // 第二个参数:false单挑消息应答,true批量应答
                        channel.basicAck(envelope.getDeliveryTag(), false);
                    }
                }
            };
            channel.basicConsume("毛利兰", false, defaultConsumer);

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

修改生产者

同样的我们也需要在生产者加上channel.basicQos(1);

java 复制代码
/*
    同一时刻服务器只会发一条消息给消费者
    限制发送给消费者不得超过一条消息
 */
channel.basicQos(1);

代码演示

java 复制代码
package com.hsh.test02;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Producer02 {
    public static void main(String[] args) {
        System.out.println("生产者启动...");
        // 获得连接
        Connection connection = ConnectionUtils.getConnection();
        try {
            // 创建通道
            Channel channel = connection.createChannel();
            // 保证一次只分发一个
            channel.basicQos(1);
            // 创建队列声明
            channel.queueDeclare("毛利兰", false, false, false, null);

            for (int i = 0; i < 20; i++){
                // 定义发送消息的数据
                String message = "我并不讨厌等待,因为等待得越久,见到他的时候,我就越开心" + i;
                // 发送消息
                channel.basicPublish("", "毛利兰", null, message.getBytes());
                System.out.println("生产者发送消息:" + message);
                // 休眠
                Thread.sleep(200);
            }

            channel.close();
            connection.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

运行结果

手动反馈处理失败

讲解

上面如果我们的手动反馈失败了可使用下面的方法。

java 复制代码
// 拒绝消息
// 参数1: 消息的编号
// 参数2:表示是否进行批量操作 默认false
// 参数3:被拒绝的消息是否重新入队 
//        当设置为 true时,RabbitMQ 会将被拒绝的消息重新放回原始队列的尾部,以便可以再次被消费
//        当设置为 false时,RabbitMQ 会将消息从队列中删除,不会重新入队
channel.basicNack(envelope.getDeliveryTag(),false,true);

代码演示

我们给消费者02一个模拟异常,看看执行情况。

java 复制代码
package com.hsh.test02;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;


public class Consumer02 {
    public static void main(String[] args) {
        System.out.println("消费者启动...");

        try {
            // 获得连接
            Connection connection = ConnectionUtils.getConnection();
            // 创建通道
            Channel channel = connection.createChannel();
            channel.basicQos(1);
            // 连接队列
            channel.queueDeclare("毛利兰", false, false, false, null);
            // 监听 true自动反馈
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    try {
                        Thread.sleep(500);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    String message ="";
                    // 这里需要包起来,无论报不保存,都需要执行 channel.basicAck(envelope.getDeliveryTag(), false);
                    try {
                        message = new String(body, "UTF-8");
                        // 模拟异常
                        if (envelope.getDeliveryTag()==2){
                            int i = 1/0;
                        }
                        System.out.println(envelope.getDeliveryTag()+"消费者2:" + message);
                    } catch (Exception e) {
                        //  e.printStackTrace();
                        System.out.println("第"+envelope.getDeliveryTag()+"条处理失败,放回队列,内容是:"+message);
                        // 拒绝消息
                        // 参数1: 消息的编号
                        // 参数2:表示是否进行批量操作 默认false
                        // 参数3:被拒绝的消息是否重新入队
                        //        当设置为 true时,RabbitMQ 会将被拒绝的消息重新放回原始队列的尾部,以便可以再次被消费
                        //        当设置为 false时,RabbitMQ 会将消息从队列中删除,不会重新入队
                        channel.basicNack(envelope.getDeliveryTag(),false,true);
                    } finally {
                        // 手动反馈
                        // 第一个参数:envelope.getDeliveryTag() 当前消息的编号 我在上面的输出打印了可以看看
                        // 第二个参数:false单挑消息应答,true批量应答
                        channel.basicAck(envelope.getDeliveryTag(), false);
                    }
                }
            };
            channel.basicConsume("毛利兰", false, defaultConsumer);

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

运行结果

消息应答和持久化

消息应答

这个相当于在消费者中保证消息不丢失,这个就是上面的手动反馈和自动反馈。这里对上面的手动和自动反馈进行进一步讲解。

bash 复制代码
ch.basicConsume(QUEUE_NAME,true,consumer);

True:自动确认

只要消息从队列中获取,无论消费者获取到消息后是否成功消费,都认为是消息已经成功消费。一旦rabbitmq将消息分发给消费者,就会从内存中删除。(会丢失数据消息)

False:手动确认

消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态。如果有一个消费者挂掉,就会交付给其他消费者。

手动告诉rabbitmq消息处理完成后,rabbitmq删除内存中的消息。

反馈:

java 复制代码
//手动回馈
ch.basicAck(envelope.getDeliveryTag(),false);

使用Nack让消息回到队列中

java 复制代码
//  处理条数    是否批量处理   是否放回队列   false丢弃
ch.basicNack(envelope.getDeliveryTag(),false,true);

如果rabbitmq挂了,我们的消息任然会丢失!那么就需要对RabbitMQ做消息持久化。

RabbitMQ消息持久化

持久化步骤

这个也是上面的简单队列和Work模式都用过了。只不过当时是设置非持久化。

RabbitMQ消息持久化分为两步

  1. 声明队列持久化

    java 复制代码
    //声明队列
    boolean b = false;
    channel.queueDeclare(QUEUE_NAME,b,false,false,null);

    我们直接将程序中的b=false;改为true是不可以的,因为QUEUE_NAME 已经存,在rabbitmq是不允许重新定义一个已存在的队列。

  2. 设置持久化指令:

    java 复制代码
    //设置生成者发送消息为持久化信息(要求保存到硬盘上)保存在内存中
    //MessageProperties.PERSISTENT_TEXT_PLAIN,指令完成持久化
    ch.basicPublish("",QUEUE_NAME,MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes());

代码演示

修改生产者

java 复制代码
package com.hsh.test02;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;

public class Producer02 {
    public static void main(String[] args) {
        System.out.println("生产者启动...");
        Connection connection = ConnectionUtils.getConnection();
        try {
            Channel channel = connection.createChannel();
            channel.basicQos(1);
            // 创建队列声明  参数二是否持久化 我们改为true
            channel.queueDeclare("毛利兰", true, false, false, null);

            for (int i = 0; i < 20; i++){
                String message = "我并不讨厌等待,因为等待得越久,见到他的时候,我就越开心" + i;
                // 发送消息 第三个参数表示是否持久化消息
                // MessageProperties.PERSISTENT_TEXT_PLAIN是持久化消息
                channel.basicPublish("",
                        "毛利兰",
                        MessageProperties.PERSISTENT_TEXT_PLAIN,
                        message.getBytes());
                System.out.println("生产者发送消息:" + message);
                Thread.sleep(200);
            }

            channel.close();
            connection.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

运行结果

需要到queues中删除队列后再创建。

然后运行生产者

重启RabbitMQ看看消息是否消失。如果不消失说明成功了。

订阅模式

一个生产者将消息首先发送到交换器,交换器绑定多个队列,然后被监听该队列的消费者所接收并消费。

一个生产者多个消费者,每个消费者都有自己的队列,生产者没有吧消息直接发送到队列,而是发送到了交换机,每个队列都绑定到交换机,生产者发送的消息,经过交换机,到达队列,实现,一个消息被多个消费者获取的目的。

也就是王者发的公告每个玩家都能收到信息。

生产者

语法

java 复制代码
// 创建交换机
//   参数一: 交换机名称
//   参数二: 交换机类型(这个后面讲不着急)
channel.exchangeDeclare("工藤新一", "fanout");

// 发送消息
//   参数一: 交换机名称
//   参数二: 队列名称(在简单队列和Work模式已经演示过了)
//   参数三: 消息的持久化
//   参数四: 要发送的消息
channel.basicPublish("工藤新一", "", null, msg.getBytes());

在test03中新建文件Producer01

java 复制代码
package com.hsh.test03;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Producer01 {
    public static void main(String[] args) {
        System.out.println("生产者启动...");
        // 获得连接
        Connection connection = ConnectionUtils.getConnection();
        try {
            // 创建通道
            Channel channel = connection.createChannel();
            // 创建交换机
            //   参数一: 交换机名称
            //   参数二: 交换机类型(这个后面讲不着急)
            channel.exchangeDeclare("工藤新一", "fanout");
            // 定义要发送的消息
            String msg = "也许杀人需要理由,然而救人是不需要什么理由的";
            // 发送消息
            //   参数一: 交换机名称
            //   参数二: 队列名称(在简单队列和Work模式已经演示过了)
            //   参数三: 消息的持久化
            //   参数四: 要发送的消息
            channel.basicPublish("工藤新一", "", null, msg.getBytes());

            System.out.println("生产者发送消息:" + msg);
            channel.close();
            connection.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

这里我们需要在消费者中绑定交换机才能使用。

消费者

绑定交换机

java 复制代码
// 绑定交换机
//   参数一:队列名称
//   参数二:交换机名称
channel.queueBind("贝尔摩德", "酒厂", "");

下面进行演示

消费者01

java 复制代码
package com.hsh.test03;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @author xrkhy
 * @date 2025/9/23 9:44
 * @description
 */
public class Consumer01 {
    public static void main(String[] args) {
        System.out.println("消费者启动...");

        try {
            // 获得连接
            Connection connection = ConnectionUtils.getConnection();
            // 创建通道
            Channel channel = connection.createChannel();
            // 连接队列
            channel.queueDeclare("贝尔摩德", false, false, false, null);
            // 绑定交换机
            //   参数一:队列名称
            //   参数二:交换机名称
            channel.queueBind("贝尔摩德", "酒厂", "");

            // 监听 true自动反馈
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String message = new String(body, "UTF-8");
                    System.out.println("消费者1:" + message);
                }
            };
            // 监听
            channel.basicConsume("贝尔摩德", true, defaultConsumer);

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

消费者02

java 复制代码
package com.hsh.test03;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @author xrkhy
 * @date 2025/9/23 9:44
 * @description
 */
public class Comsumer02 {
    public static void main(String[] args) {
        System.out.println("消费者启动...");

        try {
            // 获得连接
            Connection connection = ConnectionUtils.getConnection();
            // 创建通道
            Channel channel = connection.createChannel();
            // 连接队列
            channel.queueDeclare("琴酒", false, false, false, null);
            // 绑定交换机
            //   参数一:队列名称
            //   参数二:交换机名称
            channel.queueBind("琴酒", "酒厂", "");

            // 监听 true自动反馈
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String message = new String(body, "UTF-8");
                    System.out.println("消费者2:" + message);
                }
            };
            // 监听
            channel.basicConsume("琴酒", true, defaultConsumer);

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

运行结果

这里先去启动生产者,再去启动消费者,最后再去启动生产者。因为消费者绑定了交换机,但是此时没有运行生产者,也就没有交换机,消费者会报错。最后再次启动生产者是为了发消息。

打开可视化界面的交换机

点击交换机看看

路由模式

在学习之前我们先来知道什么是路由。

如下图

我们的生产者生产了消息,类型属于情感生活。

消费者c1只接收情感生活,游戏娱乐这两种类型。

消费者c2只接收情感生活这一种类型。

消费者c3只接收情感生活,游戏娱乐,体育竞技这三种类型。

我们中间的圆就是路由器,他负责把生产者发送的消息按类型发给不同的消费者。

这里以感情生活类型为例那么就是发给消费者查c1,消费者c2,消费者c3。

如果生产的是体育竞技类型那么就只发给消费者c3。

下面是运行流程。 Direct:处理路由键

生产者

语法

java 复制代码
// 创建交换机
//   参数一: 交换机名称
//   参数二: 处理路由键
channel.exchangeDeclare("exchange", "direct");

// 发送消息
//   参数一: 交换机名称
//   参数二: 队列名称(在简单队列和Work模式已经演示过了)/路由键
//   参数三: 消息的持久化
//   参数四: 要发送的消息
channel.basicPublish("exchange", "info", null, msg.getBytes());

在test04中写

java 复制代码
package com.hsh.test04;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Producer01 {
    public static void main(String[] args) {
        System.out.println("生产者启动...");
        // 获得连接
        Connection connection = ConnectionUtils.getConnection();
        try {
            // 创建通道
            Channel channel = connection.createChannel();
            // 创建交换机
            //   参数一: 交换机名称
            //   参数二: 处理路由键
            channel.exchangeDeclare("exchange", "direct");
            // 定义要发送的消息
            String msg = "我是info消息";
            // 发送消息
            //   参数一: 交换机名称
            //   参数二: 队列名称(在简单队列和Work模式已经演示过了)/路由键
            //   参数三: 消息的持久化
            //   参数四: 要发送的消息
            channel.basicPublish("exchange", "info", null, msg.getBytes());

            System.out.println("生产者发送消息:" + msg);
            channel.close();
            connection.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

消费者

语法

java 复制代码
// 绑定交换机
//   参数一:队列名称
//   参数二:交换机名称
//   参数三:路由key
channel.queueBind("allMessage", "exchange", "error");
channel.queueBind("allMessage", "exchange", "info");
channel.queueBind("allMessage", "exchange", "warning");

消费者01

java 复制代码
package com.hsh.test04;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer01 {
    public static void main(String[] args) {
        System.out.println("消费者启动...");

        try {
            // 获得连接
            Connection connection = ConnectionUtils.getConnection();
            // 创建通道
            Channel channel = connection.createChannel();
            // 连接队列
            channel.queueDeclare("allMessage", false, false, false, null);
            // 绑定交换机
            //   参数一:队列名称
            //   参数二:交换机名称
            //   参数三:路由key
            channel.queueBind("allMessage", "exchange", "error");
            channel.queueBind("allMessage", "exchange", "info");
            channel.queueBind("allMessage", "exchange", "warning");

            // 监听 true自动反馈
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String message = new String(body, "UTF-8");
                    System.out.println("消费者1:" + message);
                }
            };
            // 监听
            channel.basicConsume("allMessage", true, defaultConsumer);

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

消费者02

java 复制代码
package com.hsh.test04;

import com.hsh.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Comsumer02 {
    public static void main(String[] args) {
        System.out.println("消费者启动...");

        try {
            // 获得连接
            Connection connection = ConnectionUtils.getConnection();
            // 创建通道
            Channel channel = connection.createChannel();
            // 连接队列
            channel.queueDeclare("errorMessage", false, false, false, null);
            // 绑定交换机
            //   参数一:队列名称
            //   参数二:交换机名称
            //   参数三:路由key
            channel.queueBind("errorMessage", "exchange", "error");

            // 监听 true自动反馈
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String message = new String(body, "UTF-8");
                    System.out.println("消费者2:" + message);
                }
            };
            // 监听
            channel.basicConsume("errorMessage", true, defaultConsumer);

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

运行结果

这里还是先去启动生产者,再去启动消费者,最后再去启动生产者

修改为error,因为两个都有error所以都能访问。

未完待续...

相关推荐
用户8307196840822 天前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者3 天前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者5 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧6 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖6 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农6 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者6 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端
业精于勤_荒于稀6 天前
物流订单系统99.99%可用性全链路容灾体系落地操作手册
分布式
Ronin3056 天前
信道管理模块和异步线程模块
开发语言·c++·rabbitmq·异步线程·信道管理
Asher05096 天前
Hadoop核心技术与实战指南
大数据·hadoop·分布式