掌桥专利:专业的专利平台
掌桥专利
首页

一种提升多源异构数据关联性能的方法及装置

文献发布时间:2023-06-19 19:30:30


一种提升多源异构数据关联性能的方法及装置

技术领域

本发明涉及数据关联性能领域,尤其是一种提升多源异构数据关联性能的方法及装置。

背景技术

使用Spark关联异构的数据源时,当数据源是非文件数据源,例如MySQL或者Elasticsearch数据源,在进行数据关联期间,在Spark的每个分区内部,需要连接MySQL和ElasticSearch等数据源,每个分区都需要拉取全量的数据,当分区数量太多的时候,需要创建大量的连接,拉取大量的数据,关联的效率非常低。

如果使用Spark提前将非文件数据源,例如MySQL的表或Elasticsearch的索引等,抽取至文件,将产生较高的磁盘IO,消耗比较多的时间,影响数据关联处理的性能。

发明内容

为了解决现有技术存在的上述问题,本发明提供一种提升多源异构数据关联性能的方法及装置,使用Spark关联异构数据源,当数据源是非文件数据源,可以提升数据关联的性能,自动适配最优的数据关联方式。

为实现上述目的,本发明采用下述技术方案:

在本发明一实施例中,提出了一种提升多源异构数据关联性能的方法,该方法包括:

Spark对非文件数据源进行并发分区读取,并建立磁盘上的物理文件的地址和当前分区的内存空间的虚拟地址之间的映射关系;

Spark将每个分区的数据以接近内存的速度写入当前分区的内存空间;

操作系统定期异步将当前分区的内存空间的数据写入磁盘上的物理文件;

Spark使用当前分区的内存空间+磁盘上的物理文件的方式读取数据,并对磁盘上的物理文件进行预热,再将数据转换为Spark的DataFrame;

Spark根据分配的执行器内存和Spark的DataFrame大小,自动适配最优的数据关联方式。

进一步地,Spark对非文件数据源进行并发分区读取,并建立磁盘上的物理文件的地址和当前分区的内存空间的虚拟地址之间的映射关系,包括:

根据分区字段和分区数量,读取当前分区的编号,进行当前分区的数据读取;

在磁盘上新建一个物理文件,物理文件名中包含当前分区的编号;

初始化程序进程的当前分区的内存空间的虚拟地址,通过磁盘上的物理文件名,建立磁盘上的物理文件的地址和当前分区的内存空间的虚拟地址之间的映射关系。

进一步地,在当前分区的内存空间的数据的最后一行,增加一个文件写入成功的标志。

进一步地,在当前分区的内存空间的数据写入磁盘上的物理文件后,在磁盘上生成一个状态文件,根据该状态文件中的数据写入时间,判断是否需要Spark重新拉取非文件数据源到每个分区生成文件。

进一步地,Spark根据分配的执行器内存和Spark的DataFrame大小,自动适配最优的数据关联方式,包括:

Spark根据执行器的execution内存和执行器的storage内存之和占总的执行器内存的比例以及执行器的execution内存和执行器的storage内存之和的比例,计算出执行器的storage内存的大小;

根据执行器的storage内存的大小与Spark的DataFrame大小,计算出Spark的DataFrame占执行器的storage内存比例;

若Spark的DataFrame占执行器的storage内存比例小于指定阈值,则将Spark的DataFrame发送至每个执行器,存储在每个执行器的内存中,进行数据关联时,从当前执行器读取数据;

若Spark的DataFrame占执行器的storage内存比例大于指定阈值,则进行数据关联时,使用当前分区的内存空间+磁盘上的物理文件的方式读取数据。

在本发明一实施例中,还提出了一种提升多源异构数据关联性能的装置,该装置包括:

数据写入模块,用于Spark对非文件数据源进行并发分区读取,并建立磁盘上的物理文件的地址和当前分区的内存空间的虚拟地址之间的映射关系;Spark将每个分区的数据以接近内存的速度写入当前分区的内存空间;操作系统定期异步将当前分区的内存空间的数据写入磁盘上的物理文件;

数据读取模块,用于Spark使用当前分区的内存空间+磁盘上的物理文件的方式读取数据,并对磁盘上的物理文件进行预热,再将数据转换为Spark的DataFrame;

数据关联适配模块,用于Spark根据分配的执行器内存和Spark的DataFrame大小,自动适配最优的数据关联方式。

