微服务学习高级篇【5】之服务异步通信(rabbitmq的高级特性)

news2024/10/6 20:32:19

文章目录

  • 环境准备:MQ部署【docker环境】
  • 消息可靠性
    • 生产者消息确认
      • 项目配置
      • 定义Return回调和ConfirmCallback
    • 消息持久化
      • 交换机持久化
      • 队列持久化
      • 消息持久化
    • 消费者消息确认
      • none模式演示
    • 消费失败重试机制
      • 本地重试
      • 失败策略
    • 总结
  • 死信交换机
    • 初识死信交换机
    • 死信交换机接收死信演示
    • TTL(Time-To-Live)
    • 延迟队列
      • 安装DelayExchange插件
      • 使用插件
      • DelayExchange原理
      • 使用DelayExchange
    • 惰性队列
      • 消息堆积问题
    • 惰性队列
      • 基于命令行设置lazy-queue[了解]
      • 基于@Bean声明lazy-queue
      • 基于@RabbitListener声明LazyQueue
      • 实践
  • MQ集群
    • 集群分类
    • 普通集群
      • 集群结构和特征
      • 普通集群的部署
        • 获取cookie
        • 准备集群配置
        • 启动集群
        • 测试
        • 数据共享测试
    • 镜像模式
      • 集群结构和特征
        • 镜像模式的配置
        • 测试exactly模式
        • 测试数据共享
        • 测试高可用
    • 仲裁队列
      • 集群特征
      • 部署:添加仲裁队列
      • Java代码创建仲裁队列
      • SpringAMQP连接MQ集群
      • 集群扩容
        • 加入集群
        • 增加仲裁队列副本

  • MQ的一些常见问题
    在这里插入图片描述

环境准备:MQ部署【docker环境】

  1. 下载镜像
    docker pull rabbitmq:3.8-management
    
    在这里插入图片描述
  2. 安装MQ
    docker run \
     -e RABBITMQ_DEFAULT_USER=itcast \
     -e RABBITMQ_DEFAULT_PASS=123321 \
     -v mq-plugins:/plugins \
     --name mq1 \
     --hostname mq1 \
     -p 15672:15672 \
     -p 5672:5672 \
     -d \
     rabbitmq:3.8-management
    
  3. 测试
    [root@kongyue ~]# docker ps
    CONTAINER ID   IMAGE                     COMMAND                   CREATED          STATUS          PORTS                                                                                                                                                 NAMES
    7eea857604d3   rabbitmq:3.8-management   "docker-entrypoint.s…"   17 seconds ago   Up 16 seconds   4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, :::5672->5672/tcp, 15671/tcp, 15691-15692/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp, :::15672->15672/tcp   mq1
    

消息可靠性

  • 消息从发送,到消费者接收,会经理多个过程。其中的每一步都可能导致消息丢失,常见的丢失原因包括:
    在这里插入图片描述
  • 针对这些问题,RabbitMQ分别给出了解决方案:
    • 生产者确认机制
    • mq持久化
    • 消费者确认机制
    • 失败重试机制

生产者消息确认

RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。这种机制必须给每个消息指定一个唯一ID。消息发送到MQ以后,会返回一个结果给发送者,表示消息是否处理成功。返回结果有两种方式:

  • publisher-confirm,发送者确认
    • 消息成功投递到交换机,返回ack
    • 消息未投递到交换机,返回nack
    • 消息发送过程中出现异常,没有收到回执
  • publisher-return,发送者回执
    • 消息投递到交换机,但是没有路由到队列。返回ACK,及路由失败原因。
      在这里插入图片描述
  • 注意:确认机制发送消息时,需要给每个消息设置一个全局唯一id,以区分不同消息,避免ack冲突

项目配置

  • 首先,修改生产者服务中的application.yml文件,添加下面的内容:
    spring:
      rabbitmq:
        publisher-confirm-type: correlated #生产者确认类型
        publisher-returns: true #开启publish-return功能
        template:
          mandatory: true #定义消息路由失败时的策略
    
    • publish-confirm-type:开启publisher-confirm,这里支持两种类型:
      • simple:同步等待confirm结果,直到超时
      • correlated:异步回调,定义ConfirmCallback,MQ返回结果时会回调这个ConfirmCallback
    • publish-returns:开启publish-return功能,同样是基于callback机制,不过是定义ReturnCallback
    • template.mandatory:定义消息路由失败时的策略。true,则调用ReturnCallback;false:则直接丢弃消息

定义Return回调和ConfirmCallback

  • 消息成功发送到exchange,但没有路由到queue,调用ReturnCallback

  1. 每个RabbitTemplate只能配置一个ReturnCallback,因此需要在项目加载时配置:
  • 修改生产者服务,添加一个:
    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;
    
    @Slf4j
    @Configuration
    public class CommonConfig implements ApplicationContextAware {
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            // 获取RabbitTemplate
            RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
            // 设置ReturnCallback
            rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
                // 投递失败,记录日志
                log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",
                         replyCode, replyText, exchange, routingKey, message.toString());
                // 如果有业务需要,可以重发消息
            });
        }
    }
    

  1. ConfirmCallback可以在发送消息时指定,因为每个业务处理confirm成功或失败的逻辑不一定相同。
  • 定义一个单元测试方法:
    public void testSendMessage2SimpleQueue() throws InterruptedException {
        // 1.消息体
        String message = "hello, spring amqp!";
        // 2.全局唯一的消息ID,需要封装到CorrelationData中
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        // 3.添加callback
        correlationData.getFuture().addCallback(
            result -> {
                if(result.isAck()){
                    // 3.1.ack,消息成功
                    log.debug("消息发送成功, ID:{}", correlationData.getId());
                }else{
                    // 3.2.nack,消息失败
                    log.error("消息发送失败, ID:{}, 原因{}",correlationData.getId(), result.getReason());
                }
            },
            ex -> log.error("消息发送异常, ID:{}, 原因{}",correlationData.getId(),ex.getMessage())
        );
        // 4.发送消息
        rabbitTemplate.convertAndSend("task.direct", "task", message, correlationData);
    
        // 休眠一会儿,等待ack回执
        Thread.sleep(2000);
    }
    

消息持久化

  • MQ默认是内存存储消息,要想确保消息在RabbitMQ中安全保存,必须开启消息持久化机制,确保缓存在MQ中的消息不丢失。
    • 交换机持久化
    • 队列持久化
    • 消息持久化

