简介
HBase是模仿google bigtable的开源产品,又是hadoop的衍生品,hadoop作为离线计算系统已经得到业界的普遍认可,并经过N多公司大规模使用的验证,自然地认为Hbase也将随之获得成功。
《HBase: The Definitive Guide》第8章讲述hbase的架构,从架构上看,其架构很完美:
- LSM - 解决磁盘随机写问题(顺序写才是王道);
- HFile - 解决数据索引问题(只有索引才能高效读);
- WAL - 解决数据持久化(面对故障的持久化解决方案);
- zooKeeper - 解决核心数据的一致性和集群恢复;
- Replication - 引入类似MySQL的数据复制方案,解决可用性;
此外还有:自动分拆Split、自动压缩(compaction,LSM的伴生技术)、自动负载均衡、自动region迁移。
HBase的一致性
关于HBase的一致性模型,众说纷纭,到底是强一致性还是最终一致性,还需要多了解一下,这里不做讨论。但是有一点可以确认的是对于单行键,具有秒级别的读写性能(强一致性???)
HBase运算有多快
定义一些变量:
- n = 表中KeyValue条目数量(包括Put的结果和Delete留下的墓碑标示);
- b = HFile里数据块(HFile Block)的数量;
- e = 平均一个HFile里KeyValue条目的数量(如果知道行的大小,可以计算得到)
- c = 每行里列的平均数量
注意,这是在单列族语境中讨论这一点。
先来定义针对指定行健查找相关HFile数据块需要的时间。无论是你在单行上执行get()命令,还是为一次扫描查找起始键,都会有这个动作。
第一步,客户端寻找正确的RegionServer和region。花费3次固定预算找到正确的region——查找ZK,查找--ROOT--, 查找.META.。这是一次O(1)运算。
在指定的region上,行在读过程里可能存在于两个地方:如果还没有刷写到硬盘就位于MemStore,如果已经刷写则位于一个HFile里。简化起见,我们假设只有一个HFile,这一行要门在这个文件里,要么还没刷写,在MemStore里。
让我们用 e 合理代表指定时间在MemStore里的条目数量。如果一行在MemStore里,因为MemStore是使用SkipList实现的,所以查找行的时间复杂度是O(log e)。如果一行已经刷写到硬盘上,需要找到正确的HFile数据块。数据块索引是排过序的,所以查找正确的数据块是一次时间复杂度为O(log b)的运算。查找行里KeyValue对象是在数据块里的一次线性扫描操作。在你找到第一个KeyValue对象后,随后查找剩下的对象就是一次线性扫描。假设行里的单元都在同一个数据块里,扫描的时间复杂度是O(e/b)。如果行里的单元不在同一个数据块里,这种扫描需要访问多个连续数据块里的数据,所以这时的运算由读取的行数决定,其时间复杂度是O(c)。也就是说,这种扫描的时间复杂度是O(max(c,
e/b)).
总之,查找某一行的开销如下所示:
O(1) 用于查找region
+ O(log e)用来在MemStore里定位KeyValue,如果它还在MemStore里;
或者O(1)用来查找region
+ O(log b) 用来在HFile里查找正确的数据块
+ O(max(c, e/b))用来查找扫描的决定性部分,如果它已经刷写到硬盘上了
在访问HBase中的数据时,决定性因素是扫描HFile数据块找到相关KeyValue对象所花费的时间。如果使用宽行,这会在扫描过程中增加处理整行的开销。所有这些分析,都假设知道要查找的行的行健。
如果不知道行健,你需要扫描整个区间(可能是整张表)来查找你关心的行,这个时间复杂度是O(n)。在这样的情况下,你将不再有益于把扫描限定在若干Hfile数据块里。
这里没有讨卵硬盘寻道开销。如果要从HFile里读取的数据已经被加载进数据块缓存,前面的分析是正确的。如果数据还需要从HDFS读到数据块缓存,从硬盘读取数据的开销会增加很多,从学术上将这种分析已经没有意义。
因为行健是所有这些索引的决定性因素,所以结论是访问宽行要比访问窄行开销大。如果知道行健,按照HBase建立索引的内部工作原理,你会从中得到很大好处。
reference
1、HBase权威指南
2、HBase实战