RabbitMQ(二、消息模型)

1、消息模型概述

  RabbitMQ提供几种消息模型,如下图

  

  其中最后一种是RPC,不是MQ。此处不予讨论。下面用例子说明每种模式。

2、Demo准备工作

  首先新建一个springboot项目,引入依赖。

<dependencies>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.3.2</version>
		</dependency>
		<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>
		</dependency>
</dependencies>

  为了方便操作,先写一个连接MQ的工具类

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

}

3、simple

  官网对RabbitMQ简介:RabbitMQ是一个消息代理:它接受和转发消息。 你可以把它想象成一个邮局:当你把邮件放在邮箱里时,你可以确定邮差先生最终会把邮件发送给你的收件人。 在这个比喻中,RabbitMQ是邮政信箱,邮局和邮递员。RabbitMQ与邮局的主要区别是它不处理纸张,而是接受,存储和转发数据消息的二进制数据块。

  术语

  • 生产者(producer ),生产仅意味着发送。发送消息的程序是生产者:
  • 队列(queue ),队列是RabbitMQ内部的邮箱的名称。尽管消息流经RabbitMQ和您的应用程序,但它们只能存储队列中。队列仅由主机的存储器&磁盘限制约束,它本质上是一个大的消息缓冲器。许多生产者可以将消息发送到一个队列,许多消费者可以尝试从一个队列接收数据这就是我们表示队列的方式:       
  • 消费者(consumer ),消费与接收具有相似的含义。一个消费者是一个程序,主要是等待接收信息:

  最终

  生产者生产(发送)消息:

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

    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);
        // 消息内容
        String message = "Hello World!";
        // 向指定的队列中发送消息
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
        
        System.out.println(" [x] Sent '" + message + "'");

        //关闭通道和连接
        channel.close();
        connection.close();
    }
}

执行send.main方法,打开RabbitMQ控制台Queues标签页可以看到队列里已经有这条消息了。

  

  消费者消费(接收)消息

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);
    }
}

运行消费者

发现已经接收到消息了,再次查看控制台也没消息了。但是程序依旧没有停止,消费者依然在监听队列中是否有新的消息。一旦有新的消息进入队列,就会立即接收(消费)。

4、消息确认机制(ACK)

如果消息到了消费者那没有执行,消费者就宕机了。或者消费者在执行的时候有异常等情况,RabbitMQ就需要知道是否消费正常消费了消息。

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

  • 自动ACK:消息一旦被接收,消费者自动发送ACK(消息不重要时,丢失没什么影响)
  • 手动ACK:消息接收后,不会发送ACK,需要手动调用(消息重要时)

上面那个例子是自动ACK,下面写一个手动ACK例子。

public class Recv {
    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);
    }
}

 如何演示两者的区别呢?在代码里故意添加的一个异常。 在两个消费者中加入 

int i =1/0;

  

   先运行生产者,再运行自动ACK消费者可以查看控制台发现消息被消费了(虽然消费者出事了)。

 关闭自动ACK的消费者,再次运行生产者,运行手动ACK的消费者如图

虽然依旧打印了消息但是消息没有被消费,查看控制台可见。消息依旧还在,虽然程序出事了,但是消息没有丢失。ACK也算是消息持久化的手段之一。

乏了,后面会继续介绍其他种类的消息机制。

  

猜你喜欢

转载自www.cnblogs.com/zxgbky/p/13402513.html