RabbitMQ(二):基本概念梳理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/maniacer/article/details/86764706

本章概念大体过一遍即可,第一次看主要留下概念,看完可以直接进入第三章(JavaAPI客户端开发),通过实际程序走一波之后、再回过头来看概念就会一目了然了。

基本概念

  • RabbitMQ是一个生产者消费者模型,可以将Rabbit MQ比作邮局:生产者投递Message到邮局、Message经过物流系统到达消费者手中。Message到达消费者手中之前,在邮局中暂存,有先后顺序之分。
    Rabbit MQ系统结构图
  • Rabbit MQ中有这么几个重要概念:生产者、交换机、绑定、routing Key、队列、消费者等。

生产者和消费者

  • 生产者(Producer)
    生产者是消息投递的一方,生产者创建消息,然后发布到Rabbit MQ中。消息一般可以包含两部分:消息体(payload)和标签(Label)。消息体一般是一个带有业务逻辑结构的数据,比如一个JSON串。当然可以进一步对这个消息体进行序列化操作。消息的标签用来表述这条消息,比如一个交换器的名称和路由键。生产者把消息交给Rabbit MQ,Rabbit MQ之后会根据标签把这条消息发送给合适的消费者。
  • 消费者(Consumer)
    消费者连接到Rabbit MQ服务器,并订阅到队列上。当消费者消费一条消息时,只是消费消息的消息体,标签被丢弃,消费者并不知晓生产者的信息。
  • broker
    消息中间件的服务节点,一个broker等价于一台Rabbit MQ服务节点

队列

Queue,是Rabbit MQ的内部对象,用于存储消息。

Rabbit MQ的消息只能存储与队列中,这一点与Kafka相反。Kafka将消息存储在topic这种逻辑层面,而相对应的队列逻辑只是topic实际存储文件中的唯一标志。Rabbit MQ的生产者生产消息并最终将消息投递到队列中,消费者可以从队列中获取消息并消费。

多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊到多个消费者进行处理,并不是每个消费者都会接收到全部的消息,消息只会被消费一次。
多个消费者的情况

交换器

Exchange:交换器。实际上,生产者的消息并不是直接投递到队列,而是首先经过Exchange,由交换器再根据配置的路由键、将消息路由到不同的队列。如果路由不到,则消息可能直接丢弃、或者返回给生产者。
在这里插入图片描述

路由键

RoutingKey:路由键,生产者将消息发送给交换器的时候,一般需要指定一个路由键(RoutingKey),而这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才能生效。

在交换器类型和绑定键(BindingKey)都固定的情况下,生产者可以在发送消息的时候通过指定RoutingKey来决定消息流向哪里。

绑定

Binding:绑定,Rabbit MQ中通过绑定将交换器与队列联系起来,在绑定的时候一般会指定一个绑定键(BindingKey)。

生产者将消息发送给交换器时,需要一个RoutingKey,当BindingKey和RoutingKey相匹配时,消息才会被路由到相应的队列中。在绑定多个队列到同一个交换器的时候,这些绑定允许适用同样的RoutingKey。BindingKey并不是所有时候都生效,她依赖于交换器类型(下一节交换器类型有介绍),比如fanout就会无视BindingKey,而是将消息路由到所有绑定到该交换器的队列中。

交换器相当于投递包裹的邮箱,RoutingKey相当于填写到快递单上的地址,BindingKey相当于真实地址,只有RoutingKey和BindingKey根据交换器类型规则相匹配的情况下,消息才会找到正确的队列。

交换器的类型

  • fanout
    他会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中,即使制定了BingdingKey、也会无视。
  • direct
    可以理解为字典类型,此时BindingKey和RoutingKey必须完全一样、消息才会正确匹配到队列中。
/** 
 * 在这个实例中,如果我们发送一条消息,并在发送消息的时候设置RoutingKey=waring,
 * 那么他会匹配到下图中的Queue1和Queue2
 */
channel.basicPublish(EXCHANGE_NAME,
					"waring",
					MessageProperties.PERSISTENT_TEXT_PLAIN,
					message.getByte());

