Rabbit MQ 入门

消息队列解决了什么问题

异步处理

我们以用户注册为例,当用户注册成功后,我们首先将用户注册信息写入数据库,我们再给他发送一封邮件,再给他发送一条短信,当都执行成功后,我们再给用户响应,需要 150 ms

在这里插入图片描述

为了优化程序,我们想到了第二种方式,将用户注册信息写入数据库后,我们采用多线程的方式给用户发送邮件和短信,合起来响应 100 ms

在这里插入图片描述

这时候我们的消息队列就派上用场了,我们将用户注册信息写入数据库后,采用消息队列的方式,将信息写入到消息队列中,立马返回,用户相当于在 55 ms 后得到响应,发送邮件和短信这些服务可以通过异步读取的方式自己从消息队列中取出用户信息,给用户发邮件和短信

在这里插入图片描述

应用解耦

传统的订单系统和库存系统是耦合在一起的,当我们下单系统完成之后再去调用库存系统,但是一旦我们的库存系统宕机,就会出现数据的丢失

在这里插入图片描述

现在我们引入消息队列,订单系统将下单信息发送到消息队列中,库存系统从消息队列订阅消息,这样即使库存系统宕机,也可以在重新恢复服务后从消息队列中读取消息

在这里插入图片描述

流量削峰

一个典型的例子是秒杀系统,当用户的请求进来后,我们直接发送到消息队列中,假设请求有 10 万个,我们给消息队列设置一个定长,比如 1 万,10 万个请求谁快谁进队,剩下的 9 万个响应秒杀失败,然后我们的秒杀业务逻辑再从队列中读取消息

在这里插入图片描述

日志处理

简介

RabbitMQ 是一个由 Erlang 开发的 AMQP(Advanved Message Queue Protocol) 的开源实现

核心概念

Message

消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括 routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等

Publisher

消息的生产者,也是一个向交换器发布消息的客户端应用程序

Exchange

交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列

Exchange 有 4 种类型:direct(默认),fanout,topic 和 headers,不同类型的 Exchange 转发消息的策略有所区别

Queue

消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走

Binding

绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表

Exchange 和 Queue 的绑定可以是多对多的关系

Connection

网络连接,比如一个 TCP 连接

Channel

信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的 TCP 连接内的虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接

Consumer

消息的消费者,表示一个从消息队列中取得消息的客户端应用程序

Virtual Host

虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 /

Broker

表示消息队列服务器实体

在这里插入图片描述

Rabbit MQ 安装与配置

Rabbit MQ 的安装比较繁琐,建议直接在 Linux 上用 docker 安装

添加用户

在这里插入图片描述

virtual hosts 管理

virtual hosts 相当于 MySQL 的 db

在这里插入图片描述

一般以 / 开头

我们得对用户进行授权

在这里插入图片描述

简单队列

模型

在这里插入图片描述

P:消息的生产者

红色的:消息队列

C:消费者

三个对象

生产者

队列 Rabbit MQ

消费者

获取 MQ 连接

public class ConnectionUtils {

    /**
     * 获取 Rabbit MQ 的连接
     * @return
     */
    public static Connection getConnection() throws IOException, TimeoutException {
        // 定义一个连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置服务地址
        factory.setHost("47.104.218.84");
        // AMQP 5672
        factory.setPort(5672);
        // vhost
        factory.setVirtualHost("/vhost_ml");
        // 用户名
        factory.setUsername("user_ml");
        // 密码
        factory.setPassword("maolong");
        return factory.newConnection();
    }

}

生产者生产消息

public class Send {

    private static final String QUEUE_NAME = "simple_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();
        // 创建队列声明
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        String msg = "hello simple!";
        channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
        System.out.println("--send msg:" + msg);
        channel.close();
        connection.close();
    }

}

消费者接受消息

public class Receive {

    private static final String QUEUE_NAME = "simple_queue";