交换机持久化

  • SpringAMQP中可以通过代码指定交换机持久化:
    @Bean
    public DirectExchange simpleExchange(){
        // 三个参数:交换机名称、是否持久化、当没有queue与其绑定时是否自动删除
        return new DirectExchange("simple.direct", true, false);
    }
    
    • 默认情况下,由SpringAMQP声明的交换机都是持久化的。
  • 在RabbitMQ控制台看到持久化的交换机都会带上D的标示:在这里插入图片描述

队列持久化

  • SpringAMQP中可以通过代码指定交换机持久化:
    @Bean
    public Queue simpleQueue(){
        // 使用QueueBuilder构建队列,durable就是持久化的
        return QueueBuilder.durable("simple.queue").build();
    }
    
    • 默认情况下,由SpringAMQP声明的队列都是持久化的。
  • RabbitMQ控制台看到持久化的队列都会带上D的标示:
    在这里插入图片描述

消息持久化

利用SpringAMQP发送消息时,可以设置消息的属性(MessageProperties),指定delivery-mode:

  • 1:非持久化
  • 2:持久化
 @Test
    public void testDuarbleMessage() {
        //创建消息
        Message message = MessageBuilder.withBody("hello, ttl queue".getBytes(StandardCharsets.UTF_8))
                .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                .build();
        //消息ID,需要封装到需要封装到CorrelationData中
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        //发送消息
        rabbitTemplate.convertAndSend("simple.queue",message,correlationData);
        log.debug("发送消息成功!");
    }
  • 默认情况下,SpringAMQP发出的任何消息都是持久化的,不用特意指定。

消费者消息确认

  • RabbitMQ是阅后即焚机制,RabbitMQ确认消息被消费者消费后会立刻删除。而RabbitMQ支持消费者确认机制,即:消费者处理消息后可以向MQ发送ack回执,MQ收到ack回执后才会删除该消息。

设想这样的场景:

  • 1)RabbitMQ投递消息给消费者
  • 2)消费者获取消息后,返回ACK给RabbitMQ
  • 3)RabbitMQ删除消息
  • 4)消费者宕机,消息尚未处理
    这样,消息就丢失了。因此消费者返回ACK的时机非常重要。

而SpringAMQP则允许配置三种确认模式:

  • manual:手动ack,需要在业务代码结束后,调用api发送ack。
  • auto:自动ack,由spring监测listener代码是否出现异常,没有异常则返回ack;抛出异常则返回nack
  • none:关闭ack,MQ假定消费者获取消息后会成功处理,因此消息投递后立即被删除

由此可知:

  • none模式下,消息投递是不可靠的,可能丢失
  • auto模式类似事务机制,出现异常时返回nack,消息回滚到mq;没有异常,返回ack
  • manual:自己根据业务情况,判断什么时候该ack
    一般,我们都是使用默认的auto即可。

none模式演示

  • 修改确认机制:
    spring:
      rabbitmq:
        listener:
          simple:
            acknowledge-mode: auto # 关闭ack
    
  • 在异常位置打断点,再次发送消息,程序卡在断点时,可以发现此时消息状态为unack(未确定状态):
    在这里插入图片描述
  • 抛出异常后,因为Spring会自动返回nack,所以消息恢复至Ready状态,并且没有被RabbitMQ删除:
    在这里插入图片描述

消费失败重试机制

  • 当消费者出现异常后,消息会不断requeue(重入队)到队列,再重新发送给消费者,然后再次异常,再次requeue,无限循环,导致mq的消息处理飙升,带来不必要的压力:
    在这里插入图片描述 在这里插入图片描述

本地重试

  • 利用Spring的retry机制,在消费者出现异常时利用本地重试,而不是无限制的requeue到mq队列。
  • 修改consumer服务的application.yml文件,添加内容:
    spring:
      rabbitmq:
        listener:
          simple:
            retry:
              enabled: true # 开启消费者失败重试
              initial-interval: 1000 # 初识的失败等待时长为1秒
              multiplier: 1 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval
              max-attempts: 3 # 最大重试次数
              stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false
    
  • 重启consumer服务,重复测试:
    • SpringAMQP会抛出异常AmqpRejectAndDontRequeueException
    • 查看RabbitMQ控制台,发现消息被删除,说明最后SpringAMQP返回的是ack,mq删除消息
      在这里插入图片描述

结论:

  • 开启本地重试时,消息处理过程中抛出异常,不会requeue到队列,而是在消费者本地重试
  • 重试达到最大次数后,Spring会返回ack,消息会被丢弃

失败策略

在开启重试模式后,重试次数耗尽,如果消息依然失败,则需要有MessageRecoverer接口来处理,它包含三种不同的实现:

  • RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式
  • ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队
  • RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机

在这里插入图片描述

  1. 在consumer服务中定义处理失败消息的交换机和队列

    @Bean
    public DirectExchange errorMessageExchange(){
        return new DirectExchange("error.direct");
    }
    @Bean
    public Queue errorQueue(){
        return new Queue("error.queue", true);
    }
    @Bean
    public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){
        return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error");
    }
    
  2. 定义一个RepublishMessageRecoverer,关联队列和交换机

    @Bean
    public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
        return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
    }
    

  • 完整代码:
    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.context.annotation.Bean;
    
    @Configuration
    public class ErrorMessageConfig {
        @Bean
        public DirectExchange errorMessageExchange(){
            return new DirectExchange("error.direct");
        }
        @Bean
        public Queue errorQueue(){
            return new Queue("error.queue", true);
        }
        @Bean
        public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){
            return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error");
        }
    
        @Bean
        public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
            return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
        }
    }
    
  • 重启测试:
    • 重启项目,利用之前的模拟异常,进行测试,在错误队列中可以看到具体的异常信息
@RabbitListener(queues = "simple.queue")
    public void listenSimpleQueue(String msg) {
        System.out.println("消费者接收到simple.queue的消息:【" + msg + "】");
        //模拟异常
        System.out.println(1/0);
        log.debug("消息处理完毕!!!");
    }

在这里插入图片描述
在这里插入图片描述

总结

