全网最详细RabbitMQ工作流程详解

2,184 阅读5分钟

RabbitMQ工作流程

生产者发送消息的流程

  • 1.生产者连接RabbitMQ,建立TCP连接( Connection),开启信道(Channel)

  • 2.生产者声明一个Exchange(交换器),并设置相关属性,比如交换器类型、是否持久化等

  • 3.生产者声明一个队列井设置相关属性,比如是否排他、是否持久化、是否自动删除等

  • 4.生产者通过bindingKey(绑定Key)将交换器和队列绑定(binding)起来

  • 5.生产者发送消息至RabbitMQ Broker,其中包含routingKey(路由键)、交换器等信息

  • 6.相应的交换器根据接收到的routingKey查找相匹配的队列

  • 7.如果找到,则将从生产者发送过来的消息存入相应的队列中

  • 8.如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者

  • 9.关闭信道

  • 10.关闭连接

消费者接收消息的过程

  • 1.消费者连接到RabbitMQ Broker,建立一个连接(Connection ),开启一个信道(Channel)

  • 2.消费者向RabbitMQ Broker请求消费相应队列中的消息,可能会设置相应的回调函数,以及做一些准备工作

  • 3.等待RabbitMQ Broker回应并投递相应队列中的消息,消费者接收消息

  • 4.消费者确认( ack)接收到的消息

  • 5.RabbitMQ从队列中删除相应己经被确认的消息

  • 6.关闭信道

  • 7.关闭连接

简单案例(Hello world)

Hello World一对一的简单模式。

image.png

生产者直接发送消息给RabbitMQ,另一端消费。未定义和指定Exchange的情况下,使用的是AMQP default这个内置的Exchange。

代码示例

  • Producer
public class Producer {

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

        //获取TCP长连接
        Connection conn = RabbitUtils.getConnection();
        //创建通信“通道”,相当于TCP中的虚拟连接
        Channel channel = conn.createChannel();
        /**
         * 创建队列,声明并创建一个队列,如果队列已存在,则使用这个队列
         * 第一个参数:队列名称ID
         * 第二个参数:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
         * 第三个参数:是否队列私有化,false则代表所有消费者都可以访问,true代表只有第一次拥有它的消费者才能一直使用,其他消费者不让访问
         * 第四个:是否自动删除,false代表连接停掉后不自动删除掉这个队列
         * 其他额外的参数, null
         */
        channel.queueDeclare(RabbitConstant.QUEUE_HELLOWORLD, false, false, false, null);

        String message = "hello ranbbit 666";
        /**
         *  exchange 交换机
         *   queueName 队列名称
         *  Properties 额外的设置属性
         *  message[] 最后一个参数是要传递的消息字节数组
         */
        channel.basicPublish("", RabbitConstant.QUEUE_HELLOWORLD, null, message.getBytes());
        //关闭channel通道
        channel.close();
        //关闭TCP长连接
        conn.close();
        System.out.println("===发送成功===");
    }
}
  • consumer
public class Consumer {

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

        //获取TCP长连接
        Connection conn = RabbitUtils.getConnection();
        //创建通信“通道”,相当于TCP中的虚拟连接
        Channel channel = conn.createChannel();

        /**
         * 创建队列,声明并创建一个队列,如果队列已存在,则使用这个队列
         * 第一个参数:队列名称ID
         * 第二个参数:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
         * 第三个参数:是否队列私有化,false则代表所有消费者都可以访问,true代表只有第一次拥有它的消费者才能一直使用,其他消费者不让访问
         * 第四个:是否自动删除,false代表连接停掉后不自动删除掉这个队列
         * 其他额外的参数, null
         */
        channel.queueDeclare(RabbitConstant.QUEUE_HELLOWORLD, false, false, false, null);

        //从MQ服务器中获取数据

        /**
         *   创建一个消息消费者
         *    第一个参数:队列名
         *    第二个参数代表是否自动确认收到消息,false代表手动编程来确认消息,这是MQ的推荐做法
         *    第三个参数要传入DefaultConsumer的实现类
         */
        channel.basicConsume(RabbitConstant.QUEUE_HELLOWORLD, false, new Reciver(channel));
    }
}

class Reciver extends DefaultConsumer {

    private Channel channel;

    /**
     * 重写构造函数,Channel通道对象需要从外层传入,在handleDelivery中要用到
     *
     * @param channel
     */
    public Reciver(Channel channel) {
        super(channel);
        this.channel = channel;
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {

        String message = new String(body);
        System.out.println("消费者接收到的消息:" + message);

        System.out.println("消息的TagId:" + envelope.getDeliveryTag());
        //false只确认签收当前的消息,设置为true的时候则代表签收该消费者所有未签收的消息
        channel.basicAck(envelope.getDeliveryTag(), false);
    }
}
  • RabbitUtils
public class RabbitUtils {
    private static ConnectionFactory connectionFactory = new ConnectionFactory();

    static {
        //设置服务器主机名或IP地址
        connectionFactory.setHost("127.0.0.1");
        //5672是RabbitMQ的默认端口号
        connectionFactory.setPort(5672);
        //设置用户名和密码
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        //设置Erlang的虚拟主机名称
        connectionFactory.setVirtualHost("my_vhost");
    }

    public static Connection getConnection() {
        Connection conn = null;
        try {
            conn = connectionFactory.newConnection();
            return conn;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

Connection和Channel关系

生产者和消费者,需要与RabbitMQ Broker建立TCP连接,也就是Connection。

一旦TCP连接建立起来,客户端紧接着创建一个AMQP信道(Channel),每个信道都会被指派一个唯一的ID。

信道是建立在Connection之上的虚拟连接,RabbitMQ处理的每条AMQP指令都是通过信道完成的。

image.png

RabbitMQ采用类似NIO的做法,复用TCP连接,减少性能开销,便于管理。

当每个信道的流量不是很大时,复用单一的Connection可以在产生性能瓶颈的情况下有效地节省TCP连接资源。

当信道本身的流量很大时,一个Connection就会产生性能瓶颈,流量被限制。需要建立多个Connection,分摊信道。