RabbitMQ的基础学习


前言

关于RabbitMQ的基本学习

一、RabbiMQ是什么?

RabbitMQ 是一个消息中间件:它接受并转发消息。你可以把它当做一个快递站点,当你要发送一个包裹时,你把你的包裹放到快递站,快递员最终会把你的快递送到收件人那里,按照这种逻辑 RabbitMQ 是一个快递站,一个快递员帮你传递快件。RabbitMQ 与快递站的主要区别在于,它不处理快件而是接收,存储和转发消息数据。

二、它能解决什么问题?

  • 流量消峰
举个例子,如果订单系统最多能处理一万次订单,这个处理能力应付正常时段的下单时绰绰有余,正
常时段我们下单一秒后就能返回结果。但是在高峰期,如果有两万次下单操作系统是处理不了的,只能限
制订单超过一万后不允许用户下单。使用消息队列做缓冲,我们可以取消这个限制,把一秒内下的订单分
散成一段时间来处理,这时有些用户可能在下单十几秒后才能收到下单成功的操作,但是比不能下单的体
验要好
  • .应用解耦
以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。用户创建订单后,如果耦合
调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。当转变成基于
消息队列的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来修复。在
这几分钟的时间里,物流系统要处理的内存被缓存在消息队列中,用户的下单操作可以正常完成。当物流
系统恢复后,继续处理订单信息即可,中单用户感受不到物流系统的故障,提升系统的可用性。
  • 异步处理
有些服务间调用是异步的,例如 A 调用 B,B 需要花费很长时间执行,但是 A 需要知道 B 什么时候可以执行
完,以前一般有两种方式,A 过一段时间去调用 B 的查询 api 查询。或者 A 提供一个 callback api, B 执行完
之后调用 api 通知 A 服务。这两种方式都不是很优雅,使用消息总线,可以很方便解决这个问题,A 调用 B 服
务后,只需要监听 B 处理完成的消息,当 B 处理完成后,会发送一条消息给 MQ,MQ 会将此消息转发给 A 服
务。这样 A 服务既不用循环调用 B 的查询 api,也不用提供 callback api。同样B 服务也不用做这些操作。A 服
务还能及时的得到异步处理成功的消息。

三、四大核心

生产者、消费者、交换机、队列
在这里插入图片描述

四、工作原理

名词介绍

  1. Broker:接收和分发消息的应用,RabbitMQ Server 就是 Message Broker
  2. Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个 vhost,每个用户在自己的 vhost 创建 exchange/queue 等
  3. Connection:publisher/consumer 和 broker 之间的 TCP 连接
  4. Channel::如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCPConnection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个 thread 创建单独的 channel 进行通讯,AMQP method 包含了 channel id 帮助客户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的
  5. Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到 queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout
    (multicast)
  6. Queue:消息最终被送到这里等待 consumer 取走
  7. Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key,Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据
    在这里插入图片描述

五、Work Queues

轮询

同一组消费者消费同一个队列里面的消息的时候默认采用的是轮询的方式
在这里插入图片描述

不公平分发

如果需要 采用不公平分发,则需要在消费端配置:能者多劳,性能好的工作线程抢占消息
消费者端要把自动确认autoAck设置为false,basicQos才有效果
在这里插入图片描述

六、消息应答

消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成
了部分突然它挂掉了,会发生什么情况。RabbitMQ 一旦向消费者传递了一条消息,便立即将该消息标记为删除。在这种情况下,突然有个消费者挂掉了,我们将丢失正在处理的消息。以及后续发送给该消费这的消息,因为它无法接收到。
为了保证消息在发送过程中不丢失,rabbitmq 引入消息应答机制,消息应答就是:消费者在接收到消息并且处理该消息之后,告诉 rabbitmq 它已经处理了,rabbitmq 可以把该消息删除了。

关于消息应答主要有两种方案:

  1. 自动应答(不安全,错误的时候不能做相应处理)------>处理效率快(问题是:消费者那边出现连接或者 channel 关闭,那么消息就会丢失,消息过多也会内存耗尽)

  2. 手动应答(需要解决的问题是:消息不会丢失)

    Channel.basicAck(用于肯定确认)
    Channel.basicNack(用于否定确认)
    Channel.basicReject(用于否定确认)---->不处理该消息。直接丢弃

    multipletrue 和 false,在一个cannel里面有多条消息待确认,如果为true则未应答的消息都会被被确认收到消息应答
    false更加的安全,但效率没有true高

设置手动应答:
在这里插入图片描述
设置是否批量应答:
在这里插入图片描述

七、RabbitMQ 持久化

队列持久化

D:标识该队列持久化
在这里插入图片描述
代码设置:

如果原先已经存在了不持久化的队列时,在次声明的话会报错
在这里插入图片描述

消息持久化

(不能绝对保证消息不丢失)
在这里插入图片描述

八、发布确认

概念

