基本消息模型-rabbitMq消息模型(一)

1. 搭建demo工程

首先创建一个工程rabbitmq_demo(开发工具idea),之后的消息模型demo都基于此工程
在这里插入图片描述在这里插入图片描述
用的springboot版本比较高,2.2.2的,根据需要选择吧。
在这里插入图片描述创建完成后,会开始下载所需要的依赖,网速慢的话得等一小会。

pom文件如下(自动生成的,无需改动):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>rabbitmq_demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rabbitmq_demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

spring的配置文件的话暂时用不到,这里rabbitmq的连接信息配置在了一个工具类里:

public class ConnectionUtil {
    /**
     * 建立与RabbitMQ的连接
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception {
        //定义连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //设置服务地址
        factory.setHost("192.168.33.128");
        //端口
        factory.setPort(5672);
        //设置账号信息,用户名、密码、vhost
        factory.setVirtualHost("/");
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setConnectionTimeout(1000000);
        factory.setHandshakeTimeout(1000000);
        // 通过工程获取连接
        Connection connection = factory.newConnection();
        return connection;
    }
}

注意端口默认时5672,在浏览其里访问默认时15672

2. 基本消息模型概述

官网介绍如下:
在这里插入图片描述

RabbitMQ message broker:它接受和转发消息。 你可以考虑一下邮局:当你把你想要发布的邮件信箱, 可以肯定的是,先生或女士Mailperson最终会交付收件人的邮件。 在这个比喻中,RabbitMQ信箱,一个邮局和一个邮差。

在这里插入图片描述

P(producer/ publisher):生产者,一个发送消息的用户应用程序。

C(consumer):消费者,消费和接收有类似的意思,消费者是一个主要用来等待接收消息的用户应用程序

队列(红色区域):rabbitmq内部类似于邮箱的一个概念。虽然消息流经rabbitmq和你的应用程序,但是它们只能存储在队列中。队列只受主机的内存和磁盘限制,实质上是一个大的消息缓冲区。许多生产者可以发送消息到一个队列,许多消费者可以尝试从一个队列接收数据。

总之:

生产者将消息发送到队列,消费者从队列中获取消息,队列是存储消息的缓冲区。

3. 代码实现

3.1 生产者

/**
 * 生产者
 */
public class Send {

    private final static String QUEUE_NAME = "simple_queue";

    public static void main(String[] argv) throws Exception {
        // 1. 获取到连接
        Connection connection = ConnectionUtil.getConnection();
        // 2. 从连接中创建通道,使用通道才能完成消息相关的操作
        Channel channel = connection.createChannel();
        // 3. 声明(创建)队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 消息内容
        String message = "Hello 大家好啦啦啦!";
        // 向指定的队列中发送消息  (需要转换为字节)
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
       
        System.out.println(" [x] Sent '" + message + "'");
        //关闭通道和连接
        channel.close();
        connection.close();
    }
}

执行这段代码后,浏览器登录服务端管理界面如下:

http://192.168.33.128:15672/#/queues -网址ip+端口 (默认15672)

生成了通道和队列

在这里插入图片描述在这里插入图片描述(看到队列里边已经有了一条消息)

控制台:
在这里插入图片描述

3.2 消费者

代码如下:

public class Recv {
    private final static String QUEUE_NAME = "simple_queue";

    public static void main(String[] argv) throws Exception {
        // 获取到连接
        Connection connection = ConnectionUtil.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, BasicProperties properties,
                    byte[] body) throws IOException {
                // body 即消息体
                String msg = new String(body);
                System.out.println(" [x] received : " + msg + "!");
            }
        };
        // 监听队列,第二个参数:是否自动进行消息确认。
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}

运行如下:
在这里插入图片描述
消费者已经获取了消息,但是程序没有停止,一直在监听队列中是否有新的消息。一旦有新的消息进入队列,就会立即打印.

在这里插入图片描述

3.3 消息确认机制(ACK)

通过刚才的案例可以看出,消息一旦被消费者接收,队列中的消息就会被删除。

那么问题来了:RabbitMQ怎么知道消息被接收了呢?

如果消费者领取消息后,还没执行操作就挂掉了呢?或者抛出了异常?消息消费失败,但是RabbitMQ无从得知,这样消息就丢失了!

因此,RabbitMQ有一个ACK机制。当消费者获取消息后,会向RabbitMQ发送回执ACK,告知消息已经被接收。不过这种回执ACK分两种情况:

  • 自动ACK:消息一旦被接收,消费者自动发送ACK
  • 手动ACK:消息接收后,不会发送ACK,需要手动调用

那么哪种更好呢?

这需要看消息的重要性:

  • 如果消息不太重要,丢失也没有影响,那么自动ACK会比较方便
  • 如果消息非常重要,不容丢失。那么最好在消费完成后手动ACK,否则接收消息后就自动ACK,RabbitMQ就会把消息从队列中删除。如果此时消费者宕机,那么消息就丢失了。

之前的测试都是自动ACK的,如果要手动ACK,需要改动我们的代码:

public class Recv2 {
    private final static String QUEUE_NAME = "simple_queue";

    public static void main(String[] argv) throws Exception {
        // 获取到连接
        Connection connection = ConnectionUtil.getConnection();
        // 创建通道
        final 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, BasicProperties properties,
                    byte[] body) throws IOException {
                // body 即消息体
                String msg = new String(body);
                System.out.println(" [x] received : " + msg + "!");
                // 手动进行ACK
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 监听队列,第二个参数false,手动进行ACK
        channel.basicConsume(QUEUE_NAME, false, consumer);
    }
}

注意到最后一行代码:

channel.basicConsume(QUEUE_NAME, false, consumer);

如果第二个参数为true,则会自动进行ACK;如果为false,则需要手动ACK。方法的声明:

在这里插入图片描述

3.4 自动ACK存在的问题

修改消费者,添加异常,如下:
在这里插入图片描述生产者不做任何修改,直接运行,消息发送成功:
在这里插入图片描述
运行消费者,程序抛出异常。但是消息依然被消费
在这里插入图片描述管理界面:

在这里插入图片描述

3.5 演示手动ACK

修改消费者,把自动改成手动(去掉之前制造的异常)
在这里插入图片描述生产者不变,再次运行:
在这里插入图片描述

运行消费者
在这里插入图片描述但是,查看管理界面,发现:

在这里插入图片描述
停掉消费者的程序,发现:
在这里插入图片描述
这是因为虽然我们设置了手动ACK,但是代码中并没有进行消息确认!所以消息并未被真正消费掉。

当我们关掉这个消费者,消息的状态再次称为Ready

修改代码手动ACK:
在这里插入图片描述
执行:
在这里插入图片描述

发布了132 篇原创文章 · 获赞 108 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/fanbaodan/article/details/103438563