海量数据存储

怎样处理海量数据

在实际的工作环境下,很多人会遇到海量数据这个复杂而艰巨的问题,它的主要难点有下面几个方面:

一、数据量过大,数据中什么情况都可能存在。

假设说有10条数据,那么大不了每条去逐一检查,人为处理,假设有上百条数据,也能够考虑,假设数据上到千万级别,甚至过亿,那不是手工能解决的了,必须通过工具或者程序进行处理,尤其海量的数据中,什么情况都可能存在,比如,数据中某处格式出了问题,尤其在程序处理时,前面还能正常处理,突然到了某个地方问题出现了,程序终止了。

二、软硬件要求高,系统资源占用率高。

对海量的数据进行处理,除了好的方法,最重要的就是合理使用工具,合理分配系统资源。普通情况,假设处理的数据过TB级,小型机是要考虑的,普通的机子假设有好的方法能够考虑,只是也必须加大CPU和内存,就象面对着千军万马,光有勇气没有一兵一卒是非常难取胜的。

三、要求非常高的处理方法和技巧。

这也是本文的写作目的所在,好的处理方法是一位project师长期工作经验的积累,也是个人的经验的总结。没有通用的处理方法,但有通用的原理和规则。

以下我们来具体介绍一下处理海量数据的经验和技巧:

一、选用优秀的数据库工具

如今的数据库工具厂家比較多,对海量数据的处理对所使用的数据库工具要求比較高,一般使用Oracle或者DB2,微软公司近期公布的SQL Server 2005性能也不错。另外在BI领域:数据库,数据仓库,多维数据库,数据挖掘等相关工具也要进行选择,象好的ETL工具和好的OLAP工具都十分必要,比如Informatic,Eassbase等。笔者在实际数据分析项目中,对每天6000万条的日志数据进行处理,使用SQL Server 2000须要花费6小时,而使用SQL Server 2005则仅仅须要花费3小时。

二、编写优良的程序代码

处理数据离不开优秀的程序代码,尤其在进行复杂数据处理时,必须使用程序。好的程序代码对数据的处理至关重要,这不不过数据处理精确度的问题,更是数据处理效率的问题。良好的程序代码应该包括好的算法,包括好的处理流程,包括好的效率,包括好的异常处理机制等。

三、对海量数据进行分区操作

对海量数据进行分区操作十分必要,比如针对按年份存取的数据,我们能够按年进行分区,不同的数据库有不同的分区方式,只是处理机制大体同样。比如SQL Server的数据库分区是将不同的数据存于不同的文件组下,而不同的文件组存于不同的磁盘分区下,这样将数据分散开,减小磁盘I/O,减小了系统负荷,并且还能够将日志,索引等放于不同的分区下。

四、建立广泛的索引

对海量的数据处理,对大表建立索引是必行的,建立索引要考虑到详细情况,比如针对大表的分组、排序等字段,都要建立对应索引,一般还能够建立复合索引,对常常插入的表则建立索引时要小心,笔者在处理数据时,以前在一个ETL流程中,当插入表时,首先删除索引,然后插入完毕,建立索引,并实施聚合操作,聚合完毕后,再次插入前还是删除索引,所以索引要用到好的时机,索引的填充因子和聚集、非聚集索引都要考虑。

五、建立缓存机制

当数据量添加时,一般的处理工具都要考虑到缓存问题。缓存大小设置的好差也关系到数据处理的成败,比如,笔者在处理2亿条数据聚合操作时,缓存设置为100000条/Buffer,这对于这个级别的数据量是可行的。

六、加大虚拟内存

假设系统资源有限,内存提示不足,则能够靠添加虚拟内存来解决。笔者在实际项目中以前遇到针对18亿条的数据进行处理,内存为1GB,1个P42.4G的CPU,对这么大的数据量进行聚合操作是有问题的,提示内存不足,那么採用了加大虚拟内存的方法来解决,在6块磁盘分区上分别建立了6个4096M的磁盘分区,用于虚拟内存,这样虚拟的内存则添加为 4096*6 + 1024 =25600 M,攻克了数据处理中的内存不足问题。

七、分批处理

海量数据处理难由于数据量大,那么解决海量数据处理难的问题当中一个技巧是降低数据量。能够对海量数据分批处理,然后处理后的数据再进行合并操作,这样逐个击破,有利于小数据量的处理,不至于面对大数据量带来的问题,只是这样的方法也要因时因势进行,假设不同意拆分数据,还须要另想办法。只是一般的数据按天、按月、按年等存储的,都能够採用先分后合的方法,对数据进行分开处理。

八、使用暂时表和中间表

数据量添加时,处理中要考虑提前汇总。这样做的目的是化整为零,大表变小表,分块处理完毕后,再利用一定的规则进行合并,处理过程中的暂时表的使用和中间结果的保存都很重要,假设对于超海量的数据,大表处理不了,仅仅能拆分为多个小表。假设处理过程中须要多步汇总操作,可按汇总步骤一步步来,不要一条语句完毕,一口气吃掉一个胖子。

