RabbitMQ学习

165 阅读9分钟

RabbitMQ学习记录

rabbitmq

RabbitMQ基于AMQP(Advanced Message Queuing Protocol)协议,是一种消息代理和队列服务器。

RabbitMQ的作用

  • 异步
    • 生产者向RabbitMQ发送消息之后,继续执行之后的业务,消费者可以在适当的时候消费
  • 削峰
    • 当某时刻有大量的请求需要打到数据库时,为了保证数据库安全,使用RabbitMQ暂时存储消息,而消费者以一定的速率处理消息,RabbitMQ还可以实现负载均衡的功能,以一定的策略将消息分发给不同的消费者处理,降低服务器的压力。
  • 解耦
    • RabbitMQ作为中间件联系不同的服务模块,有一定的解耦作用

RabbitMQ!启动!

docker-compose

version: '3'
services:
  rabbitmq:
    image: rabbitmq:3.7.8-management        # 原镜像`rabbitmq:3.7.8-management` 【 注:该版本包含了web控制页面 】
    container_name: rabbitmq            # 容器名为'rabbitmq'
    hostname: my-rabbit
    restart: unless-stopped                                       # 指定容器退出后的重启策略为始终重启,但是不考虑在Docker守护进程启动时就已经停止了的容器
    environment:                        # 设置环境变量,相当于docker run命令中的-e
      TZ: Asia/Shanghai
      LANG: en_US.UTF-8
      RABBITMQ_DEFAULT_VHOST: my_vhost  # 主机名
      RABBITMQ_DEFAULT_USER: admin      # 登录账号
      RABBITMQ_DEFAULT_PASS: zlpen9hth      # 登录密码
    volumes: # 数据卷挂载路径设置,将本机目录映射到容器目录
      - "./rabbitmq/data:/var/lib/rabbitmq"
    ports:                              # 映射端口
      - "5672:5672"
      - "15672:15672"

启动rabbitmq

docker-compose -f docker-compose-rabbitmq-3.7.8-management.yml -p rabbitmq up -d

# 进入容器
docker exec -it rabbitmq /bin/bash
# 启用延时插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
# 查看已安装插件
rabbitmq-plugins list

web管理端:ip地址:15672

MQ基础

数据隔离(Vitual host)

RabbitMQ之中有虚拟主机(Vituall host)的概念,用于隔离数据,不同虚拟主机下的数据互不影响。每个用户可以拥有多个虚拟主机,用户只能控制拥有虚拟主机下的MQ。

交换机(exchange)

通常向RabbitMQ之中发送消息,是向交换机中发送消息,交换机通过一定的策略与队列绑定,队列再与消费者绑定,从而消费者能够消费RabbitMQ之中的消息。

其中交换机根据类型分为以下几种交换机

  • Direct exchange(直接交换机)
    • 通过路由键(Routing key)绑定队列
  • Fanout exchange(广播交换机)
    • 将消息分发到全部与之绑定的队列
  • Topic exchange(主题交换机)
    • 主题交换机是使用最多的交换机,通过路由键绑定队列,与Direct exchange不同的是路由键可以是一个句点分隔的字符串,支持通配符*(匹配一个单词)和#(匹配零个或多个单词)。
  • Headers Exchange(头交换机)
    • 根据消息头属性进行路由,而不是根据路由键。可以匹配头属性的值和存在性。

队列(queue)

队列:一种先进先出的数据结构。

消息进入队列,被消费者消费后出队列。

RabbitMQ的Java客户端

rabbitmq被springboot所集成,所以使用springboot的版本。

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

修改配置文件

spring:
  rabbitmq:
    host: localhost
    username: admin
    password: admin
    port: 5672
    virtual-host: my_vhost
public class RabbitTemplate extends RabbitAccessor implements BeanFactoryAware, RabbitOperations, ChannelAwareMessageListener, ApplicationContextAware, ListenerContainerAware, PublisherCallbackChannel.Listener, BeanNameAware, DisposableBean 

