【RabbitMQ】发布确认机制的具体实现

news2025/7/12 12:21:47

文章目录

    • 模式介绍
    • 建立连接
    • 单独确认
      • 代码实现逻辑
      • 运行结果
    • 批量确认
      • 代码实现逻辑
      • 运行结果
    • 异步确认
      • 实现逻辑介绍
      • 代码实现逻辑
      • 运行结果
    • 三种策略对比以及完整代码

模式介绍

作为消息中间件,都会面临消息丢失的问题,消息丢失大概分为三种情况:

  1. 生产者问题:因为应用程序故障,网络抖动等各种原因,生产者没有成功向 broker 发送消息
  2. 消息中间件自身问题:生产者成功发送给了 Broker,但是 Broker 没有把消息保存好,导致消息丢失
  3. 消费者问题:Broker 发送消息到消费者,消费者在消费消息时,因为没有处理好,导致 broker 将消费失败的消息从列表中删除了

image.png

  • RabbitMQ 也对上述问题给出了相应的解决方案。
    • 问题二可以通过持久化机制
    • 问题三可以采用消息应答机制
    • 问题一可以采用发布确认机制

发布确认属于 RabbitMQ 的七大工作模式之一

生产者将信道设置成 confirm(确认)模式

  • 一旦信道进入 confirm 模式,所有在该信道上面发布的消息都会被指派一个唯一的 ID(从 1 开始)
    • 同一个 channel 下,序号不可能重复
  • 一旦消息被投递到所有匹配的对类之后,brocker 就会发送一个确认(ACK)给生产者(包含消息的唯一 ID
    • 这就使得生产者知道消息已经正确到达目的的队列了
  • 如果消息和对类是可持久化的,那么确认消息会在将消息写入磁盘之后发出
    • broker 回传给生产者的确认消息中 deliveryTag 包含了确认消息的序号
    • 此外 broker 也可以设置 channel.basicAck 方法中的 multiple 参数,表示到这个序号之前的所有消息都已经得到了处理
      image.png

发送确认机制最大的好处在于它是异步的,生产者可以同时发布消息和等待信道返回确认消息

  1. 当消息最终得到确认之后,生产者可以通过回调方法来处理该确认消息
  2. 如果 RabbitMQ 因为自身内部错误导致消息丢失,就会发送一条 nack(Basic.Nack) 命令,生产者同样可以在回调方法中处理该 nack 命令

使用发布确认机制,必须要信道设置成 confirm (确认) 模式

  • 发布确认是 AMQP 0.9.1 协议的扩展,默认情况下它不会被启用
  • 生产者通过 channel.confirmSelect() 将信道设置为 confirm 模式
Channel channel = connection.createChannel();
channel.confirmSelect();

发布确认有三种策略,接下来我们来介绍这三种策略

ProducerBrockerConsumer 都有可能丢失消息

  • 发布确认是来解决生产者 Producer 消息丢失的问题
  • 生产者可以在发送消息的同时,等待返回确认消息

建立连接

因为每一个策略都需要重复建立链接这一步骤,所以我们将其提出来,单独作为一个方法,需要的时候直接调用即可

  • 之后就不用重复写这一部分代码了
  • 在类里面,main 方法外面,使用一个静态方法
public class PublisherConfirms {  
  
    static Connection createConnection() throws Exception {  
        ConnectionFactory connectionFactory = new ConnectionFactory();  
        connectionFactory.setHost(Constants.HOST);  
        connectionFactory.setPort(Constants.PORT);  
        connectionFactory.setUsername(Constants.USER_NAME);  
        connectionFactory.setPassword(Constants.PASSWORD);  
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);  
        return connectionFactory.newConnection();  
    }  
  
    public static void main(String[] args) {  
        
    }  
  
}

单独确认

Publishing Messages Individually

