基于 Fabric 的性能测试和调整实践
摘要:本文聚焦Fabric核心业务,构建一个测试模型,对社区原生的Fabric和华为云区块链(基于Fabric)进行实测,识别社区原生Fabric的性能瓶颈,并尝试通过华为区块链提供的动态伸缩、快速PBFT算法进行调优,提升几个关键的评测指标。
1、Fabric 性能测试现状
通俗的来讲,区块链是一种按照时间顺序将数据区块以顺序相连的方式组合成的一种链式数据结构,并以密码学方式保证的不可篡改和不可伪造的分布式账本。比特币(Bitcoin)、以太坊(Ethereum)、超级账本(Hyperledger)都是典型的区块链系统。其中Hyperledger Fabric是最受欢迎的企业级区块链框架,Fabric采用了松耦合的设计,将共识机制、身份验证等组件模块化,使之在应用过程中可以方便地根据应用场景来选择相应的模块。
Fabric的性能是用户最为关注的问题之一,然而,目前没有一个权威中立的机构,根据公认的规则,对Fabric进行性能测试并给出测试报告,大概有下面几个原因:
(1)Fabric还处在快速发展中,尚未给出详细中立并且公认的测试规则;
(2)Fabric网络结构(网络带宽、磁盘IO、计算资源等),配置参数(如区块大小、背书策略、通道数量、状态数据库等),共识算法(solo,kafka,pbft等)都会影响评测结果,很难构建反映fabric 全貌的测试模型;
(3)Fabric 交易过程复杂,和传统的数据库有很多区别,也不适用于传统的测试方案和工具;
本文聚焦Fabric核心业务,构建一个测试模型,对社区原生的Fabric和华为云区块链(基于Fabric)进行实测,识别社区原生Fabric的性能瓶颈,并尝试通过华为区块链提供的动态伸缩、快速PBFT算法进行调优,提升几个关键的评测指标。
2、Fabric 交易过程分析
在Fabric交易过程中,涉及不同的角色,每个角色承担不同的功能,节点(Peer)可细分为背书节点(Endorser peer)和提交节点(Committer peer),共识由排序(Orderer)角色完成。交易流程如下:
图1:fabric交易流程简图
(1):应用程序客户端通过SDK向区块链网络发起一个交易提案(Proposal),交易提案把带有本次交易要调用的合约标识、合约方法和参数信息以及客户端签名等信息发送给背书节点(Endorser)。
(2):背书节点(Endorser)收到交易提案(Proposal)后,验证签名并确定提交者是否有权执行操作,验证通过后执行智能合约,并将结果进行签名后发还给应用程序客户端。
(3):应用程序客户端收到背书节点(Endorser)返回的信息后,判断提案结果是否一致,以及是否参照指定的背书策略执行,如果没有足够的背书,则中止处理;否则,应用程序客户端把数据打包到一起组成一个交易并签名,发送给Orderers。
(4):Orderers对接收到的交易进行共识排序,然后按照区块生成策略,将一批交易打包到一起,生成新的区块,发送给提交节点(Committer);
(5):提交节点(Committer)收到区块后,会对区块中的每笔交易进行校验,检查交易依赖的输入输出是否符合当前区块链的状态,完成后将区块追加到本地的区块链,并修改世界状态。
客户端通过Fabric完成交易,要感知三个步骤(收集背书,提交排序和确认结果),而传统的数据库的读写,只要发起请求,等待确认即可。如果使用经典的测试工具如JMeter,需要将fabric sdk进行包装RESTFul接口,增加了评测的复杂度。幸运的是,2017年5月超级账本社区推出Caliper,允许用户通过一系列预置的用例来测试特定的区块链技术实现。Caliper生成的报告将会包含一系列区块链性能指标,如TPS(平均每秒交易数),时延,系统资源占用等。本文的评测结果均为Caliper工具来测试生成。
3、Fabric 测试模型构建
建立性能测试模型,主要包含两部分工作:一是根据业务特点提取评测指标;二是确立稳定可测的业务模型。
3.1 评测指标
Fabric是一个典型的分布式系统,Fabric网络中各个Peer独立部署,分别维护自己的账本(支持背书查询),内部通过Gossip通信完成状态的同步。Fabric符合分区容忍性,根据分布式系统的CAP定理,Fabric在保证可用性的前提下,无法确保一致性。Fabric是通过最终一致性(弱一致性的一种)来保证所有的节点最终就世界状态达成一致,这个过程就是Orderer共识和Peer验证确认的过程。因而在我们的测试模型中,主要考察以下指标:
查询吞吐量(Query Throughput):每秒处理的查询请求量
共识吞吐量(Consensus Throughput):每秒处理的共识请求量
一致性吞吐率(Consistency Throughput):每秒完成的同步业务数
平均时延(Avg Latency):完成一次事务的平均耗时
失败率(Fail Rate):出现业务失败(含超时)的比例
3.2 业务模型
在业务场景的选择上,我们尽可能考虑主流场景,摈弃本身就是瓶颈的选项,聚焦区块链的核心业务。
基础设施方面,Orderer和Peer节点我们选择主流的8vCPU16G规格的虚机,Client选择一台32vCPU64G的虚机。整个测试在一个稳定的子网内完成。Orderer节点我们配置4个,满足3f+1容错的最低要求。Peer节点我们配置1,根据需要最多扩容到5个。
配置参数方面,我们使用单通道,单组织背书,状态数据库选择goleveldb。落块策略使用默认策略(2s/4M/500T)。
共识算法方面,可选择solo、pbft、kafka。solo模式为测试模式,无法用于生产环节。Kafka模式一种支持CFT容错的共识算法,性能主要依赖外挂的kafka集群性能。而pbft能够防范拜占庭节点,应用场景更广泛,对性能的要求也更高。因而,本次测试选择pbft作为共识算法。
链代码方面,我们选择社区提供的chaincode_example02示例,业务数据占比很低,同时能够覆盖账本读写的基本用例。
4 、实测与调优
4.1 查询性能与动态伸缩
Fabric 查询性能其实就是就是一次背书请求。Peer端主要包含3个过程。
(1)校验Proposal签名;
(2)检查是否满足Channel ACL;
(3)模拟执行交易并对结果签名;
代码可以参考社区chaincode_example02。
Test | Name | Succ | Fail | Send Rate |
Avg Latency |
Query Throughput |
1 | query | 10000 | 0 | 962.6tps | 0.01s | 962tps |
2 | query | 25000 | 0 | 2493.5tps | 0.07s | 2492tps |
3 | query | 50000 | 0 | 4992.0tps | 6.68s | 2503tps |
图2:单组织单Peer的查询性能
可以看到,单节点(8vCPU,16G)的读性能在2500tps左右。观察监控指标发现,CPU使用率在70%左右,接近满载,而内存使用率只有25%左右[z(3] 。这个不难理解,背书过程涉及大量的验证、签名工作,这些都是计算密集型操作。根据区块链符合CAP定理的分区容忍性,我们可以水平扩展组织内Peer来提升性能。华为区块链已经提供了这个伸缩特性,我们将peer的个数扩容为5个。
图3:华为BCS的动态伸缩特性
再次运行测试脚本,结果如下:
Test | Name | Succ | Fail | Send Rate |
Avg Latency | Query Throughput |
1 | query | 10000 | 0 | 971.4tps | 0.01s | 971tps |
2 | query | 25000 | 0 | 2495.8tps | 0.01s | 2494tps |
3 | query | 50000 | 0 | 4977.1tps | 0.01s | 4974tps |
4 | query | 12000 | 0 | 11898.9tps | 0.06s | 11869tps |
图4:华为BCS单组织5Peer的查询性能
可以看到,在不断服,不牺牲稳定性的前提下,通过将单Peer动态伸为5Peer。性能可以提升4倍多,整体吞吐量超过10000tps,平均延时只有0.06s。
4.2 共识性能与共识算法
共识算法是提升共识性能的关键。社区fabric v1.0.0-alpha2版的提供了PBFT共识是一种实用拜占庭算法。实用拜占庭算法主要改进了拜占庭算法效率不高的问题,将算法复杂度由指数级降低到多项式级,使得拜占庭容错算法在实际系统应用中变得可行。
我们先用社区的PBFT共识测试下:
Test | Name | Succ | Fail | Send Rate | Avg Latency |
Consensus Throughput |
Consistency Throughput |
1 | invoke | 1000 | 0 | 959.5tps | 5.53s | 574tps | 518tps |
2 | invoke | 2000 | 0 | 1996.4tps | 14.84s | 567tps | 520tps |
3 | invoke | 5000 | 0 | 4889.8tps | 37.90s | 579tps | 503tps |
图5:社区原生PBFT的共识性能
可以看到,社区原生的PBFT共识,无论是吞吐量,还是平均延时,都比较差。华为PBFT算法具备Early-Stopping性质,即当不存在拜占庭节点时,整个网络将很快达成共识,因而速度应该很快。我们切换为华为快速PBFT共识算法,再实测一下:
Test | Name | Succ | Fail | Send Rate | Avg Latency |
Consensus Throughput |
Consistency Throughput |
1 | invoke | 10000 | 0 | 973.6tps | 1.32s | 970tps | 917tps |
2 | invoke | 20000 | 0 | 1976.5tps | 1.24s | 1971tps | 1789tps |
3 | invoke | 50000 | 0 | 4995.4tps | 4.21s | 4985tps | 1677tps |
4 | invoke | 100000 | 0 | 11133.2tps | 9.91s | 11031tps | 1502tps |
图6:华为BCS 快速PBFT的共识性能
切换到华为PBFT算法后,共识吞吐率可以达到10000tps,一致性吞吐量也接近1800tps。同时,相对社区原生版本,平均时延也大幅缩短。这样的写性能和传统的单节点关系数据库相当,可以满足大部分商用场景。
4.3 关于最终一致性
在共识性能的测试过程中,我们发现当共识吞吐量超过2000时,Peer在同步区块时会出现积压,导致平均时延增大。要详细了解原因,可以通过查阅Fabric的关键源代码 (gossip/state/state.go)来了解Peer落块的过程:
图7:gossip 同步区块流程图
在fabric中,账本数据主要由GossipStateProvider通过Gossip协议来同步,这里只能给出关键的流程。
(1)启动一个协程deliverPayloads从orderer或其它Peer获取 “毛坯块”,调用LegerResources.StoreBlock;
(2) LegerResources调用Validator校验交易是否符合背书策略,检查读集合中版本跟账本是否一致;
(3)LedgerCommittor执行区块中的合法交易,更新账本状态;
(4)ServiceMediator更新通道元数据;
笔者修改了一下源代码,增加了4个步骤的耗时统计,结果显示40000交易生成200块的情况下,步骤2(校验)耗时17s,和步骤3(写块,更新索引)耗时40s。二者占用deliverPayloads 80%的耗时,猜测是一致性吞吐率的瓶颈。开启Profile模式后,监控堆栈调用情况,也进一步验证了这个猜想。[z4]
图8:gossip 同步区块Profile火焰图
笔者能想到的优化方案:
(1)使用高速读写盘(SSD),提高区块文件的读写效率;
(2)Validator校验环节是计算密集型,是否可以借助软硬件结合的方法,大幅提升校验效能;
(3)目前Gossip拿到Payload数据后,只能串行逐一处理。是否可以根据区块的读写集进行分区,交给不同的线程处理,最后再归并落盘,来提升性能(参考多通道性能是单通道的倍级);
笔者通过媒体了解到,华为区块链等产品团队,已经在这方面投人力进行预研,期待可商用的产品早日发布,回报社区。
5、总结
Fabric作为最受欢迎的企业级区块链解决方案,已经在很多领域得到成功应用。在本次测试调优中,发现社区原生Fabric有很多局限,如不易扩展,性能较差,不建议直接用于生产环境。
华为区块链的伸缩特性和快速PBFT算法,能够快速提升Fabric交易性能。其中伸缩特性,可以在不断服的情况下,将查询性能提升到10000tps以上(单peer的4倍多)。而快速PBFT算法,可以将共识吞吐率可提高到10000tps以上(社区原生的20倍),能够满足大部分商用场景。
同时发现,在高并发的情况下,最终一致性的平均时延会出现增长,主要原因为当前区块校验和落盘为顺序串行执行,无法充分利用多核资源。如果社区后继版本或商业公司,能通过软硬件结合,分区归并的思路,提升一致性吞吐率,降低时延,Fabric将会在商用领域获得更大的成功。
6、参考资料
https://hyperledger-fabric.readthedocs.io
https://github.com/hyperledger/caliper
https://github.com/hyperledger/fabric
https://github.com/yeasy/hyperledger_code_fabric
Performance Benchmarking and Optimizing Hyperledger Fabric.pdf
点击关注,第一时间了解华为云新鲜技术~
推荐阅读
-
openEuler郑州用户组成立!openEuler与hyperfusion携手共建河南地区用户生态 - 开幕致辞 超融合操作系统业务总经理、openEuler委员会成员蒋振华先生为本次活动致辞。 在本次活动的致辞中,他提到,作为openEuler社区早期的成员,超融合见证了openEuler从成立到在各行业商业落地,再到跨越生态拐点的过程,感谢openEuler提供了一个全产业链共同创新的平台,共同推动创新技术的商业落地。 同时,本次活动得到了郑州市郑东新区大数据管理局、郑州中原科技城投资服务局的大力支持。 郑东新区大数据管理局曹光远 在活动致辞中表示,openEuler的应用和*应用设施的深度优化,为郑东新区数字化转型提供了安全、可靠、高性能的技术基础;郑州中原科技城招商服务局王林表示,郑东新区欢迎所有openEuler生态相关企业扎根当地,围绕openEuler社区共同发展,形成合力。 openEuler社区及运维功能介绍 openEuler技术委员会委员胡峰 openEuler技术委员会委员胡峰先生在本次活动中介绍了openEuler社区目前发展的整体情况,并重点从技术层面介绍了openEuler的运维功能。 openEuler 晚会 胡峰先生介绍智能运维工具 A-Ops 和 openEuler gala、 阿波罗 Apollo、智能漏洞管理解决方案等新功能,以及涵盖各种运维场景的精品运维组件。在*交流环节,许多用户就目前使用的 openEuler 在*交流环节,许多用户就自己在使用openEuler过程中遇到的一些问题与胡峰先生进行了进一步的交流。 软硬结合,构建多样化算力操作系统 Hyperfusion 基于 openEuler 的基础上,结合自身软硬件技术积累,推出了富讯服务器操作系统 FusionOS FusionOS. FusionOS 首席架构师张海亮 分享了 FusionOS FusionOS首席架构师张海亮分享了FusionOS的软硬件协同优势、卓越的性能和可靠性,以及FusionOS在金融、运营商、*、互联网等行业的实践案例,引起了众多用户的兴趣,分享结束后,不少参会者就FusionOS的特点向讲师提问并进行了交流。
-
[姿势估计] 实践记录:使用 Dlib 和 mediapipe 进行人脸姿势估计 - 本文重点介绍方法 2):方法 1:基于深度学习的方法:。 基于深度学习的方法:基于深度学习的方法利用深度学习模型,如卷积神经网络(CNN)或递归神经网络(RNN),直接从人脸图像中学习姿势估计。这些方法能够学习更复杂的特征表征,并在大规模数据集上取得优异的性能。方法二:基于二维校准信息估计三维姿态信息(计算机视觉 PnP 问题)。 特征点定位:人脸姿态估计的第一步是通过特征点定位来检测和定位人脸的关键点,如眼睛、鼻子和嘴巴。这些关键点提供了人脸的局部结构信息,可用于后续的姿势估计。 旋转表示:常见的旋转表示方法包括欧拉角和旋转矩阵。欧拉角通过三个旋转角度(通常是俯仰、偏航和滚动)描述头部的旋转姿态。旋转矩阵是一个 3x3 矩阵,表示头部从一个坐标系到另一个坐标系的变换。 三维模型重建:根据特征点的定位结果,三维人脸模型可用于姿势估计。通过将人脸的二维图像映射到三维模型上,可以估算出人脸的旋转和平移信息。这就需要建立人脸的三维模型,然后通过优化方法将模型与特征点对齐,从而获得姿势估计结果。 特征点定位 特征点定位是用于检测人脸关键部位的五官基础部分,还有其他更多的特征点表示方法,大家可以参考我上一篇文章中介绍的特征点检测方案实践:人脸校正二次定位操作来解决人脸校正的问题,客户在检测关键点的代码上略有修改,坐标转换部分客户见上图 def get_face_info(image). img_copy = image.copy image.flags.writeable = False image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) results = face_detection.process(image) # 在图像上绘制人脸检测注释。 image.flags.writeable = True image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) box_info, facial = None, None if results.detections: for detection in results. for detection in results.detections: mp_drawing.Drawing.detection = 无 mp_drawing.draw_detection(image, detection) 面部 = detection.location_data.relative_keypoints 返回面部 在上述代码中,返回的数据是五官(6 个关键点的坐标),这是用 mediapipe 库实现的,下面我们可以尝试用另一个库:dlib 来实现。 使用 dlib 使用 Dlib 库在 Python 中实现人脸关键点检测的步骤如下: 确保已安装 Dlib 库,可使用以下命令: pip install dlib 导入必要的库: 加载 Dlib 的人脸检测器和关键点检测器模型: 读取图像并将其灰度化: 使用人脸检测器检测图像中的人脸: 对检测到的人脸进行遍历,并使用关键点检测器检测人脸关键点: 显示绘制了关键点的图像: 以下代码将参数 landmarks_part 添加到要返回的关键点坐标中。
-
jmeter 性能压力测试标准和实践中遇到的问题
-
基于 Fabric 的性能测试和调整实践
-
使用Java和SikuliX进行基于图片的自动化测试实践
-
【Netty】「萌新入门」(七)ByteBuf 的性能优化-堆内存的分配和释放都是由 Java 虚拟机自动管理的,这意味着它们可以快速地被分配和释放,但是也会产生一些开销。 直接内存需要手动分配和释放,因为它由操作系统管理,这使得分配和释放的速度更快,但是也需要更多的系统资源。 另外,直接内存可以映射到本地文件中,这对于需要频繁读写文件的应用程序非常有用。 此外,直接内存还可以避免在使用 NIO 进行网络传输时发生数据拷贝的情况。在使用传统的 I/O 时,数据必须先从文件或网络中读取到堆内存中,然后再从堆内存中复制到直接缓冲区中,最后再通过 SocketChannel 发送到网络中。而使用直接缓冲区时,数据可以直接从文件或网络中读取到直接缓冲区中,并且可以直接从直接缓冲区中发送到网络中,避免了不必要的数据拷贝和内存分配。 通过 ByteBufAllocator.DEFAULT.directBuffer 方法来创建基于直接内存的 ByteBuf: ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); 通过 ByteBufAllocator.DEFAULT.heapBuffer 方法来创建基于堆内存的 ByteBuf: ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); 注意: 直接内存是一种特殊的内存分配方式,可以通过在堆外申请内存来避免 JVM 堆内存的限制,从而提高读写性能和降低 GC 压力。但是,直接内存的创建和销毁代价昂贵,因此需要慎重使用。 此外,由于直接内存不受 JVM 垃圾回收的管理,我们需要主动释放这部分内存,否则会造成内存泄漏。通常情况下,可以使用 ByteBuffer.clear 方法来释放直接内存中的数据,或者使用 ByteBuffer.cleaner 方法来手动释放直接内存空间。 测试代码: public static void testCreateByteBuf { ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(16); System.out.println(buf.getClass); ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); System.out.println(heapBuf.getClass); ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); System.out.println(directBuf.getClass); } 运行结果: class io.netty.buffer.PooledUnsafeDirectByteBuf class io.netty.buffer.PooledUnsafeHeapByteBuf class io.netty.buffer.PooledUnsafeDirectByteBuf 池化技术 在 Netty 中,池化技术指的是通过对象池来重用已经创建的对象,从而避免了频繁地创建和销毁对象,这种技术可以提高系统的性能和可伸缩性。 通过设置 VM options,来决定池化功能是否开启: -Dio.netty.allocator.type={unpooled|pooled} 在 Netty 4.1 版本以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现; 这里我们使用非池化功能进行测试,依旧使用的是上面的测试代码 testCreateByteBuf,运行结果如下所示: class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf 可以看到,ByteBuf 类由 PooledUnsafeDirectByteBuf 变成了 UnpooledUnsafeDirectByteBuf; 在没有池化的情况下,每次使用都需要创建新的 ByteBuf 实例,这个操作会涉及到内存的分配和初始化,如果是直接内存则代价更为昂贵,而且频繁的内存分配也可能导致内存碎片问题,增加 GC 压力。 使用池化技术可以避免频繁内存分配带来的开销,并且重用池中的 ByteBuf 实例,减少了内存占用和内存碎片问题。另外,池化技术还可以采用类似 jemalloc 的内存分配算法,进一步提升分配效率。 在高并发环境下,池化技术的优点更加明显,因为内存的分配和释放都是比较耗时的操作,频繁的内存分配和释放会导致系统性能下降,甚至可能出现内存溢出的风险。使用池化技术可以将内存分配和释放的操作集中到预先分配的池中,从而有效地降低系统的内存开销和风险。 内存释放 当在 Netty 中使用 ByteBuf 来处理数据时,需要特别注意内存回收问题。 Netty 提供了不同类型的 ByteBuf 实现,包括堆内存(JVM 内存)实现 UnpooledHeapByteBuf 和堆外内存(直接内存)实现 UnpooledDirectByteBuf,以及池化技术实现的 PooledByteBuf 及其子类。 UnpooledHeapByteBuf:通过 Java 的垃圾回收机制来自动回收内存; UnpooledDirectByteBuf:由于 JVM 的垃圾回收机制无法管理这些内存,因此需要手动调用 release 方法来释放内存; PooledByteBuf:使用了池化机制,需要更复杂的规则来回收内存; 由于池化技术的特殊性质,释放 PooledByteBuf 对象所使用的内存并不是立即被回收的,而是被放入一个内存池中,待下次分配内存时再次使用。因此,释放 PooledByteBuf 对象的内存可能会延迟到后续的某个时间点。为了避免内存泄漏和占用过多内存,我们需要根据实际情况来设置池化技术的相关参数,以便及时回收内存; Netty 采用了引用计数法来控制 ByteBuf 对象的内存回收,在博文 「源码解析」ByteBuf 的引用计数机制 中将会通过解读源码的形式对 ByteBuf 的引用计数法进行深入理解; 每个 ByteBuf 对象被创建时,都会初始化为1,表示该对象的初始计数为1。 在使用 ByteBuf 对象过程中,如果当前 handler 已经使用完该对象,需要通过调用 release 方法将计数减1,当计数为0时,底层内存会被回收,该对象也就被销毁了。此时即使 ByteBuf 对象还在,其各个方法均无法正常使用。 但是,如果当前 handler 还需要继续使用该对象,可以通过调用 retain 方法将计数加1,这样即使其他 handler 已经调用了 release 方法,该对象的内存仍然不会被回收。这种机制可以有效地避免了内存泄漏和意外访问已经释放的内存的情况。 一般来说,应该尽可能地保证 retain 和 release 方法成对出现,以确保计数正确。
-
DeepShip-它由四个类别的265艘不同船只的47小时4分钟的真实世界水下录音组成。建议的数据集包括全年不同海况和噪音水平的记录。所提供的数据集不仅有助于评估现有算法的性能,而且还将使研究团体在未来受益。使用提出的数据集,我们还对六种基于时频提取特征的各种机器学习和深度学习算法进行了全面研究。此外,我们提出了一种新的基于可分离卷积的自编码器网络,以提高分类精度。对比分类准确率、精密度、查全率、fl-score等方面的实验结果,并进行配对抽样统计测试,结果表明,基于CQT特征的网络分类准确率达到77.53%,优于其他方法。 1.Introduction 近年来,由于水声分类在海洋船舶分类和探测、测量这些船舶的声音对环境的影响、退出船设计和海洋生物分类等方面的应用,引起了广泛的关注(Erbe et al., 2019;Malfante, Mars, Dalla Mura, & Gervaise, 2018)。复杂的水下环境、背景噪声、声音数据的频率依赖性吸收和散射等因素使其成为一个具有挑战性的领域(Erbe et al., 2019)。此外,螺旋桨、发动机和隐形船体技术的改进使该领域更具挑战性(Khishe &摩萨维,2020 年)。