Bootstrap

消息队列-RabbitMQ学习笔记(四)

1. 如何实现消费者和生产者

1.1. 引入依赖包

<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
  <groupId>com.rabbitmq</groupId>
  <artifactId>amqp-client</artifactId>
  <version>5.20.0</version>
</dependency>

1.2. 创建生产者

package com.lz;

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 生产者
 */
public class Producer {
    public static void main(String[] args) throws Exception {
        String exchangeName = "lz_exchange";
        String queueName = "lz_queue";
        // 创建一个连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("guest");
        factory.setPassword("guest");
        // 创建连接
        Connection connection = factory.newConnection();
        // 创建通道
        Channel channel = connection.createChannel();

        /**
         * 创建交换机
         * 1.交换机名称
         * 2.交换机类型,direct,topic,fanout和headers
         * 3.指定交换机是否需要持久化,如果设置为true,那么交换机的元数据要持久化
         * 4.指定交换机在没有队列绑定时,是否需要删除,设置false表示不删除
         * 5.Map<String,Object>类型,用来指定我们交换机其他的一些结构化参数
         */
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT, true, false, null);

        /**
         * 创建队列
         * 1.队列的名称
         * 2.是否持久化,如果设置为true,那么队列名称等这些元数据会持久化,而不是队列中消息数据的持久化
         * 3.是否排外,如果设置为true,那么只有当前连接的消费者才能消费队列中的消息,其他连接的消费者无法消费队列中的消息
         * 4.是否自动删除,如果设置为true,那么当队列中没有绑定的消费者时,队列会自动删除
         * 5.Map<String,Object>类型,用来指定我们队列的其他一些结构化参数,如死信队列等
         */
        channel.queueDeclare(queueName, true, false, false, null);

        /**
         * 绑定队列和交换机
         * 1.队列名称
         * 2.交换机名称
         * 3.路由键,在直连模式下,可以为我们的队列名称
         */
        channel.queueBind(queueName, exchangeName, queueName);

        // 发送消息
        String message = "hello rabbitmq";

        /**
         * 发送消息
         * 1.发送到哪个交换机
         * 2.队列名称
         * 3.其他参数信息
         * 4.消息主体
         */
        channel.basicPublish(exchangeName, queueName, null, message.getBytes());
        channel.close();
        connection.close();
    }
}

1.3. 创建消费者

package com.lz;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.TreeMap;
import java.util.concurrent.TimeoutException;

public class Consumer {
    public static void main(String[] args) throws Exception {
        String exchangeName = "lz_exchange";
        String queueName = "lz_queue";
        // 创建一个连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("guest");
        factory.setPassword("guest");
        // 创建连接
        Connection connection = factory.newConnection();
        // 创建通道
        Channel channel = connection.createChannel();


        // 接受消息的回调
        DeliverCallback deliverCallback = (consumerTage, message) -> {
            System.out.println("接收到的消息:" + new String(message.getBody()));
        };

        // 取消消息的回调
        CancelCallback cancelCallback = (consumerTag) -> {
               System.out.println("消息消费被中断");
        };
        /**
         * 消费消息
         * 1.消费哪个队列
         * 2.消费成功之后是否需要自动应答,true 表示自动应答,false 表示手动应答
         * 3.接受消息的回调
         * 4.取消消息的回调
         */
        channel.basicConsume(queueName, true, deliverCallback,cancelCallback);

    }
}

2. RabbitMQ交换机类型

在RabbitMQ中,生产者的消息都是通过交换器来接收,然后再由交换器分发到不同的队列中去,在分发的过程中交换器类型会影响分发的逻辑。

在RabbitMQ中交换机类型主要由四种:

直连交换机:Direct exchange

扇形交换机:Fanout exchange

主题交换机:Topic exchange

首部交换机:Headers exchange

2.1. Direct(直连交换机)

直连交换机就是,路由键与队列名完全匹配的交换机。通过RoutingKey路由键将交换机和队列进行绑定,消息被发送到exchange时,需要根据消息的RoutingKey,来进行匹配,只将消息发送到完全匹配到此RoutingKey的列。

比如:如果一个队列绑定到交换机要求路由键为“lz_key”,则只转发RoutingKey标记为“lz_key”的消息,不会转发"key1",也不会转发“key.1”等等。它是完全匹配、单播的模式。

同一个key可以绑定多个queue队列,当匹配到key1时,queue1和queue2都会收到消息