代码实现逻辑

    /**  
     * 单独确认  
     */  
    private static void publishingMessagesIndividually() throws Exception {  
        // 1. 创建连接  
        // 我们将连接的建立写在 try 里面,这样就不用再去关闭了  
        try(Connection connection = createConnection()) {  
            // 2. 开启信道  
            Channel channel = connection.createChannel();  
  
            // 3. 设置信道为 confirms 模式  
            channel.confirmSelect();  
  
            // 4. 声明队列(交换机就使用内置的,就不再声明了)  
            // 队列对象、是否持久化、是否独占、是否自动删除、传递参数  
            channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE1, true, false, false,null);  
  
            // 5. 发送消息,并等待确认  
            // 这里我们需要可以再创建一个 MESSAGE_COUNT 全局变量,来指定消息的数量  
            long start = System.currentTimeMillis();  
            for (int i = 0; i < MESSAGE_COUNT; i++) {  
                String msg = "hello publisher confirms" + i;  
                // 信道的发送  
                // 交换机的名称(我们使用的是内置交换机,也就是空的)、routingKey、  
                channel.basicPublish("", Constants.PUBLISHER_CONFIRMS_QUEUE1, null, msg.getBytes());  
  
                // 等待确认(等待确认消息,只要消息被确认,这个方法就会被返回)  
                // 有 waitForConfirms() 和 waitForConfirmsOrDie() 随便用哪个  
                // 如果超时过期,则抛出 TimeoutException。如果任何消息被 nack(丢失),waitForConfirmsOrDie 则抛出 Exception  
                channel.waitForConfirmsOrDie(5000);  
            }  
            long end = System.currentTimeMillis();  
            // 这里注意是 printf            System.out.printf("单独确认==>消息条数: %d, 耗时: %d ms \n", MESSAGE_COUNT, end-start);  
        }  
    }

运行结果

单独确认==>消息条数: 200, 耗时: 5793 ms 
  • 可以发现,耗时较长

观察上面代码,会发现这种策略是每发送一条消息后就调用 channel.waitForConfirmsOrDie() 方法, 之后等待服务器的确认

  • 这其实是一种串行同步等待的方式
  • 尤其对于持久化的消息来说,需要等待消息确认存储在硬盘之后才会返回 (调用 Linux 内核中的 fsync 方法)

但是消息确认机制是支持异步的,可以一边发送消息,一边等待消息确认。由此进行了改进,我们看另外两种策略

  • Publishing Messages in Batches(批量确认):每发送一批消息之后,调用 channel.waitForConfirms 方法,等待服务器的确认返回
  • Handling Publisher Confirms Asynchronously(异步确认):提供一个回调方法,服务端确认了一条或者多条消息后,客户端会对这个方法进行处理

批量确认

Publishing Messages in Batches

代码实现逻辑

/**  
 * 批量确认  
 */  
private static void publishingMessagesInBatches() throws Exception {  
    // 1. 建立连接  
    try(Connection connection = createConnection()){  
        // 2. 开启信道  
        Channel channel = connection.createChannel();  
  
        // 3. 设置信道为 confirm 模式  
        channel.confirmSelect();  
  
        // 4. 声明队列  
        channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE2, true, false, false, null);  
  
        // 5. 发送消息,并进行确认  
        // 设置批量处理的大小和计数器  
        long start = System.currentTimeMillis();  
        int batchSize = 100;  
        int outstandingMessageCount = 0;  
        for (int i = 0; i < MESSAGE_COUNT; i++) {  
            String msg = "hello publisher confirms" + i;  
            channel.basicPublish("", Constants.PUBLISHER_CONFIRMS_QUEUE2, null, msg.getBytes());  
            outstandingMessageCount++;  
            // 当计数器的大小达到了设置的批量处理大小,就进行确认  
            if(outstandingMessageCount == batchSize) {  
                channel.waitForConfirmsOrDie(5000);  
                // 消息确认后,计数器要清零  
                outstandingMessageCount = 0;  
            }  
  
            // 当计数器大小 < 100 的时候,由于没有达到批量发送的标准,所以单独再进行发送  
            if (outstandingMessageCount > 0) {  
                channel.waitForConfirmsOrDie(5000);  
            }  
        }  
        long end = System.currentTimeMillis();  
  
        System.out.printf("批量确认==>消息条数: %d, 耗时: %d ms", MESSAGE_COUNT, end-start);  
    }  
}