direct类型的交换器
如果此时将RoutingKey设置为info或debug,那么消息只会匹配到Queue2。

  • topic
    前边的direct类型要求BindingKey和RouingKey必须完全一样才能正确匹配,但是这种严格的匹配形式在很多情况下并不能满足实际业务。topic类型的交换器在匹配规则上进行了扩展,它可以进行模糊匹配,它约定如下:
    • RoutingKey为一个点号"."分隔的字符串(被点号分隔的每一个部分称为一个单词),如:com.rabbitmq.client,java.rmq.msg。。。
    • BindingKey和RoutingKey一样也是点号分隔的字符串
    • BindingKey中可以存在两种特殊的字符串"*“和”#",用作模糊匹配,其中"*“用于匹配任何一个单词,”#"用于匹配零个或者多个单词。如下示例
      • com.rabbitmq.client的消息会匹配到Queue1和Queue2
      • com.hidden.client的消息会匹配到Queue2
      • com.hidden.demo的消息会匹配到Queue2
      • java.rabbitmq.demo的消息会匹配到Queue1
      • java.util.concurrent的消息没有匹配的Queue,那么此消息会被丢弃或者返回给生产者(需要设置),因为它没有匹配到任何的路由键。
        在这里插入图片描述
  • headers
    headers类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。在绑定队列和交换器时会制定一组键值对,当发送消息到交换器时,Rabbit MQ会获取到该消息的headers(也是键值对),对比其中的键值对是否完全匹配,如果匹配则消息会路由到该队列,否则不会路由到该队列。headers类型的交换器性能很差,而且不很实用,基本不用。

Rabbit MQ运转流程

  • 生产者发送消息的流程(代码参考第一章程序代码)
    • 生产者连接到Rabbit MQ Broker,建立一个连接(Connection),开启一个信道(Channel)。
          ConnectionFactory factory = new ConnectionFactory();
          factory.setHost(HOST);
          factory.setPort(POST);
          factory.setUsername(USERNAME);
          factory.setPassword(PASSWORD);
          Connection conn = factory.newConnection();
          Channel channel = conn.createChannel();
    
    • 生产者声明一个交换器,并设置相关属性,比如交换器类型,是否持久化等
    channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE, true);
    
    • 生产者声明一个队列并设置相关属性,比如是否排他,是否持久化,是否自动删除等
    channel.queueDeclare(QUEUE_NAME, true, false, false, null);
    
    • 生产者通过路由键将交换器和队列绑定起来
    channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY, null);
    
    • 生产者将消息发送到Rabbit MQ Broker,其中包含路由键,交换器等信息
    channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, "陈独秀".getBytes());
    
    • 相应的交换器根据接收到的路由键查找相匹配的队列
    • 如果找到,则将生产者发送过来的消息存储到相应的队列中
    • 如果没有找到,则根据生产者配置的属性选择丢弃或者返还给生产者
    • 关闭信道
    if (channel != null) {
                  try {
                      channel.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  } catch (TimeoutException e) {
                      e.printStackTrace();
                  }
              }
    
    • 关闭连接
    if (conn != null) {
                  try {
                      conn.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
    
  • 消费者接受消息流程(在消费者中也可以指定queue和exchange,代码参照第一章)
    • 消费者连接到Rabbit MQ Broker,建立一个连接(Connection),开启一个信道(Channel),代码同生产者一样
    • 消费者向Rabbit MQ Broker请求消费相应队列中的消息
    • 等待Rabbit MQ Broker回应并投递相应队列中的消息,消费者接受消息
    • 消费者确认(ack)接收到的消息
    • Rabbit MQ从队列中删除相应已经被ack过的消息
    • 关闭信道
    • 关闭连接
  • 关于Connection和Channel
    无论是生产者还是消费者,都需要和Rabbit MQ Broker建立连接,这个连接就是一个TCP丽娜姐,即Connection,为长连接。一旦Connection连接建立,客户端紧接着可以创建一个AMQP信道(Channel),每个信道都会被指定一个唯一的ID。信道是建立在Connection之上的虚拟连接,Rabbit MQ处理的每条AMQP指令都是通过信道完成。
    可以简单理解为,一个Connection一经建立便保持连接,它对应n个Channel连接,实际指令指定是通过Channel执行的。Channel存在的意义是:建立Connection长连接的成本是非常高昂的,Channel可以实现TCP连接复用,不仅可以减少性能开销,同时也便于管理。
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/maniacer/article/details/86764706