目录

一、简介

二、Fanout Exchange(广播式交换机)

三、Direct Exchange(直连交换机)

四、Topic Exchange(通配符交换机)

五、总结


一、简介

RabbitMQ中的交换机有Direct Exchange(直连交换机)、Topic Exchange(通配符交换机)、Fanout Exchange(广播式交换机)、Headers Exchange(Headers交换机)四种,常用的就前三种,本文将对前三种做一个简单的总结,加深对MQ中交换机机制的理解。

二、Fanout Exchange(广播式交换机)

【a】模型图:这种模式类似于广播的方式,所有发送到Fanout Exchange交换机上的消息,都会被发送到绑定到该交换机上面的所有队列上,这样绑定到这些队列的消费者就可以接收到该消息。

Springboot rabbit topic交换机 rabbitmq交换机类型总结_fanout exchange

【b】说明:

  • 这种模式不需要指定Routing key路由键,一个交换机可以绑定多个队列queue,一个queue可同时与多个exchange交换机进行绑定;
  • 如果消息发送到交换机上,但是这个交换机上面没有绑定的队列,那么这些消息将会被丢弃;

【c】生产者:

/**
 * @Description: 广播式交换机生产者
 * @Author: weixiaohuai
 * @Date: 2019/6/25
 * @Time: 20:43
 * <p>
 * 说明:
 * 1. 广播式交换机发送的消息的时候不需要指定routing key路由键;
 * 2. 所有发送到广播式交换机上面的消息都会被发送到与之绑定的所有队列上;
 * 3.
 */
public class Producer {
    private static final String EXCHANGE_NAME = "fanout_exchange";
    //广播式交换机
    private static final String EXCHANGE_TYPE = "fanout";

    public static void main(String[] args) {
        //获取MQ连接
        Connection connection = MQConnecitonUtils.getConnection();
        //从连接中获取Channel通道对象
        Channel channel = null;
        try {
            channel = connection.createChannel();
            //创建交换机对象
            channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE);
            //发送消息到交换机exchange上
            String msg = "hello fanout exchange!!";
            channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != channel) {
                try {
                    channel.close();
                } catch (IOException | TimeoutException e) {
                    e.printStackTrace();
                }
            }
            if (null != connection) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

【d】消费者1:

public class Consumer01 {
    private static Logger logger = LoggerFactory.getLogger(Consumer01.class);
    private static final String QUEUE_NAME = "fanout_exchange_queue01";
    private static final String EXCHANGE_NAME = "fanout_exchange";

