【RabbitMQ】发布确认机制的具体实现
文章目录
- 模式介绍
- 建立连接
- 单独确认
- 代码实现逻辑
- 运行结果
- 批量确认
- 代码实现逻辑
- 运行结果
- 异步确认
- 实现逻辑介绍
- 代码实现逻辑
- 运行结果
- 三种策略对比以及完整代码
模式介绍
作为消息中间件,都会面临消息丢失的问题,消息丢失大概分为三种情况:
- 生产者问题:因为应用程序故障,网络抖动等各种原因,生产者没有成功向
broker
发送消息 - 消息中间件自身问题:生产者成功发送给了
Broker
,但是Broker
没有把消息保存好,导致消息丢失 - 消费者问题:
Broker
发送消息到消费者,消费者在消费消息时,因为没有处理好,导致broker
将消费失败的消息从列表中删除了
RabbitMQ
也对上述问题给出了相应的解决方案。- 问题二可以通过持久化机制
- 问题三可以采用消息应答机制
- 问题一可以采用发布确认机制
发布确认属于 RabbitMQ
的七大工作模式之一
生产者将信道设置成 confirm
(确认)模式
- 一旦信道进入
confirm
模式,所有在该信道上面发布的消息都会被指派一个唯一的 ID(从 1 开始)- 同一个
channel
下,序号不可能重复
- 同一个
- 一旦消息被投递到所有匹配的对类之后,
brocker
就会发送一个确认(ACK
)给生产者(包含消息的唯一ID
)- 这就使得生产者知道消息已经正确到达目的的队列了
- 如果消息和对类是可持久化的,那么确认消息会在将消息写入磁盘之后发出
broker
回传给生产者的确认消息中deliveryTag
包含了确认消息的序号- 此外
broker
也可以设置channel.basicAck
方法中的multiple
参数,表示到这个序号之前的所有消息都已经得到了处理
发送确认机制最大的好处在于它是异步的,生产者可以同时发布消息和等待信道返回确认消息
- 当消息最终得到确认之后,生产者可以通过回调方法来处理该确认消息
- 如果
RabbitMQ
因为自身内部错误导致消息丢失,就会发送一条nack(Basic.Nack)
命令,生产者同样可以在回调方法中处理该nack
命令
使用发布确认机制,必须要信道设置成 confirm
(确认) 模式
- 发布确认是
AMQP 0.9.1
协议的扩展,默认情况下它不会被启用 - 生产者通过
channel.confirmSelect()
将信道设置为confirm
模式
Channel channel = connection.createChannel();
channel.confirmSelect();
发布确认有三种策略,接下来我们来介绍这三种策略
Producer
、Brocker
、Consumer
都有可能丢失消息
- 发布确认是来解决生产者
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
我们需要为每一个 Channel
维护一个已发送消息的序号集合
- 当收到
RabbitMQ
的confirm
回调时,从集合中删除对应的消息 - 当
Channel
开启confirm
模式后,channel
上发送消息都会附带一个从1
开始递增的deliveryTag
序号 - 我们可以使用
SortedSet
的有序性来维护这个已发消息的集合- 当收到
ack
时,从序列中删除该消息的序号。如果为批量确认消息,表示小于当前序号deliveryTag
的消息都收到了,则清楚对应集合 - 当收到
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); } } }
- 消息条数越多,异步确认的优势越明显
相关文章:

【RabbitMQ】发布确认机制的具体实现
文章目录 模式介绍建立连接单独确认代码实现逻辑运行结果 批量确认代码实现逻辑运行结果 异步确认实现逻辑介绍代码实现逻辑运行结果 三种策略对比以及完整代码 模式介绍 作为消息中间件,都会面临消息丢失的问题,消息丢失大概分为三种情况: …...

React状态管理-对state进行保留和重置
相同位置的相同组件会使得 state 被保留下来 当你勾选或清空复选框的时候,计数器 state 并没有被重置。不管 isFancy 是 true 还是 false,根组件 App 返回的 div 的第一个子组件都是 <Counter />: 你可能以为当你勾选复选框的时候 st…...

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

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

