(十)延迟队列

news2024/10/6 8:24:39

延迟队列

  • 1. 延迟队列概念
  • 2. 延迟队列使用场景
  • 3. 整合Springboot
  • 4. TTL队列
    • 1. 代码架构图
    • 2.MQ组件配置文件类代码
    • 3. 消息生产者代码
    • 4. 消息消费者代码
  • 5. 延时队列优化
    • 1. 代码架构图
    • 2. 配置文件类代码
    • 3. 消息生产者代码
  • 6. Rabbitmq插件实现延迟队列
    • 1.安装延时队列插件
    • 2.代码实现
  • 7、总结

1. 延迟队列概念

延时队列就是用来存放需要在指定时间被处理的元素的队列,延时属性,可以理解为就是有ttl时间的死信队列,就是需要我们可以控制时间处理消息

2. 延迟队列使用场景

  1. 订单在十分钟之内未支付则自动取消
  2. 新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
  3. 用户注册成功后,如果三天内没有登陆则进行短信提醒。
  4. 用户发起退款,如果三天内没有得到处理则通知相关运营人员。
  5. 预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议

特点:需要在某个事件发生之后或者之前的指定时间点完成某一项任务

3. 整合Springboot

依赖

 <!--RabbitMQ 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--RabbitMQ 测试依赖-->
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>

配置MQ服务器,用户密码啥的

在这里插入图片描述

添加Swagger 配置类

package com.feng.springbootrabbitmq.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * @Author Feng
 * @Date 2022/11/25 16:50
 * @Version 1.0
 * @Description Swagger配置类
 */

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket webApiConfig() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .build();
    }

    private ApiInfo webApiInfo() {
        return new ApiInfoBuilder()
                .title("rabbitmq 接口文档")
                .description("本文档描述了 rabbitmq 微服务接口定义")
                .version("1.0")
                .contact(new Contact("feng", "http://feng.com", "308266103@qq.com"))
                .build();
    }
}

4. TTL队列

1. 代码架构图

创建两个队列 QA 和 QB,两者队列 TTL 分别设置为 10S 和 40S,然后在创建一个交换机 X 和死信交换机 Y,它们的类型都是direct,创建一个死信队列 QD,它们的绑定关系如下:
在这里插入图片描述

因为我们没有消费者直接去正常队列中进行消费,所以消息TTL后直接进入死信队列,再由消费者消费

2.MQ组件配置文件类代码

在这里插入图片描述

package com.feng.springbootrabbitmq.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author Feng
 * @Date 2022/11/25 17:06
 * @Version 1.0
 * @Description 具有存活时间的队列配置类
 */
@Configuration
public class TtlQueueConfig {

    //普通交换机名称
    public static final String NORMAL_EXCHANGE = "X";
    //死信交换机名称
    public static final String DEAD_EXCHANGE = "Y";
    //普通队列名称
    public static final String NORMAL_QUEUE_A = "QA";
    public static final String NORMAL_QUEUE_B = "QB";
    //死信队列名称
    public static final String DEAD_QUEUE = "QD";

    //声明普通交换机
    @Bean("xExchange")//别名,不设置默认是方法名
    public DirectExchange xExchange() {
        return new DirectExchange(NORMAL_EXCHANGE);
    }

    //声明死信交换机
    @Bean("yExchange")//别名,不设置默认是方法名
    public DirectExchange yExchange() {
        return new DirectExchange(DEAD_EXCHANGE);
    }

    //声明普通队列,有TTL 10S
    @Bean("queueA")//别名,不设置默认是方法名
    public Queue queueA() {
        Map<String, Object> arguments = new HashMap<>(3);
        //过期TTL
        arguments.put("x-message-ttl", 10000);
        //设置队列的死信交换机
        arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        //设置死信交换机的RoutingKey
        arguments.put("x-dead-letter-routing-key", "YD");
        return QueueBuilder
                //是否持久化
                .durable(NORMAL_QUEUE_A)
                .withArguments(arguments)
                .build();
    }