九、优化查询SQL语句

在对海量数据进行查询处理过程中,查询的SQL语句的性能对查询效率的影响是很大的,编写高效优良的SQL脚本和存储过程是数据库工作人员的职责,也是检验数据库工作人员水平的一个标准,在对SQL语句的编写过程中,比如降低关联,少用或不用游标,设计好高效的数据库表结构等都十分必要。笔者在工作中试着对1亿行的数据使用游标,执行3个小时没有出结果,这是一定要改用程序处理了。

十、使用文本格式进行处理

对一般的数据处理能够使用数据库,假设对复杂的数据处理,必须借助程序,那么在程序操作数据库和程序操作文本之间选择,是一定要选择程序操作文本的,原由于:程序操作文本速度快;对文本进行处理不easy出错;文本的存储不受限制等。比如一般的海量的网络日志都是文本格式或者csv格式(文本格式),对它进行处理牵扯到数据清洗,是要利用程序进行处理的,而不建议导入数据库再做清洗。

十一、定制强大的清洗规则和出错处理机制

海量数据中存在着不一致性,极有可能出现某处的瑕疵。比如,相同的数据中的时间字段,有的可能为非标准的时间,出现的原因可能为应用程序的错误,系统的错误等,这是在进行数据处理时,必须制定强大的数据清洗规则和出错处理机制。

十二、建立视图或者物化视图

视图中的数据来源于基表,对海量数据的处理,能够将数据按一定的规则分散到各个基表中,查询或处理过程中能够基于视图进行,这样分散了磁盘I/O,正如10根绳子吊着一根柱子和一根吊着一根柱子的差别。

十三、避免使用32位机子(极端情况)

眼下的计算机非常多都是32位的,那么编写的程序对内存的须要便受限制,而非常多的海量数据处理是必须大量消耗内存的,这便要求更好性能的机子,当中对位数的限制也十分重要。

十四、考虑操作系统问题

海量数据处理过程中,除了对数据库,处理程序等要求比較高以外,对操作系统的要求也放到了重要的位置,通常是必须使用server的,并且对系统的安全性和稳定性等要求也比較高。尤其对操作系统自身的缓存机制,暂时空间的处理等问题都须要综合考虑。

十五、使用数据仓库和多维数据库存储

数据量加大是一定要考虑OLAP的,传统的报表可能5、6个小时出来结果,而基于Cube的查询可能仅仅须要几分钟,因此处理海量数据的利器是OLAP多维分析,即建立数据仓库,建立多维数据集,基于多维数据集进行报表展现和数据挖掘等。

十六、使用採样数据,进行数据挖掘

基于海量数据的数据挖掘正在逐步兴起,面对着超海量的数据,一般的挖掘软件或算法往往採用数据抽样的方式进行处理,这种误差不会非常高,大大提高了处理效率和处理的成功率。一般採样时要注意数据的完整性和,防止过大的偏差。笔者以前对1亿2千万行的表数据进行採样,抽取出400万行,经測试软件測试处理的误差为千分之五,客户能够接受。

另一些方法,须要在不同的情况和场合下运用,比如使用代理键等操作,这种优点是加快了聚合时间,由于对数值型的聚合比对字符型的聚合快得多。类似的情况须要针对不同的需求进行处理。

海量数据是发展趋势,对数据分析和挖掘也越来越重要,从海量数据中提取实用信息重要而紧迫,这便要求处理要准确,精度要高,并且处理时间要短,得到有价值信息要快,所以,对海量数据的研究非常有前途,也非常值得进行广泛深入的研究。

海量数据处理专题(一)——开篇

  大数据量的问题是非常多面试笔试中常常出现的问题,比方baidu google 腾讯 这种一些涉及到海量数据的公司常常会问到。

  以下的方法是我对海量数据的处理方法进行了一个一般性的总结,当然这些方法可能并不能全然覆盖全部的问题,可是这种一些方法也基本能够处理绝大多数遇到的问题。以下的一些问题基本直接来源于公司的面试笔试题目,方法不一定最优,假设你有更好的处理方法,欢迎与我讨论。

  本贴从解决这类问题的方法入手,开辟一系列专题来解决海量数据问题。拟包括 下面几个方面。

  1. Bloom Filter
  2. Hash
  3. Bit-Map
  4. 堆(Heap)
  5. 双层桶划分
  6. 数据库索引
  7. 倒排索引(Inverted Index)
  8. 外排序
  9. Trie树
  10. MapReduce

  在这些解决方式之上,再借助一定的样例来剖析海量数据处理问题的解决方式。

海量数据处理专题(二)——Bloom Filter