MySQL之基础事务
目录 引言: 什么是事务? 事务和锁 mysql数据库控制台事务的几个重要操作指令(transaction.sql) 1、事物操作示意图: 2.事务的隔离级别 四种隔离级别: 总结一下隔离指令 1. 查看当前隔离级别 …...
uniapp设置 overflow:auto;右边不显示滚动条的问题
设置了overflow:auto;或者其它overflow的属性不显示滚动条是因为在uniapp中默认隐藏了滚动条 解决方法: //强制显示滚动条 ::-webkit-scrollbar {width: 8px !important;background: #ccc !important;display: block !important;}//设置滚动条颜色.cu-…...
数据库基础复习笔记
数据库 相关概念 名称全称检查数据库存储数据的仓库,数据是有组织的进行存储DataBase(DB)数据库管理系统操作和管理数据库的大型软件DataBase Management System(DBMS)SQL操作关系型数据库的编程语言,定义了一套操作关系型数据库…...

MySQL基础关键_013_常用 DBA 命令
目 录 一、MySQL 用户信息存储位置 二、新建用户 1.创建本地用户 2.创建外网用户 三、用户授权 1.说明 2.实例 四、撤销授权 五、修改用户密码 六、修改用户名、主机名/IP地址 七、删除用户 八、数据备份 1.导出数据 2.导入数据 (1)方式…...
爬虫请求频率应控制在多少合适?
爬虫请求频率的控制是一个非常重要的问题,它不仅关系到爬虫的效率,还涉及到对目标网站服务器的影响以及避免被封禁的风险。合理的请求频率需要根据多个因素来综合考虑,以下是一些具体的指导原则和建议: 一、目标网站的政策 查看网…...
探秘网络邮差:FTP、Telnet、SMTP、NFS、SNMP介绍
引言:谁是网络世界的“邮差”? 想象一下,你正在网上冲浪——发送一封邮件、上传一份文件、远程登录服务器,甚至只是打开一个网页。这些看似简单的操作背后,其实有一群默默无闻的“邮差”在辛勤工作。它们就是应用层协…...

java基础:异常体系
目录 一、java异常体系介绍二、异常1、运行时异常2、非运行时异常 三、错误四、异常的处理方式1、方式1:throws声明抛出异常1.1、throws关键字1.2、throw关键字 2、方式2:try-catch-finally 一、java异常体系介绍 异常体系图如下: Throwable…...
CSS Grid布局:从入门到实战
CSS Grid布局:从入门到实战 一、初识Grid布局 还在为网页布局发愁吗?Flexbox虽然好用,但当遇到复杂布局时,CSS Grid才是真正的王者。Grid布局是CSS中最强大的二维布局系统,它就像一张无形的网格纸,让我们…...

记录算法笔记(20025.5.14)对称二叉树
给你一个二叉树的根节点 root , 检查它是否轴对称。 示例 1: 输入:root [1,2,2,3,4,4,3] 输出:true 示例 2: 输入:root [1,2,2,null,3,null,3] 输出:false 提示: 树中节点数目…...

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

JVM 与云原生的完美融合:引领技术潮流
最近佳作推荐: Java 大厂面试题 – 揭秘 JVM 底层原理:那些令人疯狂的技术真相(New) Java 大厂面试题 – JVM 性能优化终极指南:从入门到精通的技术盛宴(New) Java 大厂面试题 – JVM 深度剖析&…...
Ubuntu24.04编译ORB_SLAM的一系列报错解决
Ubuntu24.04编译ORB_SLAM的一系列报错解决 decay_t报错 报错信息:error: ‘decay_t’ is not a member of ‘std’;did you mean ‘decay’ 将CMakeLists.txt中第17行的c标准修改为c14即可: 修改前: CHECK_CXX_COMPILER_FLAG…...