    @Bean("queueB")//别名,不设置默认是方法名
    public Queue queueB() {
        Map<String, Object> arguments = new HashMap<>(3);
        //过期TTL
        arguments.put("x-message-ttl", 40000);
        //设置队列的死信交换机
        arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        //设置死信交换机的RoutingKey
        arguments.put("x-dead-letter-routing-key", "YD");
        return QueueBuilder
                //是否持久化
                .durable(NORMAL_QUEUE_B)
                .withArguments(arguments)
                .build();
    }

    //声明死信队列``````````````````````````````
    @Bean("queueD")//别名,不设置默认是方法名
    public Queue queueD() {
        return QueueBuilder
                //是否持久化
                .durable(DEAD_QUEUE)
                .build();
    }


    // 声明队列 A 绑定 X 交换机
    @Bean
    public Binding queueABindingX(@Qualifier("queueA") Queue queueA,
                                  @Qualifier("xExchange") DirectExchange xExchange) {
        return BindingBuilder.bind(queueA).to(xExchange).with("XA");
    }

    // 声明队列 B 绑定 X 交换机
    @Bean
    public Binding queueBBindingX(@Qualifier("queueB") Queue queueA,
                                  @Qualifier("xExchange") DirectExchange xExchange) {
        return BindingBuilder.bind(queueA).to(xExchange).with("XB");
    }

    // 声明队列 D 绑定 Y交换机
    @Bean
    public Binding queueDBindingY(@Qualifier("queueD") Queue queueA,
                                  @Qualifier("yExchange") DirectExchange xExchange) {
        return BindingBuilder.bind(queueA).to(xExchange).with("YD");
    }
}

3. 消息生产者代码

package com.feng.springbootrabbitmq.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Date;

/**
 * @Author Feng
 * @Date 2022/11/25 17:53
 * @Version 1.0
 * @Description 发送延迟消息接口
 */
@RestController
@RequestMapping("/ttl")
@Slf4j
public class SendMsgController {


    @Resource
    private RabbitTemplate rabbitTemplate;


    /**
     * 发送消息
     */
    @GetMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable("message") String msg){
        log.info("当前时间:{},发送一条信息给两个TTL队列,内容是:{}",new Date().toString(),msg);
        rabbitTemplate.convertAndSend("X","XA","消息来自TTL为10秒的队列:"+msg);
        rabbitTemplate.convertAndSend("X","XB","消息来自TTL为40秒的队列:"+msg);

    }

}

4. 消息消费者代码

package com.feng.springbootrabbitmq.consumer;

import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @Author Feng
 * @Date 2022/11/25 18:46
 * @Version 1.0
 * @Description    TTL队列消费者
 *
 *          消息是通过监听的方式进行消费的
 */
@Component
@Slf4j
public class DeadLetterQueueConsumer {


    @RabbitListener(queues = "QD")
    public void receviD(Message message, Channel channel){
        String msg = new String(message.getBody());
        log.info("当前时间:{},收到死信队列的消息,内容是:{}",new Date().toString(),msg);
    }
}

报错信息

在这里插入图片描述

原因
springboot 版本过高,使swagger 异常,或者说是引入的swagger版本过高导致的问题,或者说是springboot2.6.0更新以后引起的问题。springboot2.6.x之后将spring MVC默认路径匹配策略从ANT_PATH_MATCHER模式改为PATH_PATTERN_PARSER模式导致出错,解决方法是切换会原先的ANT_PATH_MATCHER模式,这个路径通配规则就是一种MVC对路径匹配的要求,在2.6之后更加严格,假如我们定义了一个’/hello‘接口,默认情况下,我们可以按照/hello来访问页面,也可以按照/hello.do这样带有“.do”后缀的接口来访问资源。但是在2.6之后就不行了,它严格了

解决方案

配置文件将SpringMvc的通配路径改回原来的

spring.mvc.pathmatch.matching-strategy=ant_path_matcher

测试结果

浏览器输入:http://localhost:8080/ttl/sendMsg/嘻嘻嘻

在这里插入图片描述

5. 延时队列优化

1. 代码架构图

