SpringBoot基于RabbitMQ实现消息延迟队列方案

知识小科普

在此之前,简单说明下基于RabbitMQ实现延时队列的相关知识及说明下延时队列的使用场景。

延时队列使用场景

在很多的业务场景中,延时队列可以实现很多功能,此类业务中,一般上是非实时的,需要延迟处理的,需要进行重试补偿的。

  • 订单超时关闭:在支付场景中,一般上订单在创建后30分钟或1小时内未支付的,会自动取消订单。
  • 短信或者邮件通知:在一些注册或者下单业务时,需要在1分钟或者特定时间后进行短信或者邮件发送相关资料的。本身此类业务于主业务是无关联性的,一般上的做法是进行异步发送。
  • 重试场景:比如消息通知,在第一次通知出现异常时,会在隔几分钟之后进行再次重试发送。

RabbitMQ实现延时队列

本身在RabbitMQ中是未直接提供延时队列功能的,但可以使用 TTL(Time-To-Live,存活时间)DLX(Dead-Letter-Exchange ,死信队列交换机)的特性实现延时队列的功能。

存活时间(Time-To-Live 简称 TTL)

RabbitMQ中可以对队列和消息分别设置TTL,TTL表明了一条消息可在队列中存活的最大时间。当某条消息被设置了TTL或者当某条消息进入了设置了TTL的队列时,这条消息会在TTL时间后死亡成为Dead Letter。如果既配置了消息的TTL,又配置了队列的TTL,那么较小的那个值会被取用。

死信交换(Dead Letter Exchanges 简称 DLX)

上个知识点也提到了,设置了 TTL 的消息或队列最终会成为 Dead Letter ,当消息在一个队列中变成死信之后,它能被重新发送到另一个交换机中,这个交换机就是DLX,绑定此DLX的队列就是死信队列。

一个消息变成死信一般上是由于以下几种情况;

消息被拒绝
消息过期
队列达到了最大长度。

所以,通过 TTLDLX 的特性可以模拟实现延时队列的功能。当队列中的消息超时成为死信后,会把消息死信重新发送到配置好的交换机中,然后分发到真实的消费队列。故简单来说,我们可以创建2个队列,一个队列用于发送消息,一个队列用于消息过期后的转发的目标队列。

SpringBoot集成RabbitMQ实现延时队列实战

以下使用 SpringBoot 集成 RabbitMQ 进行实战说明,在进行 http 消息通知时,若通知失败(地址不可用或者连接超时)时,将此消息转入延时队列中,待特定时间后进行重新发送。

0.引入pom依赖

    <!-- rabbit -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <!-- 简化http操作 -->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-http</artifactId>
        <version>4.5.16</version>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-json</artifactId>
        <version>4.5.16</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

1.编写rabbitmq配置文件(关键配置)RabbitConfig.java

/** 
*
* @ClassName   类名:RabbitConfig 
* @Description 功能说明:
* <p>
* TODO
*</p>
************************************************************************
* @date        创建日期:2019年7月17日
* @author      创建人:oKong
* @version     版本号:V1.0
*<p>
***************************修订记录*************************************
* 
*   2019年7月17日   oKong   创建该类功能。
*
***********************************************************************
*</p>
*/
@Configuration
public class RabbitConfig {
    
    @Autowired
    ConnectionFactory connectionFactory;
    
    /**
     * 消费者线程数 设置大点 大概率是能通知到的
     */
    @Value("${http.notify.concurrency:50}")
    int concurrency;
    
    /**
     * 延迟队列的消费者线程数 可设置小点
     */
    @Value("${http.notify.delay.concurrency:20}")
    int delayConcurrency;
    
    @Bean
    public RabbitAdmin rabbitAdmin() {
        return new RabbitAdmin(connectionFactory);
    }
    
    @Bean
    public DirectExchange httpMessageNotifyDirectExchange(RabbitAdmin rabbitAdmin) {
        //durable 是否持久化
        //autoDelete 是否自动删除,即服务端或者客服端下线后 交换机自动删除
        DirectExchange directExchange = new DirectExchange(ApplicationConstant.HTTP_MESSAGE_EXCHANGE,true,false);
        directExchange.setAdminsThatShouldDeclare(rabbitAdmin);
        return directExchange;
    }
    
