史上最简单的RabbitMQ教程 | 第二篇: RabbitMQ系统架构

RabbitMQ系统架构:

                                                                      RabbitMQ系统机构图

    由上图的系统架构图,可以得到RabbitMQ架构可以简单划分为4部分,分别用不同颜色的框表示。

1. 发送消息

    客户端(生产者)将消息发送到交换机,交换机根据自身的类型和路由键来将消息路由到指定的队列。

2. 消费消息

    客户端(消费者)监听指定的队列,如果队列有信息,将获得消息进行消费。

3. RabbitMQ Server

    RabbitMQ Server里面包含队列、交换机等,维护了一条消息从发送消息到消费消息的路径。

4. RoutingKey

    交换机收到消息,会根据发送交换机类型以及路由键将消息放到不同的对列中。交换机分为四种类型,分别是direct、fanout、topic、headers下面会有详细的介绍。

核心概念

    系统架构图中出现了生产者、消费者、队列、交换机、路由键等关键字,这些都是RabbitMQ的核心概念,下面我们一起学习下这些概念。

1. 概念介绍

    生产者:即生产消息的实例。RabbitMQ没有实际意义的生产者,生产者是根据RabbitMQ API发送消息的实例。生产者是将消息发送到交换机后,并不参与消息的分发,生产者只保证消息能够发送到交换机,不能知道这条消息将被路由器分发到那个队列上。

    消费者:即消费消息的实例。RabbitMQ中的消费者监听指定的队列,当队列中有消息时,取出消息进行消费。

    队列:暂时存储消息的地方,里面存储这生产者发送而消费者没有消费的消息。

    交换机:交换机是消息的分发地,当消费者将消息发送到交换机时,交换机会根据自己的类型以及路由键的设置将消息分发到不同的队列。

    路由键:消息发送时指定的,用来指定消息的路由规则,

    绑定键:交换机和队列绑定时指定的,绑定键的生效依靠交换机类型,当交换机类型是topic或direct时根据绑定键将消息分发到队列中。

    在实际中由于路由键和绑定键功能类似,用于路由规则制定,后来将这两个合并在一起,如没有特殊声明,统一称路由键。

    虚拟主机:虚拟主机类似Redis上的db,是一个逻辑上的分类,相当于一个高级别的路由。

2. 代码说明

    2.0 RabbitMQ消息生产和消费流程

RabbitMQ消息生产流程:本例采用 1->2->3->4->5->6
1. 生产者连接到Broker 建立一个连接,然后开启一个信道
2. 生产者发送消息至Broker ,发送的消息包含消息体和含有路由键、交换器、优先级、是否持久化、过期时间、延时时间等信息的标签
3. 相应的交换器根据接收到的路由键查找相匹配的队列如果找到 ,则将从生产者发送过来的消息存入相应的队列中
4. 如果没有找到 ,则根据生产者配置的属性选择丢弃还是回退给生产者
5. 关闭信道
6. 关闭连接

RabbitMQ消息消费流程:本例采用 1->3->5->6->7->8->9->10
1. 消费者连接到Broker ,建立一个连接,开启一个信道
2. 接着消费者声明一个交换器 ,并设置相关属性,比如交换机类型、是否持久化、是否自动删除、是否内置等
3. 消费者声明一个队列井设置相关属性,比如是否排他、是否持久化、是否自动删除、消息最大过期时间、消息最大长度、消息最大字节数等
4. 消费者通过路由键将交换器和队列绑定起来
5. 消费者向 RabbitMQ Broker 请求消费相应队列中的消息,在这个过程中可能会设置消费者标签、是否自动确认、是否排他等
6. 等待 RabbitMQ Broker 回应并投递相应队列中的消息, 消费者接收消息。
7. 消费者确认接收到的消息
8.RabbitMQ从队列中删除相应己经被确认的消息
9. 关闭信道
10. 关闭连接。
RabbitMQ 生产消费流程

    2.1 新建一个spring boot工程(可以选择新建简单的Java工程) ,导入相关依赖

        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>3.6.5</version>
        </dependency>
pom.xml 导入smqp-client依赖

    2.2 由于生产者,消费者在获得连接代码重复,在这新建一个工具类

public class ConnectionUtil {
    public static Connection getConnection() throws IOException, TimeoutException {
        //1 创建工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("xxx.xxx.xxx.xxx");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("guest");
        factory.setPassword("guest");
        //2 创建连接,并返回
        return factory.newConnection();
    }
}
ConnectionUtil.java

    2.3 新建消息生产者