    public static void main(String[] args) {
        //获取MQ连接对象
        Connection connection = MQConnecitonUtils.getConnection();
        try {
            //创建消息通道对象
            final Channel channel = connection.createChannel();
            //创建队列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            //将队列绑定到交换机上
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
            //创建消费者对象
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //消息消费者获取消息
                    String message = new String(body, StandardCharsets.UTF_8);
                    logger.info("【Consumer01】receive message: " + message);
                }
            };
            //监听消息队列
            channel.basicConsume(QUEUE_NAME, true, consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

【e】消费者2:

public class Consumer02 {
    private static Logger logger = LoggerFactory.getLogger(Consumer02.class);
    private static final String QUEUE_NAME = "fanout_exchange_queue02";
    private static final String EXCHANGE_NAME = "fanout_exchange";

    public static void main(String[] args) {
        //获取MQ连接对象
        Connection connection = MQConnecitonUtils.getConnection();
        try {
            //创建消息通道对象
            final Channel channel = connection.createChannel();
            //创建队列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            //将队列绑定到交换机上
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
            //创建消费者对象
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //消息消费者获取消息
                    String message = new String(body, StandardCharsets.UTF_8);
                    logger.info("【Consumer02】receive message: " + message);
                }
            };
            //监听消息队列
            channel.basicConsume(QUEUE_NAME, true, consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

【f】运行结果:

Springboot rabbit topic交换机 rabbitmq交换机类型总结_direct exchange_02

Springboot rabbit topic交换机 rabbitmq交换机类型总结_exchange_03

因为消费者1和消费者2分别绑定了队列fanout_exchange_queue01和fanout_exchange_queue02,而且这两个队列都给绑定到了交换机fanout_exchange上面,所有两个消费者都能够接收到此消息。

【g】MQ管理控制台交换机绑定消息

Springboot rabbit topic交换机 rabbitmq交换机类型总结_topic exchange_04

三、Direct Exchange(直连交换机)

【a】模型图:

Springboot rabbit topic交换机 rabbitmq交换机类型总结_direct exchange_05

【b】说明:

  • 任何发送到Direct Exchange的消息都会被转发到指定RouteKey中指定的队列Queue;
  • 生产者生产消息的时候需要执行Routing Key路由键;
  • 队列绑定交换机的时候需要指定Binding Key,只有路由键与绑定键相同的话,才能将消息发送到绑定这个队列的消费者;
  • 如果vhost中不存在RouteKey中指定的队列名,则该消息会被丢弃;

【c】生产者:

/**
 * @Description: Direct Exchange直连交换机
 * @Author: weixiaohuai
 * @Date: 2019/6/25
 * @Time: 21:22
 * <p>
 * 说明:
 * 1. 任何发送到Direct Exchange的消息都会被转发到指定RouteKey中指定的队列Queue;
 * 2. 生产者生产消息的时候需要执行Routing Key路由键;
 * 3. 队列绑定交换机的时候需要指定Binding Key,只有路由键与绑定键相同的话,才能将消息发送到绑定这个队列的消费者;
 * 4. 如果vhost中不存在RouteKey中指定的队列名,则该消息会被丢弃;
 */
public class CustomProducer {
    private static final String EXCHANGE_NAME = "direct_exchange";
    //交换机类型:direct
    private static final String EXCHANGE_TYPE = "direct";
    //路由键
    private static final String EXCHANGE_ROUTE_KEY = "user.add";

    public static void main(String[] args) {
        //获取MQ连接
        Connection connection = MQConnecitonUtils.getConnection();
        //从连接中获取Channel通道对象
        Channel channel = null;
        try {
            channel = connection.createChannel();
            //创建交换机对象
            channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE);
            //发送消息到交换机exchange上
            String msg = "hello direct exchange!!!";
            //指定routing key为info
            channel.basicPublish(EXCHANGE_NAME, EXCHANGE_ROUTE_KEY, null, msg.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != channel) {
                try {
                    channel.close();
                } catch (IOException | TimeoutException e) {
                    e.printStackTrace();
                }
            }
            if (null != connection) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

【d】消费者1:

public class Consumer01 {
    private static Logger logger = LoggerFactory.getLogger(Consumer01.class);
    private static final String QUEUE_NAME = "direct_exchange_queue01";
    private static final String EXCHANGE_NAME = "direct_exchange";
    //binding key
    private static final String EXCHANGE_ROUTE_KEY = "user.delete";

    public static void main(String[] args) {
        //获取MQ连接对象
        Connection connection = MQConnecitonUtils.getConnection();
        try {
            //创建消息通道对象
            final Channel channel = connection.createChannel();
            //创建队列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            //将队列绑定到交换机上,并且指定routing_key
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, EXCHANGE_ROUTE_KEY);

            channel.basicQos(1);

            //创建消费者对象
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //消息消费者获取消息
                    String message = new String(body, StandardCharsets.UTF_8);
                    logger.info("【Consumer01】receive message: " + message);
                }
            };
            //监听消息队列
            channel.basicConsume(QUEUE_NAME, true, consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

【e】消费者2:

public class Consumer02 {
    private static Logger logger = LoggerFactory.getLogger(Consumer02.class);
    private static final String QUEUE_NAME = "direct_exchange_queue02";
    private static final String EXCHANGE_NAME = "direct_exchange";
    //binding key
    private static final String EXCHANGE_ROUTE_KEY01 = "user.add";
    private static final String EXCHANGE_ROUTE_KEY02 = "user.delete";

    public static void main(String[] args) {
        //获取MQ连接对象
        Connection connection = MQConnecitonUtils.getConnection();
        try {
            //创建消息通道对象
            final Channel channel = connection.createChannel();
            //创建队列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);

            //将队列绑定到交换机上,并且指定routing_key
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, EXCHANGE_ROUTE_KEY01);
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, EXCHANGE_ROUTE_KEY02);

            //创建消费者对象
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //消息消费者获取消息
                    String message = new String(body, StandardCharsets.UTF_8);
                    logger.info("【Consumer02】receive message: " + message);
                }
            };
            //监听消息队列
            channel.basicConsume(QUEUE_NAME, true, consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

【f】运行结果:

Springboot rabbit topic交换机 rabbitmq交换机类型总结_fanout exchange_06

Springboot rabbit topic交换机 rabbitmq交换机类型总结_topic exchange_07

因为生产者生产消息指定的路由键为user.add,而且消费者1绑定的队列direct_exchange_queue01对应的绑定键为user.delete,显然消费者1接收不了该消息,而消费者2指定的绑定键为user.add和user.delete,显然消费者就能成功消费此消息。

【g】MQ管理控制台交换机绑定消息

Springboot rabbit topic交换机 rabbitmq交换机类型总结_exchange_08

 

四、Topic Exchange(通配符交换机)

【a】模型图:

Springboot rabbit topic交换机 rabbitmq交换机类型总结_topic exchange_09

【b】说明:

  • 任何发送到Topic Exchange的消息都会被转发到所有满足Route Key与Binding Key模糊匹配的队列Queue上;
  • 生产者发送消息的时候需要指定Route Key,同时绑定Exchange与Queue的时候也需要指定Binding Key;
  • #” 表示0个或多个关键字,“*”表示匹配一个关键字;
  • 如果Exchange没有发现能够与RouteKey模糊匹配的队列Queue,则会抛弃此消息;
  • 如果Binding中的Routing key *,#都没有,则路由键跟绑定键相等的时候才转发消息,类似Direct Exchange;如果Binding中的Routing key为#或者#.#,则全部转发,类似Fanout Exchange;

【c】生产者:

/**
 * @Description: Topic Exchange通配符交换机
 * @Author: weixiaohuai
 * @Date: 2019/6/25
 * @Time: 21:22
 * <p>
 * 说明:
 * 1. 任何发送到Topic Exchange的消息都会被转发到所有满足Route Key与Binding Key模糊匹配的队列Queue上;
 * 2. 生产者发送消息的时候需要指定Route Key,同时绑定Exchange与Queue的时候也需要指定Binding Key;
 * 3. #” 表示0个或多个关键字,“*”表示匹配一个关键字;
 * 4. 如果Exchange没有发现能够与RouteKey模糊匹配的队列Queue,则会抛弃此消息;
 */
public class CustomProducer {
    private static final String EXCHANGE_NAME = "topic_exchange";
    //交换机类型:direct
    private static final String EXCHANGE_TYPE = "topic";
    //路由键
    private static final String EXCHANGE_ROUTE_KEY = "user.add.submit";

    public static void main(String[] args) {
        //获取MQ连接
        Connection connection = MQConnecitonUtils.getConnection();
        //从连接中获取Channel通道对象
        Channel channel = null;
        try {
            channel = connection.createChannel();
            //创建交换机对象
            channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE);
            //发送消息到交换机exchange上
            String msg = "hello topic exchange!!!";
            //指定routing key为info
            channel.basicPublish(EXCHANGE_NAME, EXCHANGE_ROUTE_KEY, null, msg.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != channel) {
                try {
                    channel.close();
                } catch (IOException | TimeoutException e) {
                    e.printStackTrace();
                }
            }
            if (null != connection) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

【d】消费者1:

public class Consumer01 {
    private static Logger logger = LoggerFactory.getLogger(Consumer01.class);
    private static final String QUEUE_NAME = "direct_exchange_queue01";
    private static final String EXCHANGE_NAME = "topic_exchange";
    //binding key
    private static final String EXCHANGE_ROUTE_KEY = "user.#";

    public static void main(String[] args) {
        //获取MQ连接对象
        Connection connection = MQConnecitonUtils.getConnection();
        try {
            //创建消息通道对象
            final Channel channel = connection.createChannel();
            //创建队列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            //将队列绑定到交换机上,并且指定routing_key
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, EXCHANGE_ROUTE_KEY);

            channel.basicQos(1);

            //创建消费者对象
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //消息消费者获取消息
                    String message = new String(body, StandardCharsets.UTF_8);
                    logger.info("【Consumer01】receive message: " + message);
                }
            };
            //监听消息队列
            channel.basicConsume(QUEUE_NAME, true, consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

【e】消费者2:

public class Consumer02 {
    private static Logger logger = LoggerFactory.getLogger(Consumer02.class);
    private static final String QUEUE_NAME = "direct_exchange_queue02";
    private static final String EXCHANGE_NAME = "topic_exchange";
    //binding key
    private static final String EXCHANGE_ROUTE_KEY01 = "user.*";

    public static void main(String[] args) {
        //获取MQ连接对象
        Connection connection = MQConnecitonUtils.getConnection();
        try {
            //创建消息通道对象
            final Channel channel = connection.createChannel();
            //创建队列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);

            //将队列绑定到交换机上,并且指定routing_key
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, EXCHANGE_ROUTE_KEY01);

            //创建消费者对象
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //消息消费者获取消息
                    String message = new String(body, StandardCharsets.UTF_8);
                    logger.info("【Consumer02】receive message: " + message);
                }
            };
            //监听消息队列
            channel.basicConsume(QUEUE_NAME, true, consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

【f】运行结果:

Springboot rabbit topic交换机 rabbitmq交换机类型总结_topic exchange_10

Springboot rabbit topic交换机 rabbitmq交换机类型总结_exchange_11

因为生产者发送消息的时候指定了Routing Key为user.add.submit, 而消费者1所在的队列Binding Key为user.#, #能够匹配一个或者多个,所有消费者1能够消费此消息; 但是消费者2指定的Binding Key为user.*, *只能匹配一个,所有并不能够匹配到user.add.submit这个路由键,所以消费者2不能消费此消息。

【g】MQ管理控制台交换机绑定消息

Springboot rabbit topic交换机 rabbitmq交换机类型总结_topic exchange_12

五、总结

需要注意的是,RabbitMQ中还有一个默认交换机(Default Exchange):
【a】:默认的Exchange不能进行绑定操作;
【b】:任何发送到默认交换机的消息都会被转发到路由键Routing key和队列queue名字相同的Queue中;
【c】:如果vhost中不存在Routing key中指定的队列名,则该消息会被抛弃;
【d】:该种方式类似于fanout exchange广播式交换机;

本文通过一些简单的示例并且以画图的方式总结了RabbitMQ中常用的三种交换机:直连交换机、通配符交换机以及广播式交换机,相对来说,广播式交换机的效率较高一些,其次是直连交换机,通配符交换机由于要匹配路由键与绑定键的关系,效率相对较低一点点。