Lucene 中的并发错误:如何修复乐观并发失败
作者:来着 Elastic Benjamin Trent 及 Ao Li
感谢 CMU PASTA 实验室开发的确定性并发测试框架 Fray,我们找到了一个棘手的 Lucene 漏洞并将其修复。
是的,另一个修复错误博客。但这个故事有一个转折,一位开源英雄突然出现并拯救了一切。
调试并发错误并非易事,但我们会深入研究它。进入 Fray,这是 CMU 的 PASTA 实验室推出的确定性并发测试框架,它将不稳定的故障转变为可靠的可重现的故障。得益于 Fray 巧妙的影子锁设计和精确的线程控制,我们追踪到了一个棘手的 Lucene 错误并最终将其消除。这篇文章探讨了开源英雄和工具如何让并发调试变得不再那么痛苦,并且让软件世界变得更加美好。
更多有关乐观并发的文章,请参考:
-
Elasticsearch:深刻理解文档中的 verision 及乐观并发控制
-
Elasticsearch:文档版本控制和乐观并发控制
软件工程师的祸根
并发错误是最糟糕的。它们不仅难以修复,而且让它们可靠地失效也是最困难的部分。以此测试失败 TestIDVersionPostingsFormat#testGlobalVersions 为例。它产生了多个文档写入和更新线程,对 Lucene 的乐观并发模型提出了挑战。该测试暴露了乐观并发控制中的竞争条件。意思是,文档操作可能会错误地声称自己是一系列操作中的最新操作😱。这意味着,在某些条件下,更新或删除操作在乐观并发约束下应该失败,但实际上可能会成功。
org.apache.lucene.sandbox.codecs.idversion.TestIDVersionPostingsFormat > testGlobalVersions FAILEDjava.lang.AssertionError: maxSeqNo must be greater or equal to 7442 but was 7441at __randomizedtesting.SeedInfo.seed([B97A2BDBC7E40BF6:B4D76006D5101E6]:0)at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.index.DocumentsWriterDeleteQueue.close(DocumentsWriterDeleteQueue.java:325)at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.index.DocumentsWriter.flushAllThreads(DocumentsWriter.java:659)at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.index.IndexWriter.getReader(IndexWriter.java:576)at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.index.StandardDirectoryReader.doOpenFromWriter(StandardDirectoryReader.java:381)at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.index.StandardDirectoryReader.doOpenIfChanged(StandardDirectoryReader.java:355)at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.index.StandardDirectoryReader.doOpenIfChanged(StandardDirectoryReader.java:345)at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.index.DirectoryReader.openIfChanged(DirectoryReader.java:170)at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.search.SearcherManager.refreshIfNeeded(SearcherManager.java:144)at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.search.SearcherManager.refreshIfNeeded(SearcherManager.java:52)at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.search.ReferenceManager.doMaybeRefresh(ReferenceManager.java:167)at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.search.ReferenceManager.maybeRefresh(ReferenceManager.java:213)
对于那些讨厌 Java 堆栈跟踪的人,我深表歉意。注意,delete 并不一定意味着 “delete”。它还可以指示文档 “update”,因为 Lucene 的段是只读的。
Apache Lucene 通过 DocumentsWriter
类管理每个写入文档的线程。该类会创建或重用线程进行文档写入,并且每个写入操作都会在 DocumentsWriterPerThread
(DWPT)类中管理其信息。此外,写入器会在 DocumentsWriterDeleteQueue
(DWDQ)中跟踪被删除的文档。这些结构在内存中维护所有文档变更操作,并会定期刷新,以释放内存资源并将数据持久化到磁盘。
为了防止阻塞线程(blocking threads )并确保高吞吐量,在并发系统中,Apache Lucene 仅在非常关键的部分进行同步(synchronize)。虽然这种做法在实践中通常是有利的,但就像任何并发系统一样,其中仍然隐藏着潜在的风险和挑战。
虚假的希望
我最初的调查发现了一些关键部分,它们并没有被正确地同步。所有对 DocumentsWriterDeleteQueue
的交互都由其封闭的 DocumentsWriter
控制。因此,尽管 DocumentsWriterDeleteQueue
中的某些方法可能没有适当同步,但它们对外部世界的访问是(或者应该是)受控的。(我们暂且不深入讨论这如何影响所有权和访问控制 —— 毕竟,这是一个由许多贡献者长期维护的项目,得给它点宽容。)
然而,我在一次 flush 过程中发现了一个未同步的地方。
// Advance the queue, meaning create a new one to keep track of deletes
// Since we have been flushed, let's starting tracking again
DocumentsWriterDeleteQueue newQueue = documentsWriter.deleteQueue.advanceQueue(perThreadPool.size());
// OK, get the current new maximum sequence number for optimistic concurrency
seqNo = documentsWriter.deleteQueue.getMaxSeqNo();
// Reset to the new queue
documentsWriter.resetDeleteQueue(newQueue);
这些动作并未同步到单个原子操作(atomic operation)中。意思是,在创建 newQueue 和调用 getMaxSeqNo 之间,其他代码可以执行增加 documentsWriter 类中的序列号。我发现了错误!

