RabiitMQ入门基础学习一

本文是RabbitMQ入门基础学习内容。先介绍了MQ的优劣势及注意事项,接着阐述RabbitMQ的基础架构、工作模式,如Hello World、Work queues等。还讲解了其高级特性,包括消息可靠投递、消费端限流等,最后介绍了RabbitMQ集群搭建,如镜像队列和HA环境搭建。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RabiitMQ入门基础学习一

MQ 简介

MQ全称 Message Queue(*消息队列*),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信

分布式系统通信两种方式:直接远程调用 和 借助第三方 完成间接通信

消息发送方称为生产者,接收方称为消息消费者

优劣势:
在这里插入图片描述

  • ⚫系统可用性降低
    系统引入的外部依赖越多,系统稳定性越差。
    一旦 MQ 宕机,就会对业务造成影响。如何保证MQ的高可用

  • ⚫系统复杂度提高
    MQ 的加入增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。
    如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性

  • ⚫一致性问题
    A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息数据,如果 B 系统、C 系统处理成功,D 系统处理失败。如何保证消息数据处理的一致性

  • 注意:
    生产者不需要从消费者处获得反馈

    引入消息队列之前的直接调用,其接口的返回值应该为空,这才让明明下层的动作还没做,上层却当成动作做完了继续往后走,即所谓异步成为了可能。

    容许短暂的不一致性。

RabbitMQ 简介

首先介绍AMQP协议,即 Advanced Message Queuing Protocol(高级消息队列协议)是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。

基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。

RabbitMQ 采用 Erlang 语言开发。Erlang 语言由 Ericson 设计,专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。

RabbitMQ 基础架构如下图:
在这里插入图片描述

RabbitMQ 工作模式