MQ高级

消息可靠性问题

在rabbitmq之中有三个角色,分别是生产者,mq服务端,消费者,无论这三者谁出现了问题,都会导致消息可靠性的问题。

生产者可靠性

  • 生产者重试机制
    connection-timeout: 1s # MQ的连接超时的时间
    template:
      retry:
        enabled: true # 开启MQ的连接重试机制
        initial-interval: 1000ms # 失败后重试的等待时间
        multiplier: 2 # 重试的等待时间的倍数,下次失败后等待时长 = initial-interval * multiplier
        max-attempts: 4 # 最大连接次数,超过这个次数就不再重试

这里最大连接次数为4,三次重连的等待时间依次是1s,2s,4s

image-20240524220304949

==注意==

失败重试时是阻塞式等待,会阻塞当前线程继续执行后续的业务,建议禁用重试机制

  • 生产者确认机制

    • 消息投递到MQ,但是路由失败,返回ACK,告知投递成功

    • 临时消息投递到MQ,并且入队成功,返回ACK

    • 持久消息投递到MQ,入队并完成持久化,返回ACK

    • 其他情况返回NACK

spring:
  rabbitmq:
	publisher-confirm-type: correlated # 启用生产者确认

配置RabbitTemplate的回调机制


package com.forzlp.cloud.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class RabbitMQConfig implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
        rabbitTemplate.setConfirmCallback(
                (correlationData, ack, cause) -> {
                    if (ack) {
                        // 消息投递成功
                        log.debug("消息投递成功");
                    } else {
                        // 消息投递失败
                        log.info("消息投递失败" + cause);
                    }
                });
    }
}

MQ可靠性

默认情况下,RabbitMQ中的消息都是持久化的(重启MQ后依旧还在)。

持久化的过程:当生产者向RabbitMQ中发送消息,消息会先写入内存,然后从内存中写入到磁盘。

==容易崩掉==,试了一下,2核2G的服务器发送消息三十万条消息,内存直接爆掉,服务器宕机

Lazy queue

lazy queue(惰性队列)的持久化方式:所有消息直接写入磁盘而不经过内存

优点:

  1. 延迟加载消息:当消息进入lazy queue时,它们不会立即被加载到内存中,而是保存在磁盘上。这有助于减轻队列对内存的压力,适用于处理大量消息的场景。
  2. 按需加载消息:当消费者需要消费消息时,RabbitMQ会将消息从磁盘加载到内存中再传递给消费者。这种按需加载的方式可以有效地管理资源使用。
  3. 更好的可靠性:由于消息被保存在磁盘上,即使节点宕机,消息也不会丢失。这提高了队列的可靠性。
  4. 更高的吞吐量:因为不需要将所有消息一次性加载到内存中,lazy queue可以处理更高的消息吞吐量。
配置lazy queue
  1. image-20240525131748074

  2. 通过Java代码

     @RabbitListener(bindings = @QueueBinding(
                value = @Queue(
                        name = "lazy.queue2",
                        durable = "true",
                        arguments = @Argument(name = "x-queue-mode", value = "lazy")
                ),
                exchange = @Exchange(name = "lazy.exchange", type = ExchangeTypes.FANOUT)
    
        ))
        public void listenLazyQueue(String msg) {
            System.out.println("lazy.queue" + msg);
        }
    

配置为lazy queue之后,直接写入到磁盘之中,不经过内存,速度很快。

image-20240525131207154

优点是我的服务器没有爆掉了。

消费者可靠性

消费者在处理消息后会被MQ回执,告诉消息处理的状态,分别有三种类型的回执

  • ack: MQ收到后会从队列中删除消息

  • nack: MQ收到后会再次投递消息

  • reject: MQ收到后会删除消息(代表消息有问题,消费者拒收)

