RabbitMQ的基本概念

AMQP简介

AMQP概念

AMQP全称高级消息队列协议,是一种标准,兼容JMS协议。类似HTTP协议,前端不管后台是什么语言,只要通过HTTP协议调用就可以了。

  • Broker: 接收和分发消息的应用,我们在介绍消息中间件的时候所说的消息系统就是Message Broker。一个具体的MQ服务实例
  • Virtual host: 出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中的namespace概念。当多个不同的用户使用同一个RabbitMQ server提供的服务时,可以划分出多个vhost,每个用户在自己的vhost创建exchange/queue等。
  • Connection:publisher/consumer和broker之间的TCP连接。断开连接的操作只会在client端进行,Broker不会断开连接,除非出现网络故障或broker服务出现问题。
  • Channel: 如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也较低。Channel是在connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的channel进行通讯,AMQP method包含了channel id帮助客户端和message broker识别channel,所以channel之间是完全隔离的。Channel作为轻量级的Connection极大减少了操作系统建立TCP connection的开销。实际发送消息是用的NIO中的selector方式。
  • Exchange: message到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发消息到queue中去。
    • direct (路由模式): 这种类型的交换机的路由规则是根据一个routingKey的标识,交换机通过一个routingKey与队列绑定 ,在生产者生产消息的时候 指定一个routingKey 当绑定的队列的routingKey 与生产者发送的一样 那么交换机会吧这个消息发送给对应的队列。
    • fanout(发布订阅): 这种类型的交换机路由规则很简单,只要与他绑定了的队列, 他就会吧消息发送给对应队列(与routingKey没关系)
    • topic(主题模式): 这种类型的交换机路由规则也是和routingKey有关 只不过 topic他可以根据:星,#( 星号代表过滤一单词,#代表过滤后面所有单词, 用.隔开)来识别routingKey 我打个比方 假设 我绑定的routingKey 有队列A和B A的routingKey是:星.user B的routingKey是: #.user那么我生产一条消息routingKey 为: error.user 那么此时 2个队列都能接受到, 如果改为 topic.error.user 那么这时候 只有B能接受到了
    • headers(RPC): 这个类型的交换机很少用到,他的路由规则 与routingKey无关 而是通过判断header参数来识别的, 基本上没有应用场景,因为上面的三种类型已经能应付了。
  • Queue: 消息最终被送到这里等待consumer取走。一个message可以被同时拷贝到多个queue中。
  • Binding: exchange和queue之间的虚拟连接,binding中可以包含routing key。Binding信息被保存到exchange中的查询表中,用于message的分发依据。
  • Routing key: 路由键,用于指定消息路由规则(Exchange将消息路由到具体的queue中),通常需要和具体的Exchange类型、Binding的Routing key结合起来使用。

AMQP传输层架构

​ AMQP是一个二进制的协议,信息被组织成数据帧,有很多类型。数据帧携带协议方法和其他信息。所有数据帧都拥有基本相同的格式:帧头,负载,帧尾。数据帧负载的格式依赖于数据帧的类型。

​ 我们假定有一个可靠的面向流的网络传输层(TCP/IP或等价的协议)。

​ 在一个单一的socket连接中,可能有多个相互独立的控制线程,称为“channel”。每个数据帧使用通道号码编号。通过数据帧的交织,不同的通道共享一个连接。对于任意给定通道,数据帧严格按照序列传输。

​ 我们使用小的数据类型来构造数据帧,如bit,integer,string以及字段表。数据帧的字段做了轻微的封装,不会让传输变慢或解析困难。根据协议规范机械地生成成数据帧层相对简单。

​ 线级别的格式被设计为可伸缩和足够通用,以支持任意的高层协议(不仅是AMQP)。我们假定AMQP会扩展,改进以及随时间的其他变化,并要求wire-level格式支持这些变化。

AMQP数据类型

  • Integers(数值范围1-8的十进制数字):用于表示大小,数量,限制等,整数类型无符号的,可以在帧内不对齐。
  • Bits(统一为8个字节):用于表示开/关值。
  • Short strings:用于保存简短的文本属性,字符串个数限制为255,8个字节
  • Long strings:用于保存二进制数据块。
  • Field tables:包含键值对,字段值一般为字符串,整数等。