如何确保RabbitMQ消息的可靠性?

  • 开启生产者确认机制,确保生产者的消息能到达队列
  • 开启持久化功能,确保消息未消费前在队列中不会丢失
  • 开启消费者确认机制为auto,由spring确认消息处理成功后完成ack
  • 开启消费者失败重试机制,并设置MessageRecoverer,多次重试失败后将消息投递到异常交换机,交由人工处理

死信交换机

初识死信交换机

当一个队列中的消息满足下列情况之一时,可以成为死信(dead letter):

  • 消费者使用basic.reject或 basic.nack声明消费失败,并且消息的requeue参数设置为false
  • 消息是一个过期消息,超时无人消费
  • 要投递的队列消息满了,无法投递

如果这个包含死信的队列配置了dead-letter-exchange属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机中,而这个交换机称为死信交换机(Dead Letter Exchange,检查DLX)。

在这里插入图片描述
队列将死信投递给死信交换机时,必须知道两个信息:

  • 死信交换机名称
  • 死信交换机与死信队列绑定的RoutingKey

这样才能确保投递的消息能到达死信交换机,并且正确的路由到死信队列。

在这里插入图片描述

死信交换机接收死信演示

  • 给simple.queue添加一个死信交换机,给死信交换机绑定一个队列。这样消息变成死信后也不会丢弃,而是最终投递到死信交换机,路由到与死信交换机绑定的队列。
  • 在consumer服务中,定义一组死信交换机、死信队列:
// 声明普通的 simple.queue队列,并且为其指定死信交换机:dl.direct
@Bean
public Queue simpleQueue2(){
    return QueueBuilder.durable("simple.queue") // 指定队列名称,并持久化
        .deadLetterExchange("dl.direct") // 指定死信交换机
        .build();
}
// 声明死信交换机 dl.direct
@Bean
public DirectExchange dlExchange(){
    return new DirectExchange("dl.direct", true, false);
}
// 声明存储死信的队列 dl.queue
@Bean
public Queue dlQueue(){
    return new Queue("dl.queue", true);
}
// 将死信队列 与 死信交换机绑定
@Bean
public Binding dlBinding(){
    return BindingBuilder.bind(dlQueue()).to(dlExchange()).with("simple");
}

TTL(Time-To-Live)

一个队列中的消息如果超时未消费,则会变为死信,超时分为两种情况:

  • 消息所在的队列设置了超时时间
  • 消息本身设置了超时时间
    在这里插入图片描述

  1. 在consumer服务的SpringRabbitListener中,定义一个新的消费者,并且声明 死信交换机、死信队列
    @RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "dl.ttl.queue", durable = "true"),
        exchange = @Exchange(name = "dl.ttl.direct"),
        key = "ttl"
    ))
    public void listenDlQueue(String msg){
        log.info("接收到 dl.ttl.queue的延迟消息:{}", msg);
    }
    
  2. 声明一个队列,并且指定TTL&声明交换机,将ttl与交换机绑定
    @Bean
    public Queue ttlQueue(){
        return QueueBuilder.durable("ttl.queue") // 指定队列名称,并持久化
            .ttl(10000) // 设置队列的超时时间,10秒
            .deadLetterExchange("dl.ttl.direct") // 指定死信交换机
            .build();
    }
    @Bean
    public DirectExchange ttlExchange(){
        return new DirectExchange("ttl.direct");
    }
    @Bean
    public Binding ttlBinding(){
        return BindingBuilder.bind(ttlQueue()).to(ttlExchange()).with("ttl");
    }
    
  3. 发送消息,但是不要指定TTL
    @Test
    public void testTTLQueue() {
        // 创建消息
        String message = "hello, ttl queue";
        // 消息ID,需要封装到CorrelationData中
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        // 发送消息
        rabbitTemplate.convertAndSend("ttl.direct", "ttl", message, correlationData);
        // 记录日志
        log.debug("发送消息成功");
    }
    
  • 发送消息的日志:
    在这里插入图片描述
  • 查看下接收消息的日志:
    在这里插入图片描述
  • 因为队列的TTL值是是10秒。可以看到消息发送与接收之间的时差刚好是10秒。
  1. 发送消息时,设定TTL
    @Test
    public void testTTLMsg() {
        // 创建消息
        Message message = MessageBuilder
            .withBody("hello, ttl message".getBytes(StandardCharsets.UTF_8))
            .setExpiration("5000")
            .build();
        // 消息ID,需要封装到CorrelationData中
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        // 发送消息
        rabbitTemplate.convertAndSend("ttl.direct", "ttl", message, correlationData);
        log.debug("发送消息成功");
    }
    
  • 查看发送消息日志:
    15:12:16:804 DEBUG 21600 --- [           main] cn.itcast.mq.spring.SpringAmqpTest       : 发送消息成功
    
  • 接收消息日志:
    15:12:21:810  INFO 14572 --- [ntContainer#0-1] c.i.mq.listener.SpringRabbitListener     : 接收到 dl.ttl.queue的延迟消息:hello, ttl message
    
  • 当队列、消息都设置TTL时,任意一个到期就会成为死信。

延迟队列

  • 延迟队列(Delay Queue)模式:利用TTL结合死信交换机,实现消息发出后,消费者延迟收到消息的效果。
    在这里插入图片描述
  • 延迟队列的使用场景包括:
    • 延迟发送短信
    • 用户下单,如果用户在15 分钟内未支付,则自动取消
    • 预约工作会议,20分钟后自动通知所有参会人员

因为延迟队列的需求非常多,所以RabbitMQ的官方也推出DelayExchange插件,原生支持延迟队列效果。参考RabbitMQ的插件列表页面
在这里插入图片描述

  • 使用方式可以参考官网地址

安装DelayExchange插件

  1. 3.8.9版本下载地址
  • 注意DelayExchange插件和RabbitMQ的版本兼容问题:DelayExchange3.8.9插件对应RabbitMQ的3.8.5以上版本。
  1. 上传插件
    • 因为是基于Docker安装,所以需要先查看RabbitMQ的插件目录对应的数据卷。
    docker volume inspect mq-plugins
    
    在这里插入图片描述
    • 接下来,将插件上传到这个目录即可:
      在这里插入图片描述
  2. 安装插件
    • 进入MQ容器内部来执行安装,执行一下命令(注意容器名称)
    docker exec -it mq1 bash
    
    • 进入容器内部后,执行下面命令开启插件:
    rabbitmq-plugins enable rabbitmq_delayed_message_exchange
    
    • 结果如下:
    [root@kongyue ~]# docker exec -it mq1 bash
    root@mq1:/# rabbitmq-plugins enable rabbitmq_delayed_message_exchange
    Enabling plugins on node rabbit@mq1:
    rabbitmq_delayed_message_exchange
    The following plugins have been configured:
      rabbitmq_delayed_message_exchange
      rabbitmq_management
      rabbitmq_management_agent
      rabbitmq_prometheus
      rabbitmq_web_dispatch
    Applying plugin configuration to rabbit@mq1...
    The following plugins have been enabled:
      rabbitmq_delayed_message_exchange
    
    started 1 plugins.
    root@mq1:/# exit
    exit
    

使用插件

DelayExchange插件的原理是对官方原生的Exchange做了功能的升级:

  • 将DelayExchange接受到的消息暂存在内存中(官方的Exchange是无法存储消息的)

  • 在DelayExchange中计时,超时后才投递消息到队列中

  • 在RabbitMQ的管理平台声明一个DelayExchange
    在这里插入图片描述

  • 消息延迟时间需要在发送消息的时候指定
    在这里插入图片描述

DelayExchange原理

DelayExchange需要将一个交换机声明为delayed类型。当我们发送消息到delayExchange时,流程如下:

  • 接收消息
  • 判断消息是否具备x-delay属性
  • 如果有x-delay属性,说明是延迟消息,持久化到硬盘,读取x-delay值,作为延迟时间
  • 返回routing not found结果给消息发送者
  • x-delay时间到期后,重新投递消息到指定队列

使用DelayExchange

  1. 插件的使用也非常简单:声明一个交换机,交换机的类型可以是任意类型,只需要设定delayed属性为true即可,然后声明队列与其绑定即可。

    • 注解方式(推荐):
    @RabbitListener(bindings = @QueueBinding(
                value = @Queue(name = "delay.queue",durable = "true"),
                exchange = @Exchange(name = "delay.direct",delayed = "true"),
                key = "delay"
        ))
        public void listenDelayedQueue(String msg) {
            log.info("接收到delay.queue的延迟消息:{}"+msg);
        }
    
    • 基于@Bean的方式:
      在这里插入图片描述
  2. 发送消息

    • 发送消息时,一定要携带x-delay属性,指定延迟的时间:
    @Test
        public void testDelayedMsg() {
            // 创建消息
            Message message = MessageBuilder
                    .withBody("hello, delay message".getBytes(StandardCharsets.UTF_8))
                    .setHeader("x-delay",10000)
                    .build();
            // 消息ID,需要封装到CorrelationData中
            CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
            // 发送消息
            rabbitTemplate.convertAndSend("delay.direct", "delay", message, correlationData);
            log.debug("发送消息成功");
        }
    

  • 由于交换机将信息存入硬盘,返回routing not found结果给消息发送者,所以会出现以下的报错信息
    15:24:47:054 DEBUG 6124 --- [           main] cn.itcast.mq.spring.SpringAmqpTest       : 
    发送消息成功
    15:24:47:056  INFO 6124 --- [nectionFactory1] cn.itcast.mq.config.CommonConfig         : 
    消息发送失败,应答码312,原因NO_ROUTE,交换机delay.direct,路由键delay,
    消息(Body:'[B@18f05d55(byte[18])' MessageProperties [headers={spring_returned_message_correlation=aac9ebb8-f8eb-4c39-a64b-c1bf35b0c639}, 
    contentType=application/octet-stream, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, receivedDelay=10000, deliveryTag=0])
    
  1. 解决报错问题

    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;
    
    @Slf4j
    @Configuration
    public class CommonConfig implements ApplicationContextAware {
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            // 获取RabbitTemplate
            RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
            // 设置ReturnCallback
            rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
                //判断是否有延迟消息
                if(message.getMessageProperties().getReceivedDelay()>0) {
                    //判断是一个延迟消息,忽视错误
                    return;
                }
                // 投递失败,记录日志
                log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",
                         replyCode, replyText, exchange, routingKey, message.toString());
                // 如果有业务需要,可以重发消息
            });
        }
    }
    