在这里插入图片描述

  • Hello World 模式
    生产者将消息放入队列,消费者监听该队列,若有消息则消费。
  • Work queues
    竞争模式,多个消费者监听一个队列,谁先拿到消息,谁先消费。
    类比于抢红包。
  • Publish/Subscribe(BuiltinExchangeType.FANOUT)
    广播模式,生产者将消息发送到交换机,交换机一次性将消息发送到所绑定的所有队列上,而消费者监听的队列一旦有消息,即可消费。
    类比于群发邮件。
    /*
      此时交换机的队列绑定
      queueBind(String queue, String exchange, String routingKey)
      参数:
          1. queue:队列名称
          2. exchange:交换机名称
          3. routingKey:路由键,绑定规则
              如果交换机的类型为fanout ,routingKey设置为""
    */
    channel.queueBind(queueName1, exchangeName, "");
    
  • Routing
    生产者将消息发送到交换机,交换机根据指定的routeKey分发到指定的队列,而消费者监听的队列收到消息时,即可消费。
      //路由route绑定队列queue
      /*
      queueBind(String queue, String exchange, String routingKey)
      参数:
          3. routingKey:路由键,绑定规则
              如果交换机的类型为routing ,routingKey设置为指定特殊值
          案例:consumer1 只消费routingKey为error的日志消息;
               consumer2 消费routingKey为error、info、warning的日志消息;
       */
      channel.queueBind(queueName1, exchangeName, "error");
    
      channel.queueBind(queueName2, exchangeName, "error");
      channel.queueBind(queueName2, exchangeName, "info");
      channel.queueBind(queueName2, exchangeName, "warning");
    
  • Topic
    生产者将消息发送到交换机,交换机根据指定的routeKey分发到指定的队列,此时的routeKey是pattern类型(*代表一个;#代表0个或多个),而消费者监听的队列收到消息时,即可消费。
      //路由route绑定队列queue
      /*
      queueBind(String queue, String exchange, String routingKey)
      参数:
          3. routingKey:路由键,绑定规则
              如果交换机的类型为routing ,routingKey设置为指定特殊值
          案例:consumer1 只消费routingKey为.error的日志消息和order相关的日志消息;
                          匹配规则为: #.error || order.*
                          # 代表0个或任意个字符,* 代表一个字符
               consumer2 消费routingKey为任意的日志消息;
                          匹配规则为: #.*
       */
      channel.queueBind(queueName1, exchangeName, "#.error");
      channel.queueBind(queueName1, exchangeName, "order.*");
    
      channel.queueBind(queueName2, exchangeName, "#.*");
    

RabbitMQ 高级特性

  • 消息可靠投递(生产端的可靠性保证)
    (1)从生产端->exchange

     /**
      * 设置ConfirmCallback回调函数后:
      *      当消息发送到exchange后调用confirm方法。
      *      在方法中判断ack, 为true则发送成功,否则发送失败。
      */
     rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
         /**
          * @param correlationData 相关配置
          * @param ack 消息发送是否成功
          * @param cause 失败原因
          */
         @Override
         public void confirm(CorrelationData correlationData, boolean ack, String cause) {
             System.out.println("confirm 执行了");
             if (ack) System.out.println("消息发送到exchange成功:"+ cause);
             else System.out.println("消息发送到exchange失败!!!:"+ cause);
         }
     });
    

    (2)从exchange -> queue

            /**
         * 设置ReturnCallback回调函数后:
         *      当消息从exchange发送到queue成功时,不会回调。
         *      当消息从exchange发送到queue失败时:
         *          设置exchange处理消息模式:
         *              1、如果消息没有从exchange到queue时,则丢弃消息(默认)
         *              2、如果消息没有从exchange到queue时,则返回消息发送方,执行回调(需设置 rabbitTemplate.setMandatory(true))
         */
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             *
             * @param message 消息体
             * @param replyCode 错误码
             * @param replyText 错误信息
             * @param exchange 交换机
             * @param routingKey 路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                System.out.println("return 方法执行了~~~");
                System.out.println(message);
                System.out.println(replyCode);
                System.out.println(replyText);
                System.out.println(exchange);
                System.out.println(routingKey);
            }
        });
    
  • Consumer Ask(消费端的可靠性保证)
    (1)直接消费消息,不管之后的业务逻辑是否出现异常,消息消费后就直接丢弃
    (2)手动确认消息,若业务逻辑正常执行,则手动确认消息channel.basicAck()

    	/**
         * void basicAck(long deliveryTag, boolean multiple) throws IOException;
         * deliveryTag :表示当前消息的标签
         * multiple : 允许是否同时消费多条消息
         */
        channel.basicAck(deliveryTag, true);
    

    (3)手动拒绝消息,若业务逻辑出现异常,则手动拒绝消息channel.basicNack()

    	/**
         * void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;
         * requeue:是否重新回到队列。
         * 若为true,则消息重新回到queue, broker会将消息重新发送到消费端
         */
        channel.basicNack(deliveryTag, true, true);
    
  • 消费端限流

      (1)确保ack机制为手动确认
      (2)在<rabbit:listener-container>配置prefetch=1(数字代表一次拉取一条)属性
    
  • TTL(time to live)

      队列过期时间:
      	一旦队列过期时间到了,那么队列中所有的消息都会被丢弃
      	
      消息单独过期时间:
      	如果队列和消息都设置了过期时间,则以时间短的为准;
      	队列过期后,则会将所有消息丢弃;
      	消息过期后,只有在队列顶端的消息才会去判断是否过期,若在队列中间是不会判断是否过期,只有当中间的消息达到队列顶端时,才会去判断是否过期,过期则丢弃。
    
  • 死信队列

      死信队列,Dead Letter Exchange(DLX,死信交换机),当消息成为dead message 后,可以被重新发送到另一个交换机B,此时这个交换机B则为DLX。
    

    死信队列
    消息如何才会成为死信:

      (1)消息的数量超过了队列消息长度
      (2)消息自身过期或者队列过期
      (3)被手动拒绝 basicNask()/basicReject() 且不回到队列的消息 requeue=false 
    
        <!-- 1 声明正常交换机、队列 -->
        <rabbit:queue name="order_queue" id="order_queue" auto-declare="true">
            <!-- 3 绑定死信交换机、routeKey等 -->
            <rabbit:queue-arguments>
                <entry key="x-dead-letter-exchange" value="dlx_order_exchange"/>
                <entry key="x-dead-letter-routing-key" value="dlx.order.cancel"/>
                <!-- 4.1 设置过期时间 -->
                <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
                <!-- 4.2 设置队列最大长度 -->
                <entry key="x-max-length" value="5" value-type="java.lang.Integer"/>
            </rabbit:queue-arguments>
        </rabbit:queue>
    
        <rabbit:topic-exchange name="order_exchange" id="order_exchange" auto-declare="true">
            <rabbit:bindings>
                <rabbit:binding pattern="order.#" queue="order_queue"></rabbit:binding>
            </rabbit:bindings>
        </rabbit:topic-exchange>
        <!-- 2 声明死信交换机、队列 -->
        <rabbit:queue name="dlx_order_queue" id="dlx_order_queue" auto-declare="true"/>
        <rabbit:topic-exchange name="dlx_order_exchange" id="dlx_order_exchange" auto-declare="true">
            <rabbit:bindings>
                <rabbit:binding pattern="dlx.order.#" queue="dlx_order_queue"></rabbit:binding>
            </rabbit:bindings>
        </rabbit:topic-exchange>
    
  • 延迟队列

      延迟队列不会立即被消费,只有到达指定时间后,才会被消费。
      延迟队列在rabbitMq中是没有实现的,可以通过 `TTL+死信队列` 实现。
    

    案例:用户下一个订单,30min后未支付则取消订单,库存回滚。下图为TTL+死信队列实现得延迟队列。
    在这里插入图片描述

  • 日志监控

  • 消息追踪

      在使用任何消息中间件的过程中,难免会出现某条消息异常丢失的情况。
      
      对于RabbitMQ而言,可能是因为生产者或消费者与RabbitMQ断开了连接,而它们与RabbitMQ又采用了不同的确认机制;也有可能是因为交换器与队列之间不同的转发策略;
      甚至是交换器并没有与任何队列进行绑定,生产者又不感知或者没有采取相应的措施;另外RabbitMQ本身的集群策略也可能导致消息的丢失。
      
      这个时候就需要有一个较好的机制跟踪记录消息的投递过程,以此协助开发和运维人员进行问题的定位。
      	
      在rabbitmq中使用Firehose和rabbitmq-tracing插件来追踪消息。
      
      firehose的机制是将生产者投递给rabbitmq的消息,rabbitmq投递给消费者的消息按照指定的格式发送到默认的exchange上。
      这个默认的exchange的名称为amq.rabbitmq.trace,它是一个topic类型的exchange。
      
      Firehose:
      	rabbitmqctl trace_on: 开启Firehose命令
      	rabbitmqctl trace_off: 关闭Firehose命令
    
  • 消息可靠性保障(消息补偿)
    在这里插入图片描述
    消息可靠性主要体现在两个方面:producer发送消息失败;consumer消费消息失败。

      上图分为4个流程:
      1、producer操作数据库并发送消息到Q1,被consumer消费并写入DB。(步骤1、2、4)
      2、consumer成功消费后发送确认消息到Q2,并将消息写入数据库MDB。(步骤5-7)
      3、producer延迟发送同一条消息到Q3,在回调检查服务中和MDB数据库中得消息id进行对比,
      若存在则代表消息消费成功,若不存在则代表消息消费失败,执行步骤8,调用producer重新发送消息。(步骤3、8)
    
      4、若producer发送消息失败。
      	通过定时检查服务(可定时为1h),对比DB和MDB两个数据库中得消息是否一致,若DB比MDB中得消息多,
      	则代表存在消息发送失败,则执行步骤9,调用producer重新发送消息。(步骤9)
    
  • 消息幂等性保障(乐观锁解决方案)

      幂等性指:一次和多次请求一个资源,对于资源本身应具有同样得结果。也就是说,其任意多次执行对资源本身所产生得影响均与一次执行得影响相同。
    
      在MQ中指,消费多条相同的消息,得到与消费该消息一次相同得结果。
    

    在这里插入图片描述
    在此介绍一种通过数据库乐观锁得机制来应对,即通过数据中得version字段来处理多次消费相同的消息情况。

    即第一次消费消息时,version=1,执行sql version=version+1后,此时的version=2,在执行sql where id=1 and version=1时抛出异常,不再进行操作。

RabbitMQ 集群搭建

  • 镜像队列
  • HA环境搭建
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值