    //设置消息队列
    @Bean
    public Queue httpMessageStartQueue(RabbitAdmin rabbitAdmin) {
         
       /*
                       创建接收队列,4个参数
         name - 队列名称
         durable - false,不进行持有化
         exclusive - true,独占性
         autoDelete - true,自动删除*/
        Queue queue = new Queue(ApplicationConstant.HTTP_MESSAGE_START_QUEUE_NAME, true, false, false);
        queue.setAdminsThatShouldDeclare(rabbitAdmin);
        return queue;
    }
    
    //队列绑定交换机
    @Bean
    public Binding bindingStartQuene(RabbitAdmin rabbitAdmin,DirectExchange httpMessageNotifyDirectExchange, Queue httpMessageStartQueue) {
        Binding binding = BindingBuilder.bind(httpMessageStartQueue).to(httpMessageNotifyDirectExchange).with(ApplicationConstant.HTTP_MESSAGE_START_RK);
        binding.setAdminsThatShouldDeclare(rabbitAdmin);
        return binding;
    }
    
    @Bean
    public Queue httpMessageOneQueue(RabbitAdmin rabbitAdmin) {
        Queue queue = new Queue(ApplicationConstant.HTTP_MESSAGE_ONE_QUEUE_NAME, true, false, false);
        queue.setAdminsThatShouldDeclare(rabbitAdmin);
        return queue;
    }
    
    @Bean
    public Binding bindingOneQuene(RabbitAdmin rabbitAdmin,DirectExchange httpMessageNotifyDirectExchange, Queue httpMessageOneQueue) {
        Binding binding = BindingBuilder.bind(httpMessageOneQueue).to(httpMessageNotifyDirectExchange).with(ApplicationConstant.HTTP_MESSAGE_ONE_RK);
        binding.setAdminsThatShouldDeclare(rabbitAdmin);
        return binding;
    }
    
    //-------------设置延迟队列--开始--------------------
    @Bean
    public Queue httpDelayOneQueue() {
        //name - 队列名称
        //durable - true
        //exclusive - false
        //autoDelete - false
        return QueueBuilder.durable("http.message.dlx.one")
                //以下是重点:当变成死信队列时,会转发至 路由为x-dead-letter-exchange及x-dead-letter-routing-key的队列中
                .withArgument("x-dead-letter-exchange", ApplicationConstant.HTTP_MESSAGE_EXCHANGE)
                .withArgument("x-dead-letter-routing-key", ApplicationConstant.HTTP_MESSAGE_ONE_RK)
                .withArgument("x-message-ttl", 1*60*1000)//1分钟 过期时间(单位:毫秒),当过期后 会变成死信队列,之后进行转发
                .build();
    }
    //绑定到交换机上
    @Bean
    public Binding bindingDelayOneQuene(RabbitAdmin rabbitAdmin, DirectExchange httpMessageNotifyDirectExchange, Queue httpDelayOneQueue) {
        Binding binding = BindingBuilder.bind(httpDelayOneQueue).to(httpMessageNotifyDirectExchange).with("delay.one");
        binding.setAdminsThatShouldDeclare(rabbitAdmin);
        return binding;
    }
    //-------------设置延迟队列--结束--------------------

    //建议将正常的队列和延迟处理的队列分开
    //设置监听容器
    @Bean("notifyListenerContainer")
    public SimpleRabbitListenerContainerFactory httpNotifyListenerContainer() {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);// 手动ack
        factory.setConnectionFactory(connectionFactory);
        factory.setPrefetchCount(1);
        factory.setConcurrentConsumers(concurrency);
        return factory;
    }

    // 设置监听容器
    @Bean("delayNotifyListenerContainer")
    public SimpleRabbitListenerContainerFactory httpDelayNotifyListenerContainer() {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);// 手动ack
        factory.setConnectionFactory(connectionFactory);
        factory.setPrefetchCount(1);
        factory.setConcurrentConsumers(delayConcurrency);
        return factory;
    }
}

ApplicationConstant.java

public class ApplicationConstant {
    
    /**
     * 发送http通知的 exchange 队列
     */
    public static final String HTTP_MESSAGE_EXCHANGE = "http.message.exchange";
    
    /**
     * 配置消息队列和路由key值
     */
    public static final String HTTP_MESSAGE_START_QUEUE_NAME = "http.message.start";
    public static final String HTTP_MESSAGE_START_RK = "rk.start";
    
    public static final String HTTP_MESSAGE_ONE_QUEUE_NAME = "http.message.one";
    public static final String HTTP_MESSAGE_ONE_RK = "rk.one";
    