运行结果

批量确认==>消息条数: 200, 耗时: 128 ms 

异步确认

Handling Publisher Confirms Asynchronously

实现逻辑介绍

异步 confirm 方法的编程实现最为复杂

  • Channel 接口提供了一个方法 addConfirmListener()
  • 这个方法可以添加 ConfirmListener 回调接口

ConfirmListener 接口中包含两个方法:

  • handleAck(long deliveryTag, boolean multiple),处理 RabbitMQ 发送给生产者的 ack
    • deliveryTag 表示发送消息的序号
    • multiple 表示是否批量确认
  • handleNack(long deliveryTag, boolean multiple),处理 RabbitMQ 发送给生产者的 nack
    image.png|413

我们需要为每一个 Channel 维护一个已发送消息的序号集合

  • 当收到 RabbitMQconfirm 回调时,从集合中删除对应的消息
  • Channel 开启 confirm 模式后,channel 上发送消息都会附带一个从 1 开始递增的 deliveryTag 序号
  • 我们可以使用 SortedSet 的有序性来维护这个已发消息的集合
    1. 当收到 ack 时,从序列中删除该消息的序号。如果为批量确认消息,表示小于当前序号 deliveryTag 的消息都收到了,则清楚对应集合
    2. 当收到 nack 时,处理逻辑类似,不过需要结合具体业务情况,进行消息重发等操作

代码实现逻辑

/**  
 * 异步确认  
 */  
private static void handlingPublisherConfirmsAsynchronously() throws Exception {  
    // 1. 建立连接  
    try(Connection connection = createConnection()) {  
        // 2. 开启信道  
        Channel channel = connection.createChannel();  
  
        // 3. 设置信道为 confirm 模式  
        channel.confirmSelect();  
  
        // 4. 声明队列  
        channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE3, true, false, false, null);  
  
        // 5. 监听 confirm        long start = System.currentTimeMillis();  
        // 创建一个集合,用来存放未确认的消息(的id)  
  
        SortedSet<Long> confirmSeqNo = Collections.synchronizedSortedSet(new TreeSet<>());  
  
        channel.addConfirmListener(new ConfirmListener() {  
            @Override  
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {  
                // 如果是批量确认,就要将集合中 <= deliveryTag 的 id 都给清除掉  
                if(multiple) {  
                    // headSet(n)方法返回当前集合中小于 n 的集合  
                    // 先获取到这部分 id,然后一起 clear 清除掉即可  
                    confirmSeqNo.headSet(deliveryTag + 1).clear();  
                }else {  
                    // 单独确认,只需要移除当前这个 id 即可  
                    confirmSeqNo.remove(deliveryTag);  
                }  
            }  
  
            @Override  
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {  
                // 和 ack 处理模式基本是相似的,只是多了一步重发处理  
                if(multiple) {  
                    confirmSeqNo.headSet(deliveryTag + 1).clear();  
                }else {  
                    confirmSeqNo.remove(deliveryTag);  
                }  
  
                // 业务需要根据实际场景进行处理,比如重发,此处代码省略  
            }  
        });  
  
        // 6. 发送消息  
        for (int i = 0; i < MESSAGE_COUNT; i++) {  
            String msg = "hello publisher confirms" + i;  
            long seqNo = channel.getNextPublishSeqNo(); // 拿到消息的序号  
            channel.basicPublish("", Constants.PUBLISHER_CONFIRMS_QUEUE3, null, msg.getBytes());  
            confirmSeqNo.add(seqNo); // 将消息的序号加入集合中  
        }  
  
        // 确认消息已处理完  
        while (!confirmSeqNo.isEmpty()) {  
            // 没有处理完,就休眠一段时间后再确认一下,看是否处理完  
            Thread.sleep(10);  
        }  
        long end = System.currentTimeMillis();  
        System.out.printf("异步确认==>消息条数: %d, 耗时: %d ms", MESSAGE_COUNT, end-start);  
    }  
}