AMQP协议文档

RabbitMQ相关概念

  • RabbitMQ是一个Erlang开发的AMQP(Advanced Message Queuing Protocol,高级消息队列协议)的开源实现。是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在。
  • 主要特征
    1. 可靠性:持久化、传输确认、发布确认等机制来保证可靠性。
    2. 扩展性:支持动态扩展集群中的节点
    3. 高可用:队列可在集群中设置镜像,部分节点出现问题仍然可用
    4. 多协议:AMQP协议、STOMP、MOTT等多种消息中间件协议
    5. 多语言:java、Python、Ruby、PHP、C#、JavaScript、Go、Object-C等
    6. 支持插件:如web管理端。
  • 消息队列有三个基本概念: 发送方、消息队列、消费方。RabbitMQ 在这个基本概念之上, 多做了一层抽象, 在发消息者和队列之间, 加入了交换器 (Exchange)。这样发消息者和消息队列就没有直接联系,转而变成发消息者把消息发给交换器,交换器根据调度策略再把消息转发给消息队列。消息生产者并没有直接将消息发送给消息队列,而是通过建立与Exchange的Channel,将消息发送给Exchange。Exchange根据路由规则,将消息转发给指定的消息队列。消息队列储存消息,等待消费者取出消息。消费者通过建立与消息队列相连的Channel,从消息队列中获取消息。

RabbitMQ几种应用模式

工作流程

生产者发送消息的流程

  1. 生产者连接RabbitMQ,建立TCP连接( Connection),开启信道(Channel)
  2. 生产者声明一个Exchange(交换器),并设置相关属性,比如交换器类型、是否持久化等
  3. 生产者声明一个队列井设置相关属性,比如是否排他、是否持久化、是否自动删除等
  4. 生产者通过 bindingKey (绑定Key)将交换器和队列绑定( binding )起来
  5. 生产者发送消息至RabbitMQ Broker,其中包含 routingKey (路由键)、交换器等信息
  6. 相应的交换器根据接收到的 routingKey 查找相匹配的队列。
  7. 如果找到,则将从生产者发送过来的消息存入相应的队列中。
  8. 如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者
  9. 关闭信道。
  10. 关闭连接

消费者接收消息的过程

  1. 消费者连接到RabbitMQ Broker ,建立一个连接(Connection ) ,开启一个信道(Channel) 。
  2. 消费者向RabbitMQ Broker 请求消费相应队列中的消息,可能会设置相应的回调函数, 以及做一些准备工作
  3. 等待RabbitMQ Broker 回应并投递相应队列中的消息, 消费者接收消息。
  4. 消费者确认( ack) 接收到的消息。
  5. RabbitMQ 从队列中删除相应己经被确认的消息。
  6. 关闭信道。
  7. 关闭连接。

消息类型Demo

RabbitMQ几种应用模式

工作流程

生产者发送消息的流程

  1. 生产者连接RabbitMQ,建立TCP连接( Connection),开启信道(Channel)
  2. 生产者声明一个Exchange(交换器),并设置相关属性,比如交换器类型、是否持久化等
  3. 生产者声明一个队列井设置相关属性,比如是否排他、是否持久化、是否自动删除等
  4. 生产者通过 bindingKey (绑定Key)将交换器和队列绑定( binding )起来
  5. 生产者发送消息至RabbitMQ Broker,其中包含 routingKey (路由键)、交换器等信息
  6. 相应的交换器根据接收到的 routingKey 查找相匹配的队列。
  7. 如果找到,则将从生产者发送过来的消息存入相应的队列中。
  8. 如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者
  9. 关闭信道。
  10. 关闭连接

消费者接收消息的过程

  1. 消费者连接到RabbitMQ Broker ,建立一个连接(Connection ) ,开启一个信道(Channel) 。
  2. 消费者向RabbitMQ Broker 请求消费相应队列中的消息,可能会设置相应的回调函数, 以及做一些准备工作
  3. 等待RabbitMQ Broker 回应并投递相应队列中的消息, 消费者接收消息。
  4. 消费者确认( ack) 接收到的消息。
  5. RabbitMQ 从队列中删除相应己经被确认的消息。
  6. 关闭信道。
  7. 关闭连接。

