RabbitMQ发布确认和消息回退(6)

news2024/12/28 3:57:15

概念

发布确认原理

生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消息都将会被指派一个唯一的 ID(从 1 开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一 ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker 回传给生产者的确认消息中 delivery-tag 域包含了确认消息的序列号,此外 broker 也可以设置basic.ack 的 multiple 域,表示到这个序列号之前的所有消息都已经得到了处理。

confirm 模式最大的好处在于他是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果 RabbitMQ 因为自身内部错误导致消息丢失,就会发送一条 nack 消息,生产者应用程序同样可以在回调方法中处理该 nack 消息。

两种模式

1.简单确认模式(Simple Publisher Confirm)

在简单确认模式下,每次生产者发送一条消息到 RabbitMQ,都会立即等待 RabbitMQ 返回一个确认消息。如果消息成功发送到 RabbitMQ 服务器上的交换机,并且至少一个队列接收到了消息,RabbitMQ 就会返回一个确认消息给生产者。否则,如果消息发送失败,则会返回一个 Nack 消息。在这种模式下,生产者可以针对每一条消息都进行确认处理,确保消息是否被正确地发送到了 RabbitMQ。

3.批量确认模式(Publisher Confirm with Batch)

在批量确认模式下,生产者可以将一批消息发送到 RabbitMQ,然后等待一段时间后再收到确认消息。这样可以降低每条消息发送的确认成本,并提高性能。批量确认模式通过指定一个大小来定义批量确认的数量,当达到指定的数量后,RabbitMQ 会一次性发送确认消息给生产者。这种模式适用于需要发送大量消息的场景,可以减少确认消息的数量,提高消息发送的效率。

这两种发布确认模式在实现上有一些不同,可以根据实际的业务需求和性能要求来选择合适的模式。简单确认模式更适用于需要对每一条消息进行实时确认的场景,而批量确认模式适用于需要发送大量消息并且希望降低确认消息成本的场景。

开启方法

-- 确认模式配置

spring.rabbitmq.publisher-confirm-type: correlated

  • NONE

禁用发布确认模式,是默认值。

  • CORRELATED

发布消息成功到交换器后会触发回调方法。

  • SIMPLE

测试有两种效果,其一效果和 CORRELATED 值一样会触发回调方法,其二在发布消息成功后使用 rabbitTemplate 调用 waitForConfirms 或 waitForConfirmsOrDie 方法等待 broker 节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是 waitForConfirmsOrDie 方法如果返回 false 则会关闭 channel,则接下来无法发送消息到 broker。(相当于单一发布)

spring:
  # 配置RabbitMQ
  rabbitmq:
    host: 192.168.0.70
    port: 5674
    username: guest
    password: guest
    # 虚拟主机
    virtual-host: my_vhost
    # 开启确认模式
    publisher-confirm-type: correlated

 测试

为了方便测试,此机制单独编写类去测试,使用的模式为路由模式(其他模式也可以)

1.创建回调函数

package com.model.callback;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

/**
 * @Author: Haiven
 * @Time: 2024/4/22 17:26
 * @Description: TODO
 */
@Component
@Slf4j
public class MyConfirmCallBack implements RabbitTemplate.ConfirmCallback {
    /**
     * 被调用的回调方法
     * @param correlationData 相关配置信息
     * @param ack 交换机是否成功收到消息 可以根据 ack 做相关的业务逻辑处理
     * @param cause 失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        System.out.println("消息推送回调:配置信息="+correlationData+";结果="+ack+"失败原因="+cause);
        if(ack){
            //消息推送成功
            System.out.println("消息推送成功");
        }else{
            System.out.println("消息推送失败:" + cause);
        }
    }
}

回调函数会在消息推送到队列后调用

 2.配置回调函数

将上述回调函数设置到mq的配置中,这里再RabbitmqConfig文件中配置

package com.model.config;

import com.model.callback.MyConfirmCallBack;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

/**
 * @Author: Haiven
 * @Time: 2024/4/18 17:28
 * @Description: TODO
 */
@Configuration
public class RabbitmqConfig {

    @Value("${rabbitmq.work.queue}")
    private String workQueue;

    @Resource
    private MyConfirmCallBack myConfirmCallBack;

    /**
     * 工作模式的队列
     * @return 队列
     */
    @Bean(name = "workQueue")
    public Queue getWorkQueue(){
        return QueueBuilder.durable(workQueue).build();
    }

    @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        // 设置开启 Mandatory 强制执行调用回调函数
        rabbitTemplate.setMandatory(true);
        //设置回调
        rabbitTemplate.setConfirmCallback(myConfirmCallBack);
        //设置回退回调
        return rabbitTemplate;
    }

}

 3.创建交换机和队列

这里创建ConfirmConfig配置文件与其他队列进行区分

package com.model.config;

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

/**
 * @Author: Haiven
 * @Time: 2024/4/22 17:22
 * @Description: TODO
 */
@Configuration
public class ConfirmConfig {

    /**
     * 测试发布确认的交换机
     * @return exchange
     */
    @Bean(name = "confirmExchange")
    public Exchange getConfirmExchange(){
        return ExchangeBuilder
                .directExchange("exchange_confirm")
                .build();
    }

    /**
     * 队列
     * @return queue
     */
    @Bean(name = "confirmQueue")
    public Queue getConfirmQueue(){
        return QueueBuilder
                .durable("queue_confirm")
                .build();
    }

    /**
     * 绑定队列
     * @return binding
     */
    @Bean
    public Binding getConfirmBinding(){
        return BindingBuilder
                .bind(getConfirmQueue())
                .to(getConfirmExchange())
                //路由键 队列1接收debug级别的消息
                .with("confirm")
                .noargs();
    }

}

 4.创建消费者

package com.model.listener;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * @Author: Haiven
 * @Time: 2024/4/22 17:31
 * @Description: TODO
 */
@Component
public class ConfirmConsumer {

    @RabbitListener(queues = {"queue_confirm"})
    public void routingConfirm(String msg){
        System.out.println("消费者 -confirm- 接收消息:" + msg);
    }
}

 5.发送并接收消息

package com.model.controller;

import com.code.domain.Response;
import com.model.service.RabbitService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;


/**
 * @Author: Haiven
 * @Time: 2024/4/19 9:46
 * @Description: TODO
 */
@RestController
@RequestMapping("/producer")
public class ProducerController {

    @Resource
    private RabbitService rabbitService;

    @GetMapping("/simple")
    public Response<Void> simple(String msg){
        boolean res = rabbitService.simple(msg);
        return res ? Response.success() : Response.fail();
    }

    @GetMapping("/work")
    public Response<Void> work(String msg){
        boolean res = rabbitService.work(msg);
        return res ? Response.success() : Response.fail();
    }

    @GetMapping("/sub")
    public Response<Void> sub(String msg){
        boolean res = rabbitService.sub(msg);
        return res ? Response.success() : Response.fail();
    }

    @GetMapping("/routing")
    public Response<Void> routing(String msg, String type){
        boolean res = rabbitService.routing(msg, type);
        return res ? Response.success() : Response.fail();
    }

    @GetMapping("/topic")
    public Response<Void> topic(String msg, String type){
        boolean res = rabbitService.topic(msg, type);
        return res ? Response.success() : Response.fail();
    }

    @GetMapping("/confirm")
    public Response<Void> confirm(String msg, String type){
        boolean res = rabbitService.confirm(msg, type);
        return res ? Response.success() : Response.fail();
    }
}
package com.model.service.impl;

import com.model.service.RabbitService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @Author: Haiven
 * @Time: 2024/4/19 10:51
 * @Description: TODO
 */
@Service
@Slf4j
public class RabbitServiceImpl implements RabbitService {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Value("${rabbitmq.simple.queue}")
    private String simpleQueue;

    @Value("${rabbitmq.work.queue}")
    private String workQueue;

    @Override
    public boolean simple(String msg) {
        try {
            rabbitTemplate.convertAndSend(simpleQueue, msg);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean work(String msg) {
        try {
            rabbitTemplate.convertAndSend(workQueue, msg);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean sub(String msg) {
        try {
            //路由模式就不能直接发送消息到队列了, 而是发送到交换机,由交换机进行广播, routingKey为路由Key 订阅模式给""
            rabbitTemplate.convertAndSend("exchange_sub","", msg);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean routing(String msg, String type) {
        System.out.println("理由模式发送消息:msg="+msg+",type="+type+"");
        try {
            //路由模式就不能直接发送消息到队列了, 而是发送到交换机,由交换机进行广播, routingKey为路由Key 订阅模式给""
            rabbitTemplate.convertAndSend("exchange_routing",type, msg);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean topic(String msg, String type) {
        System.out.println("主题模式发送消息:msg="+msg+",type="+type+"");
        try {
            //主题模式会根据 type的通配符进行分发
            rabbitTemplate.convertAndSend("exchange_topic",type, msg);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean confirm(String msg, String type) {
        System.out.println("发布确认模式发送消息:msg="+msg+",type="+type+"");
        try {
            rabbitTemplate.convertAndSend("exchange_confirm",type, msg);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }
}

 发送消息

 接收消息

 发送成功后回调函数会执行,即使消费者没有消费该消息,回调函数仍然会执行

 消息回退

当第二条消息推送后消费者是没有消费消息的,虽然推送成功,但是却被丢弃了,而此时生产者需要知道此消息是否被消费成功,所有就使用到了消息回退机制

spring.rabbitmq.publisher-returns=true

1.设置回调函数

MyReturnCallBack

package com.model.callback;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

/**
 * @Author: Haiven
 * @Time: 2024/4/23 10:16
 * @Description: TODO
 */
@Component
@Slf4j
public class MyReturnCallBack implements RabbitTemplate.ReturnCallback {
    /**
     *
     * @param msg 消息对象
     * @param errCode 错误码
     * @param errMsg 错误信息
     * @param exchange 交换机
     * @param rout 路由键
     */
    @Override
    public void returnedMessage(Message msg, int errCode, String errMsg, String exchange, String rout) {
        log.debug("消息对象={},错误码={},错误消息={},交换机={},路由键={}", msg, errCode, errMsg, exchange, rout);
    }
}

 此函数会在消息被丢弃或者消费失败后回调

2.设置到配置中

在RabbitmqConfig配置文件设置

package com.model.config;

import com.model.callback.MyConfirmCallBack;
import com.model.callback.MyReturnCallBack;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

/**
 * @Author: Haiven
 * @Time: 2024/4/18 17:28
 * @Description: TODO
 */
@Configuration
public class RabbitmqConfig {

    @Value("${rabbitmq.work.queue}")
    private String workQueue;

    @Resource
    private MyConfirmCallBack myConfirmCallBack;

    @Resource
    private MyReturnCallBack myReturnCallBack;

    /**
     * 工作模式的队列
     * @return 队列
     */
    @Bean(name = "workQueue")
    public Queue getWorkQueue(){
        return QueueBuilder.durable(workQueue).build();
    }

    @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        // 设置开启 Mandatory 强制执行调用回调函数
        rabbitTemplate.setMandatory(true);
        //设置发布确认
        rabbitTemplate.setConfirmCallback(myConfirmCallBack);
        //设置消息回退
        rabbitTemplate.setReturnCallback(myReturnCallBack);
        //设置回退回调
        return rabbitTemplate;
    }

}

先注入,在设置到rabbitTemplate对象中,这样发送消息时就可以回调

3.发送并接收消息

此处使用confirm交换机测试

发送一条没有路由的消息

 

 此时消息推送成功,但是没有被消费者消费,而是被丢弃,所有消息回退的回调函数执行:

消息对象=(

        Body:'消息',

        MessageProperties : [

                headers={},

                contentType=text/plain,

                contentEncoding=UTF-8,                                           

                contentLength=0,

                receivedDeliveryMode=PERSISTENT,

                priority=0, deliveryTag=0])

错误码=312

错误消息=NO_ROUTE

交换机=exchange_confirm

路由键=unknown

 发送一条路由的消息

 消息推送到交换机成功,并被成功消费,回退消息的回调函数未执行

备份交换机

有了发布确认和回退消息,我们获得了对无法投递消息的感知能力,在生产者的消息无法被投递时发现并处理。但有时候,我们并不知道该如何处理这些无法路由的消息,最多打个日志,然后触发报警,再来手动处理。而通过日志来处理这些无法路由的消息是很不优雅的做法,特别是当生产者所在的服务有多台机器的时候,手动复制日志会更加麻烦而且容易出错。而且设置参数会增加生产者的复杂性,需要添加处理这些被退回的消息的逻辑。如果既不想丢失消息,又不想增加生产者的复杂性,就可以使用备份交换机

 备份交换机可以理解为 RabbitMQ 中交换机的“备份”,当我们为某一个交换机声明一个对应的备份交换机时,就是为它创建一个 备份,当交换机接收到一条不可路由消息时,将会把这条消息转发到备份交换机中,由备份交换机来进行转发和处理,通常备份交换机的类型为 Fanout ,这样就能把所有消息都投递到与其绑定的队列中,然后我们在备份交换机下绑定一个队列,这样所有那些原交换机无法被路由的消息,就会都进 入这个队列了。当然,我们还可以建立一个报警队列,用独立的消费者来进行监测和报警。

1.创建备份交换机

为了方便区分,这里新建一个配置类ConfirmBackupConfig,用于创建虚拟机和队列,备份交换机的类型一定要为fanout

package com.model.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.annotation.Order;

/**
 * @Author: Haiven
 * @Time: 2024/4/23 15:07
 * @Description: TODO
 */
@Configuration
public class ConfirmBackupConfig {
    /**
     * exchange_confirm 交换机的备份交换机
     * @return exchange
     */
    @Bean(name = "confirmBackupExchange")
    public Exchange getConfirmBackupExchange(){
        return ExchangeBuilder
                .fanoutExchange("exchange_confirm_backup")
                .build();
    }

    @Bean("confirmBackupQueue")
    public Queue getConfirmBackupQueue(){
        return QueueBuilder
                .durable("queue_confirm_backup")
                .build();
    }

    @Bean("confirmBackupBinding")
    public Binding getConfirmBackupBinding(){
        return BindingBuilder
                .bind(getConfirmBackupQueue())
                .to(getConfirmBackupExchange())
                .with("")
                .noargs();
    }
}

 2.绑定备份交换机

这里我们用上面的消息回退测试用的交换机进行测试,就不额外再创建了,在ConfirmConfig配置文件中直接绑定:

.withArgument("alternate-exchange", "exchange_confirm_backup")

在创建交换机的时候直接指定 alternate-exchange,exchange_confirm_backup为备份交换的名称

package com.model.config;

import org.springframework.amqp.core.*;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * @Author: Haiven
 * @Time: 2024/4/22 17:22
 * @Description: TODO
 */
@Configuration
public class ConfirmConfig {

    /**
     * 测试发布确认的交换机
     * @return exchange
     */
    @Bean(name = "confirmExchange")
    public Exchange getConfirmExchange(){
        return ExchangeBuilder
                .directExchange("exchange_confirm")
                .withArgument("alternate-exchange", "exchange_confirm_backup")
                .durable(true)
                .build();
    }

    /**
     * 队列
     * @return queue
     */
    @Bean(name = "confirmQueue")
    public Queue getConfirmQueue(){
        return QueueBuilder
                .durable("queue_confirm")
                .build();
    }

    /**
     * 绑定队列
     * @return binding
     */
    @Bean
    public Binding getConfirmBinding(){
        return BindingBuilder
                .bind(getConfirmQueue())
                .to(getConfirmExchange())
                //路由键 队列1接收debug级别的消息
                .with("confirm")
                .noargs();
    }

}

 由于之前创建exchange_confirm交换机的时候没有指定备份交换机,所以这里要先将该交换机删掉,然后重新创建,备份交换机一定要在创建的时候指定

 进入控制台删除,不想删可创建新的交换机用于测试

 3.备份交换机消费者

直接在ConfirmConsumer配置文件中声明confirmBackupConsumer

package com.model.listener;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * @Author: Haiven
 * @Time: 2024/4/22 17:31
 * @Description: TODO
 */
@Component
@Slf4j
public class ConfirmConsumer {

    @RabbitListener(queues = {"queue_confirm"})
    public void confirmConsumer(String msg){
        System.out.println("消费者 -confirm- 接收消息:" + msg);
    }

    @RabbitListener(queues = {"queue_confirm_backup"})
    public void confirmBackupConsumer(String msg){
        log.debug("消费者 -- 备份队列 -- 接收消息:" + msg);
    }
}

4.发送消息测试

发送一条没有路由的消息

1.可以看到发布确认的消息回调执行,说明已经推送到交换机

2.但是消息回退的回调没有执行,但该消息没有被confirm的消费者消费

3.该消息被备份交换机的消费者消费

有了备份交换机,消息如果消费失败,消息不会回退,而会被备份队列的消费者接收

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

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

相关文章

2024精武杯复现

第一部分&#xff1a;计算机和手机取证 1. 请综合分析计算机和手机检材&#xff0c;计算机最近一次登录的账户名是 用火眼分析后在登录信息查找最后登录的人&#xff0c;用户名是admin 2. 请综合分析计算机和手机检材&#xff0c;计算机最近一次插入的USB存储设备串号是 在u…

AIX7环境上一次艰难的Oracle打补丁经历

系统环境 AIX &#xff1a;7200-05-03-2148 Oracle&#xff1a;11.2.0.4 PSU: 11.2.0.4.201020&#xff08;31718723&#xff09; perl:5.28 问题一&#xff1a;AUTO patch #/u01/app/11.2.0/grid/OPatch/opatch auto /tmp/31718723 错误信息如下&#xff1a;匹配mos 2516761.1…

Spring MVC系列之九大核心组件

概述 Spring MVC是面试必问知识点其一&#xff0c;Spring MVC知识体系庞杂&#xff0c;有以下九大核心组件&#xff1a; HandlerMappingHandlerAdapterHandlerExceptionResolverViewResolverRequestToViewNameTranslatorLocaleResolverThemeResolverMultipartResolverFlashMa…

LeetCode 面试题 08.02——迷路的机器人

阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 此题就是一个典型的图搜索题&#xff0c;一种就是广度优先搜索&#xff0c;一种就是深度优先搜索。 3. 代码实现 class Solution { public:vector<vector<int>> pathWithObstacles(vector<vecto…

Go源码--Strconv库

简介 Strconv 库是一些跨类型的转换函数集合&#xff0c;大家应该很熟悉。源码没有什么难点&#xff0c;主要是面试题有可能会出这种类型的&#xff0c;所以简单介绍下&#xff0c;主要介绍 以下两种常用转换&#xff0c;其他的没细研究&#xff0c;感兴趣的可以看看。 Strco…

[笔试训练](八)

目录 022&#xff1a;求最小公倍数 023&#xff1a;数组中的最长连续子序列 024&#xff1a;字母收集 022&#xff1a;求最小公倍数 求最小公倍数_牛客题霸_牛客网 (nowcoder.com) 题目&#xff1a; 题解&#xff1a; 求最小公倍数公式&#xff1a;lcm(a,b)a*b/gcd(a,b)&am…

网页提示语闪太快的定位问题(selenium)

selenium UI自动化时&#xff0c;提示语闪太快&#xff0c;导致无法获取元素的问题 解决办法 步骤一&#xff1a; F12---》控制台输入debugger 步骤二&#xff1a;对于需要定位的部分&#xff0c;在控制台的debugger处回车&#xff0c;可以定住页面 步骤三&#xff1a;正常定…

win11 安装qt5.14.2 、qtcreator、vs编译器 。用最小安装进行 c++开发qt界面

系统 &#xff1a;win11 一、安装vs生成工具 &#xff0c;安装编译器 下载visualstudio tools 生成工具&#xff1a; 安装编译器 和 windows sdk&#xff1a; 安装debug 调试器&#xff1a; 二、Qt5.14.2下载 下载链接: Index of /archive/qt/5.14/5.14.2 安装qt 三、配置QT/…

MVP+敏捷开发

MVP敏捷开发 1. 什么是敏捷开发&#xff1f; 敏捷开发是一种软件开发方法论&#xff0c;旨在通过迭代、自组织的团队和持续反馈&#xff0c;快速响应需求变化并交付高质量的软件。相较于传统的瀑布模型&#xff0c;敏捷开发强调灵活性、适应性和与客户的紧密合作。敏捷开发方…

Llama网络结构介绍

LLaMA现在已经是开源社区里炙手可热的模型了&#xff0c;但是原文中仅仅介绍了其和标准Transformer的差别&#xff0c;并没有一个全局的模型介绍。因此打算写篇文章&#xff0c;争取让读者不参考任何其他资料把LLaMA的模型搞懂。 结构 如图所示为LLaMA的示意图&#xff0c;由…

解决ax = Axes3D(fig2)pycharm画3d图空白不显示问题

明明代码运行正确&#xff0c;却总是显示不出来 绘制出来的也是空白 改一下代码就好了 ax Axes3D(fig2) #原来代码 ax fig2.add_axes(Axes3D(fig2)) #改后代码 修改过后就可以显示了

【Jenkins】持续集成与交付 (三):有关报错解决(Jenkins (2.387.3) or higher required)

【Jenkins】持续集成与交付 (三):有关报错解决Jenkins (2.387.3) or higher required 一、Jenkins主页报错二、安装Jenkins插件报错三、解决过程(解压替换jenkins.war)四、重新访问登录💖The Begin💖点点关注,收藏不迷路💖 一、Jenkins主页报错 New version of J…

【nodejs状态库mobx之computed规则】

The above example nicely demonstrates the benefits of a computed value, it acts as a caching point. Even though we change the amount, and this will trigger the total to recompute, it won’t trigger the autorun, as total will detect its output hasn’t been …

一分钟了解期权合约转仓交易的流程

期权合约转仓交易的流程 期权合约转仓交易是指在期权交易过程中&#xff0c;交易者平掉手中现有的仓位&#xff0c;并选择更高或更低的行权价格&#xff0c;进行买入或卖出开仓的交易方法。转仓交易具有多个优势&#xff0c;包括降低风险、锁定利润、提高资金使用效率以及增加…

关于Github默认分支main和master以及如何在git init时指定默认分支

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

内置对象部分

一&#xff0c;内置对象 二&#xff0c;math对象 不是构造函数&#xff0c;不需要new来调用&#xff0c;而是直接使用里面的属性和方法即可 1.随机方法random 返回一个随机的小数 [0,1&#xff09; 2.日起格式化 返回的月份会小一&#xff0c;记得加一 周一返回1&#xff…

什么是储能电站的一次设备与二次设备?

随着国家政策导向和扶持&#xff0c;储能电站的建设&#xff0c;在各地均稳步推进&#xff0c;储能电站的设备主要分一次设备和二次设备两种&#xff0c;下面分别介绍这两方面内容&#xff1a; 储能电站一次设备 一次设备是储能电站的电路基础设施&#xff0c;包含变压器、主…

提升你的C编程技能:使用cURL下载Kwai视频

概述 本文将介绍如何利用C语言以及cURL库来实现Kwai视频的下载。cURL作为一个功能强大的网络传输工具&#xff0c;能够在C语言环境下轻松地实现数据的传输。我们还将探讨如何运用代理IP技术&#xff0c;提升爬虫的匿名性和效率&#xff0c;以适应Kwai视频平台的发展趋势。 正…

代码随想录算法训练营day40

题目&#xff1a;343. 整数拆分、96.不同的二叉搜索树 参考链接&#xff1a;代码随想录 343. 整数拆分 思路&#xff1a;五部曲来走。dp数组&#xff0c;dp[i]用于记录拆i得到的最大乘积和&#xff0c;我们要求的也就是dp[n]&#xff1b;递推公式&#xff0c;我们想拆分i&am…

简单把玩下SpringAI

Hello大家好&#xff0c;今天写一些不烧脑的文章&#xff0c;我们来体验一下Spring的新框架Spring AI&#xff0c;只是简单玩玩不深入&#x1f60f; Spring AI 简介 Spring AI是为了简化人工智能相关应用程序的开发Spring AI的诞生&#xff0c;灵感来自于LangChain等项目 Oll…