但是,与大多数复杂的错误一样,找到根本原因并不简单。就在此时,一位英雄挺身而出。
Fray 中的英雄
我们的英雄登场了:来自 PASTA Lab 的 Ao Li 及其同事。我会让他来解释,他们如何用 Fray 挽救了局面。
Fray 是由 卡内基梅隆大学 PASTA Lab 研究人员开发的一款 确定性并发测试框架。构建 Fray 的动机来源于学术界与工业界之间的明显鸿沟:尽管确定性并发测试在学术研究中已经被深入研究了 20 多年,但在实际应用中,开发人员仍然主要依赖 压力测试 来测试并发程序。然而,压力测试普遍被认为不可靠且容易出错。因此,我们希望设计并实现一个以 通用性和实际可用性 为核心目标的 确定性并发测试框架。
核心思想
从本质上讲,Fray 利用了一个简单但强大的原则:顺序执行。 Java 的并发模型提供了一个关键属性 —— 如果程序没有数据争用,则所有执行都会显得顺序一致。这意味着程序的行为可以表示为一系列程序语句。
Fray 通过按顺序运行目标程序来操作:在每个步骤中,它会暂停除一个线程之外的所有线程,从而使 Fray 能够精确控制线程调度。随机选择线程来模拟并发,但会记录这些选择以供后续确定性重放。为了优化执行,Fray 仅在线程即将执行同步指令(例如锁定或原子/易失性访问)时执行上下文切换。关于数据竞争自由的一个很好的特性是,这种有限的上下文切换足以探索由于任何线程交错而导致的所有可观察行为(我们的论文有一个证明草图)。
挑战:控制线程调度
尽管核心思想看似简单,但实施 Fray 却面临重大挑战。为了控制线程调度,Fray 必须管理每个应用程序线程的执行。乍一看,这似乎很简单 —— 用定制的实现取代并发原语。然而,JVM 中的并发控制非常复杂,涉及字节码指令、高级库和本机方法的混合。
结果,这变成了一个复杂且难以预见的难题:
- 例如,每条 MONITORENTER 指令必须在同一方法内有对应的 MONITOREXIT。如果 Fray 用一个存根(stub)或模拟(mock)方法替换 MONITORENTER,那么它也必须相应地替换 MONITOREXIT。
- 在使用 object.wait/notify 的代码中,如果 MONITORENTER 被替换,那么对应的 object.wait 也必须被替换,而这个替换链会进一步扩展到 object.notify 及其他相关操作。
- JVM 在本地代码中调用某些与并发相关的方法(例如,当一个线程结束时,可能会调用 object.notify)。替换这些操作意味着需要修改 JVM 本身。
- 此外,JVM 的核心功能(如 类加载器 和 垃圾回收(GC)线程)也使用并发原语。修改这些原语可能会导致 JVM 的功能与自定义的实现不匹配。
- 更严重的是,替换 JDK 内的并发原语往往会导致 JVM 在初始化阶段崩溃。
这些挑战表明,全面替换并发原语并不是一个可行的方案。
我们的解决方案:影子锁设计
为了应对这些挑战,Fray 采用了一种新颖的 影子锁机制 来协调线程执行,而不需要替换并发原语。影子锁充当中介,指导线程的执行。例如,在获取锁之前,应用线程必须与其对应的影子锁进行交互。影子锁决定线程是否可以获取锁。如果线程无法继续执行,影子锁会阻止它,并允许其他线程执行,从而避免死锁并实现受控并发。这个设计使得 Fray 能够透明地控制线程交替,同时保持并发语义的正确性。每个并发原语在影子锁框架中都经过精心建模,以确保其健全性和完整性。更多技术细节可以在我们的论文中找到。
此外,这一设计具有面向未来的特性。通过仅在并发原语周围插入影子锁的监控,它确保了与 JVM 新版本的兼容性。之所以可行,是因为 JVM 中并发原语的接口相对稳定,并且多年未发生变化。
测试 Fray
在构建完 Fray 后,接下来的步骤是评估。幸运的是,许多应用程序,如 Apache Lucene,已经包含了并发测试。这些并发测试通常是常规的 JUnit 测试,它们会启动多个线程,执行一些工作,然后(通常)等待这些线程完成,再断言某些属性。大多数情况下,这些测试通过了,因为它们仅测试了一个线程交替。更糟糕的是,一些测试只会在 CI/CD 环境中偶尔失败,正如前面提到的那样,这使得这些故障非常难以调试。当我们用 Fray 执行相同的测试时,我们发现了许多 bug。特别是,Fray 重新发现了一些以前报告的 bug,这些 bug 由于无法可靠地重现而一直没有修复,其中包括本博客重点提到的:TestIDVersionPostingsFormat.testGlobalVersions。幸运的是,通过 Fray,我们可以确定性地重放这些问题,并为开发人员提供详细信息,使他们能够可靠地重现并修复这些问题。
Fray 的下一步
我们很高兴听到 Elastic 的开发人员表示 Fray 对调试并发 bug 非常有帮助。我们将继续致力于 Fray 的开发,以便让更多开发者受益。
我们的短期目标包括增强 Fray 确定性重放调度的能力,即使在存在其他非确定性操作(如随机值生成器或使用 object.hashcode)的情况下,也能准确重现调度。此外,我们还计划提高 Fray 的可用性,使开发者能够分析和调试现有的并发测试,而无需手动干预。最重要的是,如果你在调试或测试程序中的并发问题时遇到困难,我们非常希望听到你的声音。请随时在 Fray 的 GitHub 仓库中创建问题。
是时候解决这个危险的事情了
感谢 Ao Li 和 PASTA 实验室,我们现在有了这个测试的可靠失败实例!我们终于可以解决这个问题了。关键问题在于 DocumentsWriterPerThreadPool 如何实现线程和资源重用。
1> new Writer: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_0, aborted=false, numDocsInRAM=0, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
1> DWDQ: [ generation: 0 ] id: 245403753 tid: t0 20 getNextSequenceNumber 1 called from stack:
<snip>...</snip>
1> new Writer: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_1, aborted=false, numDocsInRAM=0, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
1> new Writer: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_5, aborted=false, numDocsInRAM=0, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
1> new Writer: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_2, aborted=false, numDocsInRAM=0, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
1> new Writer: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_6, aborted=false, numDocsInRAM=0, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
1> new Writer: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_3, aborted=false, numDocsInRAM=0, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
1> new Writer: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_4, aborted=false, numDocsInRAM=0, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
这里我们可以看到每个被创建的线程,引用第 0 代的初始删除队列。
然后,队列前进将在刷新时发生,正确看到队列中的前 7 个操作。
1> DWDQ: [ generation: 0 ] id: 245403753 tid: t020 advanceQueue called from stack with maxSeq 9 lastSeqNo: 1 maxNumPendingOps: 7:
<snip>...</snip>
1> DWDQ: [ generation: 0 ] id: 245403753 tid: t525 getNextSequenceNumber 2 called from stack:
1> DWDQ: [ generation: 0 ] id: 245403753 tid: t828 getNextSequenceNumber 3 called from stack:
1> DWDQ: [ generation: 0 ] id: 245403753 tid: t727 getNextSequenceNumber 4 called from stack:
1> DWDQ: [ generation: 0 ] id: 245403753 tid: t626 getNextSequenceNumber 5 called from stack:
1> DWDQ: [ generation: 0 ] id: 245403753 tid: t424 getNextSequenceNumber 6 called from stack:
1> DWDQ: [ generation: 0 ] id: 245403753 tid: t323 getNextSequenceNumber 7 called from stack:
但是,在所有线程完成刷新之前,有两个线程会被重新用于处理另一个文档:
1> getAndLock: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_0, aborted=false, numDocsInRAM=1, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
1> getAndLock: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_3, aborted=false, numDocsInRAM=1, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
然后,这些将使 seqNo 增加到假定的最大值之上,该最大值在刷新期间计算为 7。请注意段 _3 和 _0 的额外 numDocsInRAM
1> DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_2, aborted=false, numDocsInRAM=1, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds] checkout to remove
1> DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_3, aborted=false, numDocsInRAM=2, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds] checkout to remove
1> DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_6, aborted=false, numDocsInRAM=1, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds] checkout to remove
1> DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_4, aborted=false, numDocsInRAM=1, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds] checkout to remove
1> DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_5, aborted=false, numDocsInRAM=1, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds] checkout to remove
1> DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_1, aborted=false, numDocsInRAM=1, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds] checkout to remove
1> DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_0, aborted=false, numDocsInRAM=2, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds] checkout to remove
因此导致 Lucene 错误地解释刷新期间文档操作的顺序并引发此测试失败。
与所有好的错误修复一样,实际修复大约需要 10 行代码。但两位工程师花了几天时间才真正弄清楚:

并非所有英雄都披着斗篷
是的,这是陈词滥调 —— 但这是事实。
并发程序调试非常重要。这些棘手的并发错误需要花费大量的时间来调试和解决。虽然像 Rust 这样的新语言已经内置了机制来帮助防止此类竞争条件,但世界上大多数软件都已经是用 Rust 以外的其他语言编写的。即使过了这么多年,Java 仍然是最常用的语言之一。改进基于 JVM 的语言的调试使得软件工程世界变得更加美好。考虑到有些人认为代码将由大型语言模型编写,也许我们作为工程师的工作最终只是调试糟糕的 LLM 代码,而不仅仅是我们自己的糟糕代码。但是,无论软件工程的未来如何,并发程序调试对于维护和构建软件仍然至关重要。
感谢 PASTA 实验室的 Ao Li 和他的同事们使它变得更加出色。
Elasticsearch 包含许多新功能,可帮助你为你的用例构建最佳的搜索解决方案。深入了解我们的示例笔记本以了解更多信息,开始免费云试用,或立即在本地机器上试用 Elastic。
原文:Concurrency bugs in Lucene: How to fix optimistic concurrency failures - Elasticsearch Labs
相关文章:
Lucene 中的并发错误:如何修复乐观并发失败
作者:来着 Elastic Benjamin Trent 及 Ao Li 感谢 CMU PASTA 实验室开发的确定性并发测试框架 Fray,我们找到了一个棘手的 Lucene 漏洞并将其修复。 是的,另一个修复错误博客。但这个故事有一个转折,一位开源英雄突然出现并拯救了…...
Oracle的学习心得和知识总结(三十三)|Oracle数据库数据库的SQL ID的底层计算原理分析
目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《Oracle Database SQL Language Reference》 2、参考书籍:《PostgreSQL中文手册》 3、EDB Postgres Advanced Server User Gui…...
C# OpenCV机器视觉:智能水果采摘
在一个风景如画的小镇边上,有一座阿强家祖传的果园。每到水果成熟的季节,果园里硕果累累,红彤彤的苹果、黄澄澄的梨子、紫莹莹的葡萄,散发着诱人的香气。然而,这丰收的喜悦却总被一件烦心事笼罩 —— 摘水果。 “哎呀…...
逻辑回归不能解决非线性问题,而svm可以解决
逻辑回归和支持向量机(SVM)是两种常用的分类算法,它们在处理数据时有一些不同的特点,特别是在面对非线性问题时。 1. 逻辑回归 逻辑回归本质上是一个线性分类模型。它的目的是寻找一个最适合数据的直线(或超平面&…...
celery + redis - 入门
文章目录 一、基本使用编写任务启动服务创建生产者获取状态和结果二、多目录结构异步执行编写服务启动服务调用服务获取结果https://www.bilibili.com/video/BV1jg4y13718 https://www.cnblogs.com/pyedu/p/12461819.html 一、基本使用 编写任务 celery_task.py import cel…...
SAP-ABAP:在LOOP循环中 ASSIGNING FIELD-SYMBOL的使用代码详解
在ABAP中,ASSIGNING FIELD-SYMBOL的作用是直接引用内表行的数据,避免不必要的数据复制,从而提升性能和代码效率。以下是其核心作用和优势: 基本语法 LOOP AT itab ASSIGNING FIELD-SYMBOL(<fs>)." 处理逻辑 ENDLOOP.i…...
SpringBoot启动流程简略版
启动入口 (main 方法) ↓ SpringApplication 初始化 ↓ 加载配置 (application.properties/yml) ↓ 创建 ApplicationContext ↓ 刷新 ApplicationContext ↓ - 加载 Bean 定义 - 执行自动配置 - 实例化 Bean - 依赖注入 - 调用初…...
Python:凯撒密码
题目内容: 凯撒密码是古罗马恺撒大帝用来对军事情报进行加密的算法,它采用了替换方法对信息中的每一个英文字符循环替换为字母表序列该字符后面第三个字符,对应关系如下: 原文:A B C D E F G H I J K L M N O P Q R …...
element-plus 解决el-dialog背后的页面滚动问题,及其内容有下拉框出现错位问题
这个问题通常是因为 el‑dialog 默认会锁定 body 的滚动(通过给 body 添加隐藏滚动条的样式),从而导致页面在打开对话框时跳转到顶部。解决方法是在使用 el‑dialog 时禁用锁定滚动功能。 <el-dialogv-model"dialogVisible":lo…...
Android和DLT日志系统
1 Linux Android日志系统 1.1 内核logger机制 drivers/staging/android/logger.c static size_t logger_offset( struct logger_log *log, size_t n) { return n & (log->size - 1); } 写的off存在logger_log中(即内核内存buffer)&am…...
Ubuntu 安装 NVIDIA 驱动实操指南(含卸载)
本文将详细介绍如何在Ubuntu上安装和配置NVIDIA显卡驱动。以下是一步步的操作流程,包括禁用开源驱动的步骤。 步骤 1:安装依赖 首先,确保系统中已安装gcc和make,这些是编译驱动所需的依赖。 sudo apt update sudo apt install …...
在postman中设置环境变量和全局变量以及五大常用响应体断言
一、什么是环境变量和全局变量 环境变量(Environment Variables)和全局变量(Global Variables)是 Postman 中用于存储和管理数据的两种变量类型,它们可以提高 API 测试的灵活性和可维护性。 1、 环境变量(…...
32单片机学习记录1之GPIO
32单片机学习记录1之GPIO 前置 GPIO口在单片机中扮演着什么角色? 在单片机中,GPIO口(General Purpose Input/Output) 是一种通用输入/输出接口,扮演着连接单片机与外部设备的桥梁角色。具体来说,它在单片…...
负载测试和压力测试的原理分别是什么
负载测试和压力测试是性能测试的两种主要类型,它们的原理和应用场景有所不同。 负载测试(Load Testing) 原理: 负载测试通过模拟实际用户行为,逐步增加系统负载,观察系统在不同负载下的表现。目的是评估系…...
openAI官方prompt技巧(二)
1. 赋予 ChatGPT 角色 为 ChatGPT 指定一个角色,让其从特定的身份或视角回答问题。这有助于生成针对特定受众或场景的定制化回答。 例如: 你是一名数据分析师,负责我们的市场营销团队。请总结上个季度的营销活动表现,并强调与未…...
javaEE-11.javaScript入门
目录 一.什么是javaScript 二.快速实现 三.JS引入方式 1.行内引入: 2.内部引入: 3.外部引入: 四.基础语法 1.变量 变量命名规则: 2.数据类型 3.运算符 五.JS对象 1.数组 创建数组: 2.操作数组 3.函数 函数注意事项: 函数参数: 4.对象 1.使用字面量 创建对象:…...
Word成功接入DeepSeek详细步骤
原理 原理是利用Word的VBA宏,写代码接入API。无需下载额外插件。 步骤一、注册硅基流动 硅基流动统一登录 注册这个是为了有一个api调用的api_key,有一些免费的额度可以使用。大概就是这个公司提供token,我们使用这个公司的模型调用deepsee…...
单片机简介
一、单片机简介 电脑和单片机性能对比 二、单片机发展历程 三、CISC VS RISC...
GitCode 助力 Dora SSR:开启游戏开发新征程
项目仓库(点击阅读原文链接可直达) https://gitcode.com/ippclub/Dora-SSR 跨越技术藩篱,构建游戏开发乐园 Dora SSR 是一款致力于打破游戏开发技术壁垒的开源游戏引擎。其诞生源于开发者对简化跨平台游戏开发环境搭建的强烈渴望࿰…...
[python SQLAlchemy数据库操作入门]-25.股票数据可视化:将 SQLAlchemy 数据呈现给用户
哈喽,大家好,我是木头左! 本文将探讨如何利用SQLAlchemy从数据库中提取股票数据,并使用现代数据可视化工具将这些数据以直观的方式呈现给用户。将通过一系列步骤来演示这个过程,包括设置环境、连接数据库、提取数据、处理数据以及最终的可视化展示。 安装必要的库 接下来…...
android的Lifecycle简介
嗯,我现在需要了解Android的Lifecycle组件。Lifecycle是Jetpack的一部分,对吧?听说它帮助管理Activity和Fragment的生命周期,避免内存泄漏。那它具体是怎么工作的呢? 首先,LifecycleOwner和LifecycleObser…...
SQL-leetcode—1327. 列出指定时间段内所有的下单产品
1327. 列出指定时间段内所有的下单产品 表: Products ------------------------- | Column Name | Type | ------------------------- | product_id | int | | product_name | varchar | | product_category | varchar | ------------------------- product_id 是该表主键(具…...
在Uniapp中使用阿里云OSS插件实现文件上传
在开发小程序时,文件上传是一个常见的需求。阿里云OSS(Object Storage Service)是一个强大的云存储服务,可以帮助我们高效地存储和管理文件。本文将介绍如何在Uniapp小程序中使用阿里云OSS插件实现文件上传功能。 1. 准备工作 首…...
C#中的序列化和反序列化
序列化是指将对象转换为可存储或传输的格式,例如将对象转换为JSON字符串或字节流。反序列化则是将存储或传输的数据转换回对象的过程。这两个过程在数据持久化、数据交换以及与外部系统的通信中非常常见 把对象转换成josn字符串格式 这个过程就是序列化 josn字符…...
IGBT的两级关断
IGBT(绝缘栅双极型晶体管)的两级关断(Two-stage turn-off)是一种优化关断过程的方法,主要用于减少关断时的电压过冲和dv/dt(电压变化率)过高的问题,特别是在大功率应用中(…...
TCP/IP 协议图解 | TCP 协议详解 | IP 协议详解
注:本文为 “TCP/IP 协议” 相关文章合辑。 未整理去重。 TCP/IP 协议图解 退休的汤姆 于 2021-07-01 16:14:25 发布 TCP/IP 协议简介 TCP/IP 协议包含了一系列的协议,也叫 TCP/IP 协议族(TCP/IP Protocol Suite,或 TCP/IP Pr…...
PyCharm结合DeepSeek-R1
PyCharm结合DeepSeek-R1,打造专属 AI 编程助手 在程序员的日常工作中,提高编程效率、快速解决代码问题是重中之重。今天给大家分享一个强强联合的组合 ——PyCharm 插件 Continue 与 DeepSeek-R1,它们能帮你打造出强大的个人 AI 编程助手。 …...
深入理解Java对接DeepSeek
其实,整个对接过程很简单,就四步,获取key,找到接口文档,接口测试,代码对接。 1.获取 KEY https://platform.deepseek.com/transactions 直接付款就是了(现在官网暂停充值2025年2月7日…...
【RabbitMQ的x-death头】消息死亡记录头流转示例
Header(name "x-death", required false) List<Map<String,Object>> xDeath 是用于捕获RabbitMQ自动生成的 消息死亡记录头信息。以下是详细解析和实际应用示例: x-death头的作用 死亡原因追踪:记录消息被拒绝/过期的完整生命周…...
【DeepSeek】在本地计算机上部署DeepSeek-R1大模型实战(完整版)
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈人工智能与大模型应用 ⌋ ⌋ ⌋ 人工智能(AI)通过算法模拟人类智能,利用机器学习、深度学习等技术驱动医疗、金融等领域的智能化。大模型是千亿参数的深度神经网络(如ChatGPT&…...
二、通义灵码插件保姆级教学-IDEA(使用篇)
一、IntelliJ IDEA 中使用指南 1.1、代码解释 选择需要解释的代码 —> 右键 —> 通义灵码 —> 解释代码 解释代码很详细,感觉很强大有木有,关键还会生成流程图,对程序员理解业务非常有帮忙,基本能做到哪里不懂点哪里。…...
北斗导航 | 基于多假设解分离(MHSS)模型的双星故障监测算法(MATLAB代码实现——ARAIM)
===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 双星故障监测算法 一、多星故障MHSS模型流程1、数据预处理2、构建假设模…...
OpenCV2D 特征框架 (22)人脸检测的一个类cv::FaceDetectorYN的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cv::FaceDetectorYN 是 OpenCV 中用于人脸检测的一个类,它基于深度学习模型实现。这个类是 OpenCV DNN(Deep Neural Netw…...
微信小程序页面制作——婚礼邀请函(含代码)
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
django中间件,中间件给下面传值
1、新建middleware.py文件 # myapp/middleware.py import time from django.http import HttpRequest import json from django.http import JsonResponse import urllib.parse class RequestTimeMiddleware:def __init__(self, get_response):self.get_response get_respons…...
调用DeepSeek官方的API接口
效果 前端样式体验链接:https://livequeen.top/deepseekshow 准备工作 1、注册deepseek官网账号 地址:DeepSeek 点击进入右上角【API开放平台】,并进行账号注册。 2、注册完成后,依次点击【API keys】-【生成API key】&#x…...
C++--iomanip库
目录 1. 设置字段宽度:std::setw() 2. 设置浮点数精度:std::setprecision() 3. 设置填充字符:std::setfill() 4. 控制对齐方式:std::left 和 std::right,std::internal 5. 控制进制输出:std::hex、std…...
程序诗篇里的灵动笔触:指针绘就数据的梦幻蓝图<8>
大家好啊,我是小象٩(๑ω๑)۶ 我的博客:Xiao Xiangζั͡ޓއއ 很高兴见到大家,希望能够和大家一起交流学习,共同进步。 今天我们复习前面学习的指针知识 目录 关于指针数组和数组指针的区别指针数组(Array of Poi…...
redo和binlog区别
事务是数据库区别于文件系统的最重要功能,数据库事务支持ACID四个特性,其中I:隔离性是通过锁的方式实现的,剩下的A:原子性 C:一致性 D:持久性是通过redo日志、undo日志、binlog日志来实现的。 我…...
2.11学习记录
web——CTFHub XSS学习 学习资料:xss(跨站攻击) 原理 1.黑客发送带有xss恶意脚本的链接给用户 2.用户点击了恶意链接,访问了目标服务器(正常的服务器) 3.目标服务器(正常的服务器)…...
Packer 手动修复安装腾讯云插件
文章目录 Packer [腾讯云插件文档](https://developer.hashicorp.com/packer/integrations/hashicorp/tencentcloud) 提供的版本:v1.2.0,目前 Packer 构建镜像时,不支持现有2种[硬盘类型](https://www.tencentcloud.com/zh/document/product/…...
【探索未来科技】2025年国际学术会议前瞻
【探索未来科技】2025年国际学术会议前瞻 【探索未来科技】2025年国际学术会议前瞻 文章目录 【探索未来科技】2025年国际学术会议前瞻前言1. 第四届电子信息工程、大数据与计算机技术国际学术会议( EIBDCT 2025)代码示例:机器学习中的线性回…...
【Elasticsearch】Bucket Count K-S Test 聚合
Bucket Count K-S Test是 Elasticsearch 中的一种兄弟管道聚合(sibling pipeline aggregation),用于执行双样本柯尔莫哥洛夫-斯米尔诺夫检验(Kolmogorov-Smirnov Test,简称 K-S 检验)。这种聚合主要用于比较…...
LINUX——基础指令
引言 Linux 操作系统以其强大的命令行工具著称,掌握基础指令是高效使用 Linux 的必备技能。无论是文件管理、文本处理还是系统监控,命令行都能以极简的操作完成复杂任务。本文系统梳理 Linux 最核心的 30 基础指令,配合实际场景案例和进阶技…...
Expo运行模拟器失败错误解决(xcrun simctl )
根据你的描述,问题主要涉及两个方面:xcrun simctl 错误和 Expo 依赖版本不兼容。以下是针对这两个问题的解决方案: 解决 xcrun simctl 错误 错误代码 72 通常表明 simctl 工具未正确配置或路径未正确设置。以下是解决步骤: 确保 …...
Spring中常见的设计模式
Spring框架是一个庞大的生态系统,设计模式的应用广泛分布在其各个模块中。以下是针对你提到的设计模式,在Spring源码中的具体应用位置: 单例模式(Singleton Pattern): 在org.springframework.beans.factory…...
502 Bad Gateway 错误详解:从表现推测原因,逐步排查直至解决
502 Bad Gateway 错误通常意味着服务器之间的通信失败,但导致的具体原因往往因场景而异。 场景一:高峰期频繁出现 502 错误 1.1 现象 在流量高峰期间(如促销活动、直播发布等),页面访问变慢甚至出现 502 错误&#…...
【DeepSeek】deepseek可视化部署
目录 1 -> 前文 2 -> 部署可视化界面 1 -> 前文 【DeepSeek】DeepSeek概述 | 本地部署deepseek 通过前文可以将deepseek部署到本地使用,可是每次都需要winR输入cmd调出命令行进入到命令模式,输入命令ollama run deepseek-r1:latest。体验很…...
基于STM32的ADS1230驱动例程
自己在练手项目中用到了ADS1230,根据芯片手册自写的驱动代码,已测可用,希望对将要用到ADS1230芯片的人有所帮助。 芯片:STM32系列任意芯片、ADS1230 环境:使用STM32CubeMX配置引脚、KEIL 部分电路: 代码…...
计算机毕业设计springboot+vue.js就业数据采集分析平台 mybatis-plus(LW文档+PPT+讲解+代码安装)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...