消息类型Demo

在这里插入图片描述

在这里插入图片描述

    public class MqConnectionFactory {
    private static final Logger logger = LoggerFactory.getLogger(MqConnectionFactory.class);

    public static Connection getConnection() throws IOException, TimeoutException {
        System.out.println("开始创建MQ连接");
        // 1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //连接是否设置自动恢复
//        factory.setAutomaticRecoveryEnabled(false);
        // 2.设置连接地址
        factory.setHost("192.168.100.102");
        // 3.设置用户名称
        factory.setUsername("admin");
        // 4.设置用户密码
        factory.setPassword("admin");
        // 5.设置amqp协议端口号
        factory.setPort(5672);
        // 6.设置VirtualHost地址
        factory.setVirtualHost("test");
        //建立TCP连接
        Connection connection = factory.newConnection();
        System.out.println("创建connection成功");
        return connection;

    }
    public static void close(Connection connection, Channel channel) {
        try {
            if (channel != null) {
                System.out.println("关闭信道");
                channel.close();
            }
            if (connection != null) {
                System.out.println("关闭连接");
                connection.close();

            }
        } catch (Exception e) {
            logger.error("关闭连接出现异常", e);
        }
    }
}

简单模式

在这里插入图片描述

  • 消息产生消息,将消息放入队列
  • 消息的消费者(consumer) 监听(while) 消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除。
  • Demo演示
   private static String QUEUE_NAME = "demo_hello";
    public static void main(String[] args) {
        try {
            System.out.println("开始发送消息");
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            // 创建一个队列:队列名称,是否持久化,是否排外,是否自动删除队列,消息队列的属性(使用默认的)
            /**
             * 1.队列名称
             * 2.是否持久化
             *      false,队列非持久化。因为队列是存放在内存中的,所以当RabbitMQ重启或者服务器重启时该队列就会丢失
             *      true,队列持久化。当RabbitMQ重启后队列不会丢失。RabbitMQ退出时它会将队列信息保存到 Erlang自带的Mnesia数据库 中,当RabbitMQ重启之后会读取该数据库
             * 3.是否排外
             *      true则设置队列为排他的。如果一个队列被声明为排他队列,该队列仅对首次声明它的连接(Connection)可见,是该Connection私有的,类似于加锁,并在连接断开connection.close()时自动删除
             *          首次是指某个连接(Connection)已经声明了排他队列,其他连接是不允许建立同名的排他队列的
             *      false则设置队列为非排他的,此时不同连接(Connection)的管道Channel可以使用该队列 ;
             * 4. 是否自动删除 ;如果autoDelete = true,当所有消费者都与这个队列断开连接时,这个队列会自动删除。注意: 不是说该队列没有消费者连接时该队列就会自动删除,因为当生产者声明了该队列且没有消费者连接消费时,该队列是不会自动删除的
             * 5.设置队列的其他一些参数,如 x-rnessage-ttl 、x-expires 、x-rnax-length 、x-rnax-length-bytes、 x-dead-letter-exchange、 x-deadletter-routing-key 、 x-rnax-priority 等。
             */
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            for (int i = 1; i <= 25; i++) {
                // 创建 msg
                String msg = "生成 ---" + i;
                System.out.println("发送消息:" + msg);
                // 生产者发送消息者     MessageProperties.PERSISTENT_TEXT_PLAIN 设置消息的持久化(消息没有接收到也不会丢失)
                channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes("UTF-8"));
            }
            System.out.println("发送消息完成");
            MqConnectionFactory.close(connection,channel);
        } catch (Exception e) {
            e.getStackTrace();
        }
    }

在这里插入图片描述