为何大模型都使用decoder-only?
第一章 架构之争的历史脉络 1.1 从双向到单向的革命 2017年,BERT的横空出世让双向注意力机制成为NLP领域的“武林盟主”。通过Masked Language Modeling(MLM),BERT在阅读理解、情感分析等任务中展现出惊人的表现,但它…...
《Effective Python》第2章 字符串和切片操作——Python 字符串格式化的现代选择f-strings
引言 本篇博客基于学习《Effective Python》第三版 Chapter 2: Strings and Slicing 的 Item 11 “Prefer Interpolated F-Strings Over C-style Format Strings and str.format” 的总结与延伸。 字符串格式化是 Python 编程中的常见操作,用于动态生成可读性高的…...

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

Ollama+OpenWebUI+docker完整版部署,附带软件下载链接,配置+中文汉化+docker源,适合内网部署,可以局域网使用
前言: 因为想到有些环境可能没法使用外网的大模型,所以可能需要内网部署,看了一下ollama适合小型的部署,所以就尝试了一下,觉得docker稍微简单一点,就做这个教程的,本文中重要的内容都会给下载…...
git push 报错:send-pack: unexpected disconnect while reading sideband packet
背景 新建了一个仓库,第一次push 代码文件,文件中有一个依赖的jar,有80MB,结果push的时候报错。 错误信息 error: RPC failed; HTTP 500 curl 22 The requested URL returned error: 500 send-pack: unexpected disconnect whi…...
考研英一真题学习笔记 2018年
2018 年全国硕士研究生招生考试 英语 (科目代码:201) Section Ⅰ Use of English Directions: Read the following text. Choose the best word(s) for each numbered blank and mark A, B, C or D on the ANSWER SHEET. (10 points) Trust i…...

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…...
Java知识框架
一、Java 基础语法 1. 基础语法 数据类型 基本类型:int, double, boolean, char 等 引用类型:String, 数组, 对象 变量与常量 final 关键字 作用域(局部变量、成员变量) 运算符 算术、逻辑、位运算 三元运算符 ? : 控制…...

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

院校机试刷题第二天:1479 01字符串、1701非素数个数
一、1479 01字符串 1.题目描述 2.解题思路 方法一:暴力法 模拟过程,列出几个数据来a[1]1, a[2]2, a[3]3, a[4]5以此类推,这就是斐波那契数列,每一项都等于前两项之和,确定好a[1], a[2]即可。 方法二:动…...
【Vue.js 的核心魅力:深入理解声明式渲染】
Vue.js 的核心魅力:深入理解声明式渲染 在现代前端框架的浪潮中,Vue.js 以其轻量、易学、高效的特点赢得了广大开发者的青睐。其核心魅力之一,便是其优雅的**声明式渲染 (Declarative Rendering)**机制。理解声明式渲染不仅能帮助我们更好地…...

制作一款打飞机游戏48:敌人转向
射击功能 有一个重要的功能我们还没实现,那就是射击。目前,敌人还不能射击,这显然是不行的。因此,我们决定添加一个射击命令,暂时用一个显示圆圈的方式来表示射击动作。 编程语言的调试 有趣的是,我们创…...
鸿蒙OSUniApp打造多功能图表展示组件 #三方框架 #Uniapp
使用UniApp打造多功能图表展示组件 在当前移动应用开发领域,数据可视化已成为不可或缺的一部分。无论是展示销售数据、用户增长趋势还是其他业务指标,一个优秀的图表组件都能有效提升用户体验。UniApp作为一款跨平台开发框架,如何在其中实现…...
Chrome浏览器实验性API computePressure的隐私保护机制如何绕过?
一、computePressure API 设计原理与隐私保护机制 1.1 API 设计目标 computePressure是W3C提出的系统状态监控API,旨在: • 提供系统资源状态的抽象指标(非精确值) • 防止通过高精度时序攻击获取用户指纹 • 平衡开发者需求与用户隐私保护 1.2 隐私保护实现方式 // 典…...