/**
 * @author magw
 * @version 1.0
 * @date 2019/7/9 下午10:38
 * @description: 消息生产者
 *
 *  exchange the exchange to publish the message to  交换机
 *  routingKey the routing key  路由key
 *  props other properties for the message - routing headers etc  属性
 *  body the message body  消息体
 *  void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
 *  发送消息时不指定交换机时,会使用(AMQP default),使用路由键作为对列的名字,如果存在这个路由键的队列,就把消息路由过去,找不到就发送不出去,发送失败
 *
 *  The default exchange is implicitly bound to every queue, with a routing key equal to the queue name.
 *  It is not possible to explicitly bind to, or unbind from the default exchange. It also cannot be deleted.
 *
 */
public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1 创建连接
        Connection connection = ConnectionUtil.getConnection();
        //2 创建信道
        Channel channel = connection.createChannel();
        //3 发送一条消息
        channel.basicPublish("","test",null,"hello,rabbitmq".getBytes());
        System.out.println("发送成功");
        //4 关闭连接,先关小的,再关大的
        channel.close();
        connection.close();
    }
}
Producer.java

    2.4 新建消息消费者

/**
 * @author magw
 * @version 1.0
 * @date 2019/7/9 下午10:52
 * @description: rabbitmq 消费者
 *
 * queue the name of the queue 对列的名字
 * durable true if we are declaring a durable queue (the queue will survive a server restart) 是否持久化
 *                持久化重启服务器不会丢失数据
 * exclusive true if we are declaring an exclusive queue (restricted to this connection) 是否独占
 *                  顺序消费,保证在多线程环境下,abc 消费的进度是有序的
 * autoDelete true if we are declaring an autodelete queue (server will delete it when no longer in use)
 *                   是否自动删除
 * arguments other properties (construction arguments) for the queue  参数
 *
 * Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
            Map<String, Object> arguments) throws IOException;
 */
public class Consumer {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //1 获得连接
        Connection connection = ConnectionUtil.getConnection();
        //2 创建channel
        Channel channel = connection.createChannel();
        //3 声明对列
        String queueName = "test";
        channel.queueDeclare(queueName,true,false,false,null);
        //4 创建消费者
        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
        //5 设置channel
        channel.basicConsume(queueName,true,queueingConsumer);
        //6 获取消息
        while(true){
            QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
            String s = new String(delivery.getBody());
            System.out.println(s);
            Envelope envelope = delivery.getEnvelope();
            System.out.println(envelope.getDeliveryTag());//全局的id
            System.out.println(envelope.getExchange());
            System.out.println(envelope.getRoutingKey());
            System.out.println("---------------");
        }
    }
}
Consumer.java

    2.5 先启动消费者,再启动生产者,这是再消费端的控台台将打印

hello,rabbitmq
1

test
---------------
消费端控制台输出内容

3. 其他说明

    3.1 如果发送消息时,没有指定交换机,只指定了路由键,此时RabbitMQ会根据路由键作为队列名进行匹配,如果有队列名和路由键一致,消息将路由到该队列中。

    3.2 交换机和队列都可autoDelete,如果设置true,表示交换机在没有队列绑定时被MQ删除,队列在没有交换机绑定时被删除。

    3.3 注意消息是以二进制流的发送的。

    3.4 交换机和队列都可以持久化,durable=true表示服务器重启时,队列不丢失,交换机不丢失。

    3.5 消费者进行应答时,可以选择自动应答或手工应答,autoAck=true表示自动应答。

交换机四种不同类型

    交换机有四种类型,常用的有3种分别是direct、topic、fanout这三种根据交换机和路由键配合来进行消息的分发,还有一种是headers,这种是根据header信息进行消息的分发,是一个Map类型的值,每条消息都必须带有这个值,效率低,因此暂不介绍。    

1. fanout 广播模式

    1 fanout定义:

        广播模式是将信息分发到绑定这个交换机的所有队列中,像是广播,发送到这个交换机里的消息都会转发到与这个交换机绑定的队列上,因为不需要任何操作,这种分发效率最高

    2 fanout图例:

    3 direct代码展示:

public class FanoutProducer {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        String exchangeName = "test_fanout_exchange";

        String msg = "测试交换机topic";
        channel.basicPublish(exchangeName,"",null,msg.getBytes());

        channel.close();
        connection.close();
    }
}
FanoutProducer.java
/**
 * @author magw
 * @version 1.0
 * @date 2019/7/9 下午10:52
 * @description: rabbitmq exchange=fanout模式下得消费者
 *   控制台输出两条信息说明2个队列都获得消息
 */