private static String QUEUE_NAME = "demo_hello";
    public static void main(String[] args) {
        try {
            System.out.println("开始接收消息");
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                // 监听获取消息
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                        throws IOException {
                    String msg = new String(body, "UTF-8");
                    System.out.println("消费消息:" + msg);
                }
            };
            // 设置应答模式 如果为true情况下 表示为自动应答模式 false 表示为手动应答
            channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
            System.out.println("接收消息完成");
//            MqConnectionFactory.close(connection,channel);
        } catch (Exception e) {
            e.getStackTrace();
        }

    }

在这里插入图片描述

work queues(工作模式)

在这里插入图片描述

  • 多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊给多个消费者进行处理,而不是每个消费
    者都收到所有的消息并处理。
  • 这时如果每个消息的处理时间不同,就有可能会导致某些消费者一直在忙,而另外一些消费者很快就处理完手头工作并一直空闲的情况。我们可以通过设置prefetchCount来限制Queue每次发送给每个消费者的消息数,比如我们设置prefetchCount=1,则Queue每次给每个消费者发送一条消息;消费者处理完这条消息后Queue会再给该消费者发送一条消息。
  • Demo演示
    工作队列只需要将上面的消费者多复制几分就可以了。
    如果发现消息分发不均匀可以设置
        /**
         * 公平队列原理:队列服务器向消费者发送消息的时候,消费者采用手动应答模式,
         * 队列服务器必须要收到消费者发送ack结果通知,才会继续发送一下一个消息
         * 此处设置一次只消费1个
         */
        channel.basicQos(1);

默认情况下,rabbitmq开启了消息的自动应答。此时,一旦rabbitmq将消息分发给了消费者,就会将消息从内存中删除。这种情况下,如果正在执行的消费者被“杀死”或“崩溃”,就会丢失正在处理的消息。 如果想要确保消息不丢失,我们需要设置消息应答方式为手动应答。设置为手工应答后,消费者接受并处理完一个消息后,会发送应答给rabbitmq,rabbitmq收到应答后,会将该条消息从内存中删除。如果一个消费者在处理消息的过程中“崩溃”,rabbitmq没有收到应答,那么”崩溃“前正在处理的这条消息会重新被分发到别的消费者。

public class WorkQueueProducer {
    private static String QUEUE_NAME = "demo_hello_rabbitmq";
    public static void main(String[] args) {
        try {
            System.out.println("开始发送消息");
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            for (int i = 1; i <= 25; i++) {
                // 创建 msg
                String msg = "生成 ---" + i;
                System.out.println("发送消息:" + msg);
                // 生产者发送消息者     MessageProperties.PERSISTENT_TEXT_PLAIN 设置消息的持久化(消息没有接收到也不会丢失)
                channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes("UTF-8"));
                Thread.sleep(i * 10);
            }
            System.out.println("发送消息完成");
            MqConnectionFactory.close(connection,channel);
        } catch (Exception e) {
            e.getStackTrace();
        }
    }
}


public class WorkQueueConsumer1 {
    private static String QUEUE_NAME = "demo_hello_rabbitmq";
    public static void main(String[] args) {
        try {
            System.out.println("开始接收消息");
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            channel.basicQos(1);
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                System.out.println("WorkQueueConsumer1消费消息:" + new String(delivery.getBody(), "UTF-8"));
            };
            // 设置应答模式 如果为true情况下 表示为自动应答模式, false 表示为手动应答
            channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {

            });
//            MqConnectionFactory.close(connection,channel);
        } catch (Exception e) {
            e.getStackTrace();
        }
    }
}

public class WorkQueueConsumer2 {
    private static String QUEUE_NAME = "demo_hello_rabbitmq";

    public static void main(String[] args) {
        try {
            System.out.println("开始接收消息");
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            channel.basicQos(1);
            // 设置应答模式 如果为true情况下 表示为自动应答模式, false 表示为手动应答
            channel.basicConsume(QUEUE_NAME, false, new Recivew(channel));
//            MqConnectionFactory.close(connection,channel);
        } catch (Exception e) {
            e.getStackTrace();
        }

    }
}