在这里新增了一个队列 QC,绑定关系如下,该队列不设置TTL 时间,我们由生产者指定消息过期时间,不用频繁去对列里进行指定

在这里插入图片描述

2. 配置文件类代码

package com.feng.springbootrabbitmq.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author Feng
 * @Date 2022/11/25 17:06
 * @Version 1.0
 * @Description 具有存活时间的队列配置类
 */
@Configuration
public class TtlQueueConfig {

    //普通交换机名称
    public static final String NORMAL_EXCHANGE = "X";
    //死信交换机名称
    public static final String DEAD_EXCHANGE = "Y";
    //普通队列名称
    public static final String NORMAL_QUEUE_A = "QA";
    public static final String NORMAL_QUEUE_B = "QB";
    //通用普通队列
    public static final String NORMAL_QUEUE_C = "QC";
    //死信队列名称
    public static final String DEAD_QUEUE = "QD";

    //声明普通交换机
    @Bean("xExchange")//别名,不设置默认是方法名
    public DirectExchange xExchange() {
        return new DirectExchange(NORMAL_EXCHANGE);
    }

    //声明死信交换机
    @Bean("yExchange")//别名,不设置默认是方法名
    public DirectExchange yExchange() {
        return new DirectExchange(DEAD_EXCHANGE);
    }

    //声明普通队列,有TTL 10S
    @Bean("queueA")//别名,不设置默认是方法名
    public Queue queueA() {
        Map<String, Object> arguments = new HashMap<>(3);
        //过期TTL
        arguments.put("x-message-ttl", 10000);
        //设置队列的死信交换机
        arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        //设置死信交换机的RoutingKey
        arguments.put("x-dead-letter-routing-key", "YD");
        return QueueBuilder
                //是否持久化
                .durable(NORMAL_QUEUE_A)
                .withArguments(arguments)
                .build();
    }

    @Bean("queueB")//别名,不设置默认是方法名
    public Queue queueB() {
        Map<String, Object> arguments = new HashMap<>(3);
        //过期TTL
        arguments.put("x-message-ttl", 40000);
        //设置队列的死信交换机
        arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        //设置死信交换机的RoutingKey
        arguments.put("x-dead-letter-routing-key", "YC");
        return QueueBuilder
                //是否持久化
                .durable(NORMAL_QUEUE_B)
                .withArguments(arguments)
                .build();
    }

    //声明死信队列
    @Bean("queueD")//别名,不设置默认是方法名
    public Queue queueD() {
        return QueueBuilder
                //是否持久化
                .durable(DEAD_QUEUE)
                .build();
    }

    //通用普通队列
    @Bean("queueC")//别名,不设置默认是方法名
    public Queue queueC() {
        Map<String, Object> arguments = new HashMap<>(3);
        //设置队列的死信交换机
        arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        //设置死信交换机的RoutingKey
        arguments.put("x-dead-letter-routing-key", "XC");
        return QueueBuilder
                //是否持久化
                .durable(NORMAL_QUEUE_C)
                .withArguments(arguments)
                .build();
    }

    // 声明队列 A 绑定 X 交换机
    @Bean
    public Binding queueABindingX(@Qualifier("queueA") Queue queueA,
                                  @Qualifier("xExchange") DirectExchange xExchange) {
        return BindingBuilder.bind(queueA).to(xExchange).with("XA");
    }

    // 声明队列 B 绑定 X 交换机
    @Bean
    public Binding queueBBindingX(@Qualifier("queueB") Queue queueB,
                                  @Qualifier("xExchange") DirectExchange xExchange) {
        return BindingBuilder.bind(queueB).to(xExchange).with("XB");
    }
    // 声明队列 C 绑定 X 交换机
    @Bean
    public Binding queueCBindingX(@Qualifier("queueC") Queue queueC,
                                  @Qualifier("xExchange") DirectExchange xExchange) {
        return BindingBuilder.bind(queueC).to(xExchange).with("XC");
    }
    // 声明队列 D 绑定 Y交换机
    @Bean
    public Binding queueDBindingY(@Qualifier("queueD") Queue queueD,
                                  @Qualifier("yExchange") DirectExchange xExchange) {
        return BindingBuilder.bind(queueD).to(xExchange).with("YD");
    }
}

