Bootstrap

RabbitMQ学习笔记(一)交换机类型、消费类型

一、交换机

1、类型

RabbitMQ共有四种交换机,交换机主要决定发布的消息该以什么的方式进入队列(queue)
四种类型如下:

Name(交换机类型)Default pre-declared names(预声明的默认名称)
Direct exchange(直连交换机)(Empty string) and amq.direct
Fanout exchange(扇型交换机)amq.fanout
Topic exchange(主题交换机)amq.topic
Headers exchange(头交换机)amq.match (and amq.headers in RabbitMQ)

交换机本身还可以携带属性,可以设置状态:持久(durable)、暂存(transient)。这里暂不介绍,如有兴趣可以翻阅文档:http://rabbitmq.mr-ping.com/AMQP/AMQP_0-9-1_Model_Explained.html

  • 默认交换机
    当我们使用RabbitMQ时,如果不指定交换机的类型,那么Rabbit会使用默认的一个交换机,这个默认的交换机类型是一个直连交换机(direct),后续新建的队列(queue)都会自动绑定到这个默认交换机上,绑定的路由键就是队列的名称,注意这个默认交换机的名称是一个空字符串 " "
 // 可通过这个代码,在指定的虚拟主机vm上创建一个特定类型的交换机
 channel.exchangeDeclare("exchange1","fanout");
 
 // 后续发布消息可通过basicPublish方法的exchange参数指定交换机名称
 // 如果第一个参数设置为空字符串"" 那么就是使用默认的直连交换机
 channel.basicPublish("exchange1", QUEUE_NAME, null, msg.getBytes());
  • 直连交换机(direct)
    在这里插入图片描述
    直连交换机往往用于做单播路由,绑定到直连交换机的队列名称和路由名称相同,那么直连交换机在路由消息的时候,会直接将队列名称作为路由键,把消息路由到该队列中

  • 扇型交换机(fanout)
    在这里插入图片描述
    扇形交换机通常用来做广播,它会把消息路由给绑定在该交换机的所有队列,与绑定的路由键无关

  • 主题交换机(topic)
    主题交换机类似扇形的升级,扇形交换机是无差别发送,主题交换机会聪明一些,可以筛选路由键,将消息发给一个或者多个队列。
    比如我们有三个队列,与topic绑定的路由键分别是(user.#)(money.#)(address.#)
    那么发布消息的路由键为user.age、user.name的话,这两个消息就会被路由到user.#所对应的队列中,money.count的消息会被路由到money.#所对应的队列中,以此类推
    其中的符号“#”可以匹配一个或者多个词,符号"*"可以配一个词

  • 头部交换机
    个人感觉可以视为直连交换机的另一种形式,直连交换之使用路由键点对点进行发布,头部交换机不使用消息发布时的路由键了,而是使用消息头里面的信息与路由进行匹配

二、消费类型

  • 交换机的不同类型,决定消息以什么样的方式从交换机分发到队列中。
  • 消费类型决定消息从队列中如何到消费者

RabbitMQ共提供了六种消费
在这里插入图片描述
在这里插入图片描述

1、简单模式

在这里插入图片描述
简单模式下是一个消费者在这里消费,队列中的所有消息都给这一个消费者,没有竞争

2、工作队列

在这里插入图片描述

与简单模式不同的是,工作队列是存在多个消费者消费同一个队列,并且同一条消息只能被一个消费者消费,此时RabbitMQ会有两种消息发送策略

  • 轮询发送(默认):每个消费者发送一条,是不同的一条,比如有两个消费者、三个消息,消费者1拿到消息1、消费者2拿到消息2,因为是轮询,所以接下来的消息3会发送给消费者1
  • 公平发送:根据消费者的消费能力进行公平发送

3、发布/订阅

在这里插入图片描述
发布订阅模式使用的是扇形交换机,其原理是基于扇形交换机的,队列绑定到交换机时可以不指定路由名称,使用空字符串""代替即可,代码如下:

/**
 * 生产者代码
 **/ 
public class ProducerFanout {

	// 队列名称
    public static final String QUEUE_NAME = "q_test_01";
    public static final String QUEUE_NAME2 = "q_test_02";
	
	// 交换机名称
    public static final String FANOUT_EXCHANGE = "fanout_queue_1";
    
    public static void main(String[] args) throws Exception {
    	// 获取连接
        Connection connection = getConnection();
        Channel channel = connection.createChannel();
        
        String msg = "测试消息";

		// 创建一个扇形交换机
        channel.exchangeDeclare(FANOUT_EXCHANGE, "fanout");
		
		// 创建队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        channel.queueDeclare(QUEUE_NAME2, false, false, false, null);

		// 将两个队列绑定到同一个扇形交换机上,第三方参数为路由名称,设为空字符串
        channel.queueBind(QUEUE_NAME, FANOUT_EXCHANGE, "");
        channel.queueBind(QUEUE_NAME2, FANOUT_EXCHANGE, "");

		//发布消息到交换机
        for (int i = 0; i < 10; i++) {
            channel.basicPublish(FANOUT_EXCHANGE, "", null, (msg + i).getBytes());
        }

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

	/**
	 * 获取连接
	 **/
    public static Connection getConnection() throws Exception {
        //定义连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //设置服务地址
        factory.setHost("localhost");
        //端口
        factory.setPort(5672);
        //设置账号信息,用户名、密码、vhost
        factory.setVirtualHost("testhost");
        factory.setUsername("admin");
        factory.setPassword("1111");
        // 通过工程获取连接
        Connection connection = factory.newConnection();
        return connection;
    }
}
/**
 * 消费者代码
 * 消费者2的代码和这里一模一样,只需要把订阅的队列名称改为2就可以了
 **/ 
 public class ConsumerClientFanout {
    public static void main(String[] args) throws Exception {
        Connection connection = Producer.getConnection();
        Channel channel = connection.createChannel();

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String message1 = new String(message.getBody());
            System.out.println("DeliverCallback:" + consumerTag + "-" + message1);
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("CancelCallback:" + consumerTag);
        };

		// 订阅队列1
        channel.basicConsume(ProducerFanout.QUEUE_NAME, true, deliverCallback, cancelCallback);
    }
}

消费者1收到的消息
在这里插入图片描述
消费者2收到的消息
在这里插入图片描述
经过测试这个路由key不论设为什么,只要绑定到扇形交换的队列都会收到消息,而且不会通过路由key筛选队列

特别说明:

很多代码会在消费者和生产者的代码中都写上队列、交换机的创建代码等,个人理解是因为不一定是消费者先执行还是生产者先执行,比如消费者没有写创建那部分代码,那么消费者先执行的话,会报错误(订阅的队列不存在等)所以可以在两边都写上创建的代码,对于RabbitMQ来说相同的代码,多次执行的话,只有第一次执行才会生效,后面几次会检测到要创建的资源(交换机、队列)已经存在了,所以什么都不做,也不会报错

4、路由模式

在这里插入图片描述
路由模式使用的直连交换机,因为要使用路由键,原理基本就与直连交换机的原理一样,只不过用在了消息发送上

public class ProducerDirect {

    public static String QUEUE_NAME = "q_test_01";
    public static String QUEUE_NAME2 = "q_test_02";
    public static final String FANOUT_EXCHANGE = "fanout_queue_1";

    public static void main(String[] args) throws Exception {
        Connection connection = getConnection();
        Channel channel = connection.createChannel();

        String msg = "测试消息";
        String msg2 = "测试消息二";

		// 创建一个直连交换机
        channel.exchangeDeclare(FANOUT_EXCHANGE, "direct");

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        channel.queueDeclare(QUEUE_NAME2, false, false, false, null);

		// 这里绑定的时候指定路由key
        channel.queueBind(QUEUE_NAME, FANOUT_EXCHANGE, "ad1");
        channel.queueBind(QUEUE_NAME2, FANOUT_EXCHANGE, "ad2");

		// 发布消息时指定路由key
        for (int i = 0; i < 10; i++) {
            channel.basicPublish(FANOUT_EXCHANGE, "ad1", null, (msg + i).getBytes());
            channel.basicPublish(FANOUT_EXCHANGE, "ad2", null, (msg2 + i).getBytes());
        }

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

    public static Connection getConnection() throws Exception {
        //定义连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //设置服务地址
        factory.setHost("localhost");
        //端口
        factory.setPort(5672);
        //设置账号信息,用户名、密码、vhost
        factory.setVirtualHost("testhost");
        factory.setUsername("admin");
        factory.setPassword("1111");
        // 通过工程获取连接
        Connection connection = factory.newConnection();
        return connection;
    }
}

在这里插入图片描述
在这里插入图片描述

5、主题模式

在这里插入图片描述
主题模式使用主题交换机,消息发送机制也就是主题交换机的机制,代码方面基本和路由模式一样,只不过在路由key上使用通配符就可以了

三、特别注意:

交换机类型和消费类型有类似的概念,因为消费类型是基于交换机类型来实现的,主要区别:
1、交换机决定消息从路由到队列的方式
2、消费类型描述的是消息从队列发送到消费者的方式

;