第四节 Topic路由交换机:消息转发到关心话题的Queue

一、基本模型

        P是生产者,是发消息的人。

        X是topic类型的交换机。它会把消息的路由键取出来,与绑定它的队列做路由键匹配。如果队列关心的路由键能匹配上消息的路由键,则将这个消息投递到这个队列中。

        Q1是一个队列,它是关心orange颜色的队列。

        O2是一个队列,它是关心rabbit与lazy的队列。

        C1是消费者,从Q1队列中获取消息。

        C2是消费者,从Q2队列中获取消息。

        

  • *(星号)   * 可以替代一个单词。
  •  (hash)  # 可以替换零个或多个单词

        上图的基本模型希望表达的意思是,如果生产者发送了一个消息,其路由键是 Routing是 a.orange.a。路由器在收到这个消息后,会把这个消息的路由键与Q1和Q2关注的主题进行模糊匹配。因为*代表一个单词的意思,所以可以匹配到Q1队列。

         如果消息的路由键是 lazy.a.b.c ,C2队列关心的主题是 lazy.#,#代表多个单词,所以可以匹配上这个消息的路由键。这个消息会被路由器转发到C2队列中。

Topic类型的交换机 

        任何发送到Topic Exchange的消息都会被转发到所有关心RouteKey中指定话题的Queue上。使用的是一种正则匹配规则。生产者会发送一个带路由键的消息。Exchange会将消息转发到所有关注主题能与RouteKey模糊匹配的队列。

Fanout类型的交换机        

        发送到exchange的所有消息会被转发到与exchange绑定的所有queue,不需要处理路由 

Direct类型的交换机

        需要处理路由键。该交换机收到消息后会把消息发送到接收指定routing-key的queue中。

二、使用代码

        我们创建了一个Topic类型的交换机,然后在创建了两个队列。

        第一个队列关心的主题是  *.orange.* ,* 代表一个单词的意思。如果消息的路由键能匹配的上这个主题,例如路由键是like.orange.color,那么交换机就会把这条消息转发到第一个队列中。

       第二个队列关心的主题是 lazy.# ,#代表多个的意思。如果消息的路由键能匹配的上这个主题,例如路由键是lazy.body.girl,那么交换机就会把这条消息转发到第二个队列中。

发送端Producer.java

package com.safesoft.topicexchanger.mq04;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author jay.zhou
 * @date 2019/4/24
 * @time 10:30
 */
public class Producer {
    private static final Logger LOGGER = LoggerFactory.getLogger(Producer.class);
    private static final String EXCHANGE_NAME = "local::mq04:exchange:e01";

    public static void main(String[] args) {
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("127.0.0.1");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();

            String message = "topic交换机很有用";
            //声明一个TOPIC类型的交换机
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

            //channel.basicPublish(EXCHANGE_NAME, "like.orange.color", null, message.getBytes("UTF-8"));
            channel.basicPublish(EXCHANGE_NAME, "lazy.boy.girl", null, message.getBytes("UTF-8"));

            LOGGER.info("消息发送成功:{}", message);
            channel.close();
            connection.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

接收端Consumer01.java 

package com.safesoft.topicexchanger.mq04;

import com.rabbitmq.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

/**
 * @author jay.zhou
 * @date 2019/4/24
 * @time 10:40
 */
public class Consumer01 {
    private static final Logger LOGGER = LoggerFactory.getLogger(Producer.class);
    private static final String EXCHANGE_NAME = "local::mq04:exchange:e01";
    private static final String QUEUE_NAME_01 = "local::mq04:queue:q01";
    private static final String QUEUE_NAME_02 = "local::mq04:queue:q02";

    public static void main(String[] args) {
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("127.0.0.1");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();

            //声明一个TOPIC类型的交换机
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
            //申明两个个队列
            /**
             * 第一个参数是queue:要创建的队列名
             * 第二个参数是durable:是否持久化。如果为true,可以在RabbitMQ崩溃后恢复消息
             * 第三个参数是exclusive:true表示一个队列只能被一个消费者占有并消费
             * 第四个参数是autoDelete:true表示服务器不在使用这个队列是会自动删除它
             * 第五个参数是arguments:其它参数
             */
            channel.queueDeclare(QUEUE_NAME_01, true, false, false, null);
            channel.queueDeclare(QUEUE_NAME_02, true, false, false, null);

            final String ROUTING_KEY_ORANGE = "*.orange.*";
            final String ROUTING_KEY_LAZY = "lazy.#";
            //队列一对ORANGE感兴趣,匹配  XXX.orange.XXX 的消息
            channel.queueBind(QUEUE_NAME_01, EXCHANGE_NAME, ROUTING_KEY_ORANGE);
            //队列二对LAZY感兴趣,匹配  lazy.XXX.XXX.XXX
            channel.queueBind(QUEUE_NAME_02, EXCHANGE_NAME, ROUTING_KEY_LAZY);

            Consumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    final String message = new String(body, "UTF-8");
                    LOGGER.info("队列一收到消息:{}", message);
                }
            };

            //队列一确认消息
            channel.basicConsume(QUEUE_NAME_01, true, consumer);

        } catch (Exception e) {
            LOGGER.error("an exception was occurred , caused by :{}", e.getMessage());
        }
    }
}

