CN110489516B - 一种快速为海量结构化数据建立前缀索引的方法 - Google Patents
一种快速为海量结构化数据建立前缀索引的方法 Download PDFInfo
- Publication number
- CN110489516B CN110489516B CN201910753609.9A CN201910753609A CN110489516B CN 110489516 B CN110489516 B CN 110489516B CN 201910753609 A CN201910753609 A CN 201910753609A CN 110489516 B CN110489516 B CN 110489516B
- Authority
- CN
- China
- Prior art keywords
- data
- layer
- function
- tree
- rows
- Prior art date
- Legal status (The legal status is an assumption and is not a legal conclusion. Google has not performed a legal analysis and makes no representation as to the accuracy of the status listed.)
- Active
Links
Images
Classifications
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F16/00—Information retrieval; Database structures therefor; File system structures therefor
- G06F16/30—Information retrieval; Database structures therefor; File system structures therefor of unstructured textual data
- G06F16/31—Indexing; Data structures therefor; Storage structures
- G06F16/316—Indexing structures
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F16/00—Information retrieval; Database structures therefor; File system structures therefor
- G06F16/30—Information retrieval; Database structures therefor; File system structures therefor of unstructured textual data
- G06F16/31—Indexing; Data structures therefor; Storage structures
- G06F16/316—Indexing structures
- G06F16/322—Trees
Landscapes
- Engineering & Computer Science (AREA)
- Theoretical Computer Science (AREA)
- Software Systems (AREA)
- Data Mining & Analysis (AREA)
- Databases & Information Systems (AREA)
- Physics & Mathematics (AREA)
- General Engineering & Computer Science (AREA)
- General Physics & Mathematics (AREA)
- Information Retrieval, Db Structures And Fs Structures Therefor (AREA)
Abstract
本发明公开了一种快速为海量结构化数据建立前缀索引的方法,所述方法的具体步骤如下:创建文件夹结构,循环,递归,分发,查询,该快速为海量结构化数据建立前缀索引的方法设计合理,能够节省大量计算时间,相比于使用数据库建立前缀索引拥有更快的速度,当前缀索引创建好之后,对数据的查询能够在毫秒级别返回。
Description
技术领域
本发明是一种快速为海量结构化数据建立前缀索引的方法,属于数据处理技术领域。
背景技术
在大数据的应用中,一种常见的场景就是有一批相对静态的数据,我们需要对这些数据进行高频率的查询,而对这些数据的更新或新增操作相对于查询操作要少很多,本发明提出的方法适用于这一类相对静态的数据,本发明提出的方法适用于千亿级别或者以上的数据量,在本发明中,将会以一千亿行数据作为例子。
本发明假设所处理的数据为结构化的文本数据,因为我们要对数据创建前缀索引,所以本方法仅适用于文本数据,当然如果是整数的数据通过前期处理转换为文本数据,也可以使用本方法建立索引,虽然本方法也可以应用于非结构化的数据,比如文档,但是本方法更适用于结构化的数据。
为结构化数据建立索引一个显而易见的方法就是使用关系型数据库,比如MySQL,我们可以在关系型数据库中创建表,然后在表中批量插入数据,把一千亿行数据插入一个表中显然是太多了,所以我们可以考虑分表,我们可以以这些数据的某些特征进行分表,比如分成一万个表,每个表有一千万行,但是这时我们会遇到第一个瓶颈,即数据库批量插入的速度,假设MySQL数据库的批量插入的速度是每秒十万行,那么插入一千亿行数据需要一百万秒,即大约11.5天,而且因为内存的限制,我们并没有办法保证每个批次都有超过十万行的数据插入单个表中,因为数据是平均分配到不同的表的,每次可能只有几千行甚至更少的数据插入一个表中,这样插入的速度将变得更慢,这还没有包括数据库建索引的时间,所以使用关系型数据库显然不是一个快速的方法,如果考虑非关系型数据库,也会有类似的瓶颈,为此,本发明提出一种快速为海量结构化数据建立前缀索引的方法。
发明内容
针对现有技术存在的不足,本发明目的是提供一种快速为海量结构化数据建立前缀索引的方法,以解决上述背景技术中提出的问题,本发明设计合理,能够节省大量计算时间,相比于使用数据库建立前缀索引拥有更快的速度,当前缀索引创建好之后,对数据的查询能够在毫秒级别返回。
为实现上述目的,本发明提供如下技术方案:一种快速为海量结构化数据建立前缀索引的方法,所述方法的具体步骤如下:
步骤一:创建文件夹结构;假设数据仅有一列,为了便于计算,假设有1024亿行的数据,因为是结构化的数据,所以预期每一行数据都有很接近的长度,比如说一行数据大约是100字节的话,那么1024亿行数据大约是10TB,这些原始数据是存放在硬盘上的,并且假设这些数据是没有排序的,这个假设也是符合一般情况,一般的商用服务器都有几GB到几十GB的内存,所以处理10TB的数据必须用分批的方法,如果每批处理2500万行的数据,那么1024亿行需要分成4096批数据处理,把2500万行数据(每行100字节)放入内存大概需要2.5GB的内存;
首先,创建一个树形的文件夹结构,以16进制的文本为例,一个16进制的文本的例子为‘6eb07aeca118bb1c’,十六进制文本只包含16个字符,即{‘0’,‘1’,‘2’,‘3’,‘4’,‘5’,‘6’,‘7’,‘8’,‘9’,‘a’,‘b’,‘c’,‘d’,‘e’,‘f’},所以,在这个例子中,文件夹结构是一个分叉为16的树形结构,并且设定树形结构的总层数为5层,树的每个结点代表一个文件夹,第一层是树的根结点,只有一个结点(即一个文件夹),记为‘MAIN’,这个根结点也是整个文件结构的根目录,根结点有16个子结点,这16个子节点即为树的第二层,分别对应于16个文件夹,这16个文件夹被分别命名为‘0’,‘1’,‘2’,…,‘d’,‘e’,‘f’,第二层的每个结点都包含16个子结点,即第二层中的名字为‘0’的节点包含16个子结点,名字为‘1’的节点包含16个子结点,以此类推,所以,树的第三层中总共有162=256个结点,第三层文件夹的命名与第二层的文件夹一致,树的第四层和第五层也是类似,是一个递归的过程,第四层总共有163=4096个文件夹,第五层总共有164=65536个文件夹,第五层为最底层的叶结点;
步骤二:循环;假设1024亿行的原始数据存放在树的根结点(MAIN)上,这些原始数据是未排序的,目标是把这些数据按规则分发到树的第五层的叶结点上,方法是先把第一层的数据全部分发到第二层16个结点上,等这一步完成后再从第二层的16个结点分发到第三层的256个结点上,以此类推,再从第三层到第四层,从第四层到第五层,因为第五层是叶结点,所以循环结束;
算法1: 循环,具体如下:
for i = 1:tree_depth-1
split(i)
end
定义算法1中的参数tree_depth为树的深度,同时定义树的深度为树的总层数,即tree_depth=5,所以算法1第一行中的参数i将循环从1到4,从1到4的每个值将会执行步骤三中的函数split(i),参数i代表的是树的第i层,所以算法1将会对数的第一层到第四层进行split操作;
步骤三:递归;此步骤是执行函数split的过程,将会对树的第i层进行分发操作,比如当i=1时把数据从第一层分发到第二层,如算法2所示,函数split将会直接调用recursive的函数,传入recursive函数的第一个参数为i,第二个参数为1,第三个参数为‘MAIN’;
算法2:split函数,具体如下:
split(i):
recursive(i,1,‘MAIN’)
算法3定义了recursive函数,该函数有三个参数,第一个参数target为目标层数,即要真正执行数据分发的层数,在split函数中,i被作为目标层数传入recursive函数,因为要分发的是第i层;
在算法3的第7行中,recursive函数调用了自己,所以recursive是一个递归函数,recursive函数将会从树的根节点(即第一层)开始往下递归,recursive函数的第二个参数current记录了当前函数已经递归到的层数,split函数传入的current参数值为1,因为总是从树的第一层开始递归的;
函数recursive的第三个参数path为当前结点(或文件夹)的路径,split函数传入recursive函数的第三个参数path=‘MAIN’,因为第一层根文件夹的路径为‘MAIN’,注意这里的文件夹路径为相对路径;
算法3: recursive函数,第一个参数target为目标层数,第二个参数current为当前层数,第三个参数path为当前文件夹的路径,具体如下:
recursive (target,current,path):
if current == target
do_split(target, path)
else
current += 1
for j = 0:f
recursive (target, current, join(path, j))
end
end
该函数的第2-3行指的是当前层数为目标层数时,将会对目标文件夹path执行步骤四中的do_split操作,比如当目标层数为1时(target=1),因为current参数的初始值为1,所以current == target将会对第一层做do_split操作;
现在假设target=2,因为current初始值为1,所以current不等于target,函数recursive将会执行第4行到第9行,其中,在第5行中current参数将会自增1,因为在这个例子中每个结点都有16个分叉,所以第6行中,将会是对当前结点的所有子结点的一个循环,从0到f刚好是子文件夹的名称;
在recursive函数中,参数target是一直保持不变的,而参数current和path会根据递归的次数不断更新,当current和path更新后,将会在第7行递归调用recursive函数,当current一直自增到等于target时,将会在第3行执行do_split函数;
步骤四:分发;算法4列出的分发函数do_split将会对当前文件夹path中的所有文件进行分发操作,把这些文件分发到当前结点的16个子结点中;
算法4中第3行的read函数将会从目标文件夹path中读取一定数量的数据,并把它放在数组rows中,每次读取到数组rows中的行数是一个全局变量(记为rows_per_batch),所以不用当做参数传入,现在假设把这个全局变量的值设为2500万行,以树的第一层为例,要读取第一层根目录的文件并分发到第二层的16个结点中,有1024亿行的数据,如果把这些数据存在4096个文件中,每个文件有2500万行,这样read函数每次只要读取根目录中的一个文件,如果1024亿行的数据是存放在40960个文件中,每个文件有250万行,那么要加载2500万行数据到内存就需要读取10个文件,这些细节都抽象到read函数中,注意数组rows是放在内存中的,假设每行数据为100字节,那么读入内存的数据大概是2.5GB,总之,如果每个批次读取的行数rows_per_batch为2500万行,那么需要分4096个批次完成数据的分发,所以算法4第2行的while true将会循环4096次;
算法4: do_split函数,具体如下:
do_split (target, path):
while true
rows = read(path)
if length(rows) == 0
break
end
result = {}
for j = 0:f
result[j] = []
end
for k = 0:length(rows)-1
result[rows[k][target-1]].append(rows[k])
end
for j = 0:f
write(result[j], join(path, j))
end
end
当数据读入rows数组后,将会对rows数组进行分组,因为是以树的第一层为例,所以要先按每行文本的第一个字符进行分组,即把‘0’字符开头的文本分为一组,把‘1’字符开头的文本分为另一组,以此类推,一个比较方便的方法是先对rows数组进行排序,那么所有的文本都会根据前缀从‘0’到‘f’排序,这样就可以按循序取出以‘0’开头,以‘1’开头,到以‘f’开头的文本,但是排序算法的时间复杂度是O(nlogn),所以算法4并不使用排序算法,算法4的第7行创建了一个名为result的字典,在第8-10行给result字典加入了从‘0’到‘f’的16个元素,并给这些元素初始化为空数组;
算法4的第11-13行遍历了rows数组,并根据数组中每个文本的前缀把该文本分配到result字典中,遍历rows数组的时间复杂度是O(n),当n很大时将会比排序算法快很多,举个例子,假设当rows数组遍历到k行(即rows[k])时的文本为‘6eb07aeca118bb1c’,因为要分发树的第一层中的数据,所以参数target=1(即目标层数为第一层),所以要按第k行文本的第1个字符进行分组,在本例中rows[k]=‘6eb07aeca118bb1c’,而rows[k]的第一个字符为rows[k][target-1]=‘6’,所以要把rows[k]放入result字典的元素‘6’中,即result[‘6’].append(rows[k]),因为rows[k][target-1]=‘6’,所以就有了算法中的第12行;
当rows数组被全部分配到result字典后,result字典会被写到文本文件中(算法4第14-16行),在本例中当前路径path为‘MAIN’,所以以‘0’开头的文本会被写到‘MAIN/0’文件夹中,以‘1’开头的文本会被写到‘MAIN/1’文件夹中,以此类推,在这个发明中,假设文本是按前缀比较均匀的分布的,所以当第一层分发完毕后,第二层的16个文件夹‘MAIN/0’,‘MAIN/1’,…,‘MAIN/f’将会各有大约1024亿/16=64亿行数据,以此类推,当数据被继续往下分发后,第三层的256个文件夹‘MAIN/0/0’,‘MAIN/0/1’,…,‘MAIN/f/e’,‘MAIN/f/f’将分别有1024亿/256=4亿行数据,第四层的4096个文件夹会各有1024亿/4096=2500万行数据,第五层的65536个文件夹会各有约160万行数据;
统计写文件操作的次数,在分发第一层的数据时,一共有4096个批次,每个批次需要写到16个子文件夹中,所以一共有4096*16=65536次写文件操作,在分发第二层数据时,一共有16个目标文件夹,每个文件夹有大约64亿行数据,即大约64亿/2500万=256批次,所以分发第二层数据共有16*256=4096批次,并有4096*16=65536次写文件操作,同样的,从第三层分发到第四层,从第四层分发到第五层一样有65536次写文件操作;
与read函数类似,不再列出write函数的细节,但需要说明的是,当在写入树的最后一层(即第五层)时,可以选择把数据写到多个文件中,比如写到16个文件中,这些文件可以用从‘0’到‘f’的字符分别命名,即‘0.txt’,‘1.txt’,…,‘f.txt’,这样增加了写文件操作的次数,但在查询时,可以加快查询速度;
需要注意的是,由于文本数据的分布情况,数据是不可能完全平均分配到16个分叉结点的,每个文件夹的数据量也不可能刚好被2500万整除,所以最后一个批次可以作为特殊批次来处理剩余的数据,这种情况只要修改一下read函数即可,在算法4的第4行中,当再没有数据可以读取的时候,即当length(rows) == 0时,循环将会结束;
步骤五:查询;当数据分发结束之后,就可以对这些数据进行查询,比如要查询文本‘6eb07aeca118bb1c’的话,需要读取路径为‘MAIN/6/e/b/0/7.txt’的文本文件,将可以在这个文件中找到这条记录,查询文本的请求将在毫秒级别返回结果。
一实施例中:所述步骤一中,假设数据仅有一列,此假设并不失一般性,因为可以很容易的扩展到多列的情况。
一实施例中:所述步骤一中,树形的文件夹结构的层数和分叉都会依赖于所处理的数据的实际情况,比如说处理的数据是16进制的文本,那么这棵树的分叉可能是16,如果数据是64进制的文本,那么树的每一层可能都有64个分叉。
一实施例中:所述步骤三中的recursive函数第7行中的函数join是常用的函数,比如join(‘MAIN’,‘0’)将会返回‘MAIN/0’,即结点‘MAIN’的子结点‘0’的文件夹路径,而join(‘MAIN/0’,‘f’)将会返回‘MAIN/0/f’,即结点‘MAIN/0’的子结点‘f’的路径。
采用上述技术方案后,一方面,使用了文件系统的文件夹结构来建立文本数据的前缀索引,在算法中只有循环和递归操作,没有使用任何第三方软件或工具,所以可以很快的计算出建立前缀索引需要的时间,相对的,用关系型数据库或非关系型数据库创建前缀索引,对我们来说是一个黑盒,相对本发明来说较难评估出创建索引所需的时间,直接写入文本文件将会比数据库批量插入的速度快很多倍,能够节省大量计算时间,相比于使用数据库建立前缀索引拥有更快的速度,当前缀索引创建好之后,对数据的查询能够在毫秒级别返回;
另一方面,本发明提出的方法将很容易进行并行计算,能够在一台计算机上进行多线程并行计算,也可以在局域网中的多台计算机中进行分布式计算,当数据从树的第一层文件夹‘MAIN’分发到第二层的16个文件夹‘MAIN/0’,‘MAIN/1’,…,‘MAIN/f’后,文件夹‘MAIN/0’中的数据将只会分发到它的子文件夹中,即‘MAIN/0/0’,‘MAIN/0/1’,…,‘MAIN/0/f’,其他的文件夹类似,也就是说,树的每个结点在分发数据到它的子结点时不会受到它兄弟结点的影响,所以树的每一个分叉都可以进行并行计算;
此外,在本发明的步骤四中,数据的分发是逐层进行的,即从第一层到第二层,再从第二层到第三层等,而每次分发都包含读文件和写文件,而且总共需要读取1024亿行和写入1024亿行,能够根据现有的硬件对步骤四进行优化,比如说可以从第一层直接分发到第三层,再从第三层到第五层,一个极端的情况是从第一层直接分发到第五层,也就是说每个批次从第一层的‘MAIN’文件夹读取数据然后直接分发到第五层的65536个文件夹中,这种情况下,一个批次就有65536个写文件操作,4096个批次将会有4096*65536=2.68亿次的写文件操作,所以,选择逐层分发还是跳层分发是需要根据具体的数据的特点,树的分叉情况,树的深度,还有硬件情况进行权衡的。
附图说明
图1为本发明一种快速为海量结构化数据建立前缀索引的方法的具体步骤流程图;
图2为本发明一种快速为海量结构化数据建立前缀索引的方法的树形文件夹结构示意图。
具体实施方式
下面将结合本发明实施例中的附图,对本发明实施例中的技术方案进行清楚、完整地描述,显然,所描述的实施例仅仅是本发明一部分实施例,而不是全部的实施例。基于本发明中的实施例,本领域普通技术人员在没有做出创造性劳动前提下所获得的所有其他实施例,都属于本发明保护的范围。
请参阅图1至图2,本发明提供一种快速为海量结构化数据建立前缀索引的方法,所述方法的具体步骤如下:
步骤一:创建文件夹结构;假设数据仅有一列,为了便于计算,假设有1024亿行的数据,因为是结构化的数据,所以预期每一行数据都有很接近的长度,比如说一行数据大约是100字节的话,那么1024亿行数据大约是10TB,这些原始数据是存放在硬盘上的,并且假设这些数据是没有排序的,这个假设也是符合一般情况,一般的商用服务器都有几GB到几十GB的内存,所以处理10TB的数据必须用分批的方法,如果每批处理2500万行的数据,那么1024亿行需要分成4096批数据处理,把2500万行数据(每行100字节)放入内存大概需要2.5GB的内存;
首先,创建一个树形的文件夹结构,以16进制的文本为例,一个16进制的文本的例子为‘6eb07aeca118bb1c’,十六进制文本只包含16个字符,即{‘0’,‘1’,‘2’,‘3’,‘4’,‘5’,‘6’,‘7’,‘8’,‘9’,‘a’,‘b’,‘c’,‘d’,‘e’,‘f’},所以,在这个例子中,文件夹结构是一个分叉为16的树形结构,并且设定树形结构的总层数为5层,树的每个结点代表一个文件夹,第一层是树的根结点,只有一个结点(即一个文件夹),记为‘MAIN’,这个根结点也是整个文件结构的根目录,根结点有16个子结点,这16个子节点即为树的第二层,分别对应于16个文件夹,这16个文件夹被分别命名为‘0’,‘1’,‘2’,…,‘d’,‘e’,‘f’,第二层的每个结点都包含16个子结点,即第二层中的名字为‘0’的节点包含16个子结点,名字为‘1’的节点包含16个子结点,以此类推,所以,树的第三层中总共有162=256个结点,第三层文件夹的命名与第二层的文件夹一致,树的第四层和第五层也是类似,是一个递归的过程,第四层总共有163=4096个文件夹,第五层总共有164=65536个文件夹,第五层为最底层的叶结点;
步骤二:循环;假设1024亿行的原始数据存放在树的根结点(MAIN)上,这些原始数据是未排序的,目标是把这些数据按规则分发到树的第五层的叶结点上,方法是先把第一层的数据全部分发到第二层16个结点上,等这一步完成后再从第二层的16个结点分发到第三层的256个结点上,以此类推,再从第三层到第四层,从第四层到第五层,因为第五层是叶结点,所以循环结束;
算法1: 循环,具体如下:
for i = 1:tree_depth-1
split(i)
end
定义算法1中的参数tree_depth为树的深度,同时定义树的深度为树的总层数,即tree_depth=5,所以算法1第一行中的参数i将循环从1到4,从1到4的每个值将会执行步骤三中的函数split(i),参数i代表的是树的第i层,所以算法1将会对数的第一层到第四层进行split操作;
步骤三:递归;此步骤是执行函数split的过程,将会对树的第i层进行分发操作,比如当i=1时把数据从第一层分发到第二层,如算法2所示,函数split将会直接调用recursive的函数,传入recursive函数的第一个参数为i,第二个参数为1,第三个参数为‘MAIN’;
算法2:split函数,具体如下:
split(i):
recursive(i,1,‘MAIN’)
算法3定义了recursive函数,该函数有三个参数,第一个参数target为目标层数,即要真正执行数据分发的层数,在split函数中,i被作为目标层数传入recursive函数,因为要分发的是第i层;
在算法3的第7行中,recursive函数调用了自己,所以recursive是一个递归函数,recursive函数将会从树的根节点(即第一层)开始往下递归,recursive函数的第二个参数current记录了当前函数已经递归到的层数,split函数传入的current参数值为1,因为总是从树的第一层开始递归的;
函数recursive的第三个参数path为当前结点(或文件夹)的路径,split函数传入recursive函数的第三个参数path=‘MAIN’,因为第一层根文件夹的路径为‘MAIN’,注意这里的文件夹路径为相对路径;
算法3: recursive函数,第一个参数target为目标层数,第二个参数current为当前层数,第三个参数path为当前文件夹的路径,具体如下:
recursive (target,current,path):
if current == target
do_split(target, path)
else
current += 1
for j = 0:f
recursive (target, current, join(path, j))
end
end
该函数的第2-3行指的是当前层数为目标层数时,将会对目标文件夹path执行步骤四中的do_split操作,比如当目标层数为1时(target=1),因为current参数的初始值为1,所以current == target将会对第一层做do_split操作;
现在假设target=2,因为current初始值为1,所以current不等于target,函数recursive将会执行第4行到第9行,其中,在第5行中current参数将会自增1,因为在这个例子中每个结点都有16个分叉,所以第6行中,将会是对当前结点的所有子结点的一个循环,从0到f刚好是子文件夹的名称;
在recursive函数中,参数target是一直保持不变的,而参数current和path会根据递归的次数不断更新,当current和path更新后,将会在第7行递归调用recursive函数,当current一直自增到等于target时,将会在第3行执行do_split函数;
步骤四:分发;算法4列出的分发函数do_split将会对当前文件夹path中的所有文件进行分发操作,把这些文件分发到当前结点的16个子结点中;
算法4中第3行的read函数将会从目标文件夹path中读取一定数量的数据,并把它放在数组rows中,每次读取到数组rows中的行数是一个全局变量(记为rows_per_batch),所以不用当做参数传入,现在假设把这个全局变量的值设为2500万行,以树的第一层为例,要读取第一层根目录的文件并分发到第二层的16个结点中,有1024亿行的数据,如果把这些数据存在4096个文件中,每个文件有2500万行,这样read函数每次只要读取根目录中的一个文件,如果1024亿行的数据是存放在40960个文件中,每个文件有250万行,那么要加载2500万行数据到内存就需要读取10个文件,这些细节都抽象到read函数中,注意数组rows是放在内存中的,假设每行数据为100字节,那么读入内存的数据大概是2.5GB,总之,如果每个批次读取的行数rows_per_batch为2500万行,那么需要分4096个批次完成数据的分发,所以算法4第2行的while true将会循环4096次;
算法4: do_split函数,具体如下:
do_split (target, path):
while true
rows = read(path)
if length(rows) == 0
break
end
result = {}
for j = 0:f
result[j] = []
end
for k = 0:length(rows)-1
result[rows[k][target-1]].append(rows[k])
end
for j = 0:f
write(result[j], join(path, j))
end
end
当数据读入rows数组后,将会对rows数组进行分组,因为是以树的第一层为例,所以要先按每行文本的第一个字符进行分组,即把‘0’字符开头的文本分为一组,把‘1’字符开头的文本分为另一组,以此类推,一个比较方便的方法是先对rows数组进行排序,那么所有的文本都会根据前缀从‘0’到‘f’排序,这样就可以按循序取出以‘0’开头,以‘1’开头,到以‘f’开头的文本,但是排序算法的时间复杂度是O(nlogn),所以算法4并不使用排序算法,算法4的第7行创建了一个名为result的字典,在第8-10行给result字典加入了从‘0’到‘f’的16个元素,并给这些元素初始化为空数组;
算法4的第11-13行遍历了rows数组,并根据数组中每个文本的前缀把该文本分配到result字典中,遍历rows数组的时间复杂度是O(n),当n很大时将会比排序算法快很多,举个例子,假设当rows数组遍历到k行(即rows[k])时的文本为‘6eb07aeca118bb1c’,因为要分发树的第一层中的数据,所以参数target=1(即目标层数为第一层),所以要按第k行文本的第1个字符进行分组,在本例中rows[k]=‘6eb07aeca118bb1c’,而rows[k]的第一个字符为rows[k][target-1]=‘6’,所以要把rows[k]放入result字典的元素‘6’中,即result[‘6’].append(rows[k]),因为rows[k][target-1]=‘6’,所以就有了算法中的第12行;
当rows数组被全部分配到result字典后,result字典会被写到文本文件中(算法4第14-16行),在本例中当前路径path为‘MAIN’,所以以‘0’开头的文本会被写到‘MAIN/0’文件夹中,以‘1’开头的文本会被写到‘MAIN/1’文件夹中,以此类推,在这个发明中,假设文本是按前缀比较均匀的分布的,所以当第一层分发完毕后,第二层的16个文件夹‘MAIN/0’,‘MAIN/1’,…,‘MAIN/f’将会各有大约1024亿/16=64亿行数据,以此类推,当数据被继续往下分发后,第三层的256个文件夹‘MAIN/0/0’,‘MAIN/0/1’,…,‘MAIN/f/e’,‘MAIN/f/f’将分别有1024亿/256=4亿行数据,第四层的4096个文件夹会各有1024亿/4096=2500万行数据,第五层的65536个文件夹会各有约160万行数据;
统计写文件操作的次数,在分发第一层的数据时,一共有4096个批次,每个批次需要写到16个子文件夹中,所以一共有4096*16=65536次写文件操作,在分发第二层数据时,一共有16个目标文件夹,每个文件夹有大约64亿行数据,即大约64亿/2500万=256批次,所以分发第二层数据共有16*256=4096批次,并有4096*16=65536次写文件操作,同样的,从第三层分发到第四层,从第四层分发到第五层一样有65536次写文件操作;
与read函数类似,不再列出write函数的细节,但需要说明的是,当在写入树的最后一层(即第五层)时,可以选择把数据写到多个文件中,比如写到16个文件中,这些文件可以用从‘0’到‘f’的字符分别命名,即‘0.txt’,‘1.txt’,…,‘f.txt’,这样增加了写文件操作的次数,但在查询时,可以加快查询速度;
需要注意的是,由于文本数据的分布情况,数据是不可能完全平均分配到16个分叉结点的,每个文件夹的数据量也不可能刚好被2500万整除,所以最后一个批次可以作为特殊批次来处理剩余的数据,这种情况只要修改一下read函数即可,在算法4的第4行中,当再没有数据可以读取的时候,即当length(rows) == 0时,循环将会结束;
步骤五:查询;当数据分发结束之后,就可以对这些数据进行查询,比如要查询文本‘6eb07aeca118bb1c’的话,需要读取路径为‘MAIN/6/e/b/0/7.txt’的文本文件,将可以在这个文件中找到这条记录,查询文本的请求将在毫秒级别返回结果。
本实施例中,所述步骤一中,假设数据仅有一列,此假设并不失一般性,因为可以很容易的扩展到多列的情况。
进一步的,所述步骤一中,树形的文件夹结构的层数和分叉都会依赖于所处理的数据的实际情况,比如说处理的数据是16进制的文本,那么这棵树的分叉可能是16,如果数据是64进制的文本,那么树的每一层可能都有64个分叉。
采用上述技术方案后,一方面,使用了文件系统的文件夹结构来建立文本数据的前缀索引,在算法中只有循环和递归操作,没有使用任何第三方软件或工具,所以可以很快的计算出建立前缀索引需要的时间,相对的,用关系型数据库或非关系型数据库创建前缀索引,对我们来说是一个黑盒,相对本发明来说较难评估出创建索引所需的时间,直接写入文本文件将会比数据库批量插入的速度快很多倍,能够节省大量计算时间,相比于使用数据库建立前缀索引拥有更快的速度,当前缀索引创建好之后,对数据的查询能够在毫秒级别返回,另一方面,本发明提出的方法将很容易进行并行计算,能够在一台计算机上进行多线程并行计算,也可以在局域网中的多台计算机中进行分布式计算,当数据从树的第一层文件夹‘MAIN’分发到第二层的16个文件夹‘MAIN/0’,‘MAIN/1’,…,‘MAIN/f’后,文件夹‘MAIN/0’中的数据将只会分发到它的子文件夹中,即‘MAIN/0/0’,‘MAIN/0/1’,…,‘MAIN/0/f’,其他的文件夹类似,也就是说,树的每个结点在分发数据到它的子结点时不会受到它兄弟结点的影响,所以树的每一个分叉都可以进行并行计算;
优选的,本实施例还具有以下配置,所述步骤三中的recursive函数第7行中的函数join是常用的函数,比如join(‘MAIN’,‘0’)将会返回‘MAIN/0’,即结点‘MAIN’的子结点‘0’的文件夹路径,而join(‘MAIN/0’,‘f’)将会返回‘MAIN/0/f’,即结点‘MAIN/0’的子结点‘f’的路径;
此外,在本发明的步骤四中,数据的分发是逐层进行的,即从第一层到第二层,再从第二层到第三层等,而每次分发都包含读文件和写文件,而且总共需要读取1024亿行和写入1024亿行,能够根据现有的硬件对步骤四进行优化,比如说可以从第一层直接分发到第三层,再从第三层到第五层,一个极端的情况是从第一层直接分发到第五层,也就是说每个批次从第一层的‘MAIN’文件夹读取数据然后直接分发到第五层的65536个文件夹中,这种情况下,一个批次就有65536个写文件操作,4096个批次将会有4096*65536=2.68亿次的写文件操作,所以,选择逐层分发还是跳层分发是需要根据具体的数据的特点,树的分叉情况,树的深度,还有硬件情况进行权衡的。
此外,应当理解,虽然本说明书按照实施方式加以描述,但并非每个实施方式仅包含一个独立的技术方案,说明书的这种叙述方式仅仅是为清楚起见,本领域技术人员应当将说明书作为一个整体,各实施例中的技术方案也可以经适当组合,形成本领域技术人员可以理解的其他实施方式。
Claims (3)
1.一种快速为海量结构化数据建立前缀索引的方法,其特征在于,所述方法的具体步骤如下:
步骤一:创建文件夹结构;数据仅有一列,为1024亿行的数据,因为是结构化的数据,所以每一行数据都有很接近的长度,一行数据是100字节的话,那么1024亿行数据是10TB,这些原始数据是存放在硬盘上的,并且这些数据是没有排序的;
首先,创建一个树形的文件夹结构,16进制的文本‘6eb07aeca118bb1c’,十六进制文本只包含16个字符,即{‘0’,‘1’,‘2’,‘3’,‘4’,‘5’,‘6’,‘7’,‘8’,‘9’,‘a’,‘b’,‘c’,‘d’,‘e’,‘f’},所以,文件夹结构是一个分叉为16的树形结构,并且设定树形结构的总层数为5层,树的每个结点代表一个文件夹,第一层是树的根结点,只有一个结点,记为‘MAIN’,这个根结点也是整个文件结构的根目录,根结点有16个子结点,这16个子节点即为树的第二层,分别对应于16个文件夹,这16个文件夹被分别命名为‘0’,‘1’,‘2’,…,‘d’,‘e’,‘f’,第二层的每个结点都包含16个子结点,即第二层中的名字为‘0’的节点包含16个子结点,名字为‘1’的节点包含16个子结点,以此类推,树的第三层中总共有162=256个结点,第三层文件夹的命名与第二层的文件夹一致,树的第四层和第五层也是类似,是一个递归的过程,第四层总共有163=4096个文件夹,第五层总共有164=65536个文件夹,第五层为最底层的叶结点;
步骤二:循环;1024亿行的原始数据存放在树的根结点(MAIN)上,这些原始数据是未排序的,目标是把这些数据按规则分发到树的第五层的叶结点上,方法是先把第一层的数据全部分发到第二层16个结点上,等这一步完成后再从第二层的16个结点分发到第三层的256个结点上,以此类推,再从第三层到第四层,从第四层到第五层,因为第五层是叶结点,所以循环结束;
定义循环算法,算法中的参数tree_depth为树的深度,同时定义树的深度为树的总层数,tree_depth=5,定义i=1:tree_depth-1,所以循环算法中的参数i将循环从1到4,从1到4的每个值将会执行步骤三中的函数split(i),参数i代表的是树的第i层,所以算法1将会对数的第一层到第四层进行split操作;
步骤三:递归;此步骤是执行函数split的过程,将会对树的第i层进行分发操作,当i=1时把数据从第一层分发到第二层,函数split将会直接调用recursive的函数,传入recursive函数的第一个参数为i,第二个参数为1,第三个参数为‘MAIN’;
定义recursive函数,该函数有三个参数,第一个参数target为目标层数,第二个参数current为当前层数,第三个参数path为当前文件夹的路径,第一个参数target为目标层数,即要真正执行数据分发的层数,在split函数中,i被作为目标层数传入recursive函数,因为要分发的是第i层;
在recursive函数中,recursive函数调用了自己,所以recursive是一个递归函数,recursive函数将会从树的根节点开始往下递归,recursive函数的第二个参数current记录了当前函数已经递归到的层数,split函数传入的current参数值为1,因为总是从树的第一层开始递归的;
函数recursive的第三个参数path为当前结点的路径,split函数传入recursive函数的第三个参数path=‘MAIN’,因为第一层根文件夹的路径为‘MAIN’,注意这里的文件夹路径为相对路径;
当前层数为目标层数时,将会对目标文件夹path执行步骤四中的do_split操作,当目标层数为1时(target=1),因为current参数的初始值为1,所以current==target将会对第一层做do_split操作;
现在target=2,因为current初始值为1,所以current不等于target,函数recursive将会执行current参数自增1的操作,因为每个结点都有16个分叉,所以,将会是对当前结点的所有子结点的一个循环,从0到f刚好是子文件夹的名称;
在recursive函数中,参数target是一直保持不变的,而参数current和path会根据递归的次数不断更新,其中更新后的path用函数join表示,函数join是常用的函数,join(‘MAIN’,‘0’)将会返回‘MAIN/0’,即结点‘MAIN’的子结点‘0’的文件夹路径,而join(‘MAIN/0’,‘f’)将会返回‘MAIN/0/f’,即结点‘MAIN/0’的子结点‘f’的路径,当current和path更新后,将会递归调用recursive函数,当current一直自增到等于target时,将会执行do_split函数;
步骤四:分发;分发函数do_split将会对当前文件夹path中的所有文件进行分发操作,把这些文件分发到当前结点的16个子结点中,分发函数do_split,第一个参数target为目标层数,第二个参数path为当前文件夹的路径,当判断条件为true时,分发函数do_split中的read函数将会从目标文件夹path中读取一定数量的数据,并把它放在数组rows中,每次读取到数组rows中的行数是一个全局变量,所以不用当做参数传入,全局变量的值设为2500万行,对于树的第一层,要读取第一层根目录的文件并分发到第二层的16个结点中,有1024亿行的数据,把这些数据存在4096个文件中,每个文件有2500万行,这样read函数每次只要读取根目录中的一个文件,1024亿行的数据是存放在40960个文件中,每个文件有250万行,那么要加载2500万行数据到内存就需要读取10个文件,这些细节都抽象到read函数中,注意数组rows是放在内存中的,每行数据为100字节,那么读入内存的数据是2.5GB,总之,每个批次读取的行数rows_per_batch为2500万行,那么需要分4096个批次完成数据的分发,所以分发函数do_split的while true将会循环4096次;
当数据读入rows数组后,将会对rows数组进行分组,对于树的第一层,要先按每行文本的第一个字符进行分组,即把‘0’字符开头的文本分为一组,把‘1’字符开头的文本分为另一组,分发函数do_split中创建了一个名为result的字典,给result字典加入了从个批次,每个批次需要写到16个子文件夹中,所以一共有4096*16=65536次写文件操作,在分发第二层数据时,一共有16个目标文件夹,每个文件夹有64亿行数据,即64亿/2500万=256批次,所以分发第二层数据共有16*256=4096批次,并有4096*16=65536次写文件操作,同样的,从第三层分发到第四层,从第四层分发到第五层一样有65536次写文件操作;
与read函数类似,不再列出write函数的细节,但需要说明的是,当在写入树的最后一层时,把数据写到16个文件中,这些文件用从‘0’到‘f’的字符分别命名,即‘0.txt’,‘1.txt’,…,‘f.txt’,这样增加了写文件操作的次数,但在查询时,可以加快查询速度;
需要注意的是,由于文本数据的分布情况,数据是不可能完全平均分配到16个分叉结点的,每个文件夹的数据量也不可能刚好被2500万整除,所以最后一个批次作为特殊批次来处理剩余的数据,这种情况只要修改一下read函数即可,在算法4的第4行中,当再没有数据可以读取的时候,即当length(rows)==0时,循环将会结束;
步骤五:查询;当数据分发结束之后,就可以对这些数据进行查询,要查询文本‘6eb07aeca118bb1c’,需要读取路径为‘MAIN/6/e/b/0/7.txt’的文本文件,在这个文件中找到这条记录,查询文本的请求将在毫秒级别返回结果。
2.根据权利要求1所述的一种快速为海量结构化数据建立前缀索引的方法,其特征在于:所述步骤一中,数据仅有一列,并不失一般性,因为可以很容易的扩展到多列的情况。
3.根据权利要求1所述的一种快速为海量结构化数据建立前缀索引的方法,其特征在于:所述步骤一中,树形的文件夹结构的层数和分叉都会依赖于所处理的数据的实际情况,处理的数据是16进制的文本,那么这棵树的分叉是16,如果数据是64进制的文本,那么树的每一层都有64个分叉。
Priority Applications (1)
Application Number | Priority Date | Filing Date | Title |
---|---|---|---|
CN201910753609.9A CN110489516B (zh) | 2019-08-15 | 2019-08-15 | 一种快速为海量结构化数据建立前缀索引的方法 |
Applications Claiming Priority (1)
Application Number | Priority Date | Filing Date | Title |
---|---|---|---|
CN201910753609.9A CN110489516B (zh) | 2019-08-15 | 2019-08-15 | 一种快速为海量结构化数据建立前缀索引的方法 |
Publications (2)
Publication Number | Publication Date |
---|---|
CN110489516A CN110489516A (zh) | 2019-11-22 |
CN110489516B true CN110489516B (zh) | 2022-03-18 |
Family
ID=68551157
Family Applications (1)
Application Number | Title | Priority Date | Filing Date |
---|---|---|---|
CN201910753609.9A Active CN110489516B (zh) | 2019-08-15 | 2019-08-15 | 一种快速为海量结构化数据建立前缀索引的方法 |
Country Status (1)
Country | Link |
---|---|
CN (1) | CN110489516B (zh) |
Family Cites Families (9)
Publication number | Priority date | Publication date | Assignee | Title |
---|---|---|---|---|
US5202986A (en) * | 1989-09-28 | 1993-04-13 | Bull Hn Information Systems Inc. | Prefix search tree partial key branching |
US7480646B2 (en) * | 2003-10-23 | 2009-01-20 | Microsoft Corporation | Type path indexing |
CN103051543B (zh) * | 2012-11-01 | 2015-09-09 | 广州键智桥网络技术有限公司 | 一种路由前缀的处理、查找、增加及删除方法 |
CN105117417B (zh) * | 2015-07-30 | 2018-04-17 | 西安交通大学 | 一种读优化的内存数据库Trie树索引方法 |
CN105426490B (zh) * | 2015-11-20 | 2019-03-26 | 四川神琥科技有限公司 | 一种基于树形结构的索引方法 |
US10496283B2 (en) * | 2016-01-22 | 2019-12-03 | Suraj Prabhakar WAGHULDE | Adaptive prefix tree based order partitioned data storage system |
CN107145588A (zh) * | 2017-05-11 | 2017-09-08 | 上海颐学网络科技有限公司 | 一种文件夹树形图自动创建方法和系统 |
CN108153907B (zh) * | 2018-01-18 | 2021-01-22 | 中国计量大学 | 通过16位Trie树实现空间优化的词典存储管理方法 |
CN108197313B (zh) * | 2018-02-01 | 2021-06-25 | 中国计量大学 | 通过16位Trie树实现空间优化的词典索引方法 |
-
2019
- 2019-08-15 CN CN201910753609.9A patent/CN110489516B/zh active Active
Also Published As
Publication number | Publication date |
---|---|
CN110489516A (zh) | 2019-11-22 |
Similar Documents
Publication | Publication Date | Title |
---|---|---|
CN105117417B (zh) | 一种读优化的内存数据库Trie树索引方法 | |
US9870382B2 (en) | Data encoding and corresponding data structure | |
US10007699B2 (en) | Optimized exclusion filters for multistage filter processing in queries | |
US20060026138A1 (en) | Real-time indexes | |
CN109325032B (zh) | 一种索引数据存储及检索方法、装置及存储介质 | |
EP0912948A1 (en) | Database apparatus | |
CN104809182A (zh) | 基于动态可分裂Bloom Filter的网络爬虫URL去重方法 | |
CN110928882B (zh) | 一种基于改进红黑树的内存数据库索引方法及系统 | |
US6735600B1 (en) | Editing protocol for flexible search engines | |
US8892566B2 (en) | Creating indexes for databases | |
CN105989015B (zh) | 一种数据库扩容方法和装置以及访问数据库的方法和装置 | |
CN112148680B (zh) | 一种基于分布式图数据库的文件系统元数据管理方法 | |
CN114691721A (zh) | 图数据的查询方法、装置、电子设备及存储介质 | |
CN114090695A (zh) | 分布式数据库的查询优化的方法和装置 | |
CN111782659A (zh) | 数据库索引创建方法、装置、计算机设备和存储介质 | |
CN105912696A (zh) | 一种基于对数归并的dns索引创建方法及查询方法 | |
CN104573112A (zh) | Oltp集群数据库中页面查询方法及数据处理节点 | |
Ferguson | Bit-tree: a data structure for fast file processing. | |
CN116662019B (zh) | 请求的分配方法、装置、存储介质及电子装置 | |
CN109254962B (zh) | 一种基于t-树的索引优化方法、装置及存储介质 | |
KR20100022565A (ko) | 해시트리를 이용한 url 검색방법 | |
CN110489516B (zh) | 一种快速为海量结构化数据建立前缀索引的方法 | |
CN111666302A (zh) | 用户排名的查询方法、装置、设备及存储介质 | |
CN111190903A (zh) | 一种用于灾备客户端的btree块索引技术 | |
WO2013097065A1 (zh) | 一种索引数据处理方法及设备 |
Legal Events
Date | Code | Title | Description |
---|---|---|---|
PB01 | Publication | ||
PB01 | Publication | ||
SE01 | Entry into force of request for substantive examination | ||
SE01 | Entry into force of request for substantive examination | ||
GR01 | Patent grant | ||
GR01 | Patent grant |