public class ProducerDirect {
    public static void main(String[] args) throws Exception {
        String exchangeName = "lz_exchange";
        String queueName1 = "lz_queue_direct_1";
        String queueName2 = "lz_queue_direct_2";
        String queueName3 = "lz_queue_direct_3";
        String queueName4 = "lz_queue_direct_4";

        String routingKey1 = "key1";
        String routingKey2 = "key2";
        String routingKey3 = "key3";
        String routingKey4 = "key4";

        Channel channel = RabbitMQConnector.createConnectionAndChannel();

        /**
         * 创建交换机
         */
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT, true, false, null);

        /**
         * 创建队列
         */
        channel.queueDeclare(queueName1, true, false, false, null);
        channel.queueDeclare(queueName2, true, false, false, null);
        channel.queueDeclare(queueName3, true, false, false, null);
        channel.queueDeclare(queueName4, true, false, false, null);

        /**
         * 绑定队列和交换机
         * 1.队列名称
         * 2.交换机名称
         * 3.路由键,在直连模式下,可以为我们的队列名称
         */
        channel.queueBind(queueName1, exchangeName, routingKey1);
        channel.queueBind(queueName2, exchangeName, routingKey1);
        channel.queueBind(queueName3, exchangeName, routingKey3);
        channel.queueBind(queueName4, exchangeName, routingKey4);


        /**
         * 发送消息
         * 1.发送到哪个交换机
         * 2.路由键 routingKey
         * 3.其他参数信息
         * 4.消息主体
         */
        channel.basicPublish(exchangeName, routingKey1, null, "key1 message".getBytes());
        channel.basicPublish(exchangeName, routingKey2, null, "key2 message".getBytes());
        channel.basicPublish(exchangeName, routingKey3, null, "key3 message".getBytes());
        channel.basicPublish(exchangeName, routingKey4, null, "key4 message".getBytes());
        RabbitMQConnector.closeResources();
    }
}
public class ConsumerDirect {
    public static void main(String[] args) throws Exception {
        String exchangeName = "lz_exchange";
        String queueName1 = "lz_queue_direct_1";
        String queueName2 = "lz_queue_direct_2";
        String queueName3 = "lz_queue_direct_3";
        String queueName4 = "lz_queue_direct_4";

        Channel channel = RabbitMQConnector.createConnectionAndChannel();


        // 接受消息的回调
        DeliverCallback deliverCallback = (consumerTage, message) -> {
            System.out.println(message.getEnvelope());
            System.out.println("接收到的消息:" + new String(message.getBody()));
        };

        // 取消消息的回调
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println("消息消费被中断");
        };
        /**
         * 消费消息
         * 1.消费哪个队列
         * 2.消费成功之后是否需要自动应答,true 表示自动应答,false 表示手动应答
         * 3.接受消息的回调
         * 4.取消消息的回调
         */
        channel.basicConsume(queueName1, true, deliverCallback,cancelCallback);
        channel.basicConsume(queueName2, true, deliverCallback,cancelCallback);
        channel.basicConsume(queueName3, true, deliverCallback,cancelCallback);
        channel.basicConsume(queueName4, true, deliverCallback,cancelCallback);

    }
}

queue1和queue2绑定的都是key1,所以接受的消息都是key1 message

2.2. Fanout(扇形交换机)

Fanout,扇出类型交换机,此种交换机,会将消息分发给所有绑定了此交换机的队列(就是广播消息),此时RoutingKey参数无效。fanout类型交换机下发送消息一条,无论RoutingKey是什么,queue1,queue2,queue3,queue4都可以收到消息。

public class ProducerFanout {
    public static void main(String[] args) throws Exception {
        String exchangeName = "lz_exchange_fanout";
        String queueName1 = "lz_queue_fanout_1";
        String queueName2 = "lz_queue_fanout_2";
        String queueName3 = "lz_queue_fanout_3";
        String queueName4 = "lz_queue_fanout_4";

        String routingKey1 = "key1";
        String routingKey2 = "key2";
        String routingKey3 = "key3";
        String routingKey4 = "key4";

        Channel channel = RabbitMQConnector.createConnectionAndChannel();

        /**
         * 创建交换机
         */
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT, true, false, null);

        /**
         * 创建队列
         */
        channel.queueDeclare(queueName1, true, false, false, null);
        channel.queueDeclare(queueName2, true, false, false, null);
        channel.queueDeclare(queueName3, true, false, false, null);
        channel.queueDeclare(queueName4, true, false, false, null);

        /**
         * 绑定队列和交换机
         * 1.队列名称
         * 2.交换机名称
         * 3.路由键,在直连模式下,可以为我们的队列名称
         */
        channel.queueBind(queueName1, exchangeName, routingKey1);
        channel.queueBind(queueName2, exchangeName, routingKey1);
        channel.queueBind(queueName3, exchangeName, routingKey3);
        channel.queueBind(queueName4, exchangeName, routingKey4);