进一步地,Spark对非文件数据源进行并发分区读取,并建立磁盘上的物理文件的地址和当前分区的内存空间的虚拟地址之间的映射关系,包括:

根据分区字段和分区数量,读取当前分区的编号,进行当前分区的数据读取;

在磁盘上新建一个物理文件,物理文件名中包含当前分区的编号;

初始化程序进程的当前分区的内存空间的虚拟地址,通过磁盘上的物理文件名,建立磁盘上的物理文件的地址和当前分区的内存空间的虚拟地址之间的映射关系。

进一步地,在当前分区的内存空间的数据的最后一行,增加一个文件写入成功的标志。

进一步地,在当前分区的内存空间的数据写入磁盘上的物理文件后,在磁盘上生成一个状态文件,根据该状态文件中的数据写入时间,判断是否需要Spark重新拉取非文件数据源到每个分区生成文件。

进一步地,Spark根据分配的执行器内存和Spark的DataFrame大小,自动适配最优的数据关联方式,包括:

Spark根据执行器的execution内存和执行器的storage内存之和占总的执行器内存的比例以及执行器的storage内存占执行器的execution内存和执行器的storage内存之和的比例,计算出执行器的storage内存的大小;

根据执行器的storage内存的大小与Spark的DataFrame大小,计算出Spark的DataFrame占执行器的storage内存比例;

若Spark的DataFrame占执行器的storage内存比例小于指定阈值,则将Spark的DataFrame发送至每个执行器,存储在每个执行器的内存中,进行数据关联时,从当前执行器读取数据;

若Spark的DataFrame占执行器的storage内存比例大于指定阈值,则进行数据关联时,使用当前分区的内存空间+磁盘上的物理文件的方式读取数据。

在本发明一实施例中,还提出了一种计算机设备,包括存储器、处理器及存储在存储器上并可在处理器上运行的计算机程序,处理器执行计算机程序时实现前述提升多源异构数据关联性能的。

在本发明一实施例中,还提出了一种计算机可读存储介质,计算机可读存储介质存储有执行提升多源异构数据关联性能的计算机程序。

有益效果:

1、本发明支持对非文件数据源进行分区读取,提升数据读取的并发度。

2、本发明使非文件数据源能够以接近内存的速度存储至文件,并且提供了容错机制。

3、本发明的数据读取能够充分利用叶缓存,极大提升数据读取的性能。

4、本发明在数据有效期内,可避免在应用程序多次运行时,重复拉取数据,从而提升性能。

5、本发明使用Spark根据分配的执行器内存和数据的大小,自动适配最优的数据关联方式。

附图说明

图1是本发明提升多源异构数据关联性能的方法流程示意图;

图2是本发明Spark执行器(Executor Memory)的内存使用的分配示意图;

图3是本发明提升多源异构数据关联性能的装置结构示意图;

图4是本发明计算机设备结构示意图。

具体实施方式

下面将参考若干示例性实施方式来描述本发明的原理和精神,应当理解,给出这些实施方式仅仅是为了使本领域技术人员能够更好地理解进而实现本发明,而并非以任何方式限制本发明的范围。相反,提供这些实施方式是为了使本公开更加透彻和完整,并且能够将本公开的范围完整地传达给本领域的技术人员。

本领域技术人员知道,本发明的实施方式可以实现为一种装置、装置、节点、方法或计算机程序产品。因此,本公开可以具体实现为以下形式,即:完全的硬件、完全的软件(包括固件、驻留软件、微代码等),或者硬件和软件结合的形式。

本发明的实施方式,提出了一种提升多源异构数据关联性能的方法及装置,使用Spark关联异构数据源,当数据源是非文件数据源,Spark对非文件数据源进行并发分区读取,并建立磁盘上的物理文件的地址和当前分区的内存空间的虚拟地址之间的映射关系;Spark将每个分区的数据以接近内存的速度写入当前分区的内存空间;操作系统定期异步将当前分区的内存空间的数据写入磁盘上的物理文件,为了避免当前分区的内存空间不能及时写入磁盘导致的宕机,提供容错机制,保证数据可以从失败中恢复。根据磁盘上的状态文件中的数据写入时间,判断是否需要Spark重新拉取非文件数据源到每个分区生成文件,如果数据在有效期内,可避免在应用程序多次运行时,重复拉取数据,从而提升性能。Spark使用当前分区的内存空间+磁盘上的物理文件的方式读取数据,并对磁盘上的物理文件进行预热,将数据尽可能多地存储至当前分区的内存空间中,提升数据读取的效率,再将数据转换为Spark的DataFrame。Spark根据分配的执行器内存和Spark的DataFrame大小,判断是否需要将Spark的DataFrame发送至每个执行器,提升性能,自动适配最优的数据关联方式。