    /**
     * 通知队列对应的延迟队列关系,即过期队列之后发送到下一个的队列信息,可以根据实际情况添加,当然也可以根据一定规则自动生成
     */
    public static final Map<String,String> delayRefMap = new HashMap<String, String>() {
        /**
         * 
         */
        private static final long serialVersionUID = -779823216035682493L;

        {
            put(HTTP_MESSAGE_START_QUEUE_NAME, "delay.one");
        }
    };    
}

简单来说,就是创建一个正常消息发送队列,用于接收http消息请求的参数,同时进行http请求。同时,创建一个延时队列,设置其 x-dead-letter-exchangex-dead-letter-routing-key
x-message-ttl 值,将其转发到正常的队列中。使用一个map对象维护一个关系,当正常消息异常时,需要发送的延时队列的队列名称,当然时间场景汇总,根据需要可以进行动态配置或者根据一定规则进行动态映射。

2.创建监听类,用于消息的消费操作,此处使用@RabbitListener来消费消息(当然也可以使用SimpleMessageListenerContainer进行消息配置的),创建了一个正常消息监听和延时队列监听,由于一般上异常通知是低概率事件,可根据不同的监听容器进行差异化配置。

/** 
*
* @ClassName   类名:HttpMessagerLister 
* @Description 功能说明:http通知消费监听接口
* <p>
* TODO
*</p>
************************************************************************
* @date        创建日期:2019年7月17日
* @author      创建人:oKong
* @version     版本号:V1.0
*<p>
***************************修订记录*************************************
* 
*   2019年7月17日   oKong   创建该类功能。
*
***********************************************************************
*</p>
*/
@Component
@Slf4j
public class HttpMessagerLister {
    
    @Autowired
    HttpMessagerService messagerService;
    
    @RabbitListener(id = "httpMessageNotifyConsumer", queues = {ApplicationConstant.HTTP_MESSAGE_START_QUEUE_NAME}, containerFactory = "notifyListenerContainer")
    public void httpMessageNotifyConsumer(Message message, Channel channel) throws Exception {
        doHandler(message, channel);
    }
    
    @RabbitListener(id= "httpDelayMessageNotifyConsumer", queues = {
            ApplicationConstant.HTTP_MESSAGE_ONE_QUEUE_NAME,}, containerFactory = "delayNotifyListenerContainer")
    public void httpDelayMessageNotifyConsumer(Message message, Channel channel) throws Exception {
        doHandler(message, channel);
    }
    
    private void doHandler(Message message, Channel channel) throws Exception {
        String body = new String(message.getBody(),"utf-8");
        String queue = message.getMessageProperties().getConsumerQueue();
        log.info("接收到通知请求:{},队列名:{}",body, queue);
        //消息对象转换
        try {
            HttpEntity httpNotifyDto = JSONUtil.toBean(body, HttpEntity.class);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            //发送通知
            messagerService.notify(queue, httpNotifyDto);
        } catch(Exception e) {
            log.error(e.getMessage());
            //ack
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
    }
}

HttpMessagerService.java :消息真正处理的类,此类是关键,这里未进行日志记录,真实场景中,强烈建议进行消息通知的日志存储,防止日后信息的查看,同时也能通过发送状态,在重试次数都失败后,进行定时再次发送功能,同时也有据可查。

@Component
@Slf4j
public class HttpMessagerService {
    
    @Autowired
    AmqpTemplate mqTemplate;    
    
    public void notify(String queue,HttpEntity httpEntity) {
        //发起请求
        log.info("开始发起http请求:{}", httpEntity);
        try {
            switch(httpEntity.getMethod().toLowerCase()) {
            case "POST":
                  HttpUtil.post(httpEntity.getUrl(), httpEntity.getParams());
                  break;
            case "GET":
            default:
                HttpUtil.get(httpEntity.getUrl(), httpEntity.getParams());
            }
        } catch (Exception e) {
            //发生异常,放入延迟队列中
            String nextRk = ApplicationConstant.delayRefMap.get(queue);
            if(ApplicationConstant.HTTP_MESSAGE_ONE_QUEUE_NAME.equals(queue)) {
                //若已经是最后一个延迟队列的消息队列了,则后续可直接放入数据库中 待后续定时策略进行再次发送
                log.warn("http通知已经通知N次失败,进入定时进行发起通知,url={}", httpEntity.getUrl());
            } else {
               log.warn("http重新发送通知:{}, 通知队列rk为:{}, 原队列:{}", httpEntity.getUrl(), nextRk, queue);
               mqTemplate.convertAndSend(ApplicationConstant.HTTP_MESSAGE_EXCHANGE, nextRk, cn.hutool.json.JSONUtil.toJsonStr(httpEntity));
            }
        }
    }
}

3.创建控制层服务(真实场景中,如SpringCloud微服务中,一般上是创建个api接口,供其他服务进行调用)

@Slf4j
@RestController
@Api(tags = "http测试接口")
public class HttpDemoController {