SpringAMQP已经实现了消费者确认机制,只需要我们修改配置文件即可,默认机制是none

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: 

消费者确认机制

  • none: 消费者收到消息后立即返回ack,不建议使用,当出现异常时会造成消息的丢失
  • mannul: 手动配置确认机制
  • auto: 使用spring的确认机制
    • 正常处理ack
    • 异常处理nack
    • 校验异常reject

当配置auto机制之后,消费者端出现异常,会返回nack,此时消息返回到MQ之中,MQ再次投递消息,消费者端再次出现异常,此时会陷入无限的循环之中,为了避免这种情况。我们可以开启消费者重试机制

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: auto
        retry:
          enabled: true

消费者重试机制

当消息失败后,不会立即向MQ返回nack,而是会在本地进行重试,重试后在根据一定的策略返回给MQ。

  • RejectAndDontRequeueRecoverer: 重试耗尽后,直接reject, 丢弃消息,默认重试机制
  • ImmediateRequeueMessageRevoverer: 重试耗尽后,重新入队
  • RepublishMessageRecoverer: 重试耗尽后,将失败消息投递到指定交换机(推荐使用)

配置RepublishMessageRecoverer机制

package com.forzlp.cloud.config;

import lombok.AllArgsConstructor;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * Description: Rabbit消费者失败重试机制配置
 */
@Configuration
@AllArgsConstructor
// 开启这个配置时,这个配置类才会生效
@ConditionalOnProperty(prefix = "spring.rabbitmq.listener.simple.retry", name = "enabled", havingValue = "true")
public class RabbitMQConsumerRetryConfig {

    private RabbitTemplate rabbitTemplate;
    @Bean
    public DirectExchange errorExchange() {
        return new DirectExchange("error.exchange");
    }
    @Bean
    public Queue errorQueue() {
        return new Queue("error.queue");
    }
    @Bean
    public Binding errorBinding(DirectExchange errorExchange, Queue errorQueue) {
        return BindingBuilder
                .bind(errorQueue)
                .to(errorExchange)
                .with("error");
    }

    @Bean
    public MessageRecoverer messageRecoverer() {
        return new RepublishMessageRecoverer(rabbitTemplate, "error.exchange", "error");
    }
}

消费幂等性问题

幂等性问题就是当消费者重复消费的问题。

  • 我们可以给消息设置一个唯一ID,用于判断重复的消息并且放弃处理

    • @Bean
      public MessageConverter jsonMessageConverter() {
          Jackson2JsonMessageConverter jjmc = new Jackson2JsonMessageConverter();
          jjmc.setCreateMessageIds(true);
          return jjmc;
      }
      
  • 业务层面解决

    • 消费消息时,查询数据库判断是否已经消费过。

延迟消息

延迟消息: 生产者发送消息时指定一个时间,消费者不会立即消费消息,而是在指定时间之后才消费消息

RabbitMQ中的延迟消息主要用于以下几种业务场景:

  1. 定时任务:
    • 将需要定时执行的任务发送到延迟队列中,在指定的时间后自动被消费。
    • 例如:定期发送邮件提醒、定期清理过期数据等。
  2. 预定功能:
    • 用户预定某个服务或商品,系统将预定信息发送到延迟队列中,在预定时间到达时触发相关的业务逻辑。
    • 例如:酒店房间预订、会议室预订等。
  3. 订单支付超时处理:
    • 用户下单后,系统将订单信息发送到延迟队列中,如果在指定时间内未支付,则自动取消订单。
    • 可以避免订单长时间占用系统资源。

死信队列

死信:MQ之中的消息在以下几种情况下会成为死信

  • 消费者返回reject或nack,并且队列配置requeue为false
  • 消息经过了过期时间还没有被消费
  • 队列中消息满了,最早的消息会成为死信

死信在默认情况下会被直接丢弃,但是我们可以给队列配置一个死信交换机,将死信传递到死信交换机之中。