下面参考本发明的若干代表性实施方式,详细阐释本发明的原理和精神。

图1是本发明提升多源异构数据关联性能的方法流程示意图。如图1所示,该方法包括:

1、自定义Spark的读取非文件数据源的接口

通过Spark提供的非文件数据源读取的接口,屏蔽底层实现细节,极大简化用户读取数据的流程。

新建数据源类,继承抽象类RelationProvider和DataSourceRegister,实现相关方法,如下:

在进行读取时,只需要指定数据源名称即可。例如:

spark.read.options(map)).format("mysql").load()

读取数据源需要的相关参数,例如:MySQL的主机ip地址、端口、分区字段和分区数量等,可以通过map数据类型的变量指定,例如分区字段为partition_field,分区数量为partition_size。指定方式如下:

val map=Map("partition_field"->"field","partition_size"->10)。

2、非文件数据源的接口的内部实现

在进行数据读取时,需指定两个参数:(1)分区字段,例如MySQL的某个字段名称);(2)分区数量,这个参数可以作为可选参数,例如默认值为10。

为了描述的清晰,假定分区字段为partition_field,分区数量为partition_size。

Spark创建数量等于partition_size大小的分区,在创建的每个分区内部,执行非文件数据源的数据的读取,全量的数据分散至每个分区中,分别进行读取。所有分区读取的数据合并在一起,就是一份全量的数据。

每个分区都有一个唯一的编号,编号从0开始。每个分区的数据读取的逻辑如下:

(1)确定读取数据范围

根据分区字段partition_field和分区数量partition_size构造当前分区的查询条件,读取当前分区的编号,使用partition_id表示。

当前分区的查询条件的构造逻辑为:(a)对分区字段进行hash计算,生成一个数值类型的hash值。(b)将生成的hash值,对分区的数量取余数。(3)该余数的值即为当前分区的编号。换算成公式如下:hash(partition_field)%partition_size=partition_id。

如果是MySQL数据源,则当前分区的查询语句类似为:select*from table wherehash(partition_field)%partition_size=partition_id。

经过以上的操作,可以保证每个分区读取的数据不重叠,并且所有分区的数据合并在一起,读取的是一份全量的数据。

(2)每个分区的数据以接近内存的速度写入磁盘

传统的数据写入流程:数据首先写入当前程序进程的内存空间,然后将内存空间的数据复制到操作系统内核的IO缓冲区,最后写入磁盘文件。整个过程,执行了两次的数据拷贝,即内存空间的数据复制到操作系统内核的IO缓冲区和操作系统内核的IO缓冲区的数据再写入磁盘文件,并且将内存空间的数据复制到操作系统内核的IO缓冲区对CPU和内存的消耗比较大,影响数据写入文件的性能。

为了解决以上的问题,本发明将每个分区的数据以接近内存的速度写入磁盘。

(a)在Spark任务的Executor(执行器)进程所在主机的磁盘新建一个物理文件,物理文件名中包含分区标志,例如,在物理文件名中加上分区编号的后缀(举例:part_0.csv,其中0是分区编号即分区标志,csv是文件格式),用于识别文件的数据来源的分区编号。

(b)初始化程序进程的当前分区的内存空间的虚拟地址,通过当前分区的物理文件名,使用Java内置的MappedByteBuffer.map函数,建立磁盘上的物理文件的地址和当前分区的内存空间的虚拟地址之间的映射关系。

(c)读取当前分区的数据,将数据写入当前分区的内存空间,即叶缓存,Linux操作系统未分配的内存都可用于叶缓存。由于当前分区的数据的写入操作是内存,数据写入的效率非常快。

(d)在当前分区数据的最后一行,增加一个文件写入成功的标志。例如,写入一个字符串_SUCCESS。增加了文件写入成功的标志的数据也是首先写入当前分区的叶缓存。