        /**
         * 发送消息
         * 1.发送到哪个交换机
         * 2.路由键 routingKey
         * 3.其他参数信息
         * 4.消息主体
         */
        channel.basicPublish(exchangeName, routingKey1, null, "fanout message".getBytes());
        System.out.println("发送消息成功");
        RabbitMQConnector.closeResources();
    }
}
public class ConsumerFanout {
    public static void main(String[] args) throws Exception {
        String exchangeName = "lz_exchange_fanout";
        String queueName1 = "lz_queue_fanout_1";
        String queueName2 = "lz_queue_fanout_2";
        String queueName3 = "lz_queue_fanout_3";
        String queueName4 = "lz_queue_fanout_4";

        Channel channel = RabbitMQConnector.createConnectionAndChannel();


        // 接受消息的回调
        DeliverCallback deliverCallback = (consumerTage, message) -> {
            System.out.println("接收到的消息:" + new String(message.getBody()));
        };

        // 取消消息的回调
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println("消息消费被中断");
        };
        /**
         * 消费消息
         * 1.消费哪个队列
         * 2.消费成功之后是否需要自动应答,true 表示自动应答,false 表示手动应答
         * 3.接受消息的回调
         * 4.取消消息的回调
         */
        channel.basicConsume(queueName1, true, deliverCallback,cancelCallback);
        channel.basicConsume(queueName2, true, deliverCallback,cancelCallback);
        channel.basicConsume(queueName3, true, deliverCallback,cancelCallback);
        channel.basicConsume(queueName4, true, deliverCallback,cancelCallback);

    }
}

只给交换机发送消息,与该交换机绑定的队列都会收到消息

 

2.3. Topic(主题交换机)

Direct能做的 Topic也能做

Topic,主题类型交换机,此种交换机与Direct类似,也是需要通过RoutingKey 路由键进行匹配分发,区别在于Topic可以进行模糊匹配,Direct是完全匹配。

  1. Topic中,将routingkey通过"."来分为多个部分
  2. "*":代表一个部分
  3. "#":代表0个或多个部分(如果绑定的路由键为“#”时,则接受所有消息,因为路由键所有都匹配)

例如,RoutingKeyuser.* 的消息可以匹配 user.createuser.update,而 user.# 可以匹配 user.create.profileuser.update.

当路由键:key1.key2,只有2和4匹配上

当路由键:key1.key2.key3,1、3、4都匹配上

public class ProducerTopic {
    public static void main(String[] args) throws Exception {
        String exchangeName = "lz_exchange_topic";
        String queueName1 = "lz_queue_topic_1";
        String queueName2 = "lz_queue_topic_2";
        String queueName3 = "lz_queue_topic_3";
        String queueName4 = "lz_queue_topic_4";

        String routingKey1 = "key1.key2.*";
        String routingKey2 = "key1.*";
        String routingKey3 = "*.key2.*";
        String routingKey4 = "#";

        Channel channel = RabbitMQConnector.createConnectionAndChannel();

        /**
         * 创建交换机
         */
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC, true, false, null);

        /**
         * 创建队列
         */
        channel.queueDeclare(queueName1, true, false, false, null);
        channel.queueDeclare(queueName2, true, false, false, null);
        channel.queueDeclare(queueName3, true, false, false, null);
        channel.queueDeclare(queueName4, true, false, false, null);

        /**
         * 绑定队列和交换机
         * 1.队列名称
         * 2.交换机名称
         * 3.路由键,在直连模式下,可以为我们的队列名称
         */
        channel.queueBind(queueName1, exchangeName, routingKey1);
        channel.queueBind(queueName2, exchangeName, routingKey2);
        channel.queueBind(queueName3, exchangeName, routingKey3);
        channel.queueBind(queueName4, exchangeName, routingKey4);


        /**
         * 发送消息
         * 1.发送到哪个交换机
         * 2.路由键 routingKey
         * 3.其他参数信息
         * 4.消息主体
         */
        channel.basicPublish(exchangeName, "key1.key2.key3", null, "topic message".getBytes());
        System.out.println("发送消息成功");
        RabbitMQConnector.closeResources();
    }
}
public class ConsumerTopic {
    public static void main(String[] args) throws Exception {
        String exchangeName = "lz_exchange_topic";
        String queueName1 = "lz_queue_topic_1";
        String queueName2 = "lz_queue_topic_2";
        String queueName3 = "lz_queue_topic_3";
        String queueName4 = "lz_queue_topic_4";

        Channel channel = RabbitMQConnector.createConnectionAndChannel();


        // 接受消息的回调
        DeliverCallback deliverCallback = (consumerTage, message) -> {
            System.out.println("接收到的消息:" + new String(message.getBody()));
        };

        // 取消消息的回调
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println("消息消费被中断");
        };
        /**
         * 消费消息
         * 1.消费哪个队列
         * 2.消费成功之后是否需要自动应答,true 表示自动应答,false 表示手动应答
         * 3.接受消息的回调
         * 4.取消消息的回调
         */
        channel.basicConsume(queueName1, true, deliverCallback,cancelCallback);
        channel.basicConsume(queueName2, true, deliverCallback,cancelCallback);
        channel.basicConsume(queueName3, true, deliverCallback,cancelCallback);
        channel.basicConsume(queueName4, true, deliverCallback,cancelCallback);

    }
}