【什么是Bloom Filter】 
Bloom Filter是一种空间效率非常高的随机数据结构,它利用位数组非常简洁地表示一个集合,并能推断一个元素是否属于这个集合。Bloom Filter的这样的高效是有一定代价的:在推断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误觉得属于这个集合(false positive)。因此,Bloom Filter不适合那些“零错误”的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter通过极少的错误换取了存储空间的极大节省。 这里有一篇关于Bloom Filter的具体介绍,不太懂的博友能够看看。 
【适用范围】 
能够用来实现数据字典,进行数据的判重,或者集合求交集 
【基本原理及要点】 
对于原理来说非常easy,位数组+k个独立hash函数。将hash函数相应的值的位数组置1,查找时假设发现全部hash函数相应位都是1说明存在,非常明显这 个过程并不保证查找的结果是100%正确的。同一时候也不支持删除一个已经插入的keyword,由于该keyword相应的位会牵动到其它的keyword。所以一个简单的改进就是 counting Bloom filter,用一个counter数组取代位数组,就能够支持删除了。 

另一个比較重要的问题,如 何依据输入元素个数n,确定位数组m的大小及hash函数个数。当hash函数个数k=(ln2)*(m/n)时错误率最小。在错误率不大于E的情况 下,m至少要等于n*lg(1/E)才干表示随意n个元素的集合。但m还应该更大些,由于还要保证bit数组里至少一半为0,则m应 该>=nlg(1/E)*lge 大概就是nlg(1/E)1.44倍(lg表示以2为底的对数)。 

举个样例我们如果错误率为0.01,则此时m应大概是n的13倍。这样k大概是8个。 

注意这里m与n的单位不同,m是bit为单位,而n则是以元素个数为单位(准确的说是不同元素的个数)。通常单个元素的长度都是有非常多bit的。所以使用bloom filter内存上通常都是节省的。 

【扩展】 
Bloom filter将集合中的元素映射到位数组中,用k(k为哈希函数个数)个映射位是否全1表示元素在不在这个集合中。Counting bloom filter(CBF)将位数组中的每一位扩展为一个counter,从而支持了元素的删除操作。Spectral Bloom Filter(SBF)将其与集合元素的出现次数关联。SBF採用counter中的最小值来近似表示元素的出现频率。 

【问题实例】 
给你A,B两个文件,各存放50亿条URL,每条URL占用64字节,内存限制是4G,让你找出A,B文件共同的URL。假设是三个乃至n个文件呢? 

依据这个问题我们来计算下内存的占用,4G=2^32大概是40亿*8大概是340亿bit,n=50亿,假设按出错率0.01算须要的大概是650亿个bit。 如今可用的是340亿,相差并不多,这样可能会使出错率上升些。另外假设这些urlip是一一相应的,就能够转换成ip,则大大简单了。

 

海量数据处理专题(三)——Hash


【什么是Hash】 
  Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把随意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这样的转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成同样的输出,而不可能从散列值来唯一的确定输入值。简单的说就是一种将随意长度的消息压缩到某一固定长度的消息摘要的函数。 
HASH主要用于信息安全领域中加密算法,它把一些不同长度的信息转化成杂乱的128位的编码,这些编码值叫做HASH值. 也能够说,hash就是找到一种数据内容和数据存放地址之间的映射关系。 
  数组的特点是:寻址easy,插入和删除困难;而链表的特点是:寻址困难,插入和删除easy。那么我们能不能综合两者的特性,做出一种寻址easy,插入删除也easy的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,我接下来解释的是最经常使用的一种方法——拉链法,我们能够理解为“链表的数组”,如图: 


 
左边非常明显是个数组,数组的每一个成员包含一个指针,指向一个链表的头,当然这个链表可能为空,也可能元素非常多。我们依据元素的一些特征把元素分配到不同的链表中去,也是依据这些特征,找到正确的链表,再从链表中找出这个元素。 
元素特征转变为数组下标的方法就是散列法。散列法当然不止一种,以下列出三种比較经常使用的。 
1,除法散列法 
最直观的一种,上图使用的就是这样的散列法,公式: 
index = value % 16 
学过汇编的都知道,求模数事实上是通过一个除法运算得到的,所以叫“除法散列法”。 
2,平方散列法 
求index是很频繁的操作,而乘法的运算要比除法来得省时(对如今的CPU来说,预计我们感觉不出来),所以我们考虑把除法换成乘法和一个位移操作。公式: 
index = (value * value) >> 28 
假设数值分配比較均匀的话这样的方法能得到不错的结果,但我上面画的那个图的各个元素的值算出来的index都是0——非常失败。或许你还有个问题,value假设非常大,value * value不会溢出吗?答案是会的,但我们这个乘法不关心溢出,由于我们根本不是为了获取相乘结果,而是为了获取index。 
3,斐波那契(Fibonacci)散列法 
平方散列法的缺点是显而易见的,所以我们能不能找出一个理想的乘数,而不是拿value本身当作乘数呢?答案是肯定的。 
1,对于16位整数而言,这个乘数是40503 
2,对于32位整数而言,这个乘数是2654435769 
3,对于64位整数而言,这个乘数是11400714819323198485 
这几个“理想乘数”是怎样得出来的呢?这跟一个法则有关,叫黄金切割法则,而描写叙述黄金切割法则的最经典表达式无疑就是著名的斐波那契数列,假设你还有兴趣,就到网上查找一下“斐波那契数列”等keyword,我数学水平有限,不知道怎么描写叙述清楚为什么,另外斐波那契数列的值竟然和太阳系八大行星的轨道半径的比例出奇吻合,非常奇妙,对么?
对我们常见的32位整数而言,公式: 
i ndex = (value * 2654435769) >> 28 
假设用这样的斐波那契散列法的话,那我上面的图就变成这样了: 

 