这一步的目的,是因为叶缓存可能存在数据丢失风险。如果叶缓存的数据没有及时写入磁盘的物理文件,主机宕机,可能导致数据的丢失,因此,需要有容错机制,从失败中恢复数据。

(e)Linux操作系统定期异步将叶缓存中的数据写入磁盘的物理文件。但是,如果操作系统没有及时将叶缓存中的数据写入磁盘的物理文件,则当前分区的程序异常退出。当程序重启时,会检查每个分区数据的最后一行,由于只查询最后一行,速度非常快。如果最后一行没有文件写入成功的标志,表示上次操作系统将叶缓存中的数据写入磁盘的的物理文件的数据不完整,也就是没有成功,则重启当前分区的数据写入流程,删除当前分区的脏数据,重写当前分区的数据,保证数据的一致性。

3、生成状态文件

在叶缓存中的数据写入磁盘上的物理文件后,在磁盘生成一个状态文件,记录当前叶缓存中的数据写入的开始时间。例如,文件格式如下:

{"lastUpdatedTime":"2022-10-2912:34:46"}

记录这个时间的目的在于,有些非文件数据源更新的频度低,例如,可能1天才会更新一次。当Spark程序在重新执行数据关联的任务时,通过将当前时间和状态文件中的时间进行对比,用于判断是否需要重新执行数据的拉取,更新文件。数据的更新频度可通过参数(数据更新频度的参数,参数值为1个小时或10分钟等)传入,Spark根据传入的参数判断是否需要重新执行数据的拉取,更新文件。

4、Spark读取文件,转换为Spark的DataFrame

(1)判断是否需要重新拉取数据;

在进行读取时,传入数据的更新频度的参数,根据参数的值、当前时间和状态文件中的时间,判断是否需要重新拉取数据至文件。规则如下:(a)如果状态文件中的时间+数据的更新频度的参数值(例如1小时或10分钟等)<=当前时间,则表示数据没有过期,不用重新拉取非文件数据源的数据。(b)反之,则需要重写拉取非文件数据源的数据,更新文件。

(2)通过Spark读取在每个分区生成的文件,将文件转换为Spark的DataFrame;

传统的数据读取流程:(a)读取磁盘数据,将数据读取至操作系统的内核IO缓冲区。(b)将操作系统内核IO缓冲区的数据读取至当前程序进程的内存空间。这个过程经历了两次数据的复制操作,即数据读取至操作系统的内核IO缓冲区和操作系统内核IO缓冲区的数据读取至当前程序进程的内存空间,并且操作系统内核IO缓冲区的数据读取至当前程序进程的内存空间,非常消耗CPU和内存资源,影响读取的性能。

为了解决上面数据读取的问题,执行如下的数据读取流程:

(a)遍历磁盘上的每个分区的物理文件,将每个分区的物理文件映射至一个内存空间的虚拟地址。

(b)在进行数据读取时,首先判断叶缓存中是否包含对应的数据,如果包含数据,则直接从叶缓存读取。如果叶缓存不包含对应的数据,则从磁盘的物理文件加载数据至叶缓存中,再次进行读取。

(c)为了提升数据读取的效率,定期对磁盘上的每个分区的物理文件进行预热,将磁盘上的每个分区的物理文件的数据尽可能多的加载至叶缓存中。

(d)在读取时,过滤掉每个文件的最后一行(文件写入成功的标志行),将数据转换为Spark的DataFrame(Spark的分布式数据集,类似分布式表,包含了数据以及数据对应的字段名等信息)。

5、根据数据量的大小,确定数据关联的策略

Spark执行器(Executor Memory)的内存使用的分配如图2所示。

Spark执行器的内存分成三部分组成:执行器的Execution内存+执行器的Storage内存+其他内存(Excution和Storage以外的内存)。

Spark执行器的内存是在提交Spark应用程序时指定的内存,根据上一步生成的Spark的DataFrame大小(Spark的DataFrame来自于磁盘,在磁盘读取数据时,会计算读取的文件大小)以及执行器内存综合判断,是否需要将上一步生成的Spark的DataFrame发送至每个执行器或者在每个分区对应的一个任务(task)中读取上一步生成的Spark的DataFrame。