3. 消息生产者代码

package com.feng.springbootrabbitmq.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Date;

/**
 * @Author Feng
 * @Date 2022/11/25 17:53
 * @Version 1.0
 * @Description 发送延迟消息接口
 */
@RestController
@RequestMapping("/ttl")
@Slf4j
public class SendMsgController {


    @Resource
    private RabbitTemplate rabbitTemplate;


    /**
     * 发送消息
     */
    @GetMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable("message") String msg){
        log.info("当前时间:{},发送一条信息给两个TTL队列,内容是:{}",new Date().toString(),msg);
        rabbitTemplate.convertAndSend("X","XA","消息来自TTL为10秒的队列:"+msg);
        rabbitTemplate.convertAndSend("X","XB","消息来自TTL为40秒的队列:"+msg);

    }
    /**
     *  有指定TTL的发消息
     */
    @GetMapping("/sendExpirationMsg/{message}/{ttl}")
    public void sendMsg(@PathVariable("message") String msg,@PathVariable("ttl") String ttl){
        log.info("当前时间:{},发送一条时长是:{}毫秒的信息给通用TTL队列,内容是:{}",new Date().toString(),ttl,msg);
        rabbitTemplate.convertAndSend("X","XC",msg,message->{
            //在生产端设置发送消息的延迟时长
            message.getMessageProperties().setExpiration(ttl);
            return message;
        });

    }
}

测试

http://localhost:8080/ttl/sendExpirationMsg/come on baby1/20000
http://localhost:8080/ttl/sendExpirationMsg/come on baby1/2000

在这里插入图片描述

问题思考

因为RabbitMQ只会检查第一个消息是否过期,如果过期则丢到死信队列,如果第一个消息的延时时长很长,而第二个消息的延时时长很短,第二个消息并不会优先得到执行,因为这是队列的特性,先进先出,解决方案使用插件

6. Rabbitmq插件实现延迟队列

由于上面的问题,如果不能实现在消息粒度上的TTL,并使其在设置的TTL时间及时死亡,就无法设计成一个通用的延时队列。那如何解决呢,接下来我们就去解决该问题。

1.安装延时队列插件

github下载链接(版本3.8.0):
https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download/v3.8.0/rabbitmq_delayed_message_exchange-3.8.0.ez

使用教程:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange

与所有第 3 方插件一样,该.ez文件必须放在节点的插件目录 中,并且可以被 RabbitMQ 进程的有效用户读取,要找出插件目录是什么,可以使用下面的命令:

rabbitmq-plugins directories -s

下载 rabbitmq_delayed_message_exchange 插件,放置到 RabbitMQ 的插件目录: /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins

在这里插入图片描述

安装插件

#安装
rabbitmq-plugins enable rabbitmq_delayed_message_exchange

在这里插入图片描述

重启MQ服务

systemctl restart rabbitmq-server

成功截图

在这里插入图片描述

禁用插件,但请注意,所有未发送的延迟消息都将丢失

rabbitmq-plugins disable rabbitmq_delayed_message_exchange

原有消息TTL是在队列里进行的,然后因为队列特性,所以没法做到时间排序

在这里插入图片描述

现在是在交换机里进行时间的TTL,再分发给队列

在这里插入图片描述

2.代码实现

这里新增了一个队列delayed.queue,一个自定义交换机 delayed.exchange,绑定关系如下:

在这里插入图片描述

在我们自定义的交换机中,这是一种新的交换类型,该类型消息支持延迟投递机制消息传递后并不会立即投递到目标队列中,而是存储在 mnesia(一个分布式数据系统)表中,当达到投递时间时,才投递到目标队列中。

配置类代码

package com.feng.springbootrabbitmq.config;


import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author Feng
 * @Date 2022/11/26 14:14
 * @Version 1.0
 * @Description 插件实现延迟队列的配置类
 */
@Configuration
public class DelayedQueueConfig {