非常明显,用斐波那契散列法调整之后要比原来的取摸散列法好非常多。 
【适用范围】 
高速查找,删除的基本数据结构,通常须要总数据量能够放入内存。 
【基本原理及要点】 
hash函数选择,针对字符串,整数,排列,详细对应的hash方法。 
碰撞处理,一种是open hashing,也称为拉链法;还有一种就是closed hashing,也称开地址法,opened addressing。 
【扩展】 
d-left hashing中的d是多个的意思,我们先简化这个问题,看一看2-left hashing。2-left hashing指的是将一个哈希表分成长度相等的两半,分别叫做T1和T2,给T1和T2分别配备一个哈希函数,h1和h2。在存储一个新的key时,同 时用两个哈希函数进行计算,得出两个地址h1[key]和h2[key]。这时须要检查T1中的h1[key]位置和T2中的h2[key]位置,哪一个 位置已经存储的(有碰撞的)key比較多,然后将新key存储在负载少的位置。假设两边一样多,比方两个位置都为空或者都存储了一个key,就把新key 存储在左边的T1子表中,2-left也由此而来。在查找一个key时,必须进行两次hash,同一时候查找两个位置。 
【问题实例】 
1).海量日志数据,提取出某日訪问百度次数最多的那个IP。 
IP的数目还是有限的,最多2^32个,所以能够考虑使用hash将ip直接存入内存,然后进行统计。

 

海量数据处理专题(四)——Bit-map

【什么是Bit-map】 
所谓的Bit-map就是用一个bit位来标记某个元素相应的Value, 而Key即是该元素。因为採用了Bit为单位来存储数据,因此在存储空间方面,能够大大节省。 
如果说了这么多还没明确什么是Bit-map,那么我们来看一个详细的样例,如果我们要对0-7内的5个元素(4,7,2,5,3)排序(这里如果这些元素没有反复)。那么我们就能够採用Bit-map的方法来达到排序的目的。要表示8个数,我们就仅仅须要8个Bit(1Bytes),首先我们开辟1Byte的空间,将这些空间的全部Bit位都置为0(例如以下图:) 


 
然后遍历这5个元素,首先第一个元素是4,那么就把4相应的位置为1(能够这样操作 p+(i/8)|(0x01<<(i%8)) 当然了这里的操作涉及到Big-ending和Little-ending的情况,这里默觉得Big-ending),由于是从零開始的,所以要把第五位置为一(例如以下图): 

 


然后再处理第二个元素7,将第八位置为1,,接着再处理第三个元素,一直到最后处理全然部的元素,将对应的位置为1,这时候的内存的Bit位的状态例如以下: 

 


然后我们如今遍历一遍Bit区域,将该位是一的位的编号输出(2,3,4,5,7),这样就达到了排序的目的。以下的代码给出了一个BitMap的使用方法:排序。 

C代码  

复制代码
 1     //定义每一个Byte中有8个Bit位  
2 #include <memory.h>
3 #define BYTESIZE 8
4 void SetBit(char *p, int posi)
5 {
6 for(int i=0; i < (posi/BYTESIZE); i++)
7 {
8 p++;
9 }
10
11 *p = *p|(0x01<<(posi%BYTESIZE));//将该Bit位赋值1
12 return;
13 }
14
15 void BitMapSortDemo()
16 {
17 //为了简单起见,我们不考虑负数
18 int num[] = {3,5,2,10,6,12,8,14,9};
19
20 //BufferLen这个值是依据待排序的数据中最大值确定的
21 //待排序中的最大值是14,因此仅仅须要2个Bytes(16个Bit)
22 //就能够了。
23 const int BufferLen = 2;
24 char *pBuffer = new char[BufferLen];
25
26 //要将全部的Bit位置为0,否则结果不可预知。
27 memset(pBuffer,0,BufferLen);
28 for(int i=0;i<9;i++)
29 {
30 //首先将对应Bit位上置为1
31 SetBit(pBuffer,num[i]);
32 }
33
34 //输出排序结果
35 for(int i=0;i<BufferLen;i++)//每次处理一个字节(Byte)
36 {
37 for(int j=0;j<BYTESIZE;j++)//处理该字节中的每一个Bit位
38 {
39 //推断该位上是否是1,进行输出,这里的推断比較笨。
40 //首先得到该第j位的掩码(0x01<<j),将内存区中的
41 //位和此掩码作与操作。最后推断掩码是否和处理后的
42 //结果同样
43 if((*pBuffer&(0x01<<j)) == (0x01<<j))
44 {
45 printf("%d ",i*BYTESIZE + j);
46 }
47 }
48 pBuffer++;
49 }
50 }
51
52 int _tmain(int argc, _TCHAR* argv[])
53 {
54 BitMapSortDemo();
55 return 0;
56 }
复制代码