运行结果

单独确认==>消息条数: 200, 耗时: 93 ms 

三种策略对比以及完整代码

package rabbitmq.publisher.confirms;  
  
import com.rabbitmq.client.Channel;  
import com.rabbitmq.client.ConfirmListener;  
import com.rabbitmq.client.Connection;  
import com.rabbitmq.client.ConnectionFactory;  
import rabbitmq.constant.Constants;  
  
import java.io.IOException;  
import java.util.Collections;  
import java.util.SortedSet;  
import java.util.TreeSet;  
  
public class PublisherConfirms {  
  
    private static final Integer MESSAGE_COUNT = 200;  
  
    static Connection createConnection() throws Exception {  
        ConnectionFactory connectionFactory = new ConnectionFactory();  
        connectionFactory.setHost(Constants.HOST);  
        connectionFactory.setPort(Constants.PORT);  
        connectionFactory.setUsername(Constants.USER_NAME);  
        connectionFactory.setPassword(Constants.PASSWORD);  
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);  
        return connectionFactory.newConnection();  
    }  
  
    public static void main(String[] args) throws Exception {  
        // Strategy #1: Publishing Messages Individually  
        // 单独确认  
        publishingMessagesIndividually();  
  
        // Strategy #2: Publishing Messages in Batches  
        // 批量确认  
        publishingMessagesInBatches();  
  
        // Strategy #3: Handling Publisher Confirms Asynchronously  
        // 异步确认  
        handlingPublisherConfirmsAsynchronously();  
    }  
  
    /**  
     * 单独确认  
     */  
    private static void publishingMessagesIndividually() throws Exception {  
        // 1. 创建连接  
        // 我们将连接的建立写在 try 里面,这样就不用再去关闭了  
        try(Connection connection = createConnection()) {  
            // 2. 开启信道  
            Channel channel = connection.createChannel();  
  
            // 3. 设置信道为 confirms 模式  
            channel.confirmSelect();  
  
            // 4. 声明队列(交换机就使用内置的,就不再声明了)  
            // 队列对象、是否持久化、是否独占、是否自动删除、传递参数  
            channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE1, true, false, false,null);  
  
            // 5. 发送消息,并等待确认  
            // 这里我们需要可以再创建一个 MESSAGE_COUNT 全局变量,来指定消息的数量  
            long start = System.currentTimeMillis();  
            for (int i = 0; i < MESSAGE_COUNT; i++) {  
                String msg = "hello publisher confirms" + i;  
                // 信道的发送  
                // 交换机的名称(我们使用的是内置交换机,也就是空的)、routingKey、  
                channel.basicPublish("", Constants.PUBLISHER_CONFIRMS_QUEUE1, null, msg.getBytes());  
  
                // 等待确认(等待确认消息,只要消息被确认,这个方法就会被返回)  
                // 有 waitForConfirms() 和 waitForConfirmsOrDie() 随便用哪个  
                // 如果超时过期,则抛出 TimeoutException。如果任何消息被 nack(丢失),waitForConfirmsOrDie 则抛出 Exception                channel.waitForConfirmsOrDie(5000);  
            }  
            long end = System.currentTimeMillis();  
            // 这里注意是 printf            System.out.printf("单独确认==>消息条数: %d, 耗时: %d ms \n", MESSAGE_COUNT, end-start);  
        }  
    }  
  
  
    /**  
     * 批量确认  
     */  
    private static void publishingMessagesInBatches() throws Exception {  
        // 1. 建立连接  
        try(Connection connection = createConnection()){  
            // 2. 开启信道  
            Channel channel = connection.createChannel();  
  
            // 3. 设置信道为 confirm 模式  
            channel.confirmSelect();  
  
            // 4. 声明队列  
            channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE2, true, false, false, null);  
  
            // 5. 发送消息,并进行确认  
            // 设置批量处理的大小和计数器  
            long start = System.currentTimeMillis();  
            int batchSize = 100;  
            int outstandingMessageCount = 0;  
            for (int i = 0; i < MESSAGE_COUNT; i++) {  
                String msg = "hello publisher confirms" + i;  
                channel.basicPublish("", Constants.PUBLISHER_CONFIRMS_QUEUE2, null, msg.getBytes());  
                outstandingMessageCount++;  
                // 当计数器的大小达到了设置的批量处理大小,就进行确认  
                if(outstandingMessageCount == batchSize) {  
                    channel.waitForConfirmsOrDie(5000);  
                    // 消息确认后,计数器要清零  
                    outstandingMessageCount = 0;  
                }  
  
                // 当计数器大小 < 100 的时候,由于没有达到批量发送的标准,所以单独再进行发送  
                if (outstandingMessageCount > 0) {  
                    channel.waitForConfirmsOrDie(5000);  
                }  
            }  
            long end = System.currentTimeMillis();  
  
            System.out.printf("批量确认==>消息条数: %d, 耗时: %d ms \n", MESSAGE_COUNT, end-start);  
        }  
    }  
  
  
    /**  
     * 异步确认  
     */  
    private static void handlingPublisherConfirmsAsynchronously() throws Exception {  
        // 1. 建立连接  
        try(Connection connection = createConnection()) {  
            // 2. 开启信道  
            Channel channel = connection.createChannel();  
  
            // 3. 设置信道为 confirm 模式  
            channel.confirmSelect();  
  
            // 4. 声明队列  
            channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE3, true, false, false, null);  
  
            // 5. 监听 confirm            long start = System.currentTimeMillis();  
            // 创建一个集合,用来存放未确认的消息(的id)  
  
            SortedSet<Long> confirmSeqNo = Collections.synchronizedSortedSet(new TreeSet<>());  
  
            channel.addConfirmListener(new ConfirmListener() {  
                @Override  
                public void handleAck(long deliveryTag, boolean multiple) throws IOException {  
                    // 如果是批量确认,就要将集合中 <= deliveryTag 的 id 都给清除掉  
                    if(multiple) {  
                        // headSet(n)方法返回当前集合中小于 n 的集合  
                        // 先获取到这部分 id,然后一起 clear 清除掉即可  
                        confirmSeqNo.headSet(deliveryTag + 1).clear();  
                    }else {  
                        // 单独确认,只需要移除当前这个 id 即可  
                        confirmSeqNo.remove(deliveryTag);  
                    }  
                }  
  
                @Override  
                public void handleNack(long deliveryTag, boolean multiple) throws IOException {  
                    // 和 ack 处理模式基本是相似的,只是多了一步重发处理  
                    if(multiple) {  
                        confirmSeqNo.headSet(deliveryTag + 1).clear();  
                    }else {  
                        confirmSeqNo.remove(deliveryTag);  
                    }  
  
                    // 业务需要根据实际场景进行处理,比如重发,此处代码省略  
                }  
            });  
  
            // 6. 发送消息  
            for (int i = 0; i < MESSAGE_COUNT; i++) {  
                String msg = "hello publisher confirms" + i;  
                long seqNo = channel.getNextPublishSeqNo(); // 拿到消息的序号  
                channel.basicPublish("", Constants.PUBLISHER_CONFIRMS_QUEUE3, null, msg.getBytes());  
                confirmSeqNo.add(seqNo); // 将消息的序号加入集合中  
            }  
  
            // 确认消息已处理完  
            while (!confirmSeqNo.isEmpty()) {  
                // 没有处理完,就休眠一段时间后再确认一下,看是否处理完  
                Thread.sleep(10);  
            }  
            long end = System.currentTimeMillis();  
            System.out.printf("异步确认==>消息条数: %d, 耗时: %d ms", MESSAGE_COUNT, end-start);  
        }  
    }  
  
}
  • 消息条数越多,异步确认的优势越明显

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

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

