RabbitMQ整体上是一个生产者与消费者模型,主要负责接受,存储和转发消息,可以把消息传递的过程想象成:当你将一个包裹送到邮局,邮局会暂存并最终将邮件通过邮递员送到收件人手上,RabbitMQ就好比邮局,邮箱和邮递员组成的一个系统。从计算机术语层面来说,RabbitMQ模型更像是一种交换机模型。

1.生产者和消费者

Producer:生产者,就是投递消息一方。
生产者创建消息,发布到RabbitMQ中,消息一般可以包含2个部分:消息体和标签(Label)。消息体也可以称之为payload,在实际应用中,消息体一般是一个带有业务逻辑结构的数据,比如JSON字符串。消息的标签用来表述这条消息,比如一个交换器的名称和一个路由键。生产者把消息交由RabbitMQ,RabbitMQ之后会根据标签把消息发送给感兴趣的消费者(Consumer)。

Consumer:消费者,就是接受消息的一方。
消费者连接到RabbitMQ服务器,并订阅到队列上。当消费者消费一条消息时,只是消费消息的消息体(payload)。在消息路由的过程中,消息的标签会丢弃,存入到队列中的消息只有消息体,消费者也只会消费到消息体,也就不知道消息的生产者是谁,当然消费者也不需要知道。

Broker:消息中间件的服务节点。
对于RabbitMQ来说,一个RabbitMQ Broker可以简单地看作一个RabbitMQ服务节点,或者RabbitMQ服务实例。大多数情况下也可以将一个RabbitMQ Broker看作一台RabbitMQ服务器。
生产者将消息存入RabbitMQ Broker,以及消费者从Broker中消费数据的整个流程:

2.队列

Queue:队列,是RabbitMQ的内部对象,用于存储消息。

RabbitMQ中消息都只能存储在队列中。RabbitMQ的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。
多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者处理,而不是每个消费者都收到所有的消息并处理。

RabbitMQ不支持队列层面的广播消费,如果需要广播消费,需要在其上进行二次开发,处理逻辑会变得异常复杂,同时也不建议这么做。

3.交换器,路由键,绑定

  • Exchange: 交换器,在上面图中,暂时理解成生产者将消息投递到队列中,实际上这个在RabbitMQ中不会发生。真实情况是,生产者将消息发送到Exchange,由交换器将消息路由到一个或者多个队列中。如果路由不到,或许会返回给生产者,或许直接丢弃。这里可以将RabbitMQ中的交换器看作一个简单的实体。

RabbitMQ中的交换器有四种类型,不同的类型有着不同的路由策略。

  • RoutingKey:路由键。生产者将消息发给交换器的时候,一般会指定一个RountingKey,用来指定这个消息的路由规则,而这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。在交换器类型和绑定键(BindingKey)固定的情况下,生产者可以在发送消息给交换器时,通过指定RoutingKey来决定消息流向哪里。

  • Binding:绑定。RabbitMQ中通过绑定将交换器与队列关联起来,在绑定的时候一般会指定一个绑定键(BindingKey),这样RabbitMQ就知道如何正确地将消息路由到队列了。

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

 举例理解:交换器相当于投递包裹的邮箱,RoutingKey相当于填写在包裹上的地址,BindingKey相当于包裹的目的地,当填写在包裹上的地址和实际想要投递的地址相匹配时,那么这个包裹就会被正确投递到目的地。如果填写的地址出错,邮递员不能正确投递到目的地。包裹可能会会退给寄件人,也有可能被丢弃。

4.交换器类型

 RabbitMQ常用的交换器类型有fanout,direct,topic,headers这四种。

  • fanout:它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中。

  • direct:direct类型的交换器路由规则也很简单,它会把消息路由到那些BindingKey和RoutingKey完全匹配的队列中。

  • topic:topic类型的交换器在匹配规则上进行了扩展,它与direct类型的交换器相似,也是将消息路由到BindingKey和RoutingKey相匹配的队列中,但匹配规则有些不同
    RoutingKey为一个点号“.”分隔的字符串(被点号“.”分隔开的每一段独立的字符串称为一个单词),如“com.rabbitmq.client”,“java.util.concurrent”,“com.hidden.client”;
    BindingKey和RoutingKey一样也是点号“.”分隔的字符串;
    BindingKey中可以存在两种特殊字符串“*”和“#”,用于做模糊匹配,符号“#”匹配路由键的一个或多个词,符号“*”匹配路由键的一个词

  • headers:headers类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的headers属性来进行匹配。(不实用,基本上不会看到它的存在)

5.RabbitMQ运转流程

生产者在发送消息的时候:

  1. 生产者连接到RabbitMQ Broker,建立一个连接(Connection),开启一个信道(Channel)。
  2. 生产者声明一个交换器,并设置相关属性,比如交换器类型,是否持久化等。
  3. 生产者声明一个队列并设置相关属性,比如是否排他是否持久化,是否自动删除等。
  4. 生产者通过路由键将交换器和队列绑定起来。
  5. 生产者发送消息至RabbitMQ Broker,其中包含路由键,交换器等信息。
  6. 相应的交换器根据接收到的路由键查找相匹配的队列。
  7. 如果找到,则将从生产者发送过来的消息存入相应的队列中。
  8. 如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者。
  9. 关闭信道。
  10. 关闭连接。

消费者接受消息的过程:

  1. 消费者连接到RabbitMQ Broker,建立一个连接(Connection),开启一个信道(Channel)。
  2. 消费者向RabbitMQ Broker请求消费相应队列中的消息,可能会设置相应的回调函数,以及做一些准备工作。
  3. 等待RabbitMQ Broker回应并投递相应队列中的消息,消费者接受消息。
  4. 消费者确认(ack)接受到的消息。
  5. RabbitMQ从队列中删除相应已经被确认的消息。
  6. 关闭信道。
  7. 关闭连接。