【适用范围】 

可进行数据的高速查找,判重,删除,一般来说数据范围是int的10倍下面 

【基本原理及要点】 

使用bit数组来表示某些元素是否存在,比方8位电话号码 

【扩展】 

Bloom filter能够看做是对bit-map的扩展 

【问题实例】 

1)已知某个文件内包括一些电话号码,每一个号码为8位数字,统计不同号码的个数。 

8位最多99 999 999,大概须要99m个bit,大概10几m字节的内存就可以。 (能够理解为从0-99 999 999的数字,每一个数字相应一个Bit位,所以仅仅须要99M个Bit==1.2MBytes,这样,就用了小小的1.2M左右的内存表示了全部的8位数的电话) 

2)2.5亿个整数中找出不反复的整数的个数,内存空间不足以容纳这2.5亿个整数。 

将bit-map扩展一下,用2bit表示一个数就可以,0表示未出现,1表示出现一次,2表示出现2次及以上,在遍历这些数的时候,假设相应位置的值是0,则将其置为1;假设是1,将其置为2;假设是2,则保持不变。或者我们不用2bit来进行表示,我们用两个bit-map就可以模拟实现这个2bit-map,都是一样的道理。 

 

海量数据处理专题(五)——堆

【什么是堆】
概念:堆是一种特殊的二叉树,具备下面两种性质
1)每一个节点的值都大于(或者都小于,称为最小堆)其子节点的值
2)树是全然平衡的,而且最后一层的树叶都在最左边
这样就定义了一个最大堆。例如以下图用一个数组来表示堆:

 

那么以下介绍二叉堆:二叉堆是一种全然二叉树,其随意子树的左右节点(假设有的话)的键值一定比根节点大,上图事实上就是一个二叉堆。

你一定发觉了,最小的一个元素就是数组第一个元素,那么二叉堆这样的有序队列怎样入队呢?看图:

 

如果要在这个二叉堆里入队一个单元,键值为2,那仅仅需在数组末尾增加这个元素,然后尽可能把这个元素往上挪,直到挪不动,经过了这样的复杂度为Ο(logn)的操作,二叉堆还是二叉堆。

那怎样出队呢?也不难,看图:


出队一定是出数组的第一个元素,这么来第一个元素曾经的位置就成了空位,我们须要把这个空位挪至叶子节点,然后把数组最后一个元素插入这个空位,把这个“空位”尽量往上挪。这样的操作的复杂度也是Ο(logn)。

【适用范围】
海量数据前n大,而且n比較小,堆能够放入内存

【基本原理及要点】
最大堆求前n小,最小堆求前n大。方法,比方求前n小,我们比較当前元素与最大堆里的最大元素,假设它小于最大元素,则应该替换那个最大元 素。这样最后得到的n个元素就是最小的n个。适合大数据量,求前n小,n的大小比較小的情况,这样能够扫描一遍就可以得到全部的前n元素,效率非常高。

【扩展】
双堆,一个最大堆与一个最小堆结合,能够用来维护中位数。

【问题实例】
1)100w个数中找最大的前100个数。
用一个100个元素大小的最小堆就可以。

 

海量数据处理专题(六)

【什么是双层桶】  
其实,与其说双层桶划分是一种数据结构,不如说它是一种算法设计思想。面对一堆大量的数据我们无法处理的时候,我们能够将其分成一个个小的单元,然后依据一定的策略来处理这些小单元,从而达到目的。

【适用范围】 
第k大,中位数,不反复或反复的数字

【基本原理及要点】 
由于元素范围非常大,不能利用直接寻址表,所以通过多次划分,逐步确定范围,然后最后在一个能够接受的范围内进行。能够通过多次缩小,双层仅仅是一个样例,分治才是其根本(仅仅是“仅仅分不治”)。

【扩展】 
当有时候须要用一个小范围的数据来构造一个大数据,也是能够利用这样的思想,相比之下不同的,仅仅是当中的逆过程。

【问题实例】 
1).2.5亿个整数中找出不反复的整数的个数,内存空间不足以容纳这2.5亿个整数。

有 点像鸽巢原理,整数个数为2^32,也就是,我们能够将这2^32个数,划分为2^8个区域(比方用单个文件代表一个区域),然后将数据分离到不同的区 域,然后不同的区域在利用bitmap就能够直接攻克了。也就是说仅仅要有足够的磁盘空间,就能够非常方便的解决。 当然这个题也能够用我们前面讲过的BitMap方法解决,正所谓条条大道通罗马~~~

2).5亿个int找它们的中位数。