class Recivew extends DefaultConsumer {
    private Channel channel;
    public Recivew(Channel channel) {
        super(channel);
        this.channel = channel;
    }
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        String s = new String(body);
        System.out.println("WorkQueueConsumer2消费消息 " + new String(body, "UTF-8"));
        // 手动应答 模式 告诉给队列服务器 已经处理成功  multiple false表示只确认当前的消息,true代表签收高消费者所有未签收的消息
        channel.basicAck(envelope.getDeliveryTag(), false);
        //消息标签,不确认是多个消息还是一个消息,是否重新入列。用于拒绝多条消息
        //channel.basicNack(envelope.getDeliveryTag(), false, true);
        //用于拒绝一条消息,对于不确认的消息是否入列然后重发
        //  channel.basicReject(envelope.getDeliveryTag(), true);
    }
} 

publish/subscribe(发布订阅)

在这里插入图片描述

  • 1个生产者,多个消费者。每一个消费者都有自己的一个队列。生产者没有将消息直接发送到队列,而是发送到了交换机。每个队列都要绑定到交换机。生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者获取的目的
  • X(Exchanges)接收生产者发送的消息。知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。
  • 这种模式不需要RouteKey
  • 广播消息到所有队列,没有任何处理,速度最快
  • Demo演示
    private static final String DESTINATION_NAME = "rabbitMq_fanout";
    public static void main(String[] args) {
        try {
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            String queueName = channel.queueDeclare().getQueue();
            System.out.println("随机生成的队列名称:" + queueName);
            //绑定队列和交换机
            //队列名称、交换机、routingKek目前用不到
            channel.queueBind(queueName, DESTINATION_NAME, "");

            // 订阅消息的回调函数
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                System.out.println(queueName + "消费者获取生产消息:" + new String(delivery.getBody(), "UTF-8"));
            };
            // 消费者,有消息时出发订阅回调函数   自动应答
            channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
                System.out.println("消费者回调" + consumerTag);
            });
//            MqConnectionFactory.close(connection, channel);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static final String DESTINATION_NAME = "rabbitMq_fanout";
    public static void main(String[] args) {
        try {
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            String queueName = channel.queueDeclare().getQueue();
            System.out.println("随机生成的队列名称:" + queueName);
            channel.queueBind(queueName, DESTINATION_NAME, "");

            // 订阅消息的回调函数
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                System.out.println(queueName + "消费者获取生产消息:" + new String(delivery.getBody(), "UTF-8"));
            };
            // 消费者,有消息时出发订阅回调函数   自动应答
            channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
                System.out.println("消费者回调" + consumerTag);
            });
//            MqConnectionFactory.close(connection, channel);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