    @Autowired
    AmqpTemplate mqTemplate;
    
    @PostMapping("/send")
    @ApiOperation(value="send",notes = "发送http测试")
    public String sendHttp(@RequestBody HttpEntity httpEntity) {
        //发送http请求
        log.info("开始发起http请求,发布异步消息:{}", httpEntity);
        mqTemplate.convertAndSend(ApplicationConstant.HTTP_MESSAGE_EXCHANGE, ApplicationConstant.HTTP_MESSAGE_START_RK, cn.hutool.json.JSONUtil.toJsonStr(httpEntity));
        return "发送成功:url=" + httpEntity.getUrl();        
    }
}

4.配置文件添加RabbitMQ相关配置信息

spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/

# 通知-消费者线程数 设置大点 大概率是能通知到的
http.notify.concurrency=150
# 延迟队列的消费者线程数 可设置小点 
http.notify.delay.concurrency=10

5.编写启动类。

@SpringBootApplication
@Slf4j
public class DelayQueueApplication {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(DelayQueueApplication.class, args);
        log.info("spring-boot-rabbitmq-delay-queue-chapter38服务启动!");
    }
}

6.启动服务。使用swagger进行简单调用测试。

  • 正常通知:

在这里插入图片描述

2019-07-20 23:52:23.792  INFO 65216 --- [nio-8080-exec-1] c.l.l.s.c.controller.HttpDemoController  : 开始发起http请求,发布异步消息:HttpEntity(url=www.baidu.com, params={a=1}, method=get)
2019-07-20 23:52:23.794  INFO 65216 --- [TaskExecutor-97] c.l.l.s.chapter38.mq.HttpMessagerLister  : 接收到通知请求:{"method":"get","params":{"a":1},"url":"www.baidu.com"},队列名:http.message.start
2019-07-20 23:52:23.794  INFO 65216 --- [TaskExecutor-97] c.l.l.s.c.service.HttpMessagerService    : 开始发起http请求:HttpEntity(url=www.baidu.com, params={a=1}, method=get)
  • 异常通知:访问一个不存在的地址
    在这里插入图片描述
2019-07-20 23:53:14.699  INFO 65216 --- [nio-8080-exec-4] c.l.l.s.c.controller.HttpDemoController  : 开始发起http请求,发布异步消息:HttpEntity(url=www.baidu.com1, params={a=1}, method=get)
2019-07-20 23:53:14.705  INFO 65216 --- [TaskExecutor-84] c.l.l.s.chapter38.mq.HttpMessagerLister  : 接收到通知请求:{"method":"get","params":{"a":1},"url":"www.baidu.com1"},队列名:http.message.start
2019-07-20 23:53:14.705  INFO 65216 --- [TaskExecutor-84] c.l.l.s.c.service.HttpMessagerService    : 开始发起http请求:HttpEntity(url=www.baidu.com1, params={a=1}, method=get)
2019-07-20 23:53:14.706  WARN 65216 --- [TaskExecutor-84] c.l.l.s.c.service.HttpMessagerService    : http重新发送通知:www.baidu.com1, 通知队列rk为:delay.one, 原队列:http.message.start

RabbitMQ 后台中,可以看见 http.message.dlx.one 队列中存在这需要延时处理的消息,在一分钟后会转发至 http.message.one 队列中。

在这里插入图片描述
在一分钟后,可以看见消息本再次消费了。

2019-07-20 23:54:14.722  INFO 65216 --- [TaskExecutor-16] c.l.l.s.chapter38.mq.HttpMessagerLister  : 接收到通知请求:{"method":"get","params":{"a":1},"url":"www.baidu.com1"},队列名:http.message.one
2019-07-20 23:54:14.723  INFO 65216 --- [TaskExecutor-16] c.l.l.s.c.service.HttpMessagerService    : 开始发起http请求:HttpEntity(url=www.baidu.com1, params={a=1}, method=get)
2019-07-20 23:54:14.723  WARN 65216 --- [TaskExecutor-16] c.l.l.s.c.service.HttpMessagerService    : http通知已经通知N次失败,进入定时进行发起通知,url=www.baidu.com1