这个样例比上面那个更明显。首先我们将int划分为2^16个区域,然后读取数据统计落到各个区域里的数的个数,之后我们依据统计结果就能够推断中位数落到那个区域,同一时候知道这个区域中的第几大数刚好是中位数。然后第二次扫描我们仅仅统计落在这个区域中的那些数就能够了。

实 际上,假设不是int是int64,我们能够经过3次这种划分就可以减少到能够接受的程度。即能够先将int64分成2^24个区域,然后确定区域的第几 大数,在将该区域分成2^20个子区域,然后确定是子区域的第几大数,然后子区域里的数的个数仅仅有2^20,就能够直接利用direct addr table进行统计了。

3).如今有一个0-30000的随机数生成器。请依据这个随机数生成器,设计一个抽奖范围是0-350000彩票中奖号码列表,当中要包括20000个中奖号码。

这个题刚好和上面两个思想相反,一个0到3万的随机数生成器要生成一个0到35万的随机数。那么我们全然能够将0-35万的区间分成35/3=12个区 间,然后每一个区间的长度都小于等于3万,这样我们就能够用题目给的随机数生成器来生成了,然后再加上该区间的基数。那么要每一个区间生成多少个随机数呢?计 算公式就是:区间长度*随机数密度,在本题目中就是30000*(20000/350000)。最后要注意一点,该题目是有隐含条件的:彩票,这意味着你 生成的随机数里面不能有反复,这也是我为什么用双层桶划分思想的另外一个原因。

海量数据处理专题(七)——数据库索引及优化

索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可高速訪问数据库表中的特定信息。

数据库索引

什么是索引

  数据库索引好比是一本书前面的文件夹,能加快数据库的查询速度。
  比如这样一个查询:select * from table1 where id=44。假设没有索引,必须遍历整个表,直到ID等于44的这一行被找到为止;有了索引之后(必须是在ID这一列上建立的索引),直接在索引里面找44(也就是在ID这一列找),就能够得知这一行的位置,也就是找到了这一行。可见,索引是用来定位的。
  索引分为聚簇索引和非聚簇索引两种,聚簇索引 是依照数据存放的物理位置为顺序的,而非聚簇索引就不一样了;聚簇索引能提高多行检索的速度,而非聚簇索引对于单行的检索非常快。

概述

  建立索引的目的是加快对表中记录的查找或排序。
  为表设置索引要付出代价的:一是添加了数据库的存储空间,二是在插入和改动数据时要花费较多的时间(由于索引也要随之变动)。

 

 

B树索引-Sql Server索引方式

为什么要创建索引

  创建索引能够大大提高系统的性能。
    第一,通过创建唯一性索引,能够保证数据库表中每一行数据的唯一性。
    第二,能够大大加快数据的检索速度,这也是创建索引的最基本的原因。
    第三,能够加速表和表之间的连接,特别是在实现数据的參考完整性方面特别有意义。
    第四,在使用分组和排序子句进行数据检索时,相同能够显著降低查询中分组和排序的时间。
    第五,通过使用索引,能够在查询的过程中,使用优化隐藏器,提高系统的性能。
  或许会有人要问:添加索引有如此多的长处,为什么不正确表中的每个列创建一个索引呢?由于,添加索引也有很多不利的方面。
    第一,创建索引和维护索引要耗费时间,这样的时间随着数据量的添加而添加。
    第二,索引须要占物理空间,除了数据表占数据空间之外,每个索引还要占一定的物理空间,假设要建立聚簇索引,那么须要的空间就会更大。
    第三,当对表中的数据进行添加、删除和改动的时候,索引也要动态的维护,这样就减少了数据的维护速度。

在哪建索引

  索引是建立在数据库表中的某些列的上面。在创建索引的时候,应该考虑在哪些列上能够创建索引,在哪些列上不能创建索引。一般来说,应该在这些列上创建索引:
  在常常须要搜索的列上,能够加快搜索的速度;
  在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;
  在常常常使用在连接的列上,这些列主要是一些外键,能够加快连接的速度;在常常须要依据范围进行搜索的列上创建索引,由于索引已经排序,其指定的范围是连续的;
  在常常须要排序的列上创建索引,由于索引已经排序,这样查询能够利用索引的排序,加快排序查询时间;
  在常常使用在WHERE子句中的列上面创建索引,加快条件的推断速度。
  相同,对于有些列不应该创建索引。一般来说,不应该创建索引的的这些列具有下列特点:
  第一,对于那些在查询中非常少使用或者參考的列不应该创建索引。这是由于,既然这些列非常少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于添加了索引,反而减少了系统的维护速度和增大了空间需求。
  第二,对于那些仅仅有非常少数据值的列也不应该添加索引。这是由于,由于这些列的取值非常少,比如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的非常大比例,即须要在表中搜索的数据行的比例非常大。添加索引,并不能明显加快检索速度。
  第三,对于那些定义为text, image和bit数据类型的列不应该添加索引。这是由于,这些列的数据量要么相当大,要么取值非常少,不利于使用索引。
  第四,当改动性能远远大于检索性能时,不应该创建索引。这是由于,改动性能和检索性能是互相矛盾的。当添加索引时,会提高检索性能,可是会减少改动性能。当减少索引时,会提高改动性能,减少检索性能。因此,当改动操作远远多于检索操作时,不应该创建索引。