如果上一步生成的Spark的DataFrame的数据量较小,则可以考虑在每个执行器存放一份完整的Spark的DataFrame的副本,每个任务(task)在进行数据关联时,无需从磁盘或者其他节点拉取数据,只需要在当前执行器读取数据,极大的提升数据关联的效率。

如果上一步生成的Spark的DataFrame的数据量较大,每个执行器的内存不足以存储一份全量的数据,则通过上一步的叶缓存+磁盘的物理文件的方式读取数据,也能对性能带来非常大的性能提升。而如果直接从数据库进行数据的关联,每个分区都要拉取一份全量的数据库的数据,产生大量的网络和磁盘读写的开销。

判断是否要将上一步生成的Spark的DataFrame发送至每个执行器(executor),判断逻辑如下:

Spark有两个参数控制内存的大小,所有spark参数均可以在提交任务时指定。

第一个参数:spark.memory.fraction,表示Executor和Storage内存之和占总的执行器内存的比例,默认值为60%。例如,如果执行器内存是10G,那么Executor和Storage内存之和默认是6G。

第二参数:spark.memory.storageFraction,表示Storage内存占Execution和Storage内存之和的比例,默认值为0.5,表示Executor和Storage内存各占一半。

当完成了上一步的Spark的DataFrame生成之后,计算数据的大小,然后根据以上两个参数计算Storage内存的大小,然后将Spark的DataFrame的大小和Storage内存的大小对比,计算上一步的Spark的DataFrame占Storage内存的比例。如果占比小于一个指定的阈值(例如,0.2是一个比较优化的占比),则将上一步的Spark的DataFrame发送至每个执行器,在这一步是通过设置参数spark.sql.autoBroadcastJoinThreshold的值为上一步的Spark的DataFrame的大小,Spark在进行数据关联时,主动将上一步的Spark的DataFrame的全量数据发送至每个执行器,存储在每个执行器的内存中,在进行数据关联时,没有数据的shuffle(shuffle是Spark的一个专有概念,表示数据会在各个节点之间的网络传输,过多的shuffle会产生大量的磁盘IO和网络IO,对性能会有一定的影响,此处的优化取主要是消除shuffle)。

如果占比大于指定的阈值,则在数据关联时,则不对DataFrame做任何处理,直接将这个DataFrame(使用前面描述的叶缓存+磁盘的物理文件的方式关联数据)。

执行完上面的流程之后,Spark将根据分配的执行器内存和Spark的DataFrame的大小,自动适配最优的数据关联方式。

需要说明的是,尽管在上述实施例及附图中以特定顺序描述了本发明方法的操作,但是,这并非要求或者暗示必须按照该特定顺序来执行这些操作,或是必须执行全部所示的操作才能实现期望的结果。附加地或备选地,可以省略某些步骤,将多个步骤合并为一个步骤执行,和/或将一个步骤分解为多个步骤执行。

为了对上述提升多源异构数据关联性能的方法进行更为清楚的解释,下面结合一个具体的实施例来进行说明,然而值得注意的是该实施例仅是为了更好地说明本发明,并不构成对本发明不当的限定。

实施例:

1、导入数据源的包

//注释:通过import语句,导入包。这个包是在“1、自定义Spark的读取非文件数据源的接口”中定义的包,.*表示导入这个包下的所有类。

import org.ds.multi.datasource.*

2、定义数据源的参数

//定义一个Map数据类型的集合,集合的变量名称为optionsMap,定义数据源连接信息和分区数量等。

3、通过定义的参数,加载数据源

//df为加载的dataframe的变量名称。数据源的名称为mysql_ds,在“1、自定义Spark的读取非文件数据源的接口”中通过shortName定义的。通过options指定了上一步定义的参数optionsMap。

val df=spark.read.format("mysql_ds").options(optionsMap).load()

4、注册表

有了上一步生成的DataFrame,就可以使用返回的DataFrame,注册临时表,然后可以编写任意的SQL,包括数据关联的sql等。

注册表的方法:

df.createOrReplaceTempView(“temp_table”)。

上面指定了表名称为temp_table。

基于同一发明构思,本发明还提出一种提升多源异构数据关联性能的装置。该装置的实施可以参见上述方法的实施,重复之处不再赘述。以下所使用的术语“模块”,可以是实现预定功能的软件和/或硬件的组合。尽管以下实施例所描述的装置较佳地以软件来实现,但是硬件,或者软件和硬件的组合的实现也是可能并被构想的。