惰性队列

消息堆积问题

  • 当生产者发送消息的速度超过了消费者处理消息的速度,就会导致队列中的消息堆积,直到队列存储消息达到上限。之后发送的消息就会成为死信,可能会被丢弃,这就是消息堆积问题。

    在这里插入图片描述

  • 解决消息堆积有三种种思路:

    • 增加更多消费者,提高消费速度
    • 在消费者内开启线程池加快消息处理速度
    • 扩大队列容积,提高堆积上限

惰性队列

从RabbitMQ的3.6.0版本开始,就增加了Lazy Queues的概念,也就是惰性队列。惰性队列的特征如下:

  • 接收到消息后直接存入磁盘而非内存
  • 消费者要消费消息时才会从磁盘中读取并加载到内存
  • 支持数百万条的消息存储

基于命令行设置lazy-queue[了解]

而要设置一个队列为惰性队列,只需要在声明队列时,指定x-queue-mode属性为lazy即可。可以通过命令行将一个运行中的队列修改为惰性队列:

rabbitmqctl set_policy Lazy "^lazy-queue$" '{"queue-mode":"lazy"}' --apply-to queues  

命令解读:

  • rabbitmqctl :RabbitMQ的命令行工具
  • set_policy :添加一个策略
  • Lazy :策略名称,可以自定义
  • "^lazy-queue$" :用正则表达式匹配队列的名字
  • '{"queue-mode":"lazy"}' :设置队列模式为lazy模式
  • --apply-to queues :策略的作用对象,是所有的队列

基于@Bean声明lazy-queue

@Bean
    public Queue lazyQueue() {
        return QueueBuilder.durable("lazy.queue")
                .lazy()
                .build();
    }

基于@RabbitListener声明LazyQueue

@RabbitListener(queuesToDeclare = cQueue(
	name = "lazy.queue",
	arguments = @Argument(name = "x-queue-mode" ,value = "lazy")
))
public void listenLazyQueue(string msg){
	log.info("接收到lazy.queue的消息:{",msg);
}