相关文章

React状态管理-对state进行保留和重置

相同位置的相同组件会使得 state 被保留下来 当你勾选或清空复选框的时候&#xff0c;计数器 state 并没有被重置。不管 isFancy 是 true 还是 false&#xff0c;根组件 App 返回的 div 的第一个子组件都是 <Counter />&#xff1a; 你可能以为当你勾选复选框的时候 st…

vue和springboot交互数据,使用axios【跨域问题】

vue和springboot交互数据&#xff0c;使用axios【跨域问题】 提示&#xff1a;帮帮志会陆续更新非常多的IT技术知识&#xff0c;希望分享的内容对您有用。本章分享的是node.js和vue的使用。前后每一小节的内容是存在的有&#xff1a;学习and理解的关联性。【帮帮志系列文章】&…

AJAX 使用 和 HTTP

ajax学习 promise和 awit Node.js 和 webpack 前端工程化 Git工具 AJAX异步的JS和XML&#xff1a; 使用XML对象和服务器通信 在这里插入图片描述 统一资源定位符 URL HTTP 超文本传输协议 域名 资源路径 资源目录和类型 URL 查询参数 使用&#xff1f;表示之后的参数…

MySQL之基础事务

目录 引言&#xff1a; 什么是事务&#xff1f; 事务和锁 mysql数据库控制台事务的几个重要操作指令&#xff08;transaction.sql&#xff09; 1、事物操作示意图&#xff1a; 2.事务的隔离级别 四种隔离级别&#xff1a; 总结一下隔离指令 1. 查看当前隔离级别​​ …