一些最佳实践

在正式场景中,一般上补偿或者重试机制大概率是不会发送的,倘若发生时,一般上是第三方业务系统出现了问题,故一般上在进行补充时,应该在非高峰期进行操作,故应该对延时监听器,应该在高峰期时停止消费,在非高峰期时进行消费。同时,还可以根据不同的通知类型,放入不一样的延时队列中,保障业务的正常。这里简单说明下,动态停止或者启动演示监听器的方式。一般上是使用RabbitListenerEndpointRegistry 对象获取延时监听器,之后进行动态停止或者启用。可设置 @RabbitListener 的id属性,直接进行获取,当然也可以直接获取所有的监听器,进行自定义判断了。

    @Autowired
    RabbitListenerEndpointRegistry registry;
    
   @GetMapping("/set")
    @ApiOperation(value = "set", notes = "设置消息监听器的状态")
    public String setSimpleMessageListenerContainer(String status) {
        if("1".equals(status)) {
            registry.getListenerContainer("httpDelayMessageNotifyConsumer").start();
        } else {
            registry.getListenerContainer("httpDelayMessageNotifyConsumer").stop();
        }
        return status;
    }

这里,只是简单进行演示说明,在真实场景下,可以使用定时器,判断当前是否为高峰期,进而进行动态设置监听器的状态。

参考资料

  1. https://www.rabbitmq.com/admin-guide.html
  2. https://www.rabbitmq.com/ttl.html

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【讲解下常见的Web前端框架】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

Linux-管道

目录 无名管道关闭未使用的管道文件描述符 管道对应的内存大小与shell命令进行通信&#xff08;popen&#xff09;命名管道FIFO创建FIFO文件打开FIFO文件 无名管道 管道是最早出现的进程间通信的手段。 管道的作用是在有亲缘关系的进程之间传递消息。所谓有亲缘关系&#xff…

【YOLOV5 入门】——Pyside6/PyQt5可视化UI界面后端逻辑

声明&#xff1a;笔记是做项目时根据B站博主视频学习时自己编写&#xff0c;请勿随意转载&#xff01; 一、环境安装 VScode/Pycharm终端进入虚拟环境后&#xff0c;输入下面代码安装pyside6&#xff0c;若用的Pycharm作为集成开发环境&#xff0c;也下载个pyqt5&#xff1a; …

移动Web学习07-适配单位vw/vh哔哩哔哩移动端vw单位适配案例

1.1、VW相对单位 前面我们已经学习了rem单位 &#xff0c;他是一个相对单位、相对于HTML表格字号大小 VW/VH也是一个相对单位&#xff0c;他是相对于视口的尺寸计算结果 VW&#xff1a;viewport width VH: viewport height <meta name"viewport" content"…