2.4. Headers(首部交换机)

Headers 交换机不使用 RoutingKey,而是通过消息头中的属性来路由消息。队列绑定时指定的头部属性必须与消息头部属性完全匹配,消息才会被路由到该队列。

消费方指定的headers中必须包含一个“x-match"的键。

键"x-match"的值有2个

  1. x-match=all:表示所有的键值对都匹配才能接受到消息
  2. x-match=any:表示只要有键值对匹配就能接受到消息

当值为{ "name":"lizheng","sex":"男"}时,全部匹配和任一匹配的两个队列都接收到消息

当值为{ "age":24,"sex":"女"}时,只有任一匹配队列符合,所以queue2接收到消息

public class ProducerHeaders {
    public static void main(String[] args) throws Exception {
        String exchangeName = "lz_exchange_headers";
        String queueName1 = "lz_queue_headers_1";
        String queueName2 = "lz_queue_headers_2";


        Channel channel = RabbitMQConnector.createConnectionAndChannel();

        /**
         * 创建交换机
         */
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.HEADERS, true, false, null);


        /**
         * 创建队列
         */
        channel.queueDeclare(queueName1, true, false, false, null);
        channel.queueDeclare(queueName2, true, false, false, null);

        // 创建一个HashMap实例,用于存储绑定队列时的额外属性
        Map<String, Object> allMap = new HashMap<>();
        // 设置匹配规则,"all"表示所有条件必须都满足
        allMap.put("x-match", "all");
        allMap.put("name","lizheng");
        allMap.put("sex","男");

        // 绑定队列到交换机,使用空的路由键和之前设置的额外属性
        channel.queueBind(queueName1, exchangeName, "", allMap);


        Map<String, Object> anyMap = new HashMap<>();
        anyMap.put("x-match", "any");
        anyMap.put("name","lizheng");
        anyMap.put("age","24");

        channel.queueBind(queueName2, exchangeName, "", anyMap);

        // 创建一个HashMap实例,用于存储消息的属性
        Map<String, Object> hashMap1 = new HashMap<>();
        hashMap1.put("name","lizheng");
        hashMap1.put("sex","男");
        // 使用hashMap1中的数据构建AMQP.BasicProperties对象
        AMQP.BasicProperties.Builder Properties1 = new AMQP.BasicProperties().builder().headers(hashMap1);


        Map<String, Object> hashMap2 = new HashMap<>();
        hashMap2.put("sex","女");
        hashMap2.put("age","24");
        AMQP.BasicProperties.Builder Properties2 = new AMQP.BasicProperties().builder().headers(hashMap2);


        /**
         * 发送消息
         * 1.发送到哪个交换机
         * 2.路由键 routingKey
         * 3.其他参数信息
         * 4.消息主体
         */
        channel.basicPublish(exchangeName, "", Properties1.build(), "all message".getBytes());
        channel.basicPublish(exchangeName, "", Properties2.build(), "any message".getBytes());
        System.out.println("发送消息成功");
        RabbitMQConnector.closeResources();
    }
}
public class ConsumerHeaders {
    public static void main(String[] args) throws Exception {
        String queueName1 = "lz_queue_headers_1";
        String queueName2 = "lz_queue_headers_2";

        Channel channel = RabbitMQConnector.createConnectionAndChannel();


        // 接受消息的回调
        DeliverCallback deliverCallback = (consumerTage, message) -> {
            System.out.println("接收到的消息:" + new String(message.getBody()));
        };

        // 取消消息的回调
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println("消息消费被中断");
        };


        /**
         * 消费消息
         * 1.消费哪个队列
         * 2.消费成功之后是否需要自动应答,true 表示自动应答,false 表示手动应答
         * 3.接受消息的回调
         * 4.取消消息的回调
         */
        channel.basicConsume(queueName1, true, deliverCallback,cancelCallback);
        channel.basicConsume(queueName2, true, deliverCallback,cancelCallback);

    }
}
;