public class FanoutConsumer {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        String exchangeName = "test_fanout_exchange";
        String queueName1 = "test_fanout_queue1";
        String queueName2 = "test_fanout_queue2";
        String type = "fanout";
        channel.queueDeclare(queueName1,false,false,false,null);
        channel.queueDeclare(queueName2,false,false,false,null);
        channel.exchangeDeclare(exchangeName,type,true,false,null);

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

        QueueingConsumer consumer = new QueueingConsumer(channel);
        channel.basicConsume(queueName1, true, consumer);
        channel.basicConsume(queueName2, true, consumer);
        while(true){
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            System.out.println(new String(delivery.getBody())+"--"+delivery.getEnvelope().getRoutingKey());
        }
    }
}
FanoutConsumer.java

2. direct 

    1 direct定义:

       该根据消息中路由键,将这条消息发送到路由键完全匹配的队列中。改类型属于精确路由键匹配。

    2 direct图

 

    3 direct代码展示:

public class DirectProducer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 获得连接
        Connection connection = ConnectionUtil.getConnection();
        //2. 创建channel
        Channel channel = connection.createChannel();
        //3. 生命交换机 发送得消息 routing
        String exchangeName = "test_exchange_direct";
        String msg = "hello direct exchange!";
        String routingKey = "test.direct1";
        //4. 发送消息
        channel.basicPublish(exchangeName,routingKey,null,msg.getBytes());
        //5. 关闭连接
        channel.close();
        connection.close();
    }
}
DirectProducer.java
public class DirectConsumer {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //1 获得连接
        Connection connection = ConnectionUtil.getConnection();
        //2 创建channel
        Channel channel = connection.createChannel();
        //3 声明
        String exchangeName = "test_exchange_direct";
        String exchangeType = "direct";
        String queueName = "test_direct_queue";
        String routingKey = "test.direct";
        //4 表示声明了一个交换机
        channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
        //5 表示声明了一个队列
        channel.queueDeclare(queueName, false, false, false, null);
        //6 建立一个绑定关系:
        channel.queueBind(queueName, exchangeName, routingKey);

        //durable 是否持久化消息
        QueueingConsumer consumer = new QueueingConsumer(channel);
        //参数:队列名称、是否自动ACK、Consumer
        channel.basicConsume(queueName, true, consumer);
        //循环获取消息
        while(true){
            //获取消息,如果没有消息,这一步将会一直阻塞
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String msg = new String(delivery.getBody());
            System.out.println("收到消息:" + msg);
        }
    }
}
DirectConsumer.java

2. topic 

    1 topic定义:

       该根据消息中路由键,将这条消息发送到路由键模糊匹配匹配的队列中。类型属于模糊路由键匹配。

       其中键值通过.(点号)进行分割,其中#代表任意个任意单词,*代表一个任意单词。

    2 topic图

    3 topic代码展示:

public class TopicProducer {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        String exchangeName = "test_topic_exchange";
        String routingKey1 = "pub.ants.rabbitmq";
        String routingKey2 = "pub.ants.stu";
        String routingKey3 = "org.rabbitmq";

        String msg = "测试交换机topic";
        channel.basicPublish(exchangeName,routingKey1,null,(msg+routingKey1).getBytes());
        channel.basicPublish(exchangeName,routingKey2,null,(msg+routingKey2).getBytes());
        channel.basicPublish(exchangeName,routingKey3,null,(msg+routingKey3).getBytes());

        channel.close();
        connection.close();
    }
}
TopicProducer.java
public class TopicConsumer {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        String exchangeName = "test_topic_exchange";
        String queueName = "test_topic_queue";
        String routingKey = "pub.ants.#";
        String type = "topic";
        channel.queueDeclare(queueName,false,false,false,null);
        channel.exchangeDeclare(exchangeName,type,true,false,null);
        channel.queueBind(queueName,exchangeName,routingKey);

        QueueingConsumer consumer = new QueueingConsumer(channel);
        channel.basicConsume(queueName, true, consumer);
        while(true){
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            System.out.println(new String(delivery.getBody()));
        }
    }
}
TopicConsumer.java

小结

    1. 本文介绍RabbitMQ系统架构,简述系统架构的四种作用范围。

    2. 描述RabbitMQ中核心概念

    3. 详细描述RabbitMQ三种常用的交换机类型。

posted @ 2019-02-19 09:51  i孤独行者  阅读(419)  评论(0)    收藏  举报