数据库优化

  此外,除了数据库索引之外,在LAMP结果如此流行的今天,数据库(尤其是MySQL)性能优化也是海量数据处理的一个热点。以下就结合自己的经验,聊一聊MySQL数据库优化的几个方面。
  首先,在数据库设计的时候,要可以充分的利用索引带来的性能提升,至于怎样建立索引,建立什么样的索引,在哪些字段上建立索引,上面已经讲的非常清楚了,这里不在赘述。另外就是设计数据库的原则就是尽可能少的进行数据库写操作(插入,更新,删除等),查询越简单越好。例如以下:

 

数据库设计


  其次,配置缓存是不可缺少的,配置缓存能够有效的减少数据库查询读取次数,从而缓解数据库server压力,达到优化的目的,一定程度上来讲,这算是一个“围魏救赵”的办法。可配置的缓存包含索引缓存(key_buffer),排序缓存(sort_buffer),查询缓存(query_buffer),表描写叙述符缓存(table_cache),例如以下图:

 

配置缓存

  第三,切表,切表也是一种比較流行的数据库优化法。分表包含两种方式:横向分表和纵向分表,当中,横向分表比較有使用意义,故名思议,横向切表就是指把记录分到不同的表中,而每条记录仍旧是完整的(纵向切表后每条记录是不完整的),比如原始表中有100条记录,我要切成2个表,那么最简单也是最经常使用的方法就是ID取摸切表法,本例中,就把ID为1,3,5,7。。。的记录存在一个表中,ID为2,4,6,8,。。。的记录存在还有一张表中。尽管横向切表能够降低查询强度,可是它也破坏了原始表的完整性,假设该表的统计操作比較多,那么就不适合横向切表。横向切表有个非常典型的使用方法,就是用户数据:每一个用户的用户数据一般都比較庞大,可是每一个用户数据之间的关系不大,因此这里非常适合横向切表。最后,要记住一句话就是:分表会造成查询的负担,因此在数据库设计之初,要想好是否真的适合切表的优化:

 

分表

第四,日志分析,在数据库执行了较长一段时间以后,会积累大量的LOG日志,事实上这里面的蕴涵的实用的信息量还是非常大的。通过分析日志,能够找到系统性能的瓶颈,从而进一步寻找优化方案。

 

性能分析

以上讲的都是单机MySQL的性能优化的一些经验,可是随着信息大爆炸,单机的数据库server已经不能满足我们的需求,于是,多多节点,分布式数据库网络出现了,其一般的结构例如以下:

 

分布式数据库结构

这样的分布式集群的技术关键就是“同步复制”。。。

 

 

海量数据处理专题(八)——倒排索引(搜索引擎之基石)

引言:

在信息大爆炸的今天,有了搜索引擎的帮助,使得我们可以高速,便捷的找到所求。提到搜索引擎,就不得不说VSM模型,说到VSM,就不得不聊倒排索引。可以毫不夸张的讲,倒排索引是搜索引擎的基石。

VSM检索模型

VSM全称是Vector Space Model(向量空间模型),是IR(Information Retrieval信息检索)模型中的一种,因为其简单,直观,高效,所以被广泛的应用到搜索引擎的架构中。98年的Google就是凭借这种一个模型,開始了它的疯狂扩张之路。废话不多说,让我们来看看究竟VSM是一个什么东东。

在開始之前,我默认大家对线性代数里面的向量(Vector)有一定了解的。向量是既有大小又有方向的量,通经常使用有向线段表示,向量有:加、减、倍数、内积、距离、模、夹角的运算。

文档(Document):一个完整的信息单元,相应的搜索引擎系统里,就是指一个个的网页。

标引项(Term):文档的基本构成单位,比如在英文中能够看做是一个单词,在中文中能够看作一个词语。

查询(Query):一个用户的输入,一般由多个Term构成。

那么用一句话概况搜索引擎所做的事情就是:对于用户输入的Query,找到最相似的Document返回给用户。而这正是IR模型所解决的问题:

信息检索模型是指怎样对查询和文档进行表示,然后对它们进行相似度计算的框架和方法。

举个简单的样例:

如今有两篇文章(Document)各自是 “春风来了,春天的脚步近了” 和 “春风不度玉门关”。然后输入的Query是“春风”,从直观上感觉,前者和输入的查询更相关一些,由于它包括有2个春,但这仅仅是我们的直观感觉,怎样量化呢,要知道计算机是门严谨的学科^_^。这个时候,我们前面讲的Term和VSM模型就派上用场了。