    //延迟交换机名
    public static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";
    //普通队列名
    public static final String  DELAYED_QUEUE_NAME = "delayed.queue";
    //路由Key
    public static final String DELAYED_ROUTING_KEY = "delayed.routingkey";

    //声明队列
    @Bean
    public Queue delayQueue(){
        return new Queue(DELAYED_QUEUE_NAME);
    }
    //声明延迟交换机,  CustomExchange:自定义交换机类
    @Bean
    public CustomExchange delayExchange(){

        Map<String, Object> arguments = new HashMap<>();
        //表示是direct的路由方式
        arguments.put("x-delayed-type", "direct");
        //x-delayed-message:表示这个交换机是延迟交换机
        return new CustomExchange(DELAYED_EXCHANGE_NAME,"x-delayed-message",true,false,arguments);
    }
    //绑定,因为是自定义的交换机,所以最后需要 noargs()因为with返回的不是Bind
    @Bean
    public Binding bindingDelayedQueue(@Qualifier("delayQueue") Queue queue,
                                       @Qualifier("delayExchange") CustomExchange delayedExchange) {
        return BindingBuilder.bind(queue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();
    }



}

生产者

package com.feng.springbootrabbitmq.controller;

import com.feng.springbootrabbitmq.config.DelayedQueueConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Date;

import static com.feng.springbootrabbitmq.config.DelayedQueueConfig.DELAYED_ROUTING_KEY;

/**
 * @Author Feng
 * @Date 2022/11/25 17:53
 * @Version 1.0
 * @Description 发送延迟消息接口
 */
@RestController
@RequestMapping("/ttl")
@Slf4j
public class SendMsgController {
    /**
     * 基于插件的延迟的生产者
     */
    @GetMapping("sendDelayMsg/{message}/{delayTime}")
    public void sendMsg(@PathVariable String message, @PathVariable Integer delayTime) {
        rabbitTemplate.convertAndSend(DelayedQueueConfig.DELAYED_EXCHANGE_NAME,
                DELAYED_ROUTING_KEY, message,
                correlationData -> {
                    //设置消息TTL
                    correlationData.getMessageProperties().setDelay(delayTime);
                    return correlationData;
                });
        log.info(" 当 前 时 间 : {}, 发 送 一 条 延 迟 {} 毫秒的信息给队列 delayed.queue:{}", new Date(), delayTime, message);
    }
}

消费者

package com.feng.springbootrabbitmq.consumer;

import com.feng.springbootrabbitmq.config.DelayedQueueConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @Author Feng
 * @Date 2022/11/26 14:47
 * @Version 1.0
 * @Description 基于插件的延迟队列消费者
 */
@Component
@Slf4j
public class DelayQueueConsumer {
    //监听消息进行消费
    @RabbitListener(queues = DelayedQueueConfig.DELAYED_QUEUE_NAME)
    public void receiveDelayQueue(Message message){
        String msg = new String(message.getBody());
        log.info("当前时间:{},收到延时队列的消息:{}", new Date().toString(), msg);
    }


}

测试

http://localhost:8080/ttl/sendDelayMsg/come on baby1/20000
http://localhost:8080/ttl/sendDelayMsg/come on baby2/2000

在这里插入图片描述

7、总结

使用 RabbitMQ 来实现延时队列可以很好的利用RabbitMQ 的特性,如:消息可靠发送、消息可靠投递、死信队列来保障消息至少被消费一次以及未被正确处理的消息不会被丢弃。另外,通过 RabbitMQ 集群的特性,可以很好的解决单点故障问题,不会因为
单个节点挂掉导致延时队列不可用或者消息丢失。

当然,延时队列还有很多其它选择,比如利用 Java 的 DelayQueue,利用 Redis 的 zset,利用 Quartz或者利用 kafka 的时间轮,这些方式各有特点,看需要适用的场景

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

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

相关文章

强强联合:OpenFeign 整合 Sentinel

书接前文&#xff1a; 微服务间的远程接口调用&#xff1a;OpenFeign 的使用 当项目中使用了 OpenFeign 后&#xff0c;可以很方便的进行远程服务调用&#xff0c;现在有个问题&#xff0c;假如远程服务出现故障了&#xff0c;调不了远程的接口&#xff0c;这边又着急等着返回…

系统启动其实就2个步骤BIOS和MBR(和之后的init/systemd的关系)

1.让计算机知道系统被放在哪个设备上了&#xff08;BIOS&#xff09; 计算机启动先启动bios&#xff0c;再去读MBR&#xff0c;MBR动了才会启动操作系统 2.让计算机知道哪里的分区是活动分区(MBR)&#xff0c;找出来把系统引导到这里来 这两部类似于早先游戏里的红色警报和星…

springcloud20:springcloudalibaba之Nacos

为什么会出现spring alibaba 整个Netflix项目进入维护模式&#xff08;不会添加新功能&#xff09; springcloud: Nerflix:eureka ribbon feign ruul config springcloud一些小技术和其整合 此时内部出问题 SpringCloud 吸收了springcloud alibaba 此时springcloud带了了什么呢…

【笔试强训】Day 3

&#x1f308;欢迎来到笔试强训专栏 (꒪ꇴ꒪(꒪ꇴ꒪ )&#x1f423;,我是Scort目前状态&#xff1a;大三非科班啃C中&#x1f30d;博客主页&#xff1a;张小姐的猫~江湖背景快上车&#x1f698;&#xff0c;握好方向盘跟我有一起打天下嘞&#xff01;送给自己的一句鸡汤&#x…

python中pytest库用法详解

Pytest 是用于测试 Python 应用的 Python 库。 官方文档&#xff1a;Full pytest documentation — pytest documentation 安装&#xff1a; pip install pytest pytest 测试发现约定规范 如果未指定任何参数&#xff0c;则在testpaths&#xff08;如果已配置&#xff09;或…

智慧水利数字孪生案例分享:数字孪生水利,助力三峡科学防洪防汛

长江是我国第一大河流&#xff0c;长江流域在我国经济发展中&#xff0c;占据举足轻重的地位。与此同时&#xff0c;长江流域频繁的洪涝、气象灾害&#xff0c;严重影响危害着流域内经济社会发展和生态环境&#xff0c;因此长江流域防汛管理被作为我国防洪体系中的关键工程。水…

阿里大咖纯手写的微服务入门笔记,从基础到进阶直接封神

前言 学习是一种基础性的能力。然而&#xff0c;“吾生也有涯&#xff0c;而知也无涯。”&#xff0c;如果学习不注意方法&#xff0c;则会“以有涯随无涯&#xff0c;殆矣”。 学习就像吃饭睡觉一样&#xff0c;是人的一种本能&#xff0c;人人都有学习的能力。我们在刚出生的…

通讯/服务器公司 测试|测试开发 面试真题|面经 汇总

浪潮 测试开发 一面 8.24 三个面试官。一个HR&#xff0c;两个技术官。 1 为什么选择测开&#xff1f;意向工作地点。 2 软件质量模型 3 测试要做哪些测试 4 集成测试和验收测试的区别&#xff1f; 5 黑盒测试和白盒测试的理解 6 知道哪些黑盒测试和白盒测试的方法 7 手工测试…

【观察】“中国算力网”向全社会开放,意味着什么?

今天&#xff0c;算力的重要性已被提升到全新的高度&#xff0c;这是因为算力作为数字经济时代新的生产力&#xff0c;对推动科技进步、行业数字化转型以及经济社会发展都发挥着至关重要的作用。根据中国信通院发布《中国算力发展指数白皮书&#xff08;2022年&#xff09;》显…

AE插件:流体渐变着色特效动画生成 Potok mac

Potok是After Effects的流体渐变插件。它会产生噪波并使用渐变对其进行着色。可以从UI Gradient Control或任何图层设置渐变。噪波可以用任何层掩盖。 Noise噪波&#xff1a;Potok 插件有一个内置的噪音发生器&#xff0c;有四种噪音类型。噪声动画可以无缝循环。此外&#xf…

ASM3142 USB 3.1控制芯片_ ASM3242 USB 3.2 2x2 控制器

一、ASM3142 USB 3.1控制芯片 ASM3142 USB 3.1控制芯片将成为通过USB进行快速数据传输的新标准。是全球最快的USB解决方案&#xff0c; 可提高性能并实现更高的功效&#xff0c;节省高达50&#xff05;的功耗。 与ASM 2142 USB控制芯片相比&#xff0c;新的ASM 3142超高速USB控…

pandas数据分析:pandas基础函数入门教程【w3school学习笔记】

系列文章目录 pandas数据分析&#xff1a;十分钟快速入门重点函数速查 文章目录系列文章目录前言一、Pandas作用二、数据结构2.1 Series2.2 DataFrame三、读取数据3.1 读取CSV文件数据3.2 读取JSON文件数据四、分析数据五、清洗数据5.1 空值5.1.1 删除包含空值的行5.1.2 填充空…

微信群发工具,纯Python编写~

前言 接到了一个需求&#xff1a;现微信有8000好友&#xff0c;需要给所有好友发送一则一样的消息。网上搜索一番后&#xff0c;发现uiautomation 可以解决该需求&#xff0c;遂有此文。这是第五篇&#xff0c;也是最后一篇。 正如上面所见&#xff0c;这是uiautomition 微信群…

web前端期末大作业——网页制作基础大二dw作业——动画漫展学习资料电影模板(6页)

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 文章目录一、网页介绍一…

「MySQL高级篇」MySQL存储引擎

本篇速览 早在MySQL基础篇的学习中&#xff0c;我们就一直看到innodb这个存储引擎&#xff0c;但是好像对于其他的存储引擎也没有去学习和了解&#xff0c;而innodb有何种特点也不得而知&#xff0c;而本篇将从一下四点&#xff0c;带你逐一攻破Ta&#xff1a; 1️⃣首先系统地…

golang 使用 make 创建 map 是否需要指定长度

大家都知道可以使用make方法来创建map类型&#xff0c;对比创建 slice 类型&#xff0c;创建map是否也需要指定len和cap两个参数呢&#xff1f; 如果map要容纳的数据比较多&#xff0c;其实是需要指定len属性的&#xff0c;我们可以从创建map的源码中了解到&#xff08;本文都…

kubernetes(K8S)学习笔记P5:K8s核心概念2

kubernetes&#xff08;K8S&#xff09;学习笔记P4&#xff1a;K8s核心概念2-Helm、持久化存储技术5.Helm5.1Helm 引入5.2Helm 介绍5.3Helm v3 变化5.4安装与仓库配置5.4.1部署 helm 客户端5.4.2配置国内 chart 仓库&#xff08;helm换源&#xff09;5.5Helm快速部署5.5.1基本命…

前端面试题记录——vue

目录 前言 一、说一下虚拟DOM&#xff1f; 1.虚拟dom是什么&#xff1f; 2.虚拟dom是怎么产生的&#xff1f; 3.虚拟dom的优点 二、说一下vue-router?有几种模式&#xff1f; 1.vue-router是什么&#xff1f; 2.vue-router有几种模式&#xff1f;区别是什么&#xff1…

Secure Boot什么意思?BIOS中Secure Boot灰色无法更改解决方法详解

在电脑Bios设置中&#xff0c;有一项“Secure Boot”相关设置&#xff0c;很多小伙伴们不知道Secure Boot什么意思&#xff0c;也不知道如何设置。另外&#xff0c;有时候这个Secure Boot是灰色的无法更改&#xff0c;这又要如何解决呢&#xff1f;下面本文就来谈谈Secure Boot…

mybatis-plus使用generator快速生成代码,并手动添加数据库命令

mybatis-plus是mybatis的增强版&#xff0c;可以根据数据库自动生成代码&#xff0c;实现常用的增删改查功能&#xff0c;也可以按照mybatis的配置方式添加数据库命令。 参考地址&#xff1a; generator: 文档 http://baomidou.com/ 代码生成器配置新 | MyBatis-Plus 1、在p…