6.不同类型交换器消息发送接受代码示例

6.1 direct类型
import com.mine.rabbitmq.rabbitmqbegin.util.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ExchangeDirectConsumer {

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {

        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();

        String exchange = "exchange_direct_test001";
        String exchange_type = "direct";
        String queue = "queue_test_001";
        String routing_key = "routing_test001";

        channel.exchangeDeclare(exchange, exchange_type, true, false, false, null);

        channel.queueDeclare(queue, false, false, false, null);

        channel.queueBind(queue, exchange, routing_key);

        QueueingConsumer consumer = new QueueingConsumer(channel);

        channel.basicConsume(queue, true, consumer);

        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();

            String msg = new String(delivery.getBody());

            System.out.println("收到消息:" + msg);
        }
    }
}

import com.mine.rabbitmq.rabbitmqbegin.util.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ExchangeDirectProducer {
    public static void main(String[] args) throws IOException, TimeoutException {

        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();


        String exchange = "exchange_direct_test001";

        String routing_key = "routing_test001";

        String msg = "Hello RabbitMQ !!!";

        for (int i = 0; i < 10; i++) {
            channel.basicPublish(exchange, routing_key, null, msg.getBytes());
        }

        channel.close();
        connection.close();
    }
}

6.2 topic类型
import com.mine.rabbitmq.rabbitmqbegin.util.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ExchangeTopicConsumer {

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();

        String exchange = "exchange_topic_test002";

        String exchange_type = "topic";

        String queue = "queue_topic_test002";

        //切换的时候需要注意解绑
        //String routing_key = "routing_topic.#";

        String routing_key = "routing_topic.*";

        channel.exchangeDeclare(exchange, exchange_type, true, false, false, null);

        channel.queueDeclare(queue, false, false, false, null);

        channel.queueBind(queue, exchange, routing_key);

        QueueingConsumer consumer = new QueueingConsumer(channel);

        channel.basicConsume(queue, true, consumer);

        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();

            String msg = new String(delivery.getBody());

            System.out.println("收到消息:" + msg);
        }

    }
}


import com.mine.rabbitmq.rabbitmqbegin.util.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ExchangeTopicProducer {

    public static void main(String[] args) throws IOException, TimeoutException {

        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();

        String exchange = "exchange_topic_test002";

        String rounting_key01 = "routing_topic.A";
        String rounting_key02 = "routing_topic.A.B";
        String rounting_key03 = "A.routing_topic";

        String msg01 = "hello 1";
        String msg02 = "hello 2";
        String msg03 = "hello 3";

        channel.basicPublish(exchange, rounting_key01, null, msg01.getBytes());
        channel.basicPublish(exchange, rounting_key02, null, msg02.getBytes());
        channel.basicPublish(exchange, rounting_key03, null, msg03.getBytes());

        channel.close();
        connection.close();
    }
}
6.3 fanout类型
import com.mine.rabbitmq.rabbitmqbegin.util.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ExchangeFanoutConsumer {

    public static void main(String[] args) throws IOException, InterruptedException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();

        String exchange = "exchange_fanout_test003";

        String exchange_type = "fanout";

        String queue = "queue_fanout_test003";

        channel.exchangeDeclare(exchange, exchange_type, true, false, false, null);

        channel.queueDeclare(queue, false, false, false, null);

        channel.queueBind(queue, exchange, "");

        QueueingConsumer consumer = new QueueingConsumer(channel);

        channel.basicConsume(queue, true, consumer);

        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();

            String msg = new String(delivery.getBody());

            System.out.println("收到消息:" + msg);
        }
    }
}


import com.mine.rabbitmq.rabbitmqbegin.util.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ExchangeFanoutProducer {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {

        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();

        String exchange = "exchange_fanout_test003";

        channel.basicPublish(exchange, "", null, "fanout msg".getBytes());
     
        //记得要关闭相关的连接
        channel.close();
        connection.close();
    }
}
6.4 默认交换机情况
import com.mine.rabbitmq.rabbitmqbegin.util.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeoutException;

public class Consumer {

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();

        String queue = "queue_default";

        //没有声明绑定指定交换机,则选择默认交换机,当向默认交换机(direct类型)发送消息routingKey等于队列名称时,消息可以路由到当先队列
        channel.queueDeclare(queue, true, false, false, null);

        QueueingConsumer consumer = new QueueingConsumer(channel);

        channel.basicConsume(queue, true, consumer);

        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String msg = new String(delivery.getBody());
            System.out.println("收到消息:" + msg);

            Map<String, Object> headers = delivery.getProperties().getHeaders();

            System.out.println("headers get my1 value:" + headers.get("my1"));
        }
    }
}


import com.mine.rabbitmq.rabbitmqbegin.util.ConnectionUtil;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;

public class Producer {

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();

        String queue = "queue_default";

        Map<String, Object> headers = new HashMap<>();
        headers.put("my1", "val1");
        headers.put("my2", "val2");
        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                .deliveryMode(2)
                .contentEncoding("utf-8")
                .expiration("10000")
                .headers(headers)
                .build();

        String msg = "message info";

        //默认交换机(direct类型,routingKey和队列名称相同即可路由到)
        channel.basicPublish("", queue, properties, msg.getBytes());

        //记得要关闭相关的连接
        channel.close();
        connection.close();
    }
}
posted on 2020-05-13 21:17  whn051799  阅读(196)  评论(0编辑  收藏  举报