实践

  1. 新建LazyConfig类
    import org.springframework.amqp.core.Queue;
    import org.springframework.amqp.core.QueueBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class LazyConfig {
        @Bean
        public Queue lazyQueue() {
            return QueueBuilder.durable("lazy.queue")
                    .lazy()
                    .build();
        }
        @Bean
        public Queue normalQueue() {
            return QueueBuilder.durable("normal.queue").build();
        }
    
    }
    
  • 这里未显式指定交换机,默认会走springAMQP的默认交换机

    NameTypeFeaturesMessage rate inMessage rate out
    (AMQP default)directD0.00/s0.00/s
  • 默认交换机隐式绑定到每个队列,路由键等于队列名。不能显式地绑定到默认交换,也不能从默认交换取消绑定。也不能删除。

@Test
    public void testLazyQueue() {
        for (int i = 0; i < 100_000; i++) {
        Message message = MessageBuilder.withBody("hello lazyQueue".getBytes(StandardCharsets.UTF_8))
                .build();

        rabbitTemplate.convertAndSend("lazy.queue",message);
        }
    }

在这里插入图片描述

MQ集群

集群分类

RabbitMQ的是基于Erlang语言编写,而Erlang又是一个面向并发的语言,天然支持集群模式。RabbitMQ的集群有两种模式:

  • 普通集群:是一种分布式集群,将队列分散到集群的各个节点,从而提高整个集群的并发能力。普通模式集群不进行数据同步,每个MQ都有自己的队列、数据信息(其它元数据信息如交换机等会同步)。例如我们有2个MQ:mq1,和mq2,如果你的消息在mq1,而你连接到了mq2,那么mq2会去mq1拉取消息,然后返回给你。如果mq1宕机,消息就会丢失。

  • 镜像集群:是一种主从集群,普通集群的基础上,添加了主从备份功能,提高集群的数据可用性。与普通模式不同,队列会在各个mq的镜像节点之间同步,因此你连接到任何一个镜像节点,均可获取到消息。而且如果一个节点宕机,并不会导致数据丢失。不过,这种方式增加了数据同步的带宽消耗。

镜像集群虽然支持主从,但主从同步并不是强一致的,某些情况下可能有数据丢失的风险。因此在RabbitMQ的3.8版本以后,推出了新的功能:仲裁队列来代替镜像集群,底层采用Raft协议确保主从的数据一致性。

普通集群

集群结构和特征

普通集群,或者叫标准集群(classic cluster),具备下列特征:

  • 会在集群的各个节点间共享部分数据,包括:交换机、队列元信息。不包含队列中的消息。
  • 当访问集群某节点时,如果队列不在该节点,会从数据所在节点传递到当前节点并返回
  • 队列所在节点宕机,队列中的消息就会丢失
    在这里插入图片描述
    在这里插入图片描述

普通集群的部署

  • 计划部署3节点的mq集群
主机名控制台端口amqp通信端口
mqNode18081 —> 156728071 —> 5672
mqNode28082 —> 156728072 —> 5672
mqNode38083 —> 156728073 —> 5672
  • 集群中的节点标示默认都是:rabbit@[hostname],因此以上三个节点的名称分别为:
    • rabbit@mqNode1
    • rabbit@mqNode2
    • rabbit@mqNode3

获取cookie

  • RabbitMQ底层依赖于Erlang,而Erlang虚拟机就是一个面向分布式的语言,默认就支持集群模式。集群模式中的每个RabbitMQ 节点使用 cookie 来确定它们是否被允许相互通信。

  • 要使两个节点能够通信,它们必须具有相同的共享秘密,称为Erlang cookie。cookie 只是一串最多 255 个字符的字母数字字符。

  • 每个集群节点必须具有相同的 cookie。实例之间也需要它来相互通信。

  • 先在之前启动的mq容器中获取一个cookie值,作为集群的cookie。执行下面的命令:

    docker exec -it mq cat /var/lib/rabbitmq/.erlang.cookie
    
  • cookie值如下:

    FXZMCVGLBIXZCDEMMVZQ
    
  • 接下来,停止并删除当前的mq容器,我们重新搭建集群

    docker rm -f mq
    

准备集群配置

echo FXZMCVGLBIXZCDEMMVZQ> .erlang.cookie
  • 在/tmp/rabbitMqCluster目录新建一个配置文件 rabbitmq.conf:
    cd /tmp/rabbitMqCluster
    touch rabbitmq.conf
    
  • 文件内容如下:
    loopback_users.guest = false
    listeners.tcp.default = 5672
    cluster_formation.peer_discovery_backend = rabbit_peer_discovery_classic_config
    cluster_formation.classic_config.nodes.1 = rabbit@mqNode1
    cluster_formation.classic_config.nodes.2 = rabbit@mqNode2
    cluster_formation.classic_config.nodes.3 = rabbit@mqNode3
    
  • 再创建一个文件,记录cookie
    cd /tmp/rabbitMqCluster
    # 创建cookie文件
    touch .erlang.cookie
    # 写入cookie 请使用自己生成的cookie
    echo "FXZMCVGLBIXZCDEMMVZQ" > .erlang.cookie
    # 修改cookie文件的权限
    chmod 600 .erlang.cookie
    
  • 准备三个目录,mqNode1、mqNode2、mqNode3:
    cd /tmp/rabbitMqCluster
    # 创建目录
    mkdir mqNode1 mqNode2 mqNode3
    
  • 然后拷贝rabbitmq.conf
    [root@kongyue rabbitMqCluster]# mkdir mqNode1 mqNode2 mqNode3
    [root@kongyue rabbitMqCluster]# cp .erlang.cookie mqNode1
    [root@kongyue rabbitMqCluster]# cp .erlang.cookie mqNode2
    [root@kongyue rabbitMqCluster]# cp .erlang.cookie mqNode3
    [root@kongyue rabbitMqCluster]# cp rabbitmq.conf mqNode1
    [root@kongyue rabbitMqCluster]# cp rabbitmq.conf mqNode2
    [root@kongyue rabbitMqCluster]# cp rabbitmq.conf mqNode3
    