MySQL基础关键_013_常用 DBA 命令

目 录 一、MySQL 用户信息存储位置 二、新建用户 1.创建本地用户 2.创建外网用户 三、用户授权 1.说明 2.实例 四、撤销授权 五、修改用户密码 六、修改用户名、主机名/IP地址 七、删除用户 八、数据备份 1.导出数据 2.导入数据 &#xff08;1&#xff09;方式…

java基础:异常体系

目录 一、java异常体系介绍二、异常1、运行时异常2、非运行时异常 三、错误四、异常的处理方式1、方式1&#xff1a;throws声明抛出异常1.1、throws关键字1.2、throw关键字 2、方式2&#xff1a;try-catch-finally 一、java异常体系介绍 异常体系图如下&#xff1a; Throwable…

记录算法笔记(20025.5.14)对称二叉树

给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false 提示&#xff1a; 树中节点数目…

QT Creator配置Kit

0、背景&#xff1a;qt5.12.12vs2022 记得先增加vs2017编译器 一、症状&#xff1a; 你是否有以下症状&#xff1f; 1、用qt新建的工程&#xff0c;用qmake&#xff0c;可惜能看见的只有一个pro文件&#xff1f; 2、安装QT Creator后&#xff0c;使用MSVC编译显示no c com…

JVM 与云原生的完美融合:引领技术潮流

最近佳作推荐&#xff1a; Java 大厂面试题 – 揭秘 JVM 底层原理&#xff1a;那些令人疯狂的技术真相&#xff08;New&#xff09; Java 大厂面试题 – JVM 性能优化终极指南&#xff1a;从入门到精通的技术盛宴&#xff08;New&#xff09; Java 大厂面试题 – JVM 深度剖析&…

为何大模型都使用decoder-only?

第一章 架构之争的历史脉络 1.1 从双向到单向的革命 2017年&#xff0c;BERT的横空出世让双向注意力机制成为NLP领域的“武林盟主”。通过Masked Language Modeling&#xff08;MLM&#xff09;&#xff0c;BERT在阅读理解、情感分析等任务中展现出惊人的表现&#xff0c;但它…

企业报表平台如何实现降本增效

一、你的企业是否正被这些问题拖累&#xff1f;‌ 财务还在手动汇总各门店的Excel销售数据&#xff1b;市场部总抱怨“客户分析全靠拍脑袋”&#xff1b;仓库突然发现爆款断货&#xff0c;但上周的报表显示库存充足…… 这些场景你是否熟悉&#xff1f;数据散落在ERP、E…

