RabbitMQ(三)聊一聊RabbitMQ的模式

RabbitMQ(三)聊一聊RabbitMQ的模式

RabbitMQ模式

RabbitMQ提供了6种消息模型,但是第6种其实是RPC,并不是MQ,因此我只写了前5种

在这里插入图片描述

  • 5种消息模型,大体分为两类:

    • 1和2属于点对点
    • 3、4、5属于发布订阅模式(一对多)
  • 点对点模式:P2P(point to point)模式包含三个角色:

    • 消息队列(queue),发送者(sender),接收者(receiver)
    • 每个消息发送到一个特定的队列中,接收者从中获得消息
    • 队列中保留这些消息,直到他们被消费或超时
    • 特点:
      1. 每个消息只有一个消费者,一旦消费,消息就不在队列中了
      2. 发送者和接收者之间没有依赖性,发送者发送完成,不管接收者是否运行,都不会影响消息发送到队列中(我给你发微信,不管你看不看手机,反正我发完了)
      3. 接收者成功接收消息之后需向对象应答成功(确认)
    • 如果希望发送的每个消息都会被成功处理,那需要P2P
  • 发布订阅模式:publish(Pub)/subscribe(Sub)

    • pub/sub模式包含三个角色:交换机(exchange),发布者(publisher),订阅者(subcriber)
    • 多个发布者将消息发送交换机,系统将这些消息传递给多个订阅者
    • 特点:
      1. 每个消息可以有多个订阅者
      2. 发布者和订阅者之间在时间上有依赖,对于某个交换机的订阅者,必须创建一个订阅后,才能消费发布者的消息
      3. 为了消费消息,订阅者必须保持运行状态;类似于,看电视直播。
    • 如果希望发送的消息被多个消费者处理,可采用本模式

简单模式

RabbitMQ is a message broker: it accepts and forwards messages. You can think about it as a post office: when you put the mail that you want posting in a post box, you can be sure that Mr. or Ms. Mailperson will eventually deliver the mail to your recipient. In this analogy, RabbitMQ is a post box, a post office and a postman.

译文:RabbitMQ是一个消息代理:它接收和转发消息。你可以把它想象成一个邮局:当你把你想要寄的邮件放到一个邮箱里,你可以确定邮递员先生或女士最终会把邮件送到你的收件人那里。在这个类比中,RabbitMQ是一个邮箱、一个邮局和一个邮递员。

RabbitMQ本身只是接收,存储和转发消息,并不会对信息进行处理!

类似邮局,处理信件的应该是收件人而不是邮局!

在这里插入图片描述

生产者P

public class Sender {

    public static void main(String[] args) {

        //定义发送信息
        String msg = "小星: Hello RbbitMQ!";
        //获得连接
        Connection connection = ConnectionUtil.getConnection();
        try {
            //在连接中创建通道
            Channel channel = connection.createChannel();
            //声明消息队列
            /**
             * 创建消息队列有五个参数
             * String var1: 队列的名称
             * boolean var2: 队列中的数据是否持久化
             * boolean var3: 是否排外(是否支持扩展,当前队列只能自己用,不能给别人用)
             * boolean var4: 是否自动删除(当队列的连接数为 0 时,队列会销毁,不管队列中是否还保存数据)
             * Map<String, Object> var5: 队列参数(没有参数为null)
             */
            channel.queueDeclare("queue1",false,false,false,null);
            //向指定的队列发送消息
            /**
             * 发送消息有四个参数
             * String var1: 交换机名称,当前是简单模式,也就是P2P模式,无交换机,所以名称为""
             * String var2: 目标队列名称
             * BasicProperties var3: 设置消息的属性(没有属性则为null)
             * byte[] var4: 消息内容(只接受字节数组)
             */
            channel.basicPublish("","queue1",null,msg.getBytes());
            System.out.println("发送: " + msg);
            channel.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //释放资源
        ConnectionUtil.close(connection);

    }

}

消费者C

public class Recer {