作用于生产者和mq之间
生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消
息都将会被指派一个唯一的 ID(从 1 开始),一旦消息被投递到所有匹配的队列之后,broker 就会发送一个确认给生产者(包含消息的唯一 ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker 回传给生产者的确认消息中 delivery-tag 域包含了确认消息的序列号,此外 broker 也可以设置basic.ack 的multiple 域,表示到这个序列号之前的所有消息都已经得到了处理。

策略

生产端设置:

单个确认

同步的----安全,但效率慢

public static void publishMessageSingle() throws Exception {
    
    //耗时136643毫秒
    Channel channel = RabbitMqUtils.getChannel();
    //队列的声明
    String queueName = UUID.randomUUID().toString();
    channel.queueDeclare(queueName,true,false,false,null);
    //开启发布确认
    channel.confirmSelect();
    //开始时间
    long begin = System.currentTimeMillis();

    //批量发消息
    for (int i = 0; i < MESSAGE_COUNT; i++) {
    
    
        String message = i + "";
        channel.basicPublish("",queueName,null,message.getBytes());
        //单个消息就马上进行确认
        boolean flag = channel.waitForConfirms();
        if(flag) {
    
    
            System.out.println("消息发送成功");
        }
    }
    //结束时间
    long end = System.currentTimeMillis();
    System.out.println("发布" + MESSAGE_COUNT + "个单独确认消息,耗时" + (end-begin) + "毫秒");
}

批量确认

确认速度快----->无法知道具体是什么消息发送失败

public static void publishMessageBatch() throws Exception {
    
    //耗时1711毫秒
    Channel channel = RabbitMqUtils.getChannel();
    //队列的声明
    String queueName = UUID.randomUUID().toString();
    channel.queueDeclare(queueName,true,false,false,null);
    //开启发布确认
    channel.confirmSelect();
    //开始时间
    long begin = System.currentTimeMillis();

    //批量确认消息大小
    int batchSize = 100;
    //批量发消息,批量发布确认
    for (int i = 1; i <= MESSAGE_COUNT; i++) {
    
    
        String message = i + "";
        channel.basicPublish("",queueName,null,message.getBytes());

        //判断达到100条消息的时候,批量确认一次
        if(i % batchSize == 0){
    
    
            boolean flag = channel.waitForConfirms();
            if(flag) {
    
    
                System.out.println("消息发送成功");
            }
        }
    }

    //结束时间
    long end = System.currentTimeMillis();
    System.out.println("发布" + MESSAGE_COUNT + "个批量确认消息,耗时" + (end-begin) + "毫秒");

}

异步确认发布

性能较好,在出现错误的情况下可以很好的地控制

//异步发布确认
public static void publishMessageAsync() throws Exception {
    
    
    Channel channel = RabbitMqUtils.getChannel();
    //队列的声明
    String queueName = UUID.randomUUID().toString();
    channel.queueDeclare(queueName,true,false,false,null);
    //开启发布确认
    channel.confirmSelect();
    //开始时间
    long begin = System.currentTimeMillis();

    /**
     * 线程安全有序的一个哈希表,适用于高并发的情况下
     *  1.轻松的将序号于消息进行关联
     *  2.轻松的批量删除条目,只要给到序号
     *  3.支持高并发
     */
    ConcurrentSkipListMap<Long, String> outstandingConfirms = new ConcurrentSkipListMap<>();

    //消息确认成功回调函数
    ConfirmCallback ackCallback = (deliveryTag, multiple) -> {
    
    
        //=====2.删除掉已经确认的消息,剩下的就是未确认的消息=====
        if(multiple) {
    
    
            ConcurrentNavigableMap<Long, String> confirmd =
                    outstandingConfirms.headMap(deliveryTag);
        } else {
    
    
            outstandingConfirms.remove(deliveryTag);
        }

        System.out.println("确认的消息:" + deliveryTag);
    };
    /**
     * 消息确认失败回调函数
     *  1.消息的标识
     *  2.是否为批量确认
     */
    ConfirmCallback nackCallback = (deliveryTag, multiple) -> {
    
    //耗时274毫秒
        //=====3.打印一下未确认的消息都有哪些=====
        String message = outstandingConfirms.get(deliveryTag);
        System.out.println("未确认的消息是:" + message + "未确认的消息tag:" + deliveryTag);
    };
    /**
     * 准备消息的监听器,监听哪些消息成功了,哪些消息失败了
     *  1.消息发送成功的监听对象
     *  2.消息发送失败的监听对象
     */
    channel.addConfirmListener(ackCallback,nackCallback);//异步通知

    for (int i = 1; i <= MESSAGE_COUNT; i++) {
    
    
        String message = i + "";
        channel.basicPublish("",queueName,null,message.getBytes());

        //======1.此处记录下所有要发送的消息,消息的总和=====
        outstandingConfirms.put(channel.getNextPublishSeqNo(),message);
    }

    //结束时间
    long end = System.currentTimeMillis();
    System.out.println("发布" + MESSAGE_COUNT + "个异步确认消息,耗时" + (end-begin) + "毫秒");
}

待续

猜你喜欢

转载自blog.csdn.net/weixin_44063083/article/details/119489596