接收端Consumer02.java  

package com.safesoft.topicexchanger.mq04;

import com.rabbitmq.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

/**
 * @author jay.zhou
 * @date 2019/4/24
 * @time 10:40
 */
public class Consumer02 {
    private static final Logger LOGGER = LoggerFactory.getLogger(Producer.class);
    private static final String EXCHANGE_NAME = "local::mq04:exchange:e01";
    private static final String QUEUE_NAME_01 = "local::mq04:queue:q01";
    private static final String QUEUE_NAME_02 = "local::mq04:queue:q02";

    public static void main(String[] args) {
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("127.0.0.1");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();

            //声明一个TOPIC类型的交换机
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
            //申明两个个队列
            /**
             * 第一个参数是queue:要创建的队列名
             * 第二个参数是durable:是否持久化。如果为true,可以在RabbitMQ崩溃后恢复消息
             * 第三个参数是exclusive:true表示一个队列只能被一个消费者占有并消费
             * 第四个参数是autoDelete:true表示服务器不在使用这个队列是会自动删除它
             * 第五个参数是arguments:其它参数
             */
            channel.queueDeclare(QUEUE_NAME_01, true, false, false, null);
            channel.queueDeclare(QUEUE_NAME_02, true, false, false, null);

            final String ROUTING_KEY_ORANGE = "*.orange.*";
            final String ROUTING_KEY_LAZY = "lazy.#";
            //队列一对ORANGE感兴趣,匹配  XXX.orange.XXX 的消息
            channel.queueBind(QUEUE_NAME_01, EXCHANGE_NAME, ROUTING_KEY_ORANGE);
            //队列二对LAZY感兴趣,匹配  lazy.XXX.XXX.XXX
            channel.queueBind(QUEUE_NAME_02, EXCHANGE_NAME, ROUTING_KEY_LAZY);

            Consumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    final String message = new String(body, "UTF-8");
                    LOGGER.info("队列二收到消息:{}", message);
                }
            };

            //队列一确认消息
            channel.basicConsume(QUEUE_NAME_02, true, consumer);

        } catch (Exception e) {
            LOGGER.error("an exception was occurred , caused by :{}", e.getMessage());
        }
    }
}

        如果消息的路由键是下面的like.orange.color,路由键会匹配到第一个队列中。运行发送端,效果如图。

 

 

         查看http://localhost:15672管理页面,队列一收到一条消息。

         同理,如果发送消息的路由键是 lazy开头的,那么队列二应该能收到消息。运行发送端后,查看管理页面,发现队列二确实也收到了消息。

         最后运行消费端Consumer01与Consumer02,可以就能够消费掉队列中存放的数据了。消费完数据后,队列中的数据数量变成0。

三、总结

Topic类型的交换机 

        任何发送到Topic Exchange的消息都会被转发到所有关心RouteKey中指定话题的Queue上。使用的是一种正则匹配规则。生产者会发送一个带路由键的消息。Exchange会将消息转发到所有关注主题能与RouteKey模糊匹配的队列。

Fanout类型的交换机        

        发送到exchange的所有消息会被转发到与exchange绑定的所有queue,不需要处理路由 

Direct类型的交换机

        需要处理路由键。该交换机收到消息后会把消息发送到接收指定routing-key的queue中。

四、源代码下载

        源代码地址:https://github.com/hairdryre/Study_RabbitMQ

        下一篇:第五节 SpringBoot集成RabbitMQ

        阅读更多:从头开始学RabbimtMQ目录贴


        
 

posted @ 2022-07-17 12:14  小大宇  阅读(82)  评论(0编辑  收藏  举报