    public static void main(String[] args) {
        //获得连接
        Connection connection = ConnectionUtil.getConnection();
        try {
            //获得通道(信道)
            Channel channel = connection.createChannel();
            //从信道中获得消息
            DefaultConsumer consumer = new DefaultConsumer(channel){
                /**
                 * 交付处理,重写方法
                 * @param consumerTag   收件人信息
                 * @param envelope      信封,包裹上的快递标签
                 * @param properties    协议配置
                 * @param body          消息
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //将消息字节构建成字符串
                    //boby就是在队列中获取的消息
                    String string = new String(body);
                    System.out.println("接收: " + string);
                }
            };
            //监听队列
            /**
             * String var1: 队列名称
             * boolean var2: 自动消息确认
             * Consumer var3: 从管道中获取信息
             */
            channel.basicConsume("queue1",true,consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

启动消费者,前往管理端查看队列中的信息,所有信息都已经处理和确认,显示0

在这里插入图片描述

消息确认机制ACK

  • 通过刚才的案例可以看出,消息一旦被消费,消息就会立刻从队列中移除

  • RabbitMQ如何得知消息被消费者接收?

    • 如果消费者接收消息后,还没执行操作就抛异常宕机导致消费失败,但是RabbitMQ无从得知,这样消息就丢失了
    • 因此,RabbitMQ有一个ACK机制,当消费者获取消息后,会向RabbitMQ发送回执ACK,告知消息已经被接收
    • ACK:(Acknowledge character)即是确认字符,在数据通信中,接收站发给发送站的一种传输类控制字符。表示发来的数据已确认接收无误我们在使用http请求时,http的状态码200就是告诉我们服务器执行成功
    • 整个过程就想快递员将包裹送到你手里,并且需要你的签字,并拍照回执
    • 不过这种回执ACK分为两种情况:
      • 自动ACK:消息接收后,消费者立刻自动发送ACK(快递放在快递柜)
      • 手动ACK:消息接收后,不会发送ACK,需要手动调用(快递必须本人签收)
    • 两种情况如何选择,需要看消息的重要性:
      • 如果消息不太重要,丢失也没有影响,自动ACK会比较方便
      • 如果消息非常重要,最好消费完成手动ACK,如果自动ACK消费后,RabbitMQ就会把消息从队列中删除,如果此时消费者抛异常宕机,那么消息就永久丢失了
  • 修改手动消息确认

    // false:手动消息确认
    channel.basicConsume("queue1",false,consumer);
    
  • 结果如下:

在这里插入图片描述

  • 解决问题
public class RecerByACK {

    public static void main(String[] args) {
        //获得连接
        Connection connection = ConnectionUtil.getConnection();
        try {
            //获得通道(信道)
            Channel channel = connection.createChannel();
            //从信道中获得消息
            DefaultConsumer consumer = new DefaultConsumer(channel){
                /**
                 * 交付处理,重写方法
                 * @param consumerTag   收件人信息
                 * @param envelope      信封,包裹上的快递标签
                 * @param properties    协议配置
                 * @param body          消息
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //将消息字节构建成字符串
                    //boby就是在队列中获取的消息
                    String string = new String(body);
                    System.out.println("接收: " + string);
                    //手动确认
                    /**
                     * long var1: 收件人信息
                     * boolean var3: 是否同时确认多个消息
                     */
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            };
            //监听队列
            /**
             * String var1: 队列名称
             * boolean var2: 自动消息确认
             * Consumer var3: 从管道中获取信息
             */
            //手动消息确认
            channel.basicConsume("queue1",false,consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

工作队列模式

在这里插入图片描述

  • 之前我们学习的简单模式,一个消费者来处理消息,如果生产者生产消息过快过多,而消费者的能力有限,就会产生消息在队列中堆积(生活中的滞销)
  • 一个烧烤师傅,一次烤50支羊肉串,就一个人吃的话,烤好的肉串会越来越多,怎么处理?
  • 多招揽客人进行消费即可。当我们运行许多消费者程序时,消息队列中的任务会被众多消费者共享,但其中某一个消息只会被一个消费者获取(100支肉串20个人吃,但是其中的某支肉串只能被一个人吃)

生产者P

public class Sender {

    public static void main(String[] args) {

        //获得连接
        Connection connection = ConnectionUtil.getConnection();
        try {
            //在连接中创建通道
            Channel channel = connection.createChannel();
            //声明消息队列
            /**
             * 创建消息队列有五个参数
             * String var1: 队列的名称
             * boolean var2: 队列中的数据是否持久化
             * boolean var3: 是否排外(是否支持扩展,当前队列只能自己用,不能给别人用)
             * boolean var4: 是否自动删除(当队列的连接数为 0 时,队列会销毁,不管队列中是否还保存数据)
             * Map<String, Object> var5: 队列参数(没有参数为null)
             */
            channel.queueDeclare("test_work_queue", false, false, false, null);
            //向指定的队列发送消息
            for (int i = 1; i < 100; i++) {
                String msg = "羊肉串 --> " + i;
                /**
                 * 发送消息有四个参数
                 * String var1: 交换机名称,当前是简单模式,也就是P2P模式,无交换机,所以名称为""
                 * String var2: 目标队列名称
                 * BasicProperties var3: 设置消息的属性(没有属性则为null)
                 * byte[] var4: 消息内容(只接受字节数组)
                 */
                channel.basicPublish("", "test_work_queue", null, msg.getBytes());
                System.out.println("新鲜出炉: " + msg);
            }
            channel.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //释放资源
        ConnectionUtil.close(connection);

    }

}

消费者1

public class Recer1 {

    public static int i = 1;    //计数,统计吃掉羊肉串的数量

    public static void main(String[] args) {
        //获得连接
        Connection connection = ConnectionUtil.getConnection();
        try {
            //获得通道(信道)
            Channel channel = connection.createChannel();
            //声明消息队列
            //queueDeclare : 此方法有双重作用,如果队列不存在则创建队列,若队列存在则获取
            channel.queueDeclare("test_work_queue", false, false, false, null);
            //从信道中获得消息
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                /**
                 * 交付处理,重写方法
                 * @param consumerTag   收件人信息
                 * @param envelope      信封,包裹上的快递标签
                 * @param properties    协议配置
                 * @param body          消息
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //将消息字节构建成字符串
                    //boby就是在队列中获取的消息
                    String string = new String(body);
                    System.out.println("[顾客一]吃掉肉串: " + string + " ! 总共吃 [" + i++ + "] 串");
                    //模拟网络延迟
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //手动确认
                    /**
                     * long var1: 收件人信息
                     * boolean var3: 是否同时确认多个消息
                     */
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            };
            //监听队列
            /**
             * String var1: 队列名称
             * boolean var2: 自动消息确认
             * Consumer var3: 从管道中获取信息
             */
            //手动消息确认
            channel.basicConsume("test_work_queue", false, consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

消费者2

public class Recer2 {

    public static int i = 1;    //计数,统计吃掉羊肉串的数量

    public static void main(String[] args) {
        //获得连接
        Connection connection = ConnectionUtil.getConnection();
        try {
            //获得通道(信道)
            Channel channel = connection.createChannel();
            //声明消息队列
            //queueDeclare : 此方法有双重作用,如果队列不存在则创建队列,若队列存在则获取
            channel.queueDeclare("test_work_queue", false, false, false, null);
            //从信道中获得消息
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                /**
                 * 交付处理,重写方法
                 * @param consumerTag   收件人信息
                 * @param envelope      信封,包裹上的快递标签
                 * @param properties    协议配置
                 * @param body          消息
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //将消息字节构建成字符串
                    //boby就是在队列中获取的消息
                    String string = new String(body);
                    System.out.println("[顾客二]吃掉肉串: " + string + " ! 总共吃 [" + i++ + "] 串");
                    //模拟网络延迟
                    try {
                        Thread.sleep(900);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //手动确认
                    /**
                     * long var1: 收件人信息
                     * boolean var3: 是否同时确认多个消息
                     */
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            };
            //监听队列
            /**
             * String var1: 队列名称
             * boolean var2: 自动消息确认
             * Consumer var3: 从管道中获取信息
             */
            //手动消息确认
            channel.basicConsume("test_work_queue", false, consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
  • 先运行2个消费者,排队等候消费(取餐),再运行生产者开始生产消息(烤肉串)
  • 虽然两个消费者的消费速度不一致(线程休眠时间),但是消费的数量却是一致的,各消费50个消息
    • 例如:工作中,A同学编码速率高,B同学编码速率低,两个人同时开发一个项目,A10天完成,B30天完成,A完成自己的编码部分,就无所事事了,等着B完成就可以了,这样是不可以的,应该遵循“能者多劳”
    • 效率高的多干点,效率低的少干点
    • 看下面官网是如何给出解决思路的:

在这里插入图片描述

公平的分配
您可能已经注意到分派仍然不能完全按照我们的要求工作。例如,如果有两个员工,当所有奇怪的消息都很重,甚至消息都很轻时,一个员工会一直很忙,而另一个人几乎什么工作都不做。好吧,RabbitMQ对此一无所知,它仍然会均匀地分派消息。
这是因为RabbitMQ只在消息进入队列时发送消息。它不查看用户未确认消息的数量。它只是盲目地将每条第n个消息分派给第n个消费者。
为了克服这个问题,我们可以使用设置为prefetchCount = 1的basicQos方法。这告诉RabbitMQ一次不要给一个worker发送一条以上的消息。或者,换句话说,在worker处理并确认前一个消息之前,不要向它发送新消息。相反,它将把它分派到下一个不繁忙的worker。
//声明消息队列
//queueDeclare : 此方法有双重作用,如果队列不存在则创建队列,若队列存在则获取
channel.queueDeclare("test_work_queue", false, false, false, null);
// 可以理解为:快递一个一个送,送完一个再送下一个,速度快的送件就多
channel.basicQos(1);
  • 能者多劳必须要配合手动的ACK机制才生效

避免消息堆积?

  1. workqueue,多个消费者监听同一个队列
  2. 接收到消息后,通过线程池,异步消费

发布订阅模式

Publish/Subscribe

In the previous tutorial we created a work queue. The assumption behind a work queue is that each task is delivered to exactly one worker. In this part we'll do something completely different -- we'll deliver a message to multiple consumers. This pattern is known as "publish/subscribe".

To illustrate the pattern, we're going to build a simple logging system. It will consist of two programs -- the first will emit log messages and the second will receive and print them.

In our logging system every running copy of the receiver program will get the messages. That way we'll be able to run one receiver and direct the logs to disk; and at the same time we'll be able to run another receiver and see the logs on the screen.

Essentially, published log messages are going to be broadcast to all the receivers.

发布-订阅

在上一篇教程中,我们创建了一个工作队列。工作队列背后的假设是,每个任务都被准确地交付给一个工作者。在这一部分中,我们将做一些完全不同的事情——将消息传递给多个消费者。此模式称为“发布/订阅”。

为了演示这个模式,我们将构建一个简单的日志记录系统。它将由两个程序组成——第一个将发送日志消息,第二个将接收和打印它们。

在我们的日志系统中,接收程序的每一个正在运行的副本都将获得消息。这样我们就可以运行一个接收器并将日志指向磁盘;与此同时,我们可以运行另一个接收器并在屏幕上看到日志。

基本上,发布的日志消息将广播到所有接收方。

生活中的案例:就是玩抖音快手,众多粉丝关注一个视频主,视频主发布视频,所有粉丝都可以得到视频通知

在这里插入图片描述

  • 上图中,X就是视频主,红色的队列就是粉丝。binding是绑定的意思(关注)
  • P生产者发送信息给X路由,X将信息转发给绑定X的队列

在这里插入图片描述

  • X队列将信息通过信道发送给消费者,从而进行消费
  • 整个过程,必须先创建路由
    • 路由在生产者程序中创建
    • 因为路由没有存储消息的能力,当生产者将信息发送给路由后,消费者还没有运行,所以没有队列,路由并不知道将信息发送给谁
    • 运行程序的顺序:
      1. Sender 创建路由
      2. Recer1和Recer2 绑定路由
      3. Sender 发送消息

生产者

public class Sender {

    public static void main(String[] args) {

        //获得连接
        Connection connection = ConnectionUtil.getConnection();
        try {
            //在连接中创建通道
            Channel channel = connection.createChannel();
            //声明路由
            /**
             * String var1: 路由名
             * String var2: 路由类型
             *      fanout : 不处理路由键(只需要将队列绑定到路由上,发送到路由的消息都会被转发到与该路由绑定的所有队列上)
             */
            channel.exchangeDeclare("test_exchange_fanout","fanout");
            //定义发送信息
            String msg = "Hello everyone !";
            /**
             * 发送消息有四个参数
             * String var1: (路由)交换机名称,当前是简单模式,也就是P2P模式,无交换机,所以名称为""
             * String var2: 目标队列名称
             * BasicProperties var3: 设置消息的属性(没有属性则为null)
             * byte[] var4: 消息内容(只接受字节数组)
             */
            channel.basicPublish("test_exchange_fanout", "", null, msg.getBytes());
            System.out.println("生产者: " + msg);
            channel.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //释放资源
        ConnectionUtil.close(connection);

    }

}

消费者1

public class Recer1 {

    public static void main(String[] args) {
        //获得连接
        Connection connection = ConnectionUtil.getConnection();
        try {
            //获得通道(信道)
            Channel channel = connection.createChannel();
            //声明消息队列
            //queueDeclare : 此方法有双重作用,如果队列不存在则创建队列,若队列存在则获取
            channel.queueDeclare("test_exchange_fanout_queue_1", false, false, false, null);
            //绑定路由(关注)
            /**
             * String var1: 队列名
             * String var2: 路由名
             * String var3:
             */
            channel.queueBind("test_exchange_fanout_queue_1","test_exchange_fanout","");
            //从信道中获得消息
            DefaultConsumer consumer = new DefaultConsumer(channel){
                /**
                 * 交付处理,重写方法
                 * @param consumerTag   收件人信息
                 * @param envelope      信封,包裹上的快递标签
                 * @param properties    协议配置
                 * @param body          消息
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //将消息字节构建成字符串
                    //boby就是在队列中获取的消息
                    String string = new String(body);
                    System.out.println("消费者 1 : " + string);
                }
            };
            //监听队列
            /**
             * String var1: 队列名称
             * boolean var2: 自动消息确认
             * Consumer var3: 从管道中获取信息
             */
            channel.basicConsume("test_exchange_fanout_queue_1",true,consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

消费者2

将消费者1代码中的1修改为2即可,具体代码略

路由模式

在这里插入图片描述

  • 路由会根据类型进行定向分发消息给不同的队列,如图所示
  • 可以理解为是快递公司的分拣中心,整个小区,东面的楼小张送货,西面的楼小王送货

生产者

public class Sender {

    public static void main(String[] args) {

        //获得连接
        Connection connection = ConnectionUtil.getConnection();
        try {
            //在连接中创建通道
            Channel channel = connection.createChannel();
            //声明路由
            /**
             * String var1: 路由名
             * String var2: 路由类型
             *      direct : 根据路由键进行定向分发消息
             */
            channel.exchangeDeclare("test_exchange_direct","direct");
            //定义发送信息
            String msg = "用户注册,[userid = S101]!";
            /**
             * 发送消息有四个参数
             * String var1: (路由)交换机名称,当前是简单模式,也就是P2P模式,无交换机,所以名称为""
             * String var2: 目标队列名称
             *      路由键
             * BasicProperties var3: 设置消息的属性(没有属性则为null)
             * byte[] var4: 消息内容(只接受字节数组)
             */
            channel.basicPublish("test_exchange_direct", "insert", null, msg.getBytes());
            System.out.println("[用户系统]: " + msg);
            channel.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //释放资源
        ConnectionUtil.close(connection);

    }

}

消费者1

public class Recer1 {

    public static void main(String[] args) {
        //获得连接
        Connection connection = ConnectionUtil.getConnection();
        try {
            //获得通道(信道)
            Channel channel = connection.createChannel();
            //声明消息队列
            //queueDeclare : 此方法有双重作用,如果队列不存在则创建队列,若队列存在则获取
            channel.queueDeclare("test_exchange_direct_queue_1", false, false, false, null);
            //绑定路由(关注)
            /**
             * String var1: 队列名
             * String var2: 路由名
             * String var3: 队列处理的相关信息(路由键)
             *      处理增删改信息
             */
            channel.queueBind("test_exchange_direct_queue_1","test_exchange_direct","insert");
            channel.queueBind("test_exchange_direct_queue_1","test_exchange_direct","update");
            channel.queueBind("test_exchange_direct_queue_1","test_exchange_direct","delete");
            //从信道中获得消息
            DefaultConsumer consumer = new DefaultConsumer(channel){
                /**
                 * 交付处理,重写方法
                 * @param consumerTag   收件人信息
                 * @param envelope      信封,包裹上的快递标签
                 * @param properties    协议配置
                 * @param body          消息
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //将消息字节构建成字符串
                    //boby就是在队列中获取的消息
                    String string = new String(body);
                    System.out.println("消费者 1 : " + string);
                }
            };
            //监听队列
            /**
             * String var1: 队列名称
             * boolean var2: 自动消息确认
             * Consumer var3: 从管道中获取信息
             */
            channel.basicConsume("test_exchange_direct_queue_1",true,consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

消费者2

public class Recer2 {

    public static void main(String[] args) {
        //获得连接
        Connection connection = ConnectionUtil.getConnection();
        try {
            //获得通道(信道)
            Channel channel = connection.createChannel();
            //声明消息队列
            //queueDeclare : 此方法有双重作用,如果队列不存在则创建队列,若队列存在则获取
            channel.queueDeclare("test_exchange_direct_queue_2", false, false, false, null);
            //绑定路由(关注)
            /**
             * String var1: 队列名
             * String var2: 路由名
             * String var3: 队列处理的相关信息(路由键)
             *      处理查询信息
             */
            channel.queueBind("test_exchange_direct_queue_2","test_exchange_direct","select");
            //从信道中获得消息
            DefaultConsumer consumer = new DefaultConsumer(channel){
                /**
                 * 交付处理,重写方法
                 * @param consumerTag   收件人信息
                 * @param envelope      信封,包裹上的快递标签
                 * @param properties    协议配置
                 * @param body          消息
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //将消息字节构建成字符串
                    //boby就是在队列中获取的消息
                    String string = new String(body);
                    System.out.println("消费者 2 : " + string);
                }
            };
            //监听队列
            /**
             * String var1: 队列名称
             * boolean var2: 自动消息确认
             * Consumer var3: 从管道中获取信息
             */
            channel.basicConsume("test_exchange_direct_queue_2",true,consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
  1. 记住运行程序的顺序,先运行一次sender(创建路由器)
  2. 有了路由器之后,在创建两个Recer1和Recer2,进行队列绑定
  3. 再次运行sender,发出消息

通配符模式

在这里插入图片描述

  • 和路由模式90%是一样的

  • 唯独的区别就是路由键支持模糊匹配

  • 匹配符号

    • *:只能匹配一个词(正好一个词,多一个不行,少一个也不行)
    • #:匹配0个或更多个词
  • 看一下官网案例:

    • Q1绑定了路由键 .orange. Q2绑定了路由键 ..rabbit 和 lazy.#
    • 下面生产者的消息会被发送给哪个队列?
quick.orange.rabbit 			# Q1 Q2
lazy.orange.elephant 			# Q1 Q2
quick.orange.fox 				# Q1
lazy.brown.fox 					# Q2
lazy.pink.rabbit 				# Q2
quick.brown.fox 				# 无
orange 							# 无
quick.orange.male.rabbit 		# 无

生产者

public class Sender {

    public static void main(String[] args) {

        //获得连接
        Connection connection = ConnectionUtil.getConnection();
        try {
            //在连接中创建通道
            Channel channel = connection.createChannel();
            //声明路由
            /**
             * String var1: 路由名
             * String var2: 路由类型
             *      topic : 模糊匹配的定向分发
             */
            channel.exchangeDeclare("test_exchange_topic","topic");
            //定义发送信息
            String msg = "Hello everyone !";
            /**
             * 发送消息有四个参数
             * String var1: (路由)交换机名称,当前是简单模式,也就是P2P模式,无交换机,所以名称为""
             * String var2: 目标队列名称
             * BasicProperties var3: 设置消息的属性(没有属性则为null)
             * byte[] var4: 消息内容(只接受字节数组)
             */
            channel.basicPublish("test_exchange_topic", "user.register", null, msg.getBytes());
            System.out.println("生产者: " + msg);
            channel.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //释放资源
        ConnectionUtil.close(connection);

    }

}

消费者1

public class Recer1 {

    public static void main(String[] args) {
        //获得连接
        Connection connection = ConnectionUtil.getConnection();
        try {
            //获得通道(信道)
            Channel channel = connection.createChannel();
            //声明消息队列
            //queueDeclare : 此方法有双重作用,如果队列不存在则创建队列,若队列存在则获取
            channel.queueDeclare("test_exchange_topic_queue_1", false, false, false, null);
            //绑定路由(关注)
            /**
             * String var1: 队列名
             * String var2: 路由名
             * String var3:
             *      匹配用户开头的所有操作
             */
            channel.queueBind("test_exchange_topic_queue_1","test_exchange_topic","user.#");
            //从信道中获得消息
            DefaultConsumer consumer = new DefaultConsumer(channel){
                /**
                 * 交付处理,重写方法
                 * @param consumerTag   收件人信息
                 * @param envelope      信封,包裹上的快递标签
                 * @param properties    协议配置
                 * @param body          消息
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //将消息字节构建成字符串
                    //boby就是在队列中获取的消息
                    String string = new String(body);
                    System.out.println("消费者 1 : " + string);
                }
            };
            //监听队列
            /**
             * String var1: 队列名称
             * boolean var2: 自动消息确认
             * Consumer var3: 从管道中获取信息
             */
            channel.basicConsume("test_exchange_topic_queue_1",true,consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

消费者2

public class Recer2 {

    public static void main(String[] args) {
        //获得连接
        Connection connection = ConnectionUtil.getConnection();
        try {
            //获得通道(信道)
            Channel channel = connection.createChannel();
            //声明消息队列
            //queueDeclare : 此方法有双重作用,如果队列不存在则创建队列,若队列存在则获取
            channel.queueDeclare("test_exchange_topic_queue_2", false, false, false, null);
            //绑定路由(关注)
            /**
             * String var2: 队列名
             * String var2: 路由名
             * String var3:
             *      绑定商品及订单相关的消息
             */
            channel.queueBind("test_exchange_topic_queue_2","test_exchange_topic","product.#");
            channel.queueBind("test_exchange_topic_queue_2","test_exchange_topic","order.#");
            //从信道中获得消息
            DefaultConsumer consumer = new DefaultConsumer(channel){
                /**
                 * 交付处理,重写方法
                 * @param consumerTag   收件人信息
                 * @param envelope      信封,包裹上的快递标签
                 * @param properties    协议配置
                 * @param body          消息
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //将消息字节构建成字符串
                    //boby就是在队列中获取的消息
                    String string = new String(body);
                    System.out.println("消费者 2 : " + string);
                }
            };
            //监听队列
            /**
             * String var2: 队列名称
             * boolean var2: 自动消息确认
             * Consumer var3: 从管道中获取信息
             */
            channel.basicConsume("test_exchange_topic_queue_2",true,consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:白松林 返回首页