C语言之探秘:访问结构体空指针与结构体空指针的地址的区别(九十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

华为HarmonyOS 4.2公测升级计划扩展至15款新机型

华为近日宣布&#xff0c;HarmonyOS 4.2操作系统的公测升级计划将扩展到包括华为P50系列在内的15款设备。这一更新旨在为用户提供更优化的系统性能和增强的功能。 参与此次公测的机型包括华为P50、华为P50 Pro及其典藏版、华为P50E、华为P50 Pocket及其艺术定制版、华为nova系…

学习STM32第十四天

软件SPI读写W25Q64 一、简介 对W25Q64模块进行读写操作时&#xff0c;输出引脚配置为推挽输出&#xff0c;输入引脚配置为浮空或上拉输入。时钟、主机输出和片选都是输出引脚&#xff0c;主机输入是输入引脚。SPI协议是通过命令和数据进行通信&#xff0c;在硬件中使用移位寄…

将自己的项目上传至Git

一、安装Git 官网:Git (git-scm.com) 二、注册gitee 官网:工作台 - Gitee.com 进入“我的”出现以下界面 三、创建仓库 点击加号&#xff0c;新建仓库 根据自己的需求取名&#xff0c;描述仓库&#xff0c;开源还是私有&#xff0c;点击创建即可&#xff0c;点击我的即可…

每日一题 — 找到字符串中所有字母异位词

解法一&#xff1a;暴力枚举 解法二&#xff1a;滑动窗口hash表优化 定义left和right为起始坐标0&#xff0c;right向后遍历&#xff0c;并加入到哈希表中&#xff0c;然后也要记录下来每次进入哈希表的有效字符&#xff08;与目标字符串中相同的字符&#xff09;的个数且这个滑…

C++修炼之路之list模拟实现--C++中的双向循环链表

目录 引言 一&#xff1a;STL源代码中关于list的成员变量的介绍 二&#xff1a;模拟实现list 1.基本结构 2.普通迭代器 const迭代器的结合 3.构造拷贝构造析构赋值重载 清空 4.inserterase头尾插入删除 5.打印不同数据类型的数据《使用模板加容器来完成》 三&#xf…

SQLite、MySQL 和 PostgreSQL 数据库速度比较(本文阐述时间很早比较,不具有最新参考性)(二十五)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;用于 SQLite 的异步 I/O 模块&#xff08;二十四&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 注意&#xff1a;本文档非常非常旧。它描述了速度比较 SQLite、MySQL 和 PostgreSQL 的古老版本。 这里…

学习一门语言的方法和套路(B站转述)

视频链接 up虽然长相英(ping)俊(ping)&#xff0c;但是讲的干活&#xff0c;没恰饭。 学习流程&#xff1a; 1.快速阅读&#xff0c;掌握概况 2.深入细节内容 例如&#xff1a;java (JDBC)、html 、netty 不管三七二十一&#xff0c;先了解套路&#xff0c;再深入研究。 高…

安装CUDNN详细过程

cuDNN&#xff08;CUDA Deep Neural Network library&#xff09;是由NVIDIA开发的深度学习GPU加速库。 cuDNN包含了许多针对神经网络操作进行高度优化的函数&#xff0c;旨在使深度学习框架能够在NVIDIA的GPU上实现最佳性能&#xff0c;这个库提供了高效计算和加速&#xff0c…

牛客网刷题 :BC50 你是天才吗

描述 据说智商140以上者称为天才&#xff0c;KiKi想知道他自己是不是天才&#xff0c;请帮他编程判断。输入一个整数表示一个人的智商&#xff0c;如果大于等于140&#xff0c;则表明他是一个天才&#xff0c;输出“Genius”。 输入描述&#xff1a; 多组输入&#xff0c;每…

在 PyCharm 中使用系统安装的 Python 和 Anaconda 的 Python什么区别

virtualenv environment &#xff1a; virtualenv 是一个用于创建独立 Python 环境的工具。它可以在同一个系统上创建多个相互独立的 Python 环境&#xff0c;每个环境都有自己的 Python 解释器和包库&#xff0c;从而可以实现不同项目之间的依赖隔离和版本控制。coda environm…

vue快速入门(二十五)本地存储与初始化使用

注释很详细&#xff0c;直接上代码 上一篇 新增内容 本地获取数据数据存储到本地 源码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial…

2024蓝桥杯——宝石问题

先展示题目 声明 以下代码仅是我的个人看法&#xff0c;在自己考试过程中的优化版&#xff0c;本人考试就踩了很多坑&#xff0c;我会—一列举出来。代码可能很多&#xff0c;但是总体时间复杂度不高只有0(N) 函数里面的动态数组我没有写开辟判断和free&#xff0c;这里我忽略…

频率域滤波基础(离散傅里叶变换使用填充的缺陷)

本来是个很简单的问题&#xff0c;作者硬是写的这么复杂&#xff0c;翻译还搞错了。重点是我发现作者真正有用的东西没讲到&#xff0c;比如相位和谱如何影响图像。连个转换公式都没有&#xff0c;我只能说作者是在混字数。 首先看关于中心对称是什么意思&#xff1f;我木太明白…

MySql 视图 存储过程 触发器

文章目录 视图数据库对象视图的理解创建、查看、更新、删除 存储过程和存储函数概述分类存储过程的创建和调用存储函数的创建和调用存储过程和存储函数的对比存储过程和存储函数的查看、修改、删除 变量GLOBAL 与 SESSION 变量的使用会话用户变量和局部变量的使用 定义条件与处…

【机器学习300问】70、向量化技术来计算神经网络时维度如何确保正确?

一、向量化技术在进行神经网络计算时的优势 向量化是一种优化技术&#xff0c;通过使用数组操作代替for循环&#xff0c;可以大大提高代码的性能和效率。在深度学习中尤其明显&#xff0c;可以提高计算效率、简化代码、优化内存使用。 二、如何确保计算时维度是正确的&#xf…