启动集群

  • 创建一个网络:
    docker network create mq-net
    
  • 运行命令
    docker run -d --net mq-net \
    -v ${PWD}/mqNode1/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
    -v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
    -v mq-plugins:/plugins \
    -e RABBITMQ_DEFAULT_USER=itcast \
    -e RABBITMQ_DEFAULT_PASS=123321 \
    --name mqNode1 \
    --hostname mqNode1 \
    -p 8071:5672 \
    -p 8081:15672 \
    rabbitmq:3.8-management
    
    docker run -d --net mq-net \
    -v ${PWD}/mqNode2/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
    -v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
    -v mq-plugins:/plugins \
    -e RABBITMQ_DEFAULT_USER=itcast \
    -e RABBITMQ_DEFAULT_PASS=123321 \
    --name mqNode2 \
    --hostname mqNode2 \
    -p 8072:5672 \
    -p 8082:15672 \
    rabbitmq:3.8-management
    
    docker run -d --net mq-net \
    -v ${PWD}/mqNode3/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
    -v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
    -v mq-plugins:/plugins \
    -e RABBITMQ_DEFAULT_USER=itcast \
    -e RABBITMQ_DEFAULT_PASS=123321 \
    --name mqNode3 \
    --hostname mqNode3 \
    -p 8073:5672 \
    -p 8083:15672 \
    rabbitmq:3.8-management
    

测试

  • 登录任意节点,查看节点
    在这里插入图片描述
  • 在mq1这个节点上添加一个队列:
    在这里插入图片描述
  • 在mq2和mq3两个控制台也都能看到:
    在这里插入图片描述

数据共享测试

  • 利用控制台发送一条消息到这个队列
    在这里插入图片描述
    在这里插入图片描述

镜像模式

集群结构和特征

镜像集群:本质是主从模式,具备下面的特征:

  • 交换机、队列、队列中的消息会在各个mq的镜像节点之间同步备份。
  • 创建队列的节点被称为该队列的主节点,备份到的其它节点叫做该队列的镜像节点。
  • 一个队列的主节点可能是另一个队列的镜像节点
  • 所有操作都是主节点完成,然后同步给镜像节点
  • 主宕机后,镜像节点会替代成新的主

在这里插入图片描述
总结:

  • 镜像队列结构是一主多从(从就是镜像)
  • 所有操作都是主节点完成,然后同步给镜像节点
  • 主宕机后,镜像节点会替代成新的主(如果在主从同步完成前,主就已经宕机,可能出现数据丢失)
  • 不具备负载均衡功能,因为所有操作都会有主节点完成(但是不同队列,其主节点可以不同,可以利用这个提高吞吐量)

镜像模式的配置

  • 镜像模式的配置有3种模式:
ha-modeha-params效果
准确模式exactly队列的副本量count集群中队列副本(主服务器和镜像服务器之和)的数量。count如果为1意味着单个副本:即队列主节点。count值为2表示2个副本:1个队列主和1个队列镜像。换句话说:count = 镜像数量 + 1。如果群集中的节点数少于count,则该队列将镜像到所有节点。如果有集群总数大于count+1,并且包含镜像的节点出现故障,则将在另一个节点上创建一个新的镜像。
all(none)队列在群集中的所有节点之间进行镜像。队列将镜像到任何新加入的节点。镜像到所有节点将对所有群集节点施加额外的压力,包括网络I / O,磁盘I / O和磁盘空间使用情况。推荐使用exactly,设置副本数为(N / 2 +1)。
nodesnode names指定队列创建到哪些节点,如果指定的节点全部不存在,则会出现异常。如果指定的节点在集群中存在,但是暂时不可用,会创建节点到当前客户端连接到的节点。

  • 以rabbitmqctl命令作为案例来讲解配置语法。
  1. exactly模式
    rabbitmqctl set_policy ha-two "^two\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
    
  • rabbitmqctl set_policy:固定写法
  • ha-two:策略名称,自定义
  • "^two\.":匹配队列的正则表达式,符合命名规则的队列才生效,这里是任何以two.开头的队列名称
  • '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}': 策略内容
    • "ha-mode":"exactly":策略模式,此处是exactly模式,指定副本数量
    • "ha-params":2:策略参数,这里是2,就是副本数量为2,1主1镜像
    • "ha-sync-mode":"automatic":同步策略,默认是manual,即新加入的镜像节点不会同步旧的消息。如果设置为automatic,则新加入的镜像节点会把主节点中所有消息都同步,会带来额外的网络开销
  1. all模式
    rabbitmqctl set_policy ha-all "^all\." '{"ha-mode":"all"}'
    
  • ha-all:策略名称,自定义
  • "^all\.":匹配所有以all.开头的队列名
  • '{"ha-mode":"all"}':策略内容
    • "ha-mode":"all":策略模式,此处是all模式,即所有节点都会称为镜像节点
  1. nodes模式
    rabbitmqctl set_policy ha-nodes "^nodes\." '{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}'
    
  • rabbitmqctl set_policy:固定写法
  • ha-nodes:策略名称,自定义
  • "^nodes\.":匹配队列的正则表达式,符合命名规则的队列才生效,这里是任何以nodes.开头的队列名称
  • '{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}': 策略内容
    • "ha-mode":"nodes":策略模式,此处是nodes模式
    • "ha-params":["rabbit@mq1", "rabbit@mq2"]:策略参数,这里指定副本所在节点名称

测试exactly模式

  • 使用exactly模式的镜像,因为集群节点数量为3,因此镜像数量就设置为2,运行下面的命令:
    docker exec -it mqNode1 rabbitmqctl set_policy ha-two "^two\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
    
[root@kongyue rabbitMqCluster]# docker exec -it mqNode1 rabbitmqctl set_policy ha-two "^two\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
Setting policy "ha-two" for pattern "^two\." to "{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}" with priority "0" for vhost "/" ...

  • 创建一个新的队列:
    在这里插入图片描述
  • 在任意一个mq控制台查看队列:
    在这里插入图片描述

测试数据共享

-  给two.queue发送一条消息:

在这里插入图片描述

  • 然后在mq1、mq2、mq3的任意控制台查看消息:
    在这里插入图片描述

测试高可用

  • 让two.queue的主节点mq1宕机:
    docker stop mqNode1
    
  • 查看集群状态:
    在这里插入图片描述
  • 查看队列状态:
    在这里插入图片描述

在这里插入图片描述

仲裁队列

集群特征

仲裁队列:仲裁队列是3.8版本以后才有的新功能,用来替代镜像队列,具备下列特征:

  • 与镜像队列一样,都是主从模式,支持主从数据同步
  • 使用非常简单,没有复杂的配置
  • 主从同步基于Raft协议,强一致