Ollama+OpenWebUI+docker完整版部署,附带软件下载链接,配置+中文汉化+docker源,适合内网部署,可以局域网使用

前言&#xff1a; 因为想到有些环境可能没法使用外网的大模型&#xff0c;所以可能需要内网部署&#xff0c;看了一下ollama适合小型的部署&#xff0c;所以就尝试了一下&#xff0c;觉得docker稍微简单一点&#xff0c;就做这个教程的&#xff0c;本文中重要的内容都会给下载…

ultralytics中tasks.py---parse_model函数解析

一、根据scale获取对应的深度、宽度和最大通道数 具体例如yaml文件内容如下: depth=0.33,那么重复的模块例如C2f原本重复次数是3,6,6,3,那么T对应的模型重复次数就是三分之一即1,1,2,1次。这个在后面定义的: width=0.25,max_channels=1024 原本c2=64,但经过make_div…

2024年业绩增速大幅回退,泸州老窖未能“重回前三”

撰稿|行星 来源|贝多财经 回望过去的2024年&#xff0c;受制于购买力与消费需求的持续疲软&#xff0c;白酒行业的发展面临诸多复杂性与不确定性&#xff0c;“量价齐跌”犹如笼罩在各大企业头顶的一片阴云。 正如巴菲特所言&#xff1a;“当潮水退去时&#xff0c;才知道谁在…

院校机试刷题第二天:1479 01字符串、1701非素数个数

一、1479 01字符串 1.题目描述 2.解题思路 方法一&#xff1a;暴力法 模拟过程&#xff0c;列出几个数据来a[1]1, a[2]2, a[3]3, a[4]5以此类推&#xff0c;这就是斐波那契数列&#xff0c;每一项都等于前两项之和&#xff0c;确定好a[1], a[2]即可。 方法二&#xff1a;动…

制作一款打飞机游戏48:敌人转向

射击功能 有一个重要的功能我们还没实现&#xff0c;那就是射击。目前&#xff0c;敌人还不能射击&#xff0c;这显然是不行的。因此&#xff0c;我们决定添加一个射击命令&#xff0c;暂时用一个显示圆圈的方式来表示射击动作。 编程语言的调试 有趣的是&#xff0c;我们创…

RK3588 串行解串板,支持8路GMSL相机

RK3588 支持的 GMSL 相机接入数量取决于所使用的解串板型号及配置方案&#xff1a; ‌xcDeserializer3.0 解串板‌ 可接入最多 ‌8 路 2M GMSL2 相机‌1。 ‌xcDeserializer4.0 解串板‌ 支持 ‌4 路 2M GMSL2 相机‌1。 ‌边缘计算盒解决方案‌ 部分商用方案可实现 ‌4 或 8…

OracleLinux7.9-ssh问题

有套rac环境&#xff0c;db1主机无法ssh db1和db1-priv&#xff0c;可以ssh登录 db2和db2-priv [rootdb1 ~]# ssh db1 ^C [rootdb1 ~]# ssh db2 Last login: Wed May 14 18:25:19 2025 from db2 [rootdb2 ~]# ssh db2 Last login: Wed May 14 18:25:35 2025 from db1 [rootdb2…

手机换IP真的有用吗?可以干什么?

在当今数字化时代&#xff0c;网络安全和个人隐私保护日益受到重视。手机作为我们日常生活中不可或缺的工具&#xff0c;其网络活动痕迹往往通过IP地址被记录和追踪。那么&#xff0c;手机换IP真的有用吗&#xff1f;它能为我们带来哪些实际好处&#xff1f;本文将为你一一解答…

如何实现一个运动会计分系统?(C语言版)

一、需求分析 设计一个运动会计分系统,计分信息包括参加学校,参与项目,性别,名次个数,各个学校获得名次信息。该系统具有以下功能 数据录入: 链表或结构体数组组织数据数据报表: 依照规定的报表格式对数据打印报表数据排序: 按照要求对数据进行统计,含简单统计及综合统计…