首先我们要确定向量的维数,这时候就须要一个字典库,字典库的大小,即是向量的维数。在该例中,字典为{春风,来了,春天, 的,脚步,近了,不度,玉门关} ,文档向量,查询向量例如以下图:

 

VSM模型演示样例

PS:为了简单起见,这里分词的粒度非常大。

将Query和Document都量化为向量以后,那么就能够计算用户的查询和哪个文档相似性更大了。简单的计算结果是D1和D2同Query的内积都是1,囧。当然了,假设分词粒度再细一些,查询的结果就是另外一个样子了,因此分词的粒度也是会对查询结果(主要是召回率和准确率)造成影响的。

上述的样例是用一个非常easy的样例来说明VSM模型的,计算文档相似度的时候也是採用最原始的内积的方法,而且仅仅考虑了词频(TF)影响因子,而没有考虑反词频(IDF),而如今比較经常使用的是cos夹角法,影响因子也非常多,据传Google的影响因子有100+之多。
大名鼎鼎的Lucene项目就是採用VSM模型构建的,VSM的核心公式例如以下(由cos夹角法演变,此处省去推导过程)

 

VSM模型公式

从上面的样例不难看出,假设向量的维度(对汉语来将,这个值一般在30w-45w)变大,并且文档数量(通常都是海量的)变多,那么计算一次相关性,开销是很大的,怎样解决问题呢?不要忘记了我们这节的主题就是 倒排索引,主角最终粉墨登场了!!!

倒排索引

倒排索引很类似我们前面提到的Hash结构。下面内容来自维基百科:

倒排索引(英语:Inverted index),也常被称为反向索引置入档案反向档案,是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。它是文档检索系统中最经常使用的数据结构。

有两种不同的反向索引形式:

  • 一条记录的水平反向索引(或者反向档案索引)包括每一个引用单词的文档的列表。
  • 一个单词的水平反向索引(或者全然反向索引)又包括每一个单词在一个文档中的位置。

后者的形式提供了很多其它的兼容性(比方短语搜索),可是须要很多其它的时间和空间来创建。

由上面的定义能够知道,一个倒排索引包括一个字典的索引和全部词的列表。当中字典索引中包括了全部的Term(通俗理解为文档中的词),索引后面跟的列表则保存该词的信息(出现的文档号,甚至包括在每一个文档中的位置信息)。以下我们还採用上面的方法举一个简单的样例来说明倒排索引。

比如如今我们要对三篇文档建立索引(实际应用中,文档的数量是海量的):

文档1(D1):中国移动互联网发展迅速

文档2(D2):移动互联网未来的潜力巨大

文档3(D3):中华民族是个勤劳的民族

那么文档中的词典集合为:{中国,移动,互联网,发展,迅速,未来,的,潜力,巨大,中华,民族,是,个,勤劳}

建好的索引例如以下图:

 

倒排索引

在上面的索引中,存储了两个信息,文档号和出现的次数。建立好索引以后,我们就能够開始查询了。比如如今有一个Query是”中国移动”。首先分词得到Term集合{中国,移动},查倒排索引,分别计算query和d1,d2,d3的距离。有没有发现,倒排表建立好以后,就不须要在检索整个文档库,而是直接从字典集合中找到“中国”和“移动”,然后遍历后面的列表直接计算。

对倒排索引结构我们已经有了初步的了解,但在实际应用中还有些须要解决的问题(主要是由海量数据引起的)。笔者列举一些问题,并给出对应的解决方式,抛砖以引玉,希望大家能够展开讨论:

1.左側的索引表怎样建立?怎么做才干最高效?

可能有人不假思索回答:左側的索引当然要採取hash结构啊,这样能够高速的定位到字典项。可是这样问题又来了,hash函数怎样选取呢?并且hash是有碰撞的,可是倒排表似乎又是不同意碰撞的存在的。其实,尽管倒排表和hash异常的相思,可是两者还是有非常大差别的,其实在这里我们能够採用前面提到的Bitmap的思想,每一个Term(单词)相应一个位置(当然了,这里不是一个比特位),并且是一一相应的。怎样能够做到呢,一般在文字处理中,有非常多的编码,汉字中的GBK编码基本上就能够包括全部用到的汉字,每一个汉字的GBK编码是确定的,因此一个Term的”ID”也就确定了,从而能够做到高速定位。注:得到一个汉字的GBK号是非常快的过程,能够理解为O(1)的时间复杂度。

2.怎样高速的加入删除更新索引?

有经验的码农都知道,一般在系统的“做加法”的代价比“做减法”的代价要低非常多,在搜索引擎中中也不例外。因此,在倒排表中,遇到要删除一个文档,事实上不是真正的删除,而是将其标记删除。这样一个减法操作的代价就比較小了。

3.那么多的海量文档,假设存储呢?有么有什么备份策略呢?

当然了,一台机器是存储不下的,分布式存储是採取的。一般的备份保存3份就足够了。

好了,倒排索引最终完工了,不足的地方请指正。谢谢

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值