部署:添加仲裁队列

  • 注意:进入每个rabbitMQ容器内部进行开启延迟DelayExchange插件
  • 在任意控制台添加一个队列,一定要选择队列类型为Quorum类型。
    在这里插入图片描述
  • 在任意控制台查看队列:
    在这里插入图片描述
    • 仲裁队列的 + 2字样。代表这个队列有2个镜像节点。
  • 因为仲裁队列默认的镜像数为5。如果集群有7个节点,那么镜像数肯定是5;现在集群只有3个节点,因此镜像数量就是3.
  • 查看队列在各个节点的详细信息
    在这里插入图片描述

Java代码创建仲裁队列

@Bean
public Queue quorumQueue() {
    return QueueBuilder
        .durable("quorum.queue") // 持久化
        .quorum() // 仲裁队列
        .build();
}

SpringAMQP连接MQ集群

spring:
  rabbitmq:
    addresses: 192.168.188.112:8071, 192.168.188.112:8072, 192.168.188.112:8073
    username: itcast
    password: 123321
    virtual-host: /
  • 重启项目,查看队列:
    在这里插入图片描述

集群扩容

加入集群

  1. 启动一个新的MQ容器:
    docker run -d --net mq-net \
    -v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
    -e RABBITMQ_DEFAULT_USER=itcast \
    -e RABBITMQ_DEFAULT_PASS=123321 \
    --name mqNode4 \
    --hostname mqNode4 \
    -p 8074:15672 \
    -p 8084:15672 \
    rabbitmq:3.8-management
    
  2. 进入容器控制台:
    docker exec -it mqNode4 bash
    
  3. 停止mq进程
    rabbitmqctl stop_app
    
  4. 重置RabbitMQ中的数据:
    rabbitmqctl reset
    
  5. 加入mqNode1:
    rabbitmqctl join_cluster rabbit@mqNode1
    
  6. 再次启动mq进程
    rabbitmqctl start_app
    
[root@kongyue rabbitMqCluster]# docker exec -it mqNode4 bash
root@mqNode4:/# rabbitmqctl stop_app
Stopping rabbit application on node rabbit@mqNode4 ...
root@mqNode4:/# rabbitmqctl reset
Resetting node rabbit@mqNode4 ...
root@mqNode4:/# rabbitmqctl join_cluster rabbit@mqNode1
Clustering node rabbit@mqNode4 with rabbit@mqNode1
root@mqNode4:/# rabbitmqctl start_app
Starting node rabbit@mqNode4 ...
  • 结果:
    在这里插入图片描述

增加仲裁队列副本

  • 先查看下quorum.queue这个队列目前的副本情况,进入mqNode1容器,执行命令:
[root@kongyue rabbitMqCluster]# docker exec -it mqNode1 bash
root@mqNode1:/# rabbitmq-queues quorum_status "quorum.queue"
Status of quorum queue quorum.queue on node rabbit@mqNode1 ...
┌────────────────┬────────────┬───────────┬──────────────┬────────────────┬──────┬─────────────────┐
│ Node Name      │ Raft State │ Log Index │ Commit Index │ Snapshot Index │ Term │ Machine Version │
├────────────────┼────────────┼───────────┼──────────────┼────────────────┼──────┼─────────────────┤
│ rabbit@mqNode3 │ follower   │ 22            │ undefined      │ 11               │
├────────────────┼────────────┼───────────┼──────────────┼────────────────┼──────┼─────────────────┤
│ rabbit@mqNode2 │ leader     │ 22            │ undefined      │ 11               │
├────────────────┼────────────┼───────────┼──────────────┼────────────────┼──────┼─────────────────┤
│ rabbit@mqNode1 │ follower   │ 22            │ undefined      │ 11               │
└────────────────┴────────────┴───────────┴──────────────┴────────────────┴──────┴─────────────────┘

  • 加入mqNode4:
root@mqNode1:/# rabbitmq-queues add_member "quorum.queue" "rabbit@mqNode4"
Adding a replica for queue quorum.queue on node rabbit@mqNode4...
root@mqNode1:/# rabbitmq-queues quorum_status "quorum.queue"
Status of quorum queue quorum.queue on node rabbit@mqNode1 ...
┌────────────────┬────────────┬───────────┬──────────────┬────────────────┬──────┬─────────────────┐
│ Node Name      │ Raft State │ Log Index │ Commit Index │ Snapshot Index │ Term │ Machine Version │
├────────────────┼────────────┼───────────┼──────────────┼────────────────┼──────┼─────────────────┤
│ rabbit@mqNode4 │ follower   │ 33            │ undefined      │ 11               │
├────────────────┼────────────┼───────────┼──────────────┼────────────────┼──────┼─────────────────┤
│ rabbit@mqNode3 │ follower   │ 33            │ undefined      │ 11               │
├────────────────┼────────────┼───────────┼──────────────┼────────────────┼──────┼─────────────────┤
│ rabbit@mqNode2 │ leader     │ 33            │ undefined      │ 11               │
├────────────────┼────────────┼───────────┼──────────────┼────────────────┼──────┼─────────────────┤
│ rabbit@mqNode1 │ follower   │ 33            │ undefined      │ 11               │
└────────────────┴────────────┴───────────┴──────────────┴────────────────┴──────┴─────────────────┘

  • 查看控制台,quorum.queue的镜像数量也从 +2 变成 +3:
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/443477.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【DHCP原理与配置】

目录 一、了解DHCP服务使用DHCP的好处DHCP的分配方式 二、DHCP的租约过程传输协议端口DHCP的IP地址自动获取工作原理 三、配置DHCP服务器安装dhcp查看配置文件根据配置文件提示查看相关文档 四、模拟外网和内网互通进入主机1的设置进入主机2的设置复制dhcp文件&#xff0c;修改…

Nginx 优化与防盗链

Ngnix优化主要有两种&#xff0c;一种是配置上的优化&#xff0c;一种是内核上的优化 实验准备&#xff1a;安装好一台Nginx服务器&#xff0c;IP&#xff1a;192.168.126.22&#xff0c;域名&#xff1a;www.accp.com 浏览器访问nginx服务器此时F12查看网页信息&#xff0c;可…

MySQL之表的约束

目录 一 空属性 定义 如何设置 示例 影响&#xff1a; 二 默认值 定义 示例 影响&#xff1a; 三 列描述 定义 示例 影响&#xff1a; 四 zerofill 定义&#xff1a; 示例&#xff1a; 影响&#xff1a; 五 主键 定义 主键的性质是什么&#xff1a; 为什么要…

