本文基于环境hadoop-0.16.4 和 hbase-0.1.3 编写
Hbase是一个分布式开源数据库,基于Hadoop分布式文件系统,模仿并提供了基于Google文件系统的Bigtable数据库的所有功能。
Hbaes的目标是处理非常庞大的表,可以用普通的计算机处理超过10亿行数据,并且有数百万列元素组成的数据表。
Hbase可以直接使用本地文件系统或者Hadoop作为数据存储方式,不过为了提高数据可靠性和系统的健壮性,发挥Hbase处理大数据量等功能,需要使用Hadoop作为文件系统,那么我们就先要了解Hadoop文件系统的基本特性和原理,才能更好地理解Hbase的工作方式。
Hadoop文件系统
Hadoop文件系统是一个能够兼容普通硬件环境的分布式文件系统, 和现有的分布式文件系统不同的地方是Hadoop更注重容错性和兼容廉价的硬件设备,这样做是为了用很小的预算甚至直接利用现有机器就实现大流量和大数据量的读取。
Hadoop 使用了POSIX的设计来实现对文件系统文件流的读取。HDFS(Hadoop FileSystem)原来是Apache Nutch搜索引擎(从Lucene发展而来)开发的一个部分,后来独立出来作为一个Apache子项目。
Hadoop的假设与目标
1、 硬件出错,Hadoop假设硬件出错是一种正常的情况,而不是异常,为的就是在硬件出错的情况下尽量保证数据完整性,HDFS设计的目标是在成百上千台服务器中存储数据,并且可以快速检测出硬件错误和快速进行数据的自动恢复。
2、 流数据读写,不同于普通的文件系统,Hadoop是为了程序批量处理数据而设计的,而不是与用户的交互或者随机读写,所以POSIX对程序增加了许多硬性限制,程序必须使用流读取来提高数据吞吐率。
3、 大数据集,HDFS上面一个典型的文件一般是用GB或者TB计算的,而且一个数百台机器组成的集群里面可以支持过千万这样的文件。
4、 简单的文件模型,HDFS上面的文件模型十分简单,就是一次写入多次读取的模型,文件一旦创建,写入并关闭了,之后就再也不会被改变了,只能被读取,这种模型刚好符合搜索引擎的需求,以后可能会实现追加写入数据这样的功能。
5、 强大的跨平台兼容性,由于是基于java的实现,无论是硬件平台或者是软件平台要求都不高,只要是jdk支持的平台都可以兼容。
Hadoop体系结构
目录节点(NameNode)和数据节点(DataNodes)
Hadoop文件系统是主从架构,一个Hadoop文件系统由唯一一个目录节点和数个数据节点组成。
Hadoop文件系统对外表现为一个普通的文件系统,用户可以用文件名去存储和访问文件,而实际上文件是被分成不同的数据块,这些数据块就是存储在数据节点上面。
目录节点是集群里面的主节点,负责文件名的维护管理,也是客户端访问文件的入口。文件名的维护包括文件和目录的创建、删除、重命名等。同时也管理数据块和数据节点的映射关系,客户端需要访问目录节点才能知道一个文件的所有数据块都保存在哪些数据节点上。
数据节点一般就是集群里面的一台机器,负责数据的存储和读取。在写入时,由目录节点分配数据块的保存,然后客户端直接写到对应的数据节点。在读取时,当客户端从目录节点获得数据块的映射关系后,就会直接到对应的数据节点读取数据。数据节点也要根据目录节点的命令创建、删除数据块,和冗余复制。
一个典型的Hadoop文件系统集群部署,是由一台性能较好的机器运行目录节点,而集群里面的其它机器每台上面运行一个数据节点。当然一个机器可以运行任意多个数据节点,甚至目录节点和数据节点一起运行,不过这种模式在正式的应用部署中很少使用。
唯一的目录节点的设计大大简化了整个体系结构,目录节点负责Hadoop文件系统里面所有元数据的仲裁和存储。这样的设计使数据不会脱离目录节点的控制。
Hadoop文件系统命名空间
Hadoop文件系统使用的是传统的分级文件体系,客户端程序可以创建目录并且在目录里面保存文件,类似与现在一般的文件系统。Hadoop允许用户创建、删除文件,在目录间转移文件,重命名文件等,但是还没有实现磁盘配额和文件访问权限等功能,也不支持文件的硬连接和软连接(快捷方式),这些功能在短期内不会实现。
目录节点负责存储和管理整个文件系统的命名空间,应用程序可以指定某一个文件需要在Hadoop文件系统中冗余多少份,这个在Hadoop中称为冗余因素,保存在目录节点里面。
Hadoop存储原理
冗余数据保存
Hadoop文件系统是为了大文件的可靠保存而设计的,一个文件被划分成一连串的数据块,除了文件的最后一块以外其它所有的数据块都是固定大小的,为了数据容错性,每一个数据块都会被冗余存储起来,而每个文件的块大小和冗余因素都是可以设置的,程序可以设置文件的数据块要被复制多少份,而且这个冗余因素除了可以在创建的时候指定,还可以在之后改变。在Hadoop文件系统里面文件只会被写入一次,并且任何时间只会有一个程序在写入这个文件。
目录节点是根据数据块的冗余状况来作出处理决策的,数据节点会定期发送一个存在信号(Heartbeat)和数据块列表给目录节点,存在信号使目录节点认为该数据节点还是有效的,而数据块列表包括了该数据节点上面的所有数据块编号。
数据存取策略
复制策略是hadoop文件系统最核心的部分,对读写性能影响很大,hadoop和其它分布式文件系统的最大区别就是可以调整冗余数据的位置,这个特性需要很多时间去优化和调整。
一、数据存放
目前hadoop采用以机柜为基础的数据存放策略,这样做的目的是提高数据可靠性和充分利用网络带宽。当前具体实现了的策略只是这个方向的尝试,hadoop短期的研究目标之一就是在实际产品环境中观察系统读写的行为,测试性能和研究更深入的规则。
一个大的hadoop集群经常横跨多个机柜,而不同机柜之间的数据通讯同经过交换机或者路由,所以同一个机柜中不同机器的通讯带宽是比不同机柜之间机器通讯时候的大。
Hadoop提供了一个api来决定数据机所属的机柜id,当文件系统启动的时候,数据机就把自己所属的机柜id发给目录机,然后目录机管理这些分组。
Hadoop默认是每个数据机都是在不同的机柜上面,这种方法没有做任何性能优化,但是也有不少优点:
1、 数据可靠性是最高的。因为这样可以防止机柜出错的时候数据丢失。
2、 在读取数据的时候充分利用不同机柜之间的带宽。
3、 而且这个策略可以很容易的完成负载平衡和错误处理。
缺点就是写入数据的时候并不能完全利用同一机柜里面机器的带宽。
在默认的配置下,hadoop的冗余复制因子是3,意思就是每一块文件数据一共有3个地方存放,hadoop目前的存放策略是其中两份放在同一个rack id的不同机器上面,另外一个放在不同rack id的机器上面,简单来说就是1/3的冗余数据在一个机柜里面,2/3的冗余数据在另外一个机柜里面,这样既可以防止机柜异常时候的数据恢复,又可以提高读写性能。
上面所说的策略目前还是在测试优化阶段。
二、数据读取
数据读取策略,根据前面所说的数据存放策略,数据读取的时候,客户端也有api确定自己的机柜id,读取的时候,如果有块数据和客户端的机柜id一样,就优先选择该数据节点,客户端直接和数据节点建立连接,读取数据。如果没有,就随机选取一个数据节点。
三、数据复制
主要是在数据写入和数据恢复的时候发生,数据复制是使用流水线复制的策略。
当客户端要在hadoop上面写一个文件,首先它先把这个文件写在本地,然后对文件进行分块,默认64m一块,每块数据都对hadoop目录服务器请求,目录服务器选择一个数据机列表,返回给客户端,然后客户端就把数据写入第一台数据机,并且把列表传给数据机,当数据机接收到4k数据的时候,写入本地并且发起连接到下一台数据机,把这个4k传过去,形成一条流水线。当最后文件写完的时候,数据复制也同时完成,这个就是流水线处理的优势。
通讯协议
hadoop的通讯协议基本是在tcp/ip的基础上开发的,客户端使用ClientProtocol和目录服务器通讯,数据机使用DatanodeProtocol和目录服务器通讯,而目录服务器一般只是应答客户端和数据机的请求,不会主动发起通讯。
数据错误和异常
hadoop文件系统的主要目标就是在硬件出错的时候保证数据的完整性,它把磁盘错误作为肯定会出现的情况来对待,而不是异常。一般数据存储中出现的错误有几种,分别是目录服务器错误,数据机错误,和网络传输异常。
1、 数据机出错,每个数据机会定时发送一个心跳信息给目录服务器,表明自己仍然存活,网络异常可能会导致一部分数据机无法和目录服务器通讯,这时候目录服务器收不到心跳信息,就认为这个数据机已经死机,从有效io列表中清除,而该数据机上面的所有数据块也会标记为不可读。这个时候某些数据块的冗余份数有可能就低于它的冗余因子了,目录服务器会定期检查每一个数据块,看看它是否需要进行数据冗余复制。
2、 出现数据异常,由于网络传输和磁盘出错的原因,从数据机读取的数据有可能出现异常,客户端实现对数据块的校验,用md5和sha1进行校验,客户端在创建文件的时候,会对每一个文件块进行信息摘录,并把这些信息写入到同一个路径的隐藏文件里面。当客户端读取文件的时候,会先读取该信息文件,然后对每个读取的数据块进行校验,如果校验出错,客户端就会请求到另外一个数据机读取该文件块,并且报告给目录服务器这个文件块有错误,目录服务器就会定期检查,并且重新复制这个块。
3、 目录服务器出错,FsImage和Editlog是目录服务器上面两个最核心的数据结构,如果其中一个文件出错的话,会造成目录服务器不起作用,由于这两个文件如此重要,所以目录服务器上面可以设置多个备份文件和辅助服务器,当这两个文件有改变的时候,目录服务器就会发起同步操作,虽然这样增加了系统的负担,但是在目前这个架构上面为了实现数据的可靠性,这个同步操作是非常必要的。
Hadoop文件系统尚未实现的功能总结:
1、 文件追加写入,这个功能近期内不会实现,没有这个功能会引起当文件尚未关闭的时候,数据服务器死机或者目录服务器死机,会引起文件文件丢失,并且不可后续恢复写入。
2、 系统快照,一个全系统的快照功能,如果没有这个功能就不能实现文件系统的回滚操作。
3、 集群负载均衡,均衡策略暂时没有实现,有几个策略十分有用,比如在某台数据机可能磁盘过低的时候,把该数据机上面的一些数据转移到还有很多空间剩余的数据机上;当某个文件突然被大量读写的时候,动态增加该文件的冗余因子,并且数据块复制到更多的数据机上面,以提高读取性能。
4、 文件系统的用户权限,这个也是近期内不会实现的了。
5、访问权限,现在是无限制访问的,没有访问权限控制。
Hadoop文件系统性能分析
由于没办法建立大型的Hadoop文件系统,只能节选一些网上的性能分析,以表示一二。
1、 和Kosmos Filesystem的比较,Kosmos Filesystem也是一个类似Google 文件系统的具体实现,所以和Hadoop具有比较的意义。KFS是用c++编写的,在代码执行效率上面比java好不少。
数据插入测试:
测试环境:
• 1 1.8GHz Dual-core Opteron Processor 2210
• 4 GB RAM
• 4 7200 RPM SATA drives (mounted JBOD)
测试使用Hypertable,这也是一个类似Google bigtable的具体实现,可以使用KFS和HDFS作为文件系统,在插入测试后,表格含有75,274,825个数据单元,每一个键值是7字节大小,每一个数据是15字节大小。
测试结果:KFS基本大幅度胜出。
HDFS (no flush)
Elapsed time: 170.66 s
Avg value size: 15.25 bytes
Avg key size: 7.10 bytes
Throughput: 1792158.60 bytes/s
Total inserts: 14825279
Throughput: 86869.79 inserts/s
Elapsed time: 167.44 s
Avg value size: 15.26 bytes
Avg key size: 7.11 bytes
Throughput: 1871062.70 bytes/s
Total inserts: 15185349
Throughput: 90690.84 inserts/s
Elapsed time: 179.91 s
Avg value size: 15.20 bytes
Avg key size: 7.03 bytes
Throughput: 1737888.10 bytes/s
Total inserts: 15208310
Throughput: 84532.68 inserts/s
Elapsed time: 169.57 s
Avg value size: 15.22 bytes
Avg key size: 7.11 bytes
Throughput: 1831688.52 bytes/s
Total inserts: 15080926
Throughput: 88937.45 inserts/s
KFS (no flush)
Elapsed time: 125.51 s
Avg value size: 15.25 bytes
Avg key size: 7.10 bytes
Throughput: 2436864.83 bytes/s
Total inserts: 14825279
Throughput: 118120.09 inserts/s
Elapsed time: 126.25 s
Avg value size: 15.26 bytes
Avg key size: 7.11 bytes
Throughput: 2481447.59 bytes/s
Total inserts: 15185349
Throughput: 120276.33 inserts/s
Elapsed time: 135.51 s
Avg value size: 15.20 bytes
Avg key size: 7.03 bytes
Throughput: 2307335.26 bytes/s
Total inserts: 15208310
Throughput: 112231.19 inserts/s
Elapsed time: 127.66 s
Avg value size: 15.22 bytes
Avg key size: 7.11 bytes
Throughput: 2433069.68 bytes/s
Total inserts: 15080926
Throughput: 118137.45 inserts/s
2、 Hadoop读取测试,与本地文件系统比较
使用hadoop自带的FileBench程序,写入两个1g大小的文件,第一个是字节流文件,随机生成,第二个是字符文件,随机字典生成。下面是本地文件系统和hadoop文件系统的比较,由于集群是在极端条件下测试,目录服务器在广州网通机房,两台数据服务器一台在北京电信机房,一台在北京网通机房,所以测试的瓶颈基本在网络传输,估计在局域网中表现应该好很多。
本地文件系统测试:
java -classpath hadoop-0.16.4-test.jar:hadoop-0.16.5-dev-core.jar:lib/commons-logging-api-1.0.4.jar:lib/log4j-1.2.13.jar:lib/commons-logging-1.0.4.jar:lib/commons-cli-2.0-SNAPSHOT.jar org.apache.hadoop.io.FileBench -dir /home/ssmax/test -nolzo -nozip
DIR: file:/home/ssmax/test
W SEQ_PLN: 42 seconds
W TXT_PLN: 31 seconds
R SEQ_PLN: 25 seconds
R TXT_PLN: 21 seconds
第一行是流文件写入,第二行是文本文件写入,第三行是流文件读取,第四行是文本文件读取。
Hadoop文件系统测试:
java -classpath build/hadoop-0.16.5-dev-test.jar:hadoop-0.16.5-dev-core.jar:lib/commons-logging-api-1.0.4.jar:lib/log4j-1.2.13.jar:lib/commons-logging-1.0.4.jar:lib/commons-cli-2.0-SNAPSHOT.jar org.apache.hadoop.io.FileBench -dir "hdfs://218.107.63.238:9000/user/ssmax" -now -nolzo -nozip
DIR: hdfs://218.107.63.238:9000/user/ssmax
W SEQ_PLN: 437 seconds
W TXT_PLN: 439 seconds
R SEQ_PLN: > 15分钟
R TXT_PLN: > 15 分钟
由于测试客户端上行比下行快很多,所以读取的时候很慢,超过了可以接受的时间,如果在数据机做读操作,读取速度会大大提高。
java -classpath hadoop-0.16.5-dev-test.jar:hadoop-0.16.5-dev-core.jar:lib/commons-logging-api-1.0.4.jar:lib/log4j-1.2.13.jar:lib/commons-logging-1.0.4.jar:lib/commons-cli-2.0-SNAPSHOT.jar org.apache.hadoop.io.FileBench -dir "hdfs://218.107.63.238:9000/user/ssmax" -now -nolzo -nozip DIR: hdfs://218.107.63.238:9000/user/ssmax
R SEQ_PLN: 80 seconds
R TXT_PLN: 63 seconds
所以得出结论就是rack id的配置十分重要,需要区分机柜,传输的瓶颈主要在网络。
上面就是关于Hadoop文件系统的原理和测试,Hbase可以通过配置使用本地文件系统或者Hadoop文件系统。而测试的过程中也发现了一个更成熟的组合,也是开源项目的Hypertable和KFS,这两个也是类似Bigtable和GFS的实现,主要是使用c++实现的,这里先记录一下,以后再做研究。
Hypertable作者语:Hypertable与HBase的差别是,Hypertable是Bigtable的一个更高性能的实现(InfoQ同样采访了HBase的团队)。我开始的时候跟Jim Kellerman以及Hadoop团队的一些成员一起为HBase工作。但我们对HBase应该变成什么样子有不同意见,对实现语言的选择也有不同意见。他们坚持用Java,而我力推C++。于是我就分出来,开始了Hypertable项目。
Hbase分布式数据库
数据模型
Hbase是一个类似Bigtable的分布式数据库,大部分特性和Bigtable一样,是一个稀疏的,长期存储的{存在硬盘上},多维度的,排序的映射表。这张表的索引是行关键字,列关键字和时间戳。每个值是一个不解释的字符数组,数据都是字符串,没类型。
用户在表格中存储数据,每一行都有一个可排序的主键和任意多的列。由于是稀疏存储的,所以同一张表里面的每一行数据都可以有截然不同的列。
列名字的格式是"<family>:<label>",都是由字符串组成,每一张表有一个family集合,这个集合是固定不变的,相当于表的结构,只能通过改变表结构来改变。但是label值相对于每一行来说都是可以改变的。
Hbase把同一个family里面的数据存储在同一个目录底下,而Hbase的写操作是锁行的,每一行都是一个原子元素,都可以加锁。
所有数据库的更新都有一个时间戳标记,每个更新都是一个新的版本,而hbase会保留一定数量的版本,这个值是可以设定的。客户端可以选择获取距离某个时间最近的版本,或者一次获取所有版本。
概念视图:
一个表可以想象成一个大的映射关系,通过主键,或者主键+时间戳,可以定位一行数据,由于是稀疏数据,所以某些列可以是空白的,下面就是数据的概念视图:
Row Key Time Stamp Column "contents:" Column "anchor:" Column "mime:"
"com.cnn.www" t9 "anchor:cnnsi.com" "CNN"
t8 "anchor:my.look.ca" "CNN.com"
t6 "<html>..." "text/html"
t5 "<html>..."
t3 "<html>..."
上图是一个存储Web网页的范例列表片断。行名是一个反向URL{即com.cnn.www}。contents列族{原文用 family,译为族,详见列族}存放网页内容,anchor列族存放引用该网页的锚链接文本。CNN的主页被Sports Illustrater{即所谓SI,CNN的王牌体育节目}和MY-look的主页引用,因此该行包含了名叫“anchor:cnnsi.com”和 “anchhor:my.look.ca”的列。每个锚链接只有一个版本{由时间戳标识,如t9,t8};而contents列则有三个版本,分别由时间 戳t3,t5,和t6标识。
物理视图
虽然从概念视图来看每个表格是由很多行组成,但是在物理存储上面,它是按照列来保存的,这点在数据设计和程序开发的时候必须牢记。
上面的概念视图在物理存储的时候应该表现成下面那样子:
Row Key Time Stamp Column "contents:"
"com.cnn.www" t6 "<html>..."
t5 "<html>..."
t3 "<html>..."
Row Key Time Stamp Column "anchor:"
"com.cnn.www" t9 "anchor:cnnsi.com" "CNN"
t8 "anchor:my.look.ca" "CNN.com"
Row Key Time Stamp Column "mime:"
"com.cnn.www" t6 "text/html"
需要注意的是在概念视图上面有些列是空白的,这样的列实际上并不会被存储,当请求这些空白的单元格的时候,会返回null值。
如果在查询的时候不提供时间戳,那么会返回距离现在最近的那一个版本的数据。因为在存储的时候,数据会按照时间戳排序。
例子:
一个程序写9行数据,row[0-9],先写入 anchor:foo列,再写入 anchor:bar 列,最后重复写入 anchor:foo列,由于是同一个列族,写到同一个映射文件里面,最后写到文件里面是这个样子的:
row=row0, column=anchor:bar, timestamp=1174184619081
row=row0, column=anchor:foo, timestamp=1174184620720
row=row0, column=anchor:foo, timestamp=1174184617161
row=row1, column=anchor:bar, timestamp=1174184619081
row=row1, column=anchor:foo, timestamp=1174184620721
row=row1, column=anchor:foo, timestamp=1174184617167
row=row2, column=anchor:bar, timestamp=1174184619081
row=row2, column=anchor:foo, timestamp=1174184620724
row=row2, column=anchor:foo, timestamp=1174184617167
row=row3, column=anchor:bar, timestamp=1174184619081
row=row3, column=anchor:foo, timestamp=1174184620724
row=row3, column=anchor:foo, timestamp=1174184617168
row=row4, column=anchor:bar, timestamp=1174184619081
row=row4, column=anchor:foo, timestamp=1174184620724
row=row4, column=anchor:foo, timestamp=1174184617168
row=row5, column=anchor:bar, timestamp=1174184619082
row=row5, column=anchor:foo, timestamp=1174184620725
row=row5, column=anchor:foo, timestamp=1174184617168
row=row6, column=anchor:bar, timestamp=1174184619082
row=row6, column=anchor:foo, timestamp=1174184620725
row=row6, column=anchor:foo, timestamp=1174184617168
row=row7, column=anchor:bar, timestamp=1174184619082
row=row7, column=anchor:foo, timestamp=1174184620725
row=row7, column=anchor:foo, timestamp=1174184617168
row=row8, column=anchor:bar, timestamp=1174184619082
row=row8, column=anchor:foo, timestamp=1174184620725
row=row8, column=anchor:foo, timestamp=1174184617169
row=row9, column=anchor:bar, timestamp=1174184619083
row=row9, column=anchor:foo, timestamp=1174184620725
row=row9, column=anchor:foo, timestamp=1174184617169
其中anchor:foo被保存了两次,由于时间戳不同,是两个不同的版本,而最新的数据排在前面,所以最新那次更新会先被找到。
分布式数据库体系结构
Hbase的服务器体系结构也是遵从简单的主从服务器架构,又Hregion服务器群和HBase Master主服务器构成。
Hregion服务器
对用户来说,每个表是一堆数据的集合,靠主键来区分。物理上,一张表是被拆分成多块,每一块就称呼为一个Hregion。用表名+开始/结束主键,来区分一个Hregion,一个Hregion会保存一个表里面某段连续的数据,从开始主键到结束主键,一张完整的表格是保存在多个Hregion上面的。
所有的数据库数据一般是保存在Hadoop分布式文件系统上面,用户通过一系列Hregion服务器获取这些数据,一般一台机器上面运行一个Hregion服务器,而每一个区段Hregion只会被一个Hregion服务器维护。
当用户需要更新数据的时候,他会被分配到对应的Hregion 服务器提交修改,这些修改先是被写到Hmemcache 缓存和 服务器的Hlog文件里面,Hmemcache是在内存中的缓存,保存最近更新的数据,Hlog是磁盘上面的记录文件,它记录着所有的更新操作,当操作写入Hlog之后,commit()调用才会返回给客户端。
当读取数据的时候,Hregion服务器会先访问Hmemcache缓存,如果缓存里面没有该数据,才回到Hstores磁盘上面寻找,每一个列族都会有一个Hstore集合,每个Hstore集合包含很多HstoreFiles具体文件,这些文件都是B树结构的,方便快速读取。
系统会定期调用HRegion.flushcache() 把缓存里面的内容写到文件中,一般这样会增加一个新的HstoreFile文件,而此时高速缓存就会被清空,并且写入一个标记到Hlog,表示上面的内容已经被写入到文件中保存。
在启动的时候,每个Hregion服务器都会检查自己的Hlog 文件,看看最近一次执行flushcache之后有没有新的更新写入操作。如果没有更新,就表示所有数据都已经更新到文件中了;如果有更新,服务器就会先把这些更新写入高速缓存,然后调用flushcache写入到文件。最后服务器会删除旧的Hlog文件,并开始给用户访问数据。
因此,为了节省时间可以很少调用flushcache,但是这样会增加内存占用,而且在服务器重启的时候会延长很多时间。如果可以定期调用flushcache,缓存大小会控制在一个较低的水平,而且Hlog文件也会很快地重构,但是调用flushcache的时候会造成系统负载瞬间增加。
Hlog会被定期回滚,回滚的时候是按照时间备份文件,每当回滚的时候,系统会删除那些已经被写到文件的更新,回滚Hlog只会占用很少的时间,建议经常回滚以减少文件尺寸。
每一次调用flushcache会生成一个新的HstoreFile文件,从一个Hstore里面获取一个值都需要访问所有的HstoreFile文件,这样十分耗时,所以我们要定期把这些分散的文件合并到一个大文件里面,HStore.compact()就可以完成这样的工作。这样的合并工作是十分占用资源的,当HstoreFile文件的数量超过一个设定值的时候才会触发。
Google的Bigtable有高级合并和低级合并的区别,但是Hbase没有这个概念,只要记住下面两点就可以了:
1、 flushcache会建立一个新的HstoreFile文件,并把缓存中所有需要更新的数据写到文件里面,flushcache之后,log的重建次数会清零。
2、 compact会把所有HstoreFile文件合并成一个大文件。
3、 和Bigtable不同的是,Hbase每个更新如果是被正确提交了,commit没有返回错误的话,它就一定是被写到记录文件里面了,这样不会造成数据丢失。
两个Hregion可以通过调用HRegion.closeAndMerge()合并成一个新的Hregion,当前版本这个操作是需要两台Hregion都停机才能操作。
当一个Hregion变得太过巨大的时候,超过了设定的阈值,HRegion服务器会调用HRegion.closeAndSplit(),这个Hregion会被拆分为两个,并且报告给主服务器让它决定由哪个Hregion服务器来存放新的Hregion。这个拆分过程是十分迅速的,因为两个新的Hregion最初只是保留原来HregionFile文件的引用,而这个时候旧的Hregion会处于停止服务的状态,当新的Hregion合并完成并且把引用删除了以后,旧的Hregion才会删除。
最后总结几点:
1、 客户端以表格的形式读取数据
2、 一张表是被划分成多个Hregion区域
3、 Hregion是被Hregion服务器管理的,当客户端需要访问某行数据的时候,需要访问对应的Hregion服务器。
4、 Hregions服务器里面有三种方式保存数据:
A、 Hmemcache高速缓存,保留是最新写入的数据
B、 Hlog记录文件,保留的是提交成功了,但未被写入文件的数据
C、 Hstores文件,数据的物理存放形式。
Hbase主服务器
每个Hregion服务器都会和Hmaster服务器通讯,Hmaster的主要任务就是要告诉每个Hregion服务器它要维护哪些Hregion。
Hmaster服务器会和每个Hregion服务器保持一个长连接。如果这个连接超时或者断开,会导致:
A、 Hregion服务器自动重启。
B、 Hmaster认为Hregion已经死机,同时把它负责的Hregion分配到其它Hregion服务器。
和Google的Bigtable不同的是,当Bigtable的TabletServer和主服务器通讯中断的情况下,它仍然能提供服务。而Hbase不能这么做,因为Hbase没有Bigtable那样额外的加锁系统,Bigtable是由主服务器管理TabletServer,同时加锁服务器提供数据访问的,而Hbase只有唯一一个接入点,就是Hmaster服务器。
当一个新的Hregion服务器登陆到Hmaster服务器,Hmaster会告诉它先等待分配数据。而当一个Hregion死机的时候,Hmaster会把它负责的Hregion标记为未分配,然后把它们分配到其它Hregion服务器。
元数据表
之前我们说过Hregion是按照表名和主键范围区分的,由于主键范围是连续的,所以一般用开始主键就可以表达出来。
但是如果只要开始主键还是不够的,因为我们有合并和分割操作,如果正好在执行这些操作的过程中出现死机,那么就可能存在多份表名和开始主键一样的数据,这个就要通过Hbase的元数据信息来区分哪一份才是正确的数据文件了,为了区分这样的情况,每个Hregion都有一个'regionId'来标识它的唯一性。
所以一个Hregion的表达符最后是 表名+开始主键+唯一id(tablename + startkey + regionId)
举个例子:hbaserepository,w-nk5YNZ8TBb2uWFIRJo7V==,6890601455914043877
我们可以用这个识别符来区分不同的Hregion,这些数据就称呼为元数据,而元数据本身也是被保存在Hregion里面的,我们称呼这个表为元数据表,里面保存的就是Hregion标识符和实际Hregion服务器的映射关系。
元数据表本身也会增长,并且可能被分割为几个Hregion,为了定位这些Hregion,有一个根数据表(ROOT table),保存了所有元数据表的位置,而根数据表是不能被分割的,永远之存在一个Hregion。
在Hbase启动的时候,主服务器先去扫描根数据表,因为这个表只会有一个Hregion,所以这个Hregion的名字是被写死的。当然要把根数据表分配到一个Hregion服务器需要一定的时间。
当根数据表被分配好之后,主服务器就会去扫描根数据表,获取元数据表的名字和位置,然后把元数据表分配到不同的Hregion服务器。
最后就是扫描元数据表,找到所有Hregion区域的信息,然后把它们分配给不同的Hregion服务器。
主服务器在内存中保存着当前活跃的Hregion服务器的数据,因此如果主服务器死机的话,整个系统也就无法访问了,而服务器的信息也没有必要保存到文件里面。
元数据表和根数据表的每一行都包含一个列族,info列族:
1. info:regioninfo 包含了一个串行化的HregionInfo对象。
2. info:server 保存了一个字符串,是服务器地址HServerAddress.toString()
3. info:startcode 一个长整型的数字的字符串,是Hregion服务器启动的时候传给主服务器的,让主服务器决定这个Hregion服务器的信息有没有更改。
因此,当一个客户端拿到根数据表地址以后,就没有必要再连接主服务器了。主服务器的负载相对就小了很多,它只会处理超时的Hregion服务器,在启动的时候扫描根数据表和元数据表,和返回根数据表的Hregion服务器地址。
因此Hbase的客户端是十分复杂的,它经常需要浏览元数据表和根数据表,在查询表格的时候,如果一个Hregion服务器死机或者它上面的数据更改了,客户端就会继续重试,客户端保留的映射关系并不会一直正确的。这里的机制还需要进一步完善。
总结:
1、 Hregion服务器提供Hregion访问,一个Hregion只会保存在一个Hregion服务器上面。
2、 Hregion会注册到主服务器上面。
3、 如果主服务器死机,那么整个系统都会无效。
4、 当前的Hregion服务器列表只有主服务器知道。
5、 Hregion区域和Hregion服务器的对应关系保存在两个特别的Hregion里面,它们像其它Hregion一样被分配到不同的服务器。
6、 根数据表是最特别的一个表,主服务器永远知道它的位置(在程序中写死)
7、 客户端需要自己浏览这些表,来找到数据在哪里。
Hbase和传统关系数据库的对比分析
Hbase是大大不同于以前的关系数据库,它是按照Bigtable来开发的,套用一个Bigtable的定义就是:
A Bigtable is a sparse, distributed, persistent multidimensional sorted map.
Bigtable是一个稀疏的,分布的,持续多维度的排序映射数组。
Hbase就是这样一个基于列模式的映射数据库,它只能表示很简单的键-数据的映射关系,它大大简化了传统的关系数据库。
1、 数据类型,Hbase只有简单的字符串类型,所有类型都是交由用户自己处理,它只保存字符串。而关系数据库有丰富的类型选择和存储方式。
2、 数据操作,Hbase操作只有很简单的插入、查询、删除、清空等,表和表之间是分离的,没有复杂的表和表之间的关系,所以也不能也没有必要实现表和表之间的关联等操作。而传统的关系数据通常有各种各样的函数、连接操作。
Hbase的操作列表:
alter Alter column family schema; pass table name and a dictionary
specifying new column family schema. Dictionaries are described
below in the GENERAL NOTES section. Dictionary must include name
of column family to alter. For example, to change the 'f1' column
family in table 't1' from defaults to instead keep a maximum of 5
cell VERSIONS, do:
hbase> alter 't1', {NAME => 'f1', VERSIONS => 5}
count Count the number of rows in a table. This operation may take a LONG
time (Run '$HADOOP_HOME/bin/hadoop jar hbase.jar rowcount' to run a
counting mapreduce job). Current count is shown every 1000 rows by
default. Count interval may be optionally specified. Examples:
hbase> count 't1'
hbase> count 't1', 100000
create Create table; pass table name, a dictionary of specifications per
column family, and optionally a dictionary of table configuration.
Dictionaries are described below in the GENERAL NOTES section.
Examples:
hbase> create 't1', {NAME => 'f1', VERSIONS => 5}
hbase> create 't1', {NAME => 'f1'}, {NAME => 'f2'}, {NAME => 'f3'}
hbase> # The above in shorthand would be the following:
hbase> create 't1', 'f1', 'f2', 'f3'
hbase> create 't1', {NAME => 'f1', VERSIONS => 1, TTL => 2592000, \
BLOCKCACHE => true}
describe Describe the named table: e.g. "hbase> describe 't1'"
delete Put a delete cell value at specified table/row/column and optionally
timestamp coordinates. Deletes must match the deleted cell's
coordinates exactly. When scanning, a delete cell suppresses older
versions. Takes arguments like the 'put' command described below
deleteall Delete all cells in a given row; pass a table name, row, and optionally
a column and timestamp
disable Disable the named table: e.g. "hbase> disable 't1'"
drop Drop the named table. Table must first be disabled
enable Enable the named table
exists Does the named table exist? e.g. "hbase> exists 't1'"
exit Type "hbase> exit" to leave the HBase Shell
get Get row or cell contents; pass table name, row, and optionally
a dictionary of column(s), timestamp and versions. Examples:
hbase> get 't1', 'r1'
hbase> get 't1', 'r1', {COLUMN => 'c1'}
hbase> get 't1', 'r1', {COLUMN => ['c1', 'c2', 'c3']}
hbase> get 't1', 'r1', {COLUMN => 'c1', TIMESTAMP => ts1}
hbase> get 't1', 'r1', {COLUMN => 'c1', TIMESTAMP => ts1, VERSIONS = 4}
list List all tables in hbase
put Put a cell 'value' at specified table/row/column and optionally
timestamp coordinates. To put a cell value into table 't1' at
row 'r1' under column 'c1' marked with the time 'ts1', do:
hbase> put 't1', 'r1', 'c1', 'value', ts1
scan Scan a table; pass table name and optionally an array of column
names OR an array of column names AND a dictionary of scanner
specifications. If you wish to include scanner specifications,
you must also include an array of columns. Scanner specifications
may include one or more of the following: LIMIT, STARTROW, STOPROW,
or TIMESTAMP. To scan all members of a column family, leave the
qualifier empty as in 'col_family:'. Examples:
hbase> scan '.META.'
hbase> scan '.META.', ['info:regioninfo']
hbase> scan 't1', ['c1', 'c2'], {LIMIT => 10, STARTROW => 'xyz'}
version Output this HBase version
3、 存储模式,Hbase是基于列存储的,每个列族都有几个文件保存,不同列族的文件是分离的。传统的关系数据库是基于表格结构和行模式保存的。
4、 数据维护,Hbase的更新正确来说应该不叫更新,而且一个主键或者列对应的新的版本,而它旧有的版本仍然会保留,所以它实际上是插入了新的数据,而不是传统关系数据库里面的替换修改。
5、 可伸缩性,Hbase和Bigtable这类分布式数据库就是直接为了这个目的开发出来的,能够轻易的增加或者减少(在硬件错误的时候)硬件数量,而且对错误的兼容性比较高。而传统的关系数据库通常需要增加中间层才能实现类似的功能。
当前的关系数据库基本都是从上世纪70年代发展而来的,它们基本都有一下的体系特点:
1、 面向磁盘存储和索引结构
2、 多线程访问
3、 基于锁的同步访问机制
4、 基于log记录的恢复机制
而Bigtable和Hbase之类基于列模式的分布式数据库,更适应海量存储和互联网应用的需求,灵活的分布式架构可以使其利用廉价的硬件设备就组建一个大的数据仓库,而互联网应用就是以字符为基础的,Bigtable和Hbase就针对这些应用而开发出来的数据库。
由于其中的时间戳特性,Bigtable和Hbase与生俱来就特别适合于开发wiki、archiveorg之类的服务,而Hbase直接就是作为一个搜索引擎的一部分被开发出来的。
Bigtable的应用案例:
Google各个产品应用里面的大表:
1、 Google Analytics 网站流量分析(analytics.google.com)
这个服务主要提供给网站管理员两个数据,一个就是独立访问者的数量(cookie判定),另外一个就是页面浏览量(PageView),网站管理员只要在每一个需要统计的页面加上google提供javascript代码,就可以每天在后台看到相关的统计信息了。
这个服务的数据保存主要由两个打表实现:
第一个是原始点击表,记录了用户点击页面的原始数据,这个表的列包括:网站名称,url和用户点击时间、ip等资料,按用户点击时间排序,大小控制在200TB左右,定期需要做压缩备份等操作。
第二个表就是统计数据表,这个表是从原始点击表中计算而来,定期运行批量计算任务生成数据(使用Map/Reduce程序),这个表大概在20TB左右。
2、 Google Earth 地图(maps.google.com)
这个服务包括网页版的google地图和客户端版的google地球。用户通过这些服务,能选择不同的分辨率浏览地图、卫星照片等数据。
这个系统主要包括一个数据处理表,和一系列的数据服务表(用户读取时候用)。
原始的图片信息通过程序批量输入到数据处理表,形成格式化数据。这个数据处理表的每一行表示了物理地图上面的每一块,而键值的命名确保这些地理块是连续的,由于地理的信息很多,所以有很多列族,基本上每个列族都有图片数据,多列族确保数据是稀疏的,单个存储文件不会太大。后台处理程序定期处理这些数据,把它们整理并录入数据服务表,并清空处理过的原始数据。
数据服务表主要由一个索引表和数个数据表组成,索引表保证了用户请求数据的时候不需要遍历所有数据表。
3、 网络历史记录(www.google.com/psearch)
主要功能:
• 查看并搜索您过去曾访问过的网页,包括 Google 的搜索记录。
• 查找有关网络活动的搜索趋势,如最常访问的网站和热门搜索等。
• 根据您搜索的内容以及曾访问过的网站,获取更具个性化的搜索结果。
这个服务中的网络历史记录是需要安装Google工具栏并在浏览器中启用才能搜集数据。
这个服务把每个用户的数据保存在同一个大表里面,每个用户有一个唯一的用户id,而每种类型的操作(搜索关键字、浏览网页等)都有一个不同列族,用户搜索记录是通过后台程序从搜索引擎端批量生成并插入的,而网页浏览记录是通过用户的Google工具栏定期上传数据并插入的。
这个服务一开始是设计成在客户端保存个人数据备份的方式,最新改进了以后是按照不同地区的用户再建立多个大表集群,让用户可以就近访问,加快传输速度。
为了保证用户之间的共享不会占用太多资源,我们为每个用户加上了简单的配额机制,分别在客户端和大表集群上面实现。
结论:
Hbase就是一个分布式存储的简单键值对应的表。
因为Hbase的结构与传统关系数据库大为不同,所以到目前为止,基于它的应用都是直接从头开始就使用Hbase设计的,还没有见到过从关系数据库转移到Hbase的应用,而且它们之间的api差距太大,而Hbase基于JDBC的驱动开发还是遥遥无期。
而且最重要的一点是当前hbase还是处于开发阶段,所有api都没有稳定,比如服务器上面装的时候是hbase-0.1.3,一个月后版本发展到hbase-0.2.0,所有API基本都改变了,连HQL的语法都重新定义了一次,而且升级hbase要同时升级hadoop,这样升级所需要的维护工作量太大。
当然,据闻Hbase和Hypertable的目的就是在web应用市场上面取代Mysql。
数据库读写性能分析
1、 单机模拟集群测试
测试环境:
由3台服务器组成的hadoop集群组成分布式文件系统
由一台单独的机器单机模拟Hbase集群
由一台机器单机测试Mysql
测试规模:50万条记录以上,单线程、多线程测试
测试结果:
HBase Mysql
单线程 插入 100 条记录 155 ms / 154ms 243 ms / 198ms
插入 1000 条记录 740 ms / 884ms 1506 ms / 1554ms
插入 10000 条记录 8304ms/ 6610ms 14110ms/ 12839m
插入 100000 条记录 43090ms /64715ms 108082ms /110664ms
读取 500000左右的条记录 640 ms / 721ms 2779 ms / 2794ms?
100线程 插入 100 条记录 5929ms / 3825ms / 4134ms 15352ms / 12912ms / 12853ms
插入 1000 条记录 35684ms / 52677ms / 34208ms 135839ms / 161711ms / 119909ms
读取 500000左右的条记录 最快的 1104 ms/最慢的 110897 ms 如果不加limit,数据库连接超时
1000线程 插入 100 条记录 325907ms / 322465ms / 342163ms 17455ms / 18953ms / 15169ms
读取500000左右的条记录 最快的 717 ms 如果不加limit,数据库连接超时
HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库.另一个不同的是HBase基于列的而不是基于行的模式(字段内容都是char).
上面两种 特性,导致HBase的数据表结构非常松散,字段内容单一,表与表之前没有任何关联,正因为这样在查询的时候效率非常高
HBase数据表的性能选项:
MAX_VERSIONS:每一个单元保存多少版本的数据(默认是3)
MAX_LENGTH:每个单元中的版本能够保存多少字节的数据(默认字节数是32位有符号整数最大值)
COMPRESSION:数据压缩,有BLOCK压缩和RECORD压缩
IN_MEMORY:将这个列组装载数据到内存,加快读写速度,缺点耗费内存和干预HDFS的备份
BLOOMFILTER:如果这个列组支持布隆过滤器(BLOOMFILTER),那么在内存中有个索引来快速地判断要查找的列是否存在这个行中,减少磁盘IO操作.如
果在这个列组你拥有大量的列,每一个列的数据包含的数据非常小,你可能需要在这个列组中应用布隆过滤器(BLOOMFILTER)
2、 列族测试:
测试目标:测试列族增长对性能的影响
测试数据:建表时候定义列族数量,每个列族写入1000字节数据,读取5000次,随机读取任意一列。
测试结果:
单机集群
列族数量 10 100 500 1000
建表时间 12.3 19.2 45.9 Timeout
每秒写入 164 323 419 Timeout
每秒读取 99 139 122 Timeout
5机器集群
列族数量 10 100 500 1000
建表时间 12.2 18.7 46.4 Timeout
每秒写入 29 153 376 Timeout
每秒读取 119 111 120 Timeout
测试结论:
Hbase建表时间过长,对大列族的时候支持不好
写入速度在多机集群的时候提高较快
3、 排序测试
测试目标:Hbase的行排序是根据主键排序,测试动态或者反序插入时候的性能。
测试数据:动态生成字母数据,zzzzz-aaaaa,还有随机插入
测试结果:
单机集群(每秒多少行)
写入行 10,000 100,000 1,000,000
顺序 485 432 334
反序 451 477 354
随机 462 421 334
5机集群(每秒多少行)
写入行 10,000 100,000 1,000,000
顺序 488 440 346
反序 522 387 343
随机 468 441 370
测试结论:
采用B树存储和写入缓存,写入数量和顺序对速度影响并不大,应该只是cpu占用的不同。
主要瓶颈还是在网络传输速度上。
4、 随机读写测试
测试目标:测试大表下的读取性能
测试数据:在一定规模的表中随机读取5000条记录的时间
测试数据:
单机集群(每秒多少行)
规模(行) 10,000 100,000 1,000,000
读取 95 42 24
5机集群(每秒多少行)
规模(行) 10,000 100,000 1,000,000
读取 95 34 35
测试结论:
读取速度随着表规模的增加而降低,集群模式下面读取表现比较优秀。
估计如果加上数据合并以后集群的读取能力会更加强劲。
总结:
Hypertable和Hbase尚在开发阶段,还不适合产品化,暂时没有对应的产品案例,但是照这个方向发展下去在web应用端有很好的前景。
Hadoop文件系统相对较稳定,可以考虑在新产品中试验。
参考文献:
The Hadoop Distributed File System: Architecture and Design
http://hadoop.apache.org/core/docs/r0.16.4/hdfs_design.html
HDFS Under the Hood Presentation 1
http://assets.en.oreilly.com/1/event/12/HDFS%20Under%20the%20Hood%20Presentation%201.pdf
Kosmos File System (KFS) is a New High End Google File System Option
http://highscalability.com/kosmos-file-system-kfs-new-high-end-google-file-system-option
Hadoop HBase Performance Evaluation Introduction
http://www.cs.duke.edu/~kcd/hadoop/kcd-hadoop-report.pdf
Hbase/HbaseArchitecture
http://wiki.apache.org/hadoop/Hbase/HbaseArchitecture
Hbase/DataModel
http://wiki.apache.org/hadoop/Hbase/DataModel
Understanding HBase and BigTable
http://jimbojw.com/wiki/index.php?title=Understanding_Hbase_and_BigTable
The End of an Architectural Era (It's Time for a Complete Rewrite)
http://www.vldb.org/conf/2007/papers/industrial/p1150-stonebraker.pdf
HBase Leads Discuss Hadoop, BigTable and Distributed Databases
http://www.infoq.com/news/2008/04/hbase-interview
Validating the Real-time Performance of Hbase
http://wiki.apache.org/hadoop/Hbase/HbaseRTDS
Hadoop Summit and Data-Intensive Computing Symposium Videos and Slides
http://research.yahoo.com/node/2104
Google Datastore and the shift from a RDBMS
http://groovie.org/2008/04/13/google-datastore-and-the-shift-from-a-rdbms
One Size Fits All? - Part 2: Benchmarking Results
http://nms.csail.mit.edu/~stavros/pubs/osfa.pdf
Bigtable: A Distributed Storage System for Structured Data
http://labs.google.com/papers/bigtable-osdi06.pdf
Hypertable领导者:Hadoop和分布式数据库
http://www.builder.com.cn/2008/0506/847804.shtml
Hbase是一个分布式开源数据库,基于Hadoop分布式文件系统,模仿并提供了基于Google文件系统的Bigtable数据库的所有功能。
Hbaes的目标是处理非常庞大的表,可以用普通的计算机处理超过10亿行数据,并且有数百万列元素组成的数据表。
Hbase可以直接使用本地文件系统或者Hadoop作为数据存储方式,不过为了提高数据可靠性和系统的健壮性,发挥Hbase处理大数据量等功能,需要使用Hadoop作为文件系统,那么我们就先要了解Hadoop文件系统的基本特性和原理,才能更好地理解Hbase的工作方式。
Hadoop文件系统
Hadoop文件系统是一个能够兼容普通硬件环境的分布式文件系统, 和现有的分布式文件系统不同的地方是Hadoop更注重容错性和兼容廉价的硬件设备,这样做是为了用很小的预算甚至直接利用现有机器就实现大流量和大数据量的读取。
Hadoop 使用了POSIX的设计来实现对文件系统文件流的读取。HDFS(Hadoop FileSystem)原来是Apache Nutch搜索引擎(从Lucene发展而来)开发的一个部分,后来独立出来作为一个Apache子项目。
Hadoop的假设与目标
1、 硬件出错,Hadoop假设硬件出错是一种正常的情况,而不是异常,为的就是在硬件出错的情况下尽量保证数据完整性,HDFS设计的目标是在成百上千台服务器中存储数据,并且可以快速检测出硬件错误和快速进行数据的自动恢复。
2、 流数据读写,不同于普通的文件系统,Hadoop是为了程序批量处理数据而设计的,而不是与用户的交互或者随机读写,所以POSIX对程序增加了许多硬性限制,程序必须使用流读取来提高数据吞吐率。
3、 大数据集,HDFS上面一个典型的文件一般是用GB或者TB计算的,而且一个数百台机器组成的集群里面可以支持过千万这样的文件。
4、 简单的文件模型,HDFS上面的文件模型十分简单,就是一次写入多次读取的模型,文件一旦创建,写入并关闭了,之后就再也不会被改变了,只能被读取,这种模型刚好符合搜索引擎的需求,以后可能会实现追加写入数据这样的功能。
5、 强大的跨平台兼容性,由于是基于java的实现,无论是硬件平台或者是软件平台要求都不高,只要是jdk支持的平台都可以兼容。
Hadoop体系结构
目录节点(NameNode)和数据节点(DataNodes)
Hadoop文件系统是主从架构,一个Hadoop文件系统由唯一一个目录节点和数个数据节点组成。
Hadoop文件系统对外表现为一个普通的文件系统,用户可以用文件名去存储和访问文件,而实际上文件是被分成不同的数据块,这些数据块就是存储在数据节点上面。
目录节点是集群里面的主节点,负责文件名的维护管理,也是客户端访问文件的入口。文件名的维护包括文件和目录的创建、删除、重命名等。同时也管理数据块和数据节点的映射关系,客户端需要访问目录节点才能知道一个文件的所有数据块都保存在哪些数据节点上。
数据节点一般就是集群里面的一台机器,负责数据的存储和读取。在写入时,由目录节点分配数据块的保存,然后客户端直接写到对应的数据节点。在读取时,当客户端从目录节点获得数据块的映射关系后,就会直接到对应的数据节点读取数据。数据节点也要根据目录节点的命令创建、删除数据块,和冗余复制。
一个典型的Hadoop文件系统集群部署,是由一台性能较好的机器运行目录节点,而集群里面的其它机器每台上面运行一个数据节点。当然一个机器可以运行任意多个数据节点,甚至目录节点和数据节点一起运行,不过这种模式在正式的应用部署中很少使用。
唯一的目录节点的设计大大简化了整个体系结构,目录节点负责Hadoop文件系统里面所有元数据的仲裁和存储。这样的设计使数据不会脱离目录节点的控制。
Hadoop文件系统命名空间
Hadoop文件系统使用的是传统的分级文件体系,客户端程序可以创建目录并且在目录里面保存文件,类似与现在一般的文件系统。Hadoop允许用户创建、删除文件,在目录间转移文件,重命名文件等,但是还没有实现磁盘配额和文件访问权限等功能,也不支持文件的硬连接和软连接(快捷方式),这些功能在短期内不会实现。
目录节点负责存储和管理整个文件系统的命名空间,应用程序可以指定某一个文件需要在Hadoop文件系统中冗余多少份,这个在Hadoop中称为冗余因素,保存在目录节点里面。
Hadoop存储原理
冗余数据保存
Hadoop文件系统是为了大文件的可靠保存而设计的,一个文件被划分成一连串的数据块,除了文件的最后一块以外其它所有的数据块都是固定大小的,为了数据容错性,每一个数据块都会被冗余存储起来,而每个文件的块大小和冗余因素都是可以设置的,程序可以设置文件的数据块要被复制多少份,而且这个冗余因素除了可以在创建的时候指定,还可以在之后改变。在Hadoop文件系统里面文件只会被写入一次,并且任何时间只会有一个程序在写入这个文件。
目录节点是根据数据块的冗余状况来作出处理决策的,数据节点会定期发送一个存在信号(Heartbeat)和数据块列表给目录节点,存在信号使目录节点认为该数据节点还是有效的,而数据块列表包括了该数据节点上面的所有数据块编号。
数据存取策略
复制策略是hadoop文件系统最核心的部分,对读写性能影响很大,hadoop和其它分布式文件系统的最大区别就是可以调整冗余数据的位置,这个特性需要很多时间去优化和调整。
一、数据存放
目前hadoop采用以机柜为基础的数据存放策略,这样做的目的是提高数据可靠性和充分利用网络带宽。当前具体实现了的策略只是这个方向的尝试,hadoop短期的研究目标之一就是在实际产品环境中观察系统读写的行为,测试性能和研究更深入的规则。
一个大的hadoop集群经常横跨多个机柜,而不同机柜之间的数据通讯同经过交换机或者路由,所以同一个机柜中不同机器的通讯带宽是比不同机柜之间机器通讯时候的大。
Hadoop提供了一个api来决定数据机所属的机柜id,当文件系统启动的时候,数据机就把自己所属的机柜id发给目录机,然后目录机管理这些分组。
Hadoop默认是每个数据机都是在不同的机柜上面,这种方法没有做任何性能优化,但是也有不少优点:
1、 数据可靠性是最高的。因为这样可以防止机柜出错的时候数据丢失。
2、 在读取数据的时候充分利用不同机柜之间的带宽。
3、 而且这个策略可以很容易的完成负载平衡和错误处理。
缺点就是写入数据的时候并不能完全利用同一机柜里面机器的带宽。
在默认的配置下,hadoop的冗余复制因子是3,意思就是每一块文件数据一共有3个地方存放,hadoop目前的存放策略是其中两份放在同一个rack id的不同机器上面,另外一个放在不同rack id的机器上面,简单来说就是1/3的冗余数据在一个机柜里面,2/3的冗余数据在另外一个机柜里面,这样既可以防止机柜异常时候的数据恢复,又可以提高读写性能。
上面所说的策略目前还是在测试优化阶段。
二、数据读取
数据读取策略,根据前面所说的数据存放策略,数据读取的时候,客户端也有api确定自己的机柜id,读取的时候,如果有块数据和客户端的机柜id一样,就优先选择该数据节点,客户端直接和数据节点建立连接,读取数据。如果没有,就随机选取一个数据节点。
三、数据复制
主要是在数据写入和数据恢复的时候发生,数据复制是使用流水线复制的策略。
当客户端要在hadoop上面写一个文件,首先它先把这个文件写在本地,然后对文件进行分块,默认64m一块,每块数据都对hadoop目录服务器请求,目录服务器选择一个数据机列表,返回给客户端,然后客户端就把数据写入第一台数据机,并且把列表传给数据机,当数据机接收到4k数据的时候,写入本地并且发起连接到下一台数据机,把这个4k传过去,形成一条流水线。当最后文件写完的时候,数据复制也同时完成,这个就是流水线处理的优势。
通讯协议
hadoop的通讯协议基本是在tcp/ip的基础上开发的,客户端使用ClientProtocol和目录服务器通讯,数据机使用DatanodeProtocol和目录服务器通讯,而目录服务器一般只是应答客户端和数据机的请求,不会主动发起通讯。
数据错误和异常
hadoop文件系统的主要目标就是在硬件出错的时候保证数据的完整性,它把磁盘错误作为肯定会出现的情况来对待,而不是异常。一般数据存储中出现的错误有几种,分别是目录服务器错误,数据机错误,和网络传输异常。
1、 数据机出错,每个数据机会定时发送一个心跳信息给目录服务器,表明自己仍然存活,网络异常可能会导致一部分数据机无法和目录服务器通讯,这时候目录服务器收不到心跳信息,就认为这个数据机已经死机,从有效io列表中清除,而该数据机上面的所有数据块也会标记为不可读。这个时候某些数据块的冗余份数有可能就低于它的冗余因子了,目录服务器会定期检查每一个数据块,看看它是否需要进行数据冗余复制。
2、 出现数据异常,由于网络传输和磁盘出错的原因,从数据机读取的数据有可能出现异常,客户端实现对数据块的校验,用md5和sha1进行校验,客户端在创建文件的时候,会对每一个文件块进行信息摘录,并把这些信息写入到同一个路径的隐藏文件里面。当客户端读取文件的时候,会先读取该信息文件,然后对每个读取的数据块进行校验,如果校验出错,客户端就会请求到另外一个数据机读取该文件块,并且报告给目录服务器这个文件块有错误,目录服务器就会定期检查,并且重新复制这个块。
3、 目录服务器出错,FsImage和Editlog是目录服务器上面两个最核心的数据结构,如果其中一个文件出错的话,会造成目录服务器不起作用,由于这两个文件如此重要,所以目录服务器上面可以设置多个备份文件和辅助服务器,当这两个文件有改变的时候,目录服务器就会发起同步操作,虽然这样增加了系统的负担,但是在目前这个架构上面为了实现数据的可靠性,这个同步操作是非常必要的。
Hadoop文件系统尚未实现的功能总结:
1、 文件追加写入,这个功能近期内不会实现,没有这个功能会引起当文件尚未关闭的时候,数据服务器死机或者目录服务器死机,会引起文件文件丢失,并且不可后续恢复写入。
2、 系统快照,一个全系统的快照功能,如果没有这个功能就不能实现文件系统的回滚操作。
3、 集群负载均衡,均衡策略暂时没有实现,有几个策略十分有用,比如在某台数据机可能磁盘过低的时候,把该数据机上面的一些数据转移到还有很多空间剩余的数据机上;当某个文件突然被大量读写的时候,动态增加该文件的冗余因子,并且数据块复制到更多的数据机上面,以提高读取性能。
4、 文件系统的用户权限,这个也是近期内不会实现的了。
5、访问权限,现在是无限制访问的,没有访问权限控制。
Hadoop文件系统性能分析
由于没办法建立大型的Hadoop文件系统,只能节选一些网上的性能分析,以表示一二。
1、 和Kosmos Filesystem的比较,Kosmos Filesystem也是一个类似Google 文件系统的具体实现,所以和Hadoop具有比较的意义。KFS是用c++编写的,在代码执行效率上面比java好不少。
数据插入测试:
测试环境:
• 1 1.8GHz Dual-core Opteron Processor 2210
• 4 GB RAM
• 4 7200 RPM SATA drives (mounted JBOD)
测试使用Hypertable,这也是一个类似Google bigtable的具体实现,可以使用KFS和HDFS作为文件系统,在插入测试后,表格含有75,274,825个数据单元,每一个键值是7字节大小,每一个数据是15字节大小。
测试结果:KFS基本大幅度胜出。
HDFS (no flush)
Elapsed time: 170.66 s
Avg value size: 15.25 bytes
Avg key size: 7.10 bytes
Throughput: 1792158.60 bytes/s
Total inserts: 14825279
Throughput: 86869.79 inserts/s
Elapsed time: 167.44 s
Avg value size: 15.26 bytes
Avg key size: 7.11 bytes
Throughput: 1871062.70 bytes/s
Total inserts: 15185349
Throughput: 90690.84 inserts/s
Elapsed time: 179.91 s
Avg value size: 15.20 bytes
Avg key size: 7.03 bytes
Throughput: 1737888.10 bytes/s
Total inserts: 15208310
Throughput: 84532.68 inserts/s
Elapsed time: 169.57 s
Avg value size: 15.22 bytes
Avg key size: 7.11 bytes
Throughput: 1831688.52 bytes/s
Total inserts: 15080926
Throughput: 88937.45 inserts/s
KFS (no flush)
Elapsed time: 125.51 s
Avg value size: 15.25 bytes
Avg key size: 7.10 bytes
Throughput: 2436864.83 bytes/s
Total inserts: 14825279
Throughput: 118120.09 inserts/s
Elapsed time: 126.25 s
Avg value size: 15.26 bytes
Avg key size: 7.11 bytes
Throughput: 2481447.59 bytes/s
Total inserts: 15185349
Throughput: 120276.33 inserts/s
Elapsed time: 135.51 s
Avg value size: 15.20 bytes
Avg key size: 7.03 bytes
Throughput: 2307335.26 bytes/s
Total inserts: 15208310
Throughput: 112231.19 inserts/s
Elapsed time: 127.66 s
Avg value size: 15.22 bytes
Avg key size: 7.11 bytes
Throughput: 2433069.68 bytes/s
Total inserts: 15080926
Throughput: 118137.45 inserts/s
2、 Hadoop读取测试,与本地文件系统比较
使用hadoop自带的FileBench程序,写入两个1g大小的文件,第一个是字节流文件,随机生成,第二个是字符文件,随机字典生成。下面是本地文件系统和hadoop文件系统的比较,由于集群是在极端条件下测试,目录服务器在广州网通机房,两台数据服务器一台在北京电信机房,一台在北京网通机房,所以测试的瓶颈基本在网络传输,估计在局域网中表现应该好很多。
本地文件系统测试:
java -classpath hadoop-0.16.4-test.jar:hadoop-0.16.5-dev-core.jar:lib/commons-logging-api-1.0.4.jar:lib/log4j-1.2.13.jar:lib/commons-logging-1.0.4.jar:lib/commons-cli-2.0-SNAPSHOT.jar org.apache.hadoop.io.FileBench -dir /home/ssmax/test -nolzo -nozip
DIR: file:/home/ssmax/test
W SEQ_PLN: 42 seconds
W TXT_PLN: 31 seconds
R SEQ_PLN: 25 seconds
R TXT_PLN: 21 seconds
第一行是流文件写入,第二行是文本文件写入,第三行是流文件读取,第四行是文本文件读取。
Hadoop文件系统测试:
java -classpath build/hadoop-0.16.5-dev-test.jar:hadoop-0.16.5-dev-core.jar:lib/commons-logging-api-1.0.4.jar:lib/log4j-1.2.13.jar:lib/commons-logging-1.0.4.jar:lib/commons-cli-2.0-SNAPSHOT.jar org.apache.hadoop.io.FileBench -dir "hdfs://218.107.63.238:9000/user/ssmax" -now -nolzo -nozip
DIR: hdfs://218.107.63.238:9000/user/ssmax
W SEQ_PLN: 437 seconds
W TXT_PLN: 439 seconds
R SEQ_PLN: > 15分钟
R TXT_PLN: > 15 分钟
由于测试客户端上行比下行快很多,所以读取的时候很慢,超过了可以接受的时间,如果在数据机做读操作,读取速度会大大提高。
java -classpath hadoop-0.16.5-dev-test.jar:hadoop-0.16.5-dev-core.jar:lib/commons-logging-api-1.0.4.jar:lib/log4j-1.2.13.jar:lib/commons-logging-1.0.4.jar:lib/commons-cli-2.0-SNAPSHOT.jar org.apache.hadoop.io.FileBench -dir "hdfs://218.107.63.238:9000/user/ssmax" -now -nolzo -nozip DIR: hdfs://218.107.63.238:9000/user/ssmax
R SEQ_PLN: 80 seconds
R TXT_PLN: 63 seconds
所以得出结论就是rack id的配置十分重要,需要区分机柜,传输的瓶颈主要在网络。
上面就是关于Hadoop文件系统的原理和测试,Hbase可以通过配置使用本地文件系统或者Hadoop文件系统。而测试的过程中也发现了一个更成熟的组合,也是开源项目的Hypertable和KFS,这两个也是类似Bigtable和GFS的实现,主要是使用c++实现的,这里先记录一下,以后再做研究。
Hypertable作者语:Hypertable与HBase的差别是,Hypertable是Bigtable的一个更高性能的实现(InfoQ同样采访了HBase的团队)。我开始的时候跟Jim Kellerman以及Hadoop团队的一些成员一起为HBase工作。但我们对HBase应该变成什么样子有不同意见,对实现语言的选择也有不同意见。他们坚持用Java,而我力推C++。于是我就分出来,开始了Hypertable项目。
Hbase分布式数据库
数据模型
Hbase是一个类似Bigtable的分布式数据库,大部分特性和Bigtable一样,是一个稀疏的,长期存储的{存在硬盘上},多维度的,排序的映射表。这张表的索引是行关键字,列关键字和时间戳。每个值是一个不解释的字符数组,数据都是字符串,没类型。
用户在表格中存储数据,每一行都有一个可排序的主键和任意多的列。由于是稀疏存储的,所以同一张表里面的每一行数据都可以有截然不同的列。
列名字的格式是"<family>:<label>",都是由字符串组成,每一张表有一个family集合,这个集合是固定不变的,相当于表的结构,只能通过改变表结构来改变。但是label值相对于每一行来说都是可以改变的。
Hbase把同一个family里面的数据存储在同一个目录底下,而Hbase的写操作是锁行的,每一行都是一个原子元素,都可以加锁。
所有数据库的更新都有一个时间戳标记,每个更新都是一个新的版本,而hbase会保留一定数量的版本,这个值是可以设定的。客户端可以选择获取距离某个时间最近的版本,或者一次获取所有版本。
概念视图:
一个表可以想象成一个大的映射关系,通过主键,或者主键+时间戳,可以定位一行数据,由于是稀疏数据,所以某些列可以是空白的,下面就是数据的概念视图:
Row Key Time Stamp Column "contents:" Column "anchor:" Column "mime:"
"com.cnn.www" t9 "anchor:cnnsi.com" "CNN"
t8 "anchor:my.look.ca" "CNN.com"
t6 "<html>..." "text/html"
t5 "<html>..."
t3 "<html>..."
上图是一个存储Web网页的范例列表片断。行名是一个反向URL{即com.cnn.www}。contents列族{原文用 family,译为族,详见列族}存放网页内容,anchor列族存放引用该网页的锚链接文本。CNN的主页被Sports Illustrater{即所谓SI,CNN的王牌体育节目}和MY-look的主页引用,因此该行包含了名叫“anchor:cnnsi.com”和 “anchhor:my.look.ca”的列。每个锚链接只有一个版本{由时间戳标识,如t9,t8};而contents列则有三个版本,分别由时间 戳t3,t5,和t6标识。
物理视图
虽然从概念视图来看每个表格是由很多行组成,但是在物理存储上面,它是按照列来保存的,这点在数据设计和程序开发的时候必须牢记。
上面的概念视图在物理存储的时候应该表现成下面那样子:
Row Key Time Stamp Column "contents:"
"com.cnn.www" t6 "<html>..."
t5 "<html>..."
t3 "<html>..."
Row Key Time Stamp Column "anchor:"
"com.cnn.www" t9 "anchor:cnnsi.com" "CNN"
t8 "anchor:my.look.ca" "CNN.com"
Row Key Time Stamp Column "mime:"
"com.cnn.www" t6 "text/html"
需要注意的是在概念视图上面有些列是空白的,这样的列实际上并不会被存储,当请求这些空白的单元格的时候,会返回null值。
如果在查询的时候不提供时间戳,那么会返回距离现在最近的那一个版本的数据。因为在存储的时候,数据会按照时间戳排序。
例子:
一个程序写9行数据,row[0-9],先写入 anchor:foo列,再写入 anchor:bar 列,最后重复写入 anchor:foo列,由于是同一个列族,写到同一个映射文件里面,最后写到文件里面是这个样子的:
row=row0, column=anchor:bar, timestamp=1174184619081
row=row0, column=anchor:foo, timestamp=1174184620720
row=row0, column=anchor:foo, timestamp=1174184617161
row=row1, column=anchor:bar, timestamp=1174184619081
row=row1, column=anchor:foo, timestamp=1174184620721
row=row1, column=anchor:foo, timestamp=1174184617167
row=row2, column=anchor:bar, timestamp=1174184619081
row=row2, column=anchor:foo, timestamp=1174184620724
row=row2, column=anchor:foo, timestamp=1174184617167
row=row3, column=anchor:bar, timestamp=1174184619081
row=row3, column=anchor:foo, timestamp=1174184620724
row=row3, column=anchor:foo, timestamp=1174184617168
row=row4, column=anchor:bar, timestamp=1174184619081
row=row4, column=anchor:foo, timestamp=1174184620724
row=row4, column=anchor:foo, timestamp=1174184617168
row=row5, column=anchor:bar, timestamp=1174184619082
row=row5, column=anchor:foo, timestamp=1174184620725
row=row5, column=anchor:foo, timestamp=1174184617168
row=row6, column=anchor:bar, timestamp=1174184619082
row=row6, column=anchor:foo, timestamp=1174184620725
row=row6, column=anchor:foo, timestamp=1174184617168
row=row7, column=anchor:bar, timestamp=1174184619082
row=row7, column=anchor:foo, timestamp=1174184620725
row=row7, column=anchor:foo, timestamp=1174184617168
row=row8, column=anchor:bar, timestamp=1174184619082
row=row8, column=anchor:foo, timestamp=1174184620725
row=row8, column=anchor:foo, timestamp=1174184617169
row=row9, column=anchor:bar, timestamp=1174184619083
row=row9, column=anchor:foo, timestamp=1174184620725
row=row9, column=anchor:foo, timestamp=1174184617169
其中anchor:foo被保存了两次,由于时间戳不同,是两个不同的版本,而最新的数据排在前面,所以最新那次更新会先被找到。
分布式数据库体系结构
Hbase的服务器体系结构也是遵从简单的主从服务器架构,又Hregion服务器群和HBase Master主服务器构成。
Hregion服务器
对用户来说,每个表是一堆数据的集合,靠主键来区分。物理上,一张表是被拆分成多块,每一块就称呼为一个Hregion。用表名+开始/结束主键,来区分一个Hregion,一个Hregion会保存一个表里面某段连续的数据,从开始主键到结束主键,一张完整的表格是保存在多个Hregion上面的。
所有的数据库数据一般是保存在Hadoop分布式文件系统上面,用户通过一系列Hregion服务器获取这些数据,一般一台机器上面运行一个Hregion服务器,而每一个区段Hregion只会被一个Hregion服务器维护。
当用户需要更新数据的时候,他会被分配到对应的Hregion 服务器提交修改,这些修改先是被写到Hmemcache 缓存和 服务器的Hlog文件里面,Hmemcache是在内存中的缓存,保存最近更新的数据,Hlog是磁盘上面的记录文件,它记录着所有的更新操作,当操作写入Hlog之后,commit()调用才会返回给客户端。
当读取数据的时候,Hregion服务器会先访问Hmemcache缓存,如果缓存里面没有该数据,才回到Hstores磁盘上面寻找,每一个列族都会有一个Hstore集合,每个Hstore集合包含很多HstoreFiles具体文件,这些文件都是B树结构的,方便快速读取。
系统会定期调用HRegion.flushcache() 把缓存里面的内容写到文件中,一般这样会增加一个新的HstoreFile文件,而此时高速缓存就会被清空,并且写入一个标记到Hlog,表示上面的内容已经被写入到文件中保存。
在启动的时候,每个Hregion服务器都会检查自己的Hlog 文件,看看最近一次执行flushcache之后有没有新的更新写入操作。如果没有更新,就表示所有数据都已经更新到文件中了;如果有更新,服务器就会先把这些更新写入高速缓存,然后调用flushcache写入到文件。最后服务器会删除旧的Hlog文件,并开始给用户访问数据。
因此,为了节省时间可以很少调用flushcache,但是这样会增加内存占用,而且在服务器重启的时候会延长很多时间。如果可以定期调用flushcache,缓存大小会控制在一个较低的水平,而且Hlog文件也会很快地重构,但是调用flushcache的时候会造成系统负载瞬间增加。
Hlog会被定期回滚,回滚的时候是按照时间备份文件,每当回滚的时候,系统会删除那些已经被写到文件的更新,回滚Hlog只会占用很少的时间,建议经常回滚以减少文件尺寸。
每一次调用flushcache会生成一个新的HstoreFile文件,从一个Hstore里面获取一个值都需要访问所有的HstoreFile文件,这样十分耗时,所以我们要定期把这些分散的文件合并到一个大文件里面,HStore.compact()就可以完成这样的工作。这样的合并工作是十分占用资源的,当HstoreFile文件的数量超过一个设定值的时候才会触发。
Google的Bigtable有高级合并和低级合并的区别,但是Hbase没有这个概念,只要记住下面两点就可以了:
1、 flushcache会建立一个新的HstoreFile文件,并把缓存中所有需要更新的数据写到文件里面,flushcache之后,log的重建次数会清零。
2、 compact会把所有HstoreFile文件合并成一个大文件。
3、 和Bigtable不同的是,Hbase每个更新如果是被正确提交了,commit没有返回错误的话,它就一定是被写到记录文件里面了,这样不会造成数据丢失。
两个Hregion可以通过调用HRegion.closeAndMerge()合并成一个新的Hregion,当前版本这个操作是需要两台Hregion都停机才能操作。
当一个Hregion变得太过巨大的时候,超过了设定的阈值,HRegion服务器会调用HRegion.closeAndSplit(),这个Hregion会被拆分为两个,并且报告给主服务器让它决定由哪个Hregion服务器来存放新的Hregion。这个拆分过程是十分迅速的,因为两个新的Hregion最初只是保留原来HregionFile文件的引用,而这个时候旧的Hregion会处于停止服务的状态,当新的Hregion合并完成并且把引用删除了以后,旧的Hregion才会删除。
最后总结几点:
1、 客户端以表格的形式读取数据
2、 一张表是被划分成多个Hregion区域
3、 Hregion是被Hregion服务器管理的,当客户端需要访问某行数据的时候,需要访问对应的Hregion服务器。
4、 Hregions服务器里面有三种方式保存数据:
A、 Hmemcache高速缓存,保留是最新写入的数据
B、 Hlog记录文件,保留的是提交成功了,但未被写入文件的数据
C、 Hstores文件,数据的物理存放形式。
Hbase主服务器
每个Hregion服务器都会和Hmaster服务器通讯,Hmaster的主要任务就是要告诉每个Hregion服务器它要维护哪些Hregion。
Hmaster服务器会和每个Hregion服务器保持一个长连接。如果这个连接超时或者断开,会导致:
A、 Hregion服务器自动重启。
B、 Hmaster认为Hregion已经死机,同时把它负责的Hregion分配到其它Hregion服务器。
和Google的Bigtable不同的是,当Bigtable的TabletServer和主服务器通讯中断的情况下,它仍然能提供服务。而Hbase不能这么做,因为Hbase没有Bigtable那样额外的加锁系统,Bigtable是由主服务器管理TabletServer,同时加锁服务器提供数据访问的,而Hbase只有唯一一个接入点,就是Hmaster服务器。
当一个新的Hregion服务器登陆到Hmaster服务器,Hmaster会告诉它先等待分配数据。而当一个Hregion死机的时候,Hmaster会把它负责的Hregion标记为未分配,然后把它们分配到其它Hregion服务器。
元数据表
之前我们说过Hregion是按照表名和主键范围区分的,由于主键范围是连续的,所以一般用开始主键就可以表达出来。
但是如果只要开始主键还是不够的,因为我们有合并和分割操作,如果正好在执行这些操作的过程中出现死机,那么就可能存在多份表名和开始主键一样的数据,这个就要通过Hbase的元数据信息来区分哪一份才是正确的数据文件了,为了区分这样的情况,每个Hregion都有一个'regionId'来标识它的唯一性。
所以一个Hregion的表达符最后是 表名+开始主键+唯一id(tablename + startkey + regionId)
举个例子:hbaserepository,w-nk5YNZ8TBb2uWFIRJo7V==,6890601455914043877
我们可以用这个识别符来区分不同的Hregion,这些数据就称呼为元数据,而元数据本身也是被保存在Hregion里面的,我们称呼这个表为元数据表,里面保存的就是Hregion标识符和实际Hregion服务器的映射关系。
元数据表本身也会增长,并且可能被分割为几个Hregion,为了定位这些Hregion,有一个根数据表(ROOT table),保存了所有元数据表的位置,而根数据表是不能被分割的,永远之存在一个Hregion。
在Hbase启动的时候,主服务器先去扫描根数据表,因为这个表只会有一个Hregion,所以这个Hregion的名字是被写死的。当然要把根数据表分配到一个Hregion服务器需要一定的时间。
当根数据表被分配好之后,主服务器就会去扫描根数据表,获取元数据表的名字和位置,然后把元数据表分配到不同的Hregion服务器。
最后就是扫描元数据表,找到所有Hregion区域的信息,然后把它们分配给不同的Hregion服务器。
主服务器在内存中保存着当前活跃的Hregion服务器的数据,因此如果主服务器死机的话,整个系统也就无法访问了,而服务器的信息也没有必要保存到文件里面。
元数据表和根数据表的每一行都包含一个列族,info列族:
1. info:regioninfo 包含了一个串行化的HregionInfo对象。
2. info:server 保存了一个字符串,是服务器地址HServerAddress.toString()
3. info:startcode 一个长整型的数字的字符串,是Hregion服务器启动的时候传给主服务器的,让主服务器决定这个Hregion服务器的信息有没有更改。
因此,当一个客户端拿到根数据表地址以后,就没有必要再连接主服务器了。主服务器的负载相对就小了很多,它只会处理超时的Hregion服务器,在启动的时候扫描根数据表和元数据表,和返回根数据表的Hregion服务器地址。
因此Hbase的客户端是十分复杂的,它经常需要浏览元数据表和根数据表,在查询表格的时候,如果一个Hregion服务器死机或者它上面的数据更改了,客户端就会继续重试,客户端保留的映射关系并不会一直正确的。这里的机制还需要进一步完善。
总结:
1、 Hregion服务器提供Hregion访问,一个Hregion只会保存在一个Hregion服务器上面。
2、 Hregion会注册到主服务器上面。
3、 如果主服务器死机,那么整个系统都会无效。
4、 当前的Hregion服务器列表只有主服务器知道。
5、 Hregion区域和Hregion服务器的对应关系保存在两个特别的Hregion里面,它们像其它Hregion一样被分配到不同的服务器。
6、 根数据表是最特别的一个表,主服务器永远知道它的位置(在程序中写死)
7、 客户端需要自己浏览这些表,来找到数据在哪里。
Hbase和传统关系数据库的对比分析
Hbase是大大不同于以前的关系数据库,它是按照Bigtable来开发的,套用一个Bigtable的定义就是:
A Bigtable is a sparse, distributed, persistent multidimensional sorted map.
Bigtable是一个稀疏的,分布的,持续多维度的排序映射数组。
Hbase就是这样一个基于列模式的映射数据库,它只能表示很简单的键-数据的映射关系,它大大简化了传统的关系数据库。
1、 数据类型,Hbase只有简单的字符串类型,所有类型都是交由用户自己处理,它只保存字符串。而关系数据库有丰富的类型选择和存储方式。
2、 数据操作,Hbase操作只有很简单的插入、查询、删除、清空等,表和表之间是分离的,没有复杂的表和表之间的关系,所以也不能也没有必要实现表和表之间的关联等操作。而传统的关系数据通常有各种各样的函数、连接操作。
Hbase的操作列表:
alter Alter column family schema; pass table name and a dictionary
specifying new column family schema. Dictionaries are described
below in the GENERAL NOTES section. Dictionary must include name
of column family to alter. For example, to change the 'f1' column
family in table 't1' from defaults to instead keep a maximum of 5
cell VERSIONS, do:
hbase> alter 't1', {NAME => 'f1', VERSIONS => 5}
count Count the number of rows in a table. This operation may take a LONG
time (Run '$HADOOP_HOME/bin/hadoop jar hbase.jar rowcount' to run a
counting mapreduce job). Current count is shown every 1000 rows by
default. Count interval may be optionally specified. Examples:
hbase> count 't1'
hbase> count 't1', 100000
create Create table; pass table name, a dictionary of specifications per
column family, and optionally a dictionary of table configuration.
Dictionaries are described below in the GENERAL NOTES section.
Examples:
hbase> create 't1', {NAME => 'f1', VERSIONS => 5}
hbase> create 't1', {NAME => 'f1'}, {NAME => 'f2'}, {NAME => 'f3'}
hbase> # The above in shorthand would be the following:
hbase> create 't1', 'f1', 'f2', 'f3'
hbase> create 't1', {NAME => 'f1', VERSIONS => 1, TTL => 2592000, \
BLOCKCACHE => true}
describe Describe the named table: e.g. "hbase> describe 't1'"
delete Put a delete cell value at specified table/row/column and optionally
timestamp coordinates. Deletes must match the deleted cell's
coordinates exactly. When scanning, a delete cell suppresses older
versions. Takes arguments like the 'put' command described below
deleteall Delete all cells in a given row; pass a table name, row, and optionally
a column and timestamp
disable Disable the named table: e.g. "hbase> disable 't1'"
drop Drop the named table. Table must first be disabled
enable Enable the named table
exists Does the named table exist? e.g. "hbase> exists 't1'"
exit Type "hbase> exit" to leave the HBase Shell
get Get row or cell contents; pass table name, row, and optionally
a dictionary of column(s), timestamp and versions. Examples:
hbase> get 't1', 'r1'
hbase> get 't1', 'r1', {COLUMN => 'c1'}
hbase> get 't1', 'r1', {COLUMN => ['c1', 'c2', 'c3']}
hbase> get 't1', 'r1', {COLUMN => 'c1', TIMESTAMP => ts1}
hbase> get 't1', 'r1', {COLUMN => 'c1', TIMESTAMP => ts1, VERSIONS = 4}
list List all tables in hbase
put Put a cell 'value' at specified table/row/column and optionally
timestamp coordinates. To put a cell value into table 't1' at
row 'r1' under column 'c1' marked with the time 'ts1', do:
hbase> put 't1', 'r1', 'c1', 'value', ts1
scan Scan a table; pass table name and optionally an array of column
names OR an array of column names AND a dictionary of scanner
specifications. If you wish to include scanner specifications,
you must also include an array of columns. Scanner specifications
may include one or more of the following: LIMIT, STARTROW, STOPROW,
or TIMESTAMP. To scan all members of a column family, leave the
qualifier empty as in 'col_family:'. Examples:
hbase> scan '.META.'
hbase> scan '.META.', ['info:regioninfo']
hbase> scan 't1', ['c1', 'c2'], {LIMIT => 10, STARTROW => 'xyz'}
version Output this HBase version
3、 存储模式,Hbase是基于列存储的,每个列族都有几个文件保存,不同列族的文件是分离的。传统的关系数据库是基于表格结构和行模式保存的。
4、 数据维护,Hbase的更新正确来说应该不叫更新,而且一个主键或者列对应的新的版本,而它旧有的版本仍然会保留,所以它实际上是插入了新的数据,而不是传统关系数据库里面的替换修改。
5、 可伸缩性,Hbase和Bigtable这类分布式数据库就是直接为了这个目的开发出来的,能够轻易的增加或者减少(在硬件错误的时候)硬件数量,而且对错误的兼容性比较高。而传统的关系数据库通常需要增加中间层才能实现类似的功能。
当前的关系数据库基本都是从上世纪70年代发展而来的,它们基本都有一下的体系特点:
1、 面向磁盘存储和索引结构
2、 多线程访问
3、 基于锁的同步访问机制
4、 基于log记录的恢复机制
而Bigtable和Hbase之类基于列模式的分布式数据库,更适应海量存储和互联网应用的需求,灵活的分布式架构可以使其利用廉价的硬件设备就组建一个大的数据仓库,而互联网应用就是以字符为基础的,Bigtable和Hbase就针对这些应用而开发出来的数据库。
由于其中的时间戳特性,Bigtable和Hbase与生俱来就特别适合于开发wiki、archiveorg之类的服务,而Hbase直接就是作为一个搜索引擎的一部分被开发出来的。
Bigtable的应用案例:
Google各个产品应用里面的大表:
1、 Google Analytics 网站流量分析(analytics.google.com)
这个服务主要提供给网站管理员两个数据,一个就是独立访问者的数量(cookie判定),另外一个就是页面浏览量(PageView),网站管理员只要在每一个需要统计的页面加上google提供javascript代码,就可以每天在后台看到相关的统计信息了。
这个服务的数据保存主要由两个打表实现:
第一个是原始点击表,记录了用户点击页面的原始数据,这个表的列包括:网站名称,url和用户点击时间、ip等资料,按用户点击时间排序,大小控制在200TB左右,定期需要做压缩备份等操作。
第二个表就是统计数据表,这个表是从原始点击表中计算而来,定期运行批量计算任务生成数据(使用Map/Reduce程序),这个表大概在20TB左右。
2、 Google Earth 地图(maps.google.com)
这个服务包括网页版的google地图和客户端版的google地球。用户通过这些服务,能选择不同的分辨率浏览地图、卫星照片等数据。
这个系统主要包括一个数据处理表,和一系列的数据服务表(用户读取时候用)。
原始的图片信息通过程序批量输入到数据处理表,形成格式化数据。这个数据处理表的每一行表示了物理地图上面的每一块,而键值的命名确保这些地理块是连续的,由于地理的信息很多,所以有很多列族,基本上每个列族都有图片数据,多列族确保数据是稀疏的,单个存储文件不会太大。后台处理程序定期处理这些数据,把它们整理并录入数据服务表,并清空处理过的原始数据。
数据服务表主要由一个索引表和数个数据表组成,索引表保证了用户请求数据的时候不需要遍历所有数据表。
3、 网络历史记录(www.google.com/psearch)
主要功能:
• 查看并搜索您过去曾访问过的网页,包括 Google 的搜索记录。
• 查找有关网络活动的搜索趋势,如最常访问的网站和热门搜索等。
• 根据您搜索的内容以及曾访问过的网站,获取更具个性化的搜索结果。
这个服务中的网络历史记录是需要安装Google工具栏并在浏览器中启用才能搜集数据。
这个服务把每个用户的数据保存在同一个大表里面,每个用户有一个唯一的用户id,而每种类型的操作(搜索关键字、浏览网页等)都有一个不同列族,用户搜索记录是通过后台程序从搜索引擎端批量生成并插入的,而网页浏览记录是通过用户的Google工具栏定期上传数据并插入的。
这个服务一开始是设计成在客户端保存个人数据备份的方式,最新改进了以后是按照不同地区的用户再建立多个大表集群,让用户可以就近访问,加快传输速度。
为了保证用户之间的共享不会占用太多资源,我们为每个用户加上了简单的配额机制,分别在客户端和大表集群上面实现。
结论:
Hbase就是一个分布式存储的简单键值对应的表。
因为Hbase的结构与传统关系数据库大为不同,所以到目前为止,基于它的应用都是直接从头开始就使用Hbase设计的,还没有见到过从关系数据库转移到Hbase的应用,而且它们之间的api差距太大,而Hbase基于JDBC的驱动开发还是遥遥无期。
而且最重要的一点是当前hbase还是处于开发阶段,所有api都没有稳定,比如服务器上面装的时候是hbase-0.1.3,一个月后版本发展到hbase-0.2.0,所有API基本都改变了,连HQL的语法都重新定义了一次,而且升级hbase要同时升级hadoop,这样升级所需要的维护工作量太大。
当然,据闻Hbase和Hypertable的目的就是在web应用市场上面取代Mysql。
数据库读写性能分析
1、 单机模拟集群测试
测试环境:
由3台服务器组成的hadoop集群组成分布式文件系统
由一台单独的机器单机模拟Hbase集群
由一台机器单机测试Mysql
测试规模:50万条记录以上,单线程、多线程测试
测试结果:
HBase Mysql
单线程 插入 100 条记录 155 ms / 154ms 243 ms / 198ms
插入 1000 条记录 740 ms / 884ms 1506 ms / 1554ms
插入 10000 条记录 8304ms/ 6610ms 14110ms/ 12839m
插入 100000 条记录 43090ms /64715ms 108082ms /110664ms
读取 500000左右的条记录 640 ms / 721ms 2779 ms / 2794ms?
100线程 插入 100 条记录 5929ms / 3825ms / 4134ms 15352ms / 12912ms / 12853ms
插入 1000 条记录 35684ms / 52677ms / 34208ms 135839ms / 161711ms / 119909ms
读取 500000左右的条记录 最快的 1104 ms/最慢的 110897 ms 如果不加limit,数据库连接超时
1000线程 插入 100 条记录 325907ms / 322465ms / 342163ms 17455ms / 18953ms / 15169ms
读取500000左右的条记录 最快的 717 ms 如果不加limit,数据库连接超时
HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库.另一个不同的是HBase基于列的而不是基于行的模式(字段内容都是char).
上面两种 特性,导致HBase的数据表结构非常松散,字段内容单一,表与表之前没有任何关联,正因为这样在查询的时候效率非常高
HBase数据表的性能选项:
MAX_VERSIONS:每一个单元保存多少版本的数据(默认是3)
MAX_LENGTH:每个单元中的版本能够保存多少字节的数据(默认字节数是32位有符号整数最大值)
COMPRESSION:数据压缩,有BLOCK压缩和RECORD压缩
IN_MEMORY:将这个列组装载数据到内存,加快读写速度,缺点耗费内存和干预HDFS的备份
BLOOMFILTER:如果这个列组支持布隆过滤器(BLOOMFILTER),那么在内存中有个索引来快速地判断要查找的列是否存在这个行中,减少磁盘IO操作.如
果在这个列组你拥有大量的列,每一个列的数据包含的数据非常小,你可能需要在这个列组中应用布隆过滤器(BLOOMFILTER)
2、 列族测试:
测试目标:测试列族增长对性能的影响
测试数据:建表时候定义列族数量,每个列族写入1000字节数据,读取5000次,随机读取任意一列。
测试结果:
单机集群
列族数量 10 100 500 1000
建表时间 12.3 19.2 45.9 Timeout
每秒写入 164 323 419 Timeout
每秒读取 99 139 122 Timeout
5机器集群
列族数量 10 100 500 1000
建表时间 12.2 18.7 46.4 Timeout
每秒写入 29 153 376 Timeout
每秒读取 119 111 120 Timeout
测试结论:
Hbase建表时间过长,对大列族的时候支持不好
写入速度在多机集群的时候提高较快
3、 排序测试
测试目标:Hbase的行排序是根据主键排序,测试动态或者反序插入时候的性能。
测试数据:动态生成字母数据,zzzzz-aaaaa,还有随机插入
测试结果:
单机集群(每秒多少行)
写入行 10,000 100,000 1,000,000
顺序 485 432 334
反序 451 477 354
随机 462 421 334
5机集群(每秒多少行)
写入行 10,000 100,000 1,000,000
顺序 488 440 346
反序 522 387 343
随机 468 441 370
测试结论:
采用B树存储和写入缓存,写入数量和顺序对速度影响并不大,应该只是cpu占用的不同。
主要瓶颈还是在网络传输速度上。
4、 随机读写测试
测试目标:测试大表下的读取性能
测试数据:在一定规模的表中随机读取5000条记录的时间
测试数据:
单机集群(每秒多少行)
规模(行) 10,000 100,000 1,000,000
读取 95 42 24
5机集群(每秒多少行)
规模(行) 10,000 100,000 1,000,000
读取 95 34 35
测试结论:
读取速度随着表规模的增加而降低,集群模式下面读取表现比较优秀。
估计如果加上数据合并以后集群的读取能力会更加强劲。
总结:
Hypertable和Hbase尚在开发阶段,还不适合产品化,暂时没有对应的产品案例,但是照这个方向发展下去在web应用端有很好的前景。
Hadoop文件系统相对较稳定,可以考虑在新产品中试验。
参考文献:
The Hadoop Distributed File System: Architecture and Design
http://hadoop.apache.org/core/docs/r0.16.4/hdfs_design.html
HDFS Under the Hood Presentation 1
http://assets.en.oreilly.com/1/event/12/HDFS%20Under%20the%20Hood%20Presentation%201.pdf
Kosmos File System (KFS) is a New High End Google File System Option
http://highscalability.com/kosmos-file-system-kfs-new-high-end-google-file-system-option
Hadoop HBase Performance Evaluation Introduction
http://www.cs.duke.edu/~kcd/hadoop/kcd-hadoop-report.pdf
Hbase/HbaseArchitecture
http://wiki.apache.org/hadoop/Hbase/HbaseArchitecture
Hbase/DataModel
http://wiki.apache.org/hadoop/Hbase/DataModel
Understanding HBase and BigTable
http://jimbojw.com/wiki/index.php?title=Understanding_Hbase_and_BigTable
The End of an Architectural Era (It's Time for a Complete Rewrite)
http://www.vldb.org/conf/2007/papers/industrial/p1150-stonebraker.pdf
HBase Leads Discuss Hadoop, BigTable and Distributed Databases
http://www.infoq.com/news/2008/04/hbase-interview
Validating the Real-time Performance of Hbase
http://wiki.apache.org/hadoop/Hbase/HbaseRTDS
Hadoop Summit and Data-Intensive Computing Symposium Videos and Slides
http://research.yahoo.com/node/2104
Google Datastore and the shift from a RDBMS
http://groovie.org/2008/04/13/google-datastore-and-the-shift-from-a-rdbms
One Size Fits All? - Part 2: Benchmarking Results
http://nms.csail.mit.edu/~stavros/pubs/osfa.pdf
Bigtable: A Distributed Storage System for Structured Data
http://labs.google.com/papers/bigtable-osdi06.pdf
Hypertable领导者:Hadoop和分布式数据库
http://www.builder.com.cn/2008/0506/847804.shtml