图3是本发明提升多源异构数据关联性能的装置结构示意图。如图3所示,该装置包括:

数据写入模块101,用于Spark对非文件数据源进行并发分区读取,并建立磁盘上的物理文件的地址和当前分区的内存空间的虚拟地址之间的映射关系;具体如下:

根据分区字段和分区数量,读取当前分区的编号,进行当前分区的数据读取;

在磁盘上新建一个物理文件,物理文件名中包含当前分区的编号;

初始化程序进程的当前分区的内存空间的虚拟地址,通过磁盘上的物理文件名,建立磁盘上的物理文件的地址和当前分区的内存空间的虚拟地址之间的映射关系。

Spark将每个分区的数据以接近内存的速度写入当前分区的内存空间。在当前分区的内存空间的数据的最后一行,增加一个文件写入成功的标志。

操作系统定期异步将当前分区的内存空间的数据写入磁盘上的物理文件。

在当前分区的内存空间的数据写入磁盘上的物理文件后,在磁盘上生成一个状态文件,根据该状态文件中的数据写入时间,判断是否需要Spark重新拉取非文件数据源到每个分区生成文件。

数据读取模块102,用于Spark使用当前分区的内存空间+磁盘上的物理文件的方式读取数据,并对磁盘上的物理文件进行预热,再将数据转换为Spark的DataFrame;

数据关联适配模块103,用于Spark根据分配的执行器内存和Spark的DataFrame大小,自动适配最优的数据关联方式;具体如下:

Spark根据执行器的execution内存和执行器的storage内存之和占总的执行器内存的比例以及执行器的execution内存和执行器的storage内存之和的比例,计算出执行器的storage内存的大小;

根据执行器的storage内存的大小与Spark的DataFrame大小,计算出Spark的DataFrame占执行器的storage内存比例;

若Spark的DataFrame占执行器的storage内存比例小于指定阈值,则将Spark的DataFrame发送至每个执行器,存储在每个执行器的内存中,进行数据关联时,从当前执行器读取数据;

若Spark的DataFrame占执行器的storage内存比例大于指定阈值,则进行数据关联时,使用当前分区的内存空间+磁盘上的物理文件的方式读取数据。

应当注意,尽管在上文详细描述中提及了提升多源异构数据关联性能的装置的若干模块,但是这种划分仅仅是示例性的并非强制性的。实际上,根据本发明的实施方式,上文描述的两个或更多模块的特征和功能可以在一个模块中具体化。反之,上文描述的一个模块的特征和功能可以进一步划分为由多个模块来具体化。

基于前述发明构思,如图4所示,本发明还提出一种计算机设备200,包括存储器210、处理器220及存储在存储器210上并可在处理器220上运行的计算机程序230,处理器220执行计算机程序230时实现前述提升多源异构数据关联性能的方法。

基于前述发明构思,本发明还提出一种计算机可读存储介质,计算机可读存储介质存储有执行前述提升多源异构数据关联性能的的计算机程序。

本发明提出的提升多源异构数据关联性能的方法及装置,支持对非文件数据源进行分区读取,提升数据读取的并发度;非文件数据源能够以接近内存的速度存储至磁盘的物理文件,并且提供了容错机制;数据读取能够充分利用叶缓存,极大提升数据读取的性能;如果数据在有效期内,可避免在应用程序多次运行时,重复拉取非文件数据源的数据,从而提升性能;Spark将根据分配的执行器内存和Spark的DataFrame的大小,自动适配最优的数据关联方式。

虽然已经参考若干具体实施方式描述了本发明的精神和原理,但是应该理解,本发明并不限于所公开的具体实施方式,对各方面的划分也不意味着这些方面中的特征不能组合以进行受益,这种划分仅是为了表述的方便。本发明旨在涵盖所附权利要求的精神和范围内所包含的各种修改和等同布置。

对本发明保护范围的限制,所属领域技术人员应该明白,在本发明的技术方案的基础上,本领域技术人员不需要付出创造性劳动即可做出的各种修改或变形仍在本发明的保护范围以内。

相关技术
  • 一种基于多源异构海量数据的深度分析方法及装置
  • 农业面源污染多源异构大数据关联方法及采用该方法的大数据监管平台
  • 基于时空特征的多源异构面源污染大数据的关联和检索方法及监管平台
技术分类

06120115929572