C++——一种特殊的二叉搜索树之红黑树

目录 1 红黑树的概念2 红黑树的性质3 红黑树节点的定义4 红黑树的插入操作情况一: cur为红&#xff0c;p为红&#xff0c;g为黑&#xff0c;u存在且为红。情况二: cur为红&#xff0c;p为红&#xff0c;g为黑&#xff08;不存在连续的红结点&#xff09;&#xff0c;u不存在/u存…

设计模式 -- 适配器模式

前言 月是一轮明镜,晶莹剔透,代表着一张白纸(啥也不懂) 央是一片海洋,海乃百川,代表着一块海绵(吸纳万物) 泽是一柄利剑,千锤百炼,代表着千百锤炼(输入输出) 月央泽,学习的一种过程,从白纸->吸收各种知识->不断输入输出变成自己的内容 希望大家一起坚持这个过程,也同…

第五篇 Spring 集合注入、作用域

《Spring》篇章整体栏目 ————————————————————————————— 【第一章】spring 概念与体系结构 【第二章】spring IoC 的工作原理 【第三章】spring IOC与Bean环境搭建与应用 【第四章】spring bean定义 【第五章】Spring 集合注入、作用域 【第六章】…

时间序列分析卫星重力梯度观测值

阚昊宇 1 时间序列图 导入数据&#xff0c;共2880历元&#xff08;86370 s&#xff09;数据&#xff0c;取前2400历元&#xff08;72000 s&#xff09;数据作为训练集&#xff0c;后480历元作为测试集。将训练集绘制时间序列图如下图 1红色线所示&#xff0c;可见明显的自相关…

elementUI-el-table组件使用总结

一、背景 vue2项目中用到el-table这个组件&#xff0c;但基础的功能不够用&#xff0c;所以需要自定义 二、表头自定义 比如要让表头展现出下面的形式&#xff1a; 只需使用 slot"header" slot-scope"scope" 对插槽进行定义&#xff0c;并绑定变量 <…

快手视频艾特实操教学分享,什么是艾特脚本,评论区艾特引流脚本讲解!

大家好我是你们的小编一辞脚本&#xff0c;今天给大家分享新的知识&#xff0c;很开心可以在CSDN平台分享知识给大家,很多伙伴看不到代码我先录制一下视频 在给大家做代码&#xff0c;给大家分享一下快手艾特脚本的知识和视频演示 不懂的小伙伴可以认真看一下&#xff0c;我们…

Windows系统开启防火墙,Ubuntu与Windows互通

1.启用Windows系统防火墙&#xff0c;&#xff0c;单击"高级设置" 2.单击“入站2规则” 3.单击 “新建规则“ 4.选择“端口” 端口号设置hanwin NFS中包括的端口号。 5.选择 TCP 填写 hanewin中的端口号&#xff0c;然后单击“确认” 6.单击“入站规则” 7.右击 nf…

JAVAWeb06-动态WEB开发核心Servlet-03

1. HttpServletRequest 1.1 HttpServletRequest 介绍 HttpServletRequest 对象代表客户端的请求当客户端/浏览器通过 HTTP 协议访问服务器时&#xff0c;HTTP 请求头中的所有信息都封装在这 个对象中通过这个对象的方法&#xff0c;可以获得客户端这些信息。 1.2 HttpServle…

HTTP请求知识

一次完整的HTTP请求所经历的步骤 1&#xff1a;首先进行DNS域名解析(本地浏览器缓存&#xff0c;操作系统缓存或者DNS服务器)&#xff0c;首先会搜索浏览器自身的DNS缓存(缓存时间比较短&#xff0c;大概只有1分钟&#xff0c;且只能容纳1000条缓存) 如果浏览器自身的缓存里面…

什么是 MySQL 监控

MySQL是一个开源的关系数据库管理系统&#xff0c;它基于客户端-服务器模型运行&#xff0c;使用SQL作为其通信模式。它是世界上第二受欢迎的数据库&#xff0c;因为它具有灵活性和可扩展性、高安全性、易用性以及无缝处理大型数据集的能力。由于其广泛的功能&#xff0c;MySQL…

ThinkPHP5.1框架通过Composer下载安装

5.1版本开始&#xff0c;官网不再提供下载版本&#xff0c;请使用Composer或者git方式安装和更新。所以想安装Tp51需要通过Composer或者git方式安装 通过Composer方式安装&#xff1a; 一、 先下载Composer Wondows 平台上&#xff0c;我们只需要下载 [Composer-Setup.exe] (h…

4个Python库来美化你的Matplotlib图表

Matplotlib是一个被广泛使用的Python数据可视化库&#xff0c;相信很多人都使用过。 但是有时候总会觉得&#xff0c;Matplotlib做出来的图表不是很好看、不美观。 今天就给大家分享四个美化Matplotlib图表的Python库&#xff0c;它们可以轻松让你的Matplotlib图表变得好看&…

移远通信笔试题

限时60分钟 1.下列关于栈叙述正确的是 A A) 栈顶元素最先能被删除 B&#xff09;栈顶元素最后才能被删除 C&#xff09;栈底元素永远不能被删除 D&#xff09;以上三种都不对 在栈中&#xff0c;最后被压入的元素总是在栈顶上方&#xff0c;而栈顶元素总是最先被弹出的元…

面试题30天打卡-day09

1、一条 SQL 语句在 MySQL 中的执行过程是怎样的&#xff1f; client 根据SQL语法&#xff0c;定义好SQL语句&#xff0c;向MySQL建立连接连接器&#xff1a;client 首先要与 MySQL 建立连接&#xff0c;这就需要一个连接器&#xff0c;负责与 client 建立连接、权限验证、管理…

User accounts with SSH access to Amazon EC2 Linux instance

文章目录 一、Need to prepare ssh remote connection tool二、Enter the command and submit the pem file三、Access successful and operation performed 一、Need to prepare ssh remote connection tool For example, using XSHELL 二、Enter the command and submit t…

云通讯服务商有哪些?

随着语聊、视频通话、直播等行业的兴起&#xff0c;云通讯厂商的作用越来越凸显&#xff0c;解决画面卡顿、解决声音延迟以及基于互动领域更多的行业解决方案已经成为开发者和企业所需。 从长远来看&#xff0c;随着5G的不断普及&#xff0c;低延迟、高质量的网络环境不断催生线…