public class FanoutProducer {
    private static final String DESTINATION_NAME = "rabbitMq_fanout";
    public static void main(String[] args) {
        try {
            System.out.println("开始发送消息");
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            // 3.生产者绑定交换机 参数1:交换机名称 参数2:交换机类型,是否持久化,是否排外,是否自动删除队列,消息队列的属性(使用默认的)
            channel.exchangeDeclare(DESTINATION_NAME, BuiltinExchangeType.FANOUT, true, false, false, null);
            // 4.创建消息
            for (int i = 0; i < 10; i++) {
                String msg = "rabbitMq_fanout" + i;
                System.out.println("生产者投递消息:" + msg);
                // 5.发送消息
                channel.basicPublish(DESTINATION_NAME, "", null, msg.getBytes());
                System.out.println("发送消息完成");
            }
            MqConnectionFactory.close(connection, channel);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

routing(路由模式)

在这里插入图片描述

  • 处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “dog”,则只有被标记为“dog”的消息才被转发,不会转发dog.puppy,也不会转发dog.guard,只会转发dog。
  • 任何发送到Direct Exchange的消息都会被转发到RouteKey中指定的Queue
  • 一般情况可以使用rabbitMQ自带的Exchange:”"(该Exchange的名字为空字符串,下文称其为default Exchange)。
  • 这种模式下不需要将Exchange进行任何绑定(binding)操作。
  • 消息传递时需要一个“RouteKey”,可以简单的理解为要发送到的队列名字。
  • 如果vhost中不存在RouteKey中指定的队列名,则该消息会被抛弃。
  • Demo演示
    private static final String DESTINATION_NAME = "rabbitMq_direct";
    private static final String SMS_QUEUE = "Sms_msg";
    public static void main(String[] args) {
        try {
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            // 消费声明队列
            channel.queueDeclare(SMS_QUEUE, false, false, false, null);
            // 消费者队列绑定交换机
            channel.queueBind(SMS_QUEUE, DESTINATION_NAME, "sms");
            channel.queueBind(SMS_QUEUE, DESTINATION_NAME, "email");
            channel.basicQos(1);
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                System.out.println(SMS_QUEUE + "消费者获取生产消息:" + new String(delivery.getBody(), "UTF-8"));
            };
            channel.basicConsume(SMS_QUEUE, true, deliverCallback, consumerTag -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static final String DESTINATION_NAME = "rabbitMq_direct";
    private static final String EMAIL_QUEUE = "Email_msg";

    public static void main(String[] args) {
        try {
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            // 消费声明队列
            channel.queueDeclare(EMAIL_QUEUE, false, false, false, null);
            // 消费者队列绑定交换机
            channel.queueBind(EMAIL_QUEUE, DESTINATION_NAME, "email");
            channel.basicQos(1);
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                System.out.println(EMAIL_QUEUE + "消费者获取生产消息:" + new String(delivery.getBody(), "UTF-8"));
            };
            channel.basicConsume(EMAIL_QUEUE, true, deliverCallback, consumerTag -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 交换机名称
    private static final String DESTINATION_NAME = "rabbitMq_direct";

    public static void main(String[] args) {
        try {
            System.out.println("开始发送消息");
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            // 3.生产者绑定交换机 参数1:交换机名称 参数2:交换机类型
            channel.exchangeDeclare(DESTINATION_NAME, BuiltinExchangeType.DIRECT);
            for (int i = 1; i < 10; i++) {
                String routingKey = "";
                //生成路由键
                if (i % 2 == 0) {
                    routingKey = "email";
                } else {
                    routingKey = "sms";
                }
                String msg = "rabbitMq_direct---:" + routingKey;
                System.out.println("生产者投递消息:" + msg);
                channel.basicPublish(DESTINATION_NAME, routingKey, null, msg.getBytes());
            }
            System.out.println("发送消息完成");
            MqConnectionFactory.close(connection, channel);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

topic(主题模式)

在这里插入图片描述

  • Topic Exchange – 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号“#”匹配一个或多个词,符号“ * ”匹配不多不少一个词。因此“audit.#”能够匹配到“audit.irs.corporate”,但是“audit.*” 只会匹配到“audit.irs”
  • 任何发送到Topic Exchange的消息都会被转发到所有关心RouteKey中指定话题的Queue上
  • 这种模式较为复杂,简单来说,就是每个队列都有其关心的主题,所有的消息都带有一个“标题”(RouteKey),Exchange会将消息转发到所有关注主题能与RouteKey模糊匹配的队列。
  • 这种模式需要RouteKey,也许要提前绑定Exchange与Queue。
  • 在进行绑定时,要提供一个该队列关心的主题,如“#.log.#”表示该队列关心所有涉及log的消息(一个RouteKey为”MQ.log.error”的消息会被转发到该队列)。
  • “#”表示0个或若干个关键字,“ * ”表示一个关键字。如“log.*”能与“log.warn”匹配,无法与“log.warn.timeout”匹配;但是“log.#”能与上述两者匹配。
  • 同样,如果Exchange没有发现能够与RouteKey匹配的Queue,则会抛弃此消息。
  • Demo演示
      private static final String DESTINATION_NAME = "rabbitMq_topic";
    private static final String SMS_QUEUE = "Sms_msg_topic";
    public static void main(String[] args) {
        try {
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            // 消费声明队列
            channel.queueDeclare(SMS_QUEUE, false, false, false, null);
            // 消费者队列绑定交换机
            //只要前缀相同都能收到
            channel.queueBind(SMS_QUEUE, DESTINATION_NAME, "sms.*");
            //只能匹配到email.demo
            channel.queueBind(SMS_QUEUE, DESTINATION_NAME, "email.demo");
            channel.basicQos(1);
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                System.out.println(SMS_QUEUE + "消费者获取生产消息:" + new String(delivery.getBody(), "UTF-8"));
            };
            channel.basicConsume(SMS_QUEUE, true, deliverCallback, consumerTag -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private static final String DESTINATION_NAME = "rabbitMq_topic";
    private static final String EMAIL_QUEUE = "Email_msg_topic";
    public static void main(String[] args) {
        try {
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            // 消费声明队列
            channel.queueDeclare(EMAIL_QUEUE, false, false, false, null);
            // 消费者队列绑定交换机
            channel.queueBind(EMAIL_QUEUE, DESTINATION_NAME, "email.*");
            channel.basicQos(1);
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                System.out.println(EMAIL_QUEUE + "消费者获取生产消息:" + new String(delivery.getBody(), "UTF-8"));
            };
            channel.basicConsume(EMAIL_QUEUE, true, deliverCallback, consumerTag -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static final String DESTINATION_NAME = "rabbitMq_topic";
    private static final String SMS_QUEUE_1 = "Sms_msg_topic_1";
    public static void main(String[] args) {
        try {
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            // 消费声明队列
            channel.queueDeclare(SMS_QUEUE_1, false, false, false, null);
            // 消费者队列绑定交换机
            //可以匹配后面所有的词
            channel.queueBind(SMS_QUEUE_1, DESTINATION_NAME, "sms.#");
            channel.basicQos(1);
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                System.out.println(SMS_QUEUE_1 + "消费者获取生产消息:" + new String(delivery.getBody(), "UTF-8"));
            };
            channel.basicConsume(SMS_QUEUE_1, true, deliverCallback, consumerTag -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static final String DESTINATION_NAME = "rabbitMq_topic";
    public static void main(String[] args) {
        try {
            System.out.println("开始发送消息");
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            // 3.生产者绑定交换机 参数1:交换机名称 参数2:交换机类型
            channel.exchangeDeclare(DESTINATION_NAME, BuiltinExchangeType.TOPIC);
            String routingKey1="sms.1";
            System.out.println("生产者投递消息:" + routingKey1);
            channel.basicPublish(DESTINATION_NAME, routingKey1, null, routingKey1.getBytes());
            String routingKey2="sms.2";
            System.out.println("生产者投递消息:" + routingKey2);
            channel.basicPublish(DESTINATION_NAME, routingKey2, null, routingKey2.getBytes());
            String routingKey3="sms.1.1";
            System.out.println("生产者投递消息:" + routingKey3);
            channel.basicPublish(DESTINATION_NAME, routingKey3, null, routingKey3.getBytes());
            String routingKey4="sms.1.3";
            System.out.println("生产者投递消息:" + routingKey4);
            channel.basicPublish(DESTINATION_NAME, routingKey4, null, routingKey4.getBytes());
            String routingKey5="email.demo";
            System.out.println("生产者投递消息:" + routingKey5);
            channel.basicPublish(DESTINATION_NAME, routingKey5, null, routingKey5.getBytes());
            String routingKey6="email.123";
            System.out.println("生产者投递消息:" + routingKey6);
            channel.basicPublish(DESTINATION_NAME, routingKey6, null, routingKey6.getBytes());

            System.out.println("发送消息完成");
            MqConnectionFactory.close(connection, channel);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

RPC

在这里插入图片描述

死信队列的作用

死信交换机有什么用呢? 在创建队列的时候 可以给这个队列附带一个交换机, 那么这个队列作废的消息就会被重新发到附带的交换机,然后让这个交换机重新路由这条消息。

死信消息产生的来源

  • 消息被拒绝(basic.reject或basic.nack)并且requeue=false
  • 消息TTL过期
  • 队列达到最大长度(队列满了,无法再添加数据到mq中)

死信队列处理的方式

  • 丢弃,如果不是很重要,可以选择丢弃
  • 记录死信入库,然后做后续的业务分析或处理
  • 通过死信队列,由负责监听死信的应用程序进行处理

消息超时进入死信队列

通俗的说,就是消息产生之后,因为设置了超时时间,在这段时间内消息没有被消费就会被扔到死信队列里面。

// 交换机名称
private static final String DESTINATION_NAME = "rabbitMq_topic_dlx";
//消息队列
private static final String queueName = "topic_queue_dlx";
//routingKey
private static final String routingKey = "topic.#";

//配置死信队列
private static final String dlxExchangeName = "dlx.exchange";
private static final String dlxQueueName = "dlx.queue";
private static final String dlxRoutingKey = "#";

public static void main(String[] args) {
    try {
        System.out.println("开始发送消息");
        Connection connection = MqConnectionFactory.getConnection();
        Channel channel = connection.createChannel();

        Map<String, Object> arguments = new HashMap<String, Object>(16);
        // 为队列设置队列交换器
        arguments.put("x-dead-letter-exchange", dlxExchangeName);
        // 设置队列中的消息 60s 钟后过期
        arguments.put("x-message-ttl", 60000);

        //消费声明队列
        channel.exchangeDeclare(DESTINATION_NAME, BuiltinExchangeType.TOPIC);
        channel.queueDeclare(queueName, true, false, false, arguments);
        //消费者队列绑定交换机 绑定路由件 路由键
        channel.queueBind(queueName, DESTINATION_NAME, routingKey);

        String message = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 测试消息超时,传递到死信队列";
        
        // 创建死信交换器和队列
        channel.exchangeDeclare(dlxExchangeName, BuiltinExchangeType.TOPIC, true, false, null);
        channel.queueDeclare(dlxQueueName, true, false, false, null);
        channel.queueBind(dlxQueueName, dlxExchangeName, dlxRoutingKey);

        channel.basicPublish(DESTINATION_NAME, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
        System.out.println("发送消息完成");
        MqConnectionFactory.close(connection, channel);
        
    } catch (Exception e) {
        e.printStackTrace();
    }
}

只监听了死信队列的消息,正常消息无需监听接收

private static final String queueName = "topic_queue_dlx";
public static void main(String[] args) {
    try {
        Connection connection = MqConnectionFactory.getConnection();
        Channel channel = connection.createChannel();
        System.out.println("正常消费者启动 ..........");
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            System.out.println("正常消费者生产消息:" + new String(delivery.getBody(), "UTF-8"));
        };
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
        });
    } catch (Exception e) {
        e.printStackTrace();
    }
}


private static final String dlxQueueName = "dlx.queue";
public static void main(String[] args) {
    try {
        Connection connection = MqConnectionFactory.getConnection();
        Channel channel = connection.createChannel();
        System.out.println("死信消费者启动 ..........");
        Thread.sleep(65000);
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            System.out.println("消死信消费者生产消息:" + new String(delivery.getBody(), "UTF-8"));
        };
        channel.basicConsume(dlxQueueName, true, deliverCallback, consumerTag -> {
        });
    } catch (Exception e) {
        e.printStackTrace();
    }
}

消息被退回

channel.basicNack(envelope.getDeliveryTag(),false,false);

队列达到最大长度

这个和消息超时差不多,只不过是设置了队列的最大容量而已。
只需要把上面的代码修改一下就可以了。

   生产者需要修改
                   // 设置队列中的消息 60s 钟后过期
//            arguments.put("x-message-ttl", 60000);
            //设置队列长度为3
            arguments.put("x-max-length", 3);
然后消息多发送几次
 private static final String queueName = "topic_queue_dlx";
    public static void main(String[] args) {
        try {
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            channel.basicQos(1);
            System.out.println("正常消费者启动 ..........");
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                System.out.println("正常消费者生产消息:" + new String(delivery.getBody(), "UTF-8"));
                channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
            };
            channel.basicConsume(queueName, false, deliverCallback, consumerTag -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



  private static final String dlxQueueName = "dlx.queue";
    public static void main(String[] args) {
        try {
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            System.out.println("死信消费者启动 ..........");
//            Thread.sleep(65000);
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                System.out.println("消死信消费者生产消息:" + new String(delivery.getBody(), "UTF-8"));
            };
            channel.basicConsume(dlxQueueName, true, deliverCallback, consumerTag -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

请添加图片描述
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值