    @SuppressWarnings("deprecation")
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();
        // 创建队列声明
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 定义消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[recv] msg:" + msg);
            }
        };

        // 监听队列
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }

}

简单队列的不足

耦合性高,生产者一一对应消费者(如果我想有多个消费者消费队列中消息,这时候就不行了),队列名变更,这时候得同时变更

Worker queues 工作队列

模型

在这里插入图片描述

为什么会出现工作队列

Simple 队列是一一对应的,而且我们实际开发,生产者发送消息是毫不费力的,而消费者一般是要跟业务相结合的,消费者接收到消息之后就需要处理,可能需要花费时间,这时候队列就会积压了很多消息

生产者

public class Send {

    private final static String QUEUE_NAME = "queue_work";

    public static void main(String[] args) throws Exception {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();

        // 创建队列声明
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        for (int i = 0; i < 50; i++) {
            // 消息内容
            String message = "" + i;
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println("[x] Send '" + message + "'");

            Thread.sleep(i * 20);
        }

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

消费者1

public class Receive1 {

    private final static String QUEUE_NAME = "queue_work";

    public static void main(String[] args) throws Exception {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();
        // 创建队列声明
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 定义队列的消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            /**
             * 获取到到达的消息
             * @param consumerTag
             * @param envelope
             * @param properties
             * @param body
             * @throws IOException
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[1] recv msg:" + msg);

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                }
            }
        };

        // 监听队列
        boolean autoAck = true;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer);
    }

}

消费者2

public class Receive2 {

    private final static String QUEUE_NAME = "queue_work";

    public static void main(String[] args) throws Exception {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();
        // 创建队列声明
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 定义队列的消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            /**
             * 获取到到达的消息
             * @param consumerTag
             * @param envelope
             * @param properties
             * @param body
             * @throws IOException
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[2] recv msg:" + msg);

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[2] done");
                }
            }
        };

        // 监听队列
        boolean autoAck = true;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer);
    }

}

现象

消费者 1 和消费者 2 处理的消息是一样的

消费者 1:偶数

消费者 2:奇数

这种方式叫做轮询分发(round-robin),结果就是不管谁忙活着谁清闲,都不会多给一个消息

公平分发 fair dispatch

生产者

public class Send {

    private final static String QUEUE_NAME = "queue_work";

    public static void main(String[] args) throws Exception {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();

        // 创建队列声明
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        /**
         * 每个消费者发送确认消息之前,消息队列不发送下一个消息到消费者,一次只处理一个消息
         * 限制发送给同一个消费者不得超过一条数据
         */
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);

        for (int i = 0; i < 50; i++) {
            // 消息内容
            String message = "" + i;
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println("[x] Send '" + message + "'");

            Thread.sleep(i * 20);
        }

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

}

消费者1

public class Receive1 {

    private final static String QUEUE_NAME = "queue_work";

    public static void main(String[] args) throws Exception {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();
        // 创建队列声明
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 保证一次只分发一个
        channel.basicQos(1);

        // 定义队列的消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            /**
             * 获取到到达的消息
             * @param consumerTag
             * @param envelope
             * @param properties
             * @param body
             * @throws IOException
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[1] recv msg:" + msg);

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                    // 手动回执
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        // 监听队列,自动应答改为 false
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer);
    }

}

消费者2

public class Receive2 {

    private final static String QUEUE_NAME = "queue_work";

    public static void main(String[] args) throws Exception {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();
        // 创建队列声明
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 保证一次只分发一个
        channel.basicQos(1);

        // 定义队列的消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            /**
             * 获取到到达的消息
             * @param consumerTag
             * @param envelope
             * @param properties
             * @param body
             * @throws IOException
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[2] recv msg:" + msg);

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[2] done");
                    // 手动回执
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        // 监听队列,自动应答改为 false
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer);
    }

}

现象

消费者 2 处理的消息比消费者 1 多,能者多劳

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值