用 HBase 做高性能键值查询?

sjjt-215

最近碰到几家用户在使用 HBase 或者试图使用 HBase 来做高性能查询,场景也比较类似,就是从几十亿甚至上百亿记录中按键值找出相关记录来。按说,这种 key-value 式的数据库很适合用键值查询,HBase 看起来就是个不错的选择。

然而,已经实施过的用户却反映:效果非常差!

其实,这是预料之中的结果,因为 HBase 根本不适合做这件事!


从实现原理上看,key-value 式的数据库无非也就是按 key 建了索引来查找。而索引技术,无论是传统数据库用的 B 树还是 NoSQL 数据库常用的 LSM 树,其本质都是利用键值有序,把遍历查找变成二分(或 n- 分)查找,在查找性能上并没有根本差异。LSM 树的优势在于一定程度克服了 B 树在更新时要面对的复杂的平衡调整,并利用了硬件的特点,对于并发高频写入的操作更为擅长,在读取方面却反而有所牺牲。而对于很少更新的历史数据,用 NoSQL 数据库在按键值查找时,和传统关系数据库相比,并不会有优势,大概率还会有劣势。

不过,对于只要找出一条记录的情况,这个优势或劣势是察觉不到的,就算差了 10 倍,也不过是 10 毫秒和 100 毫秒的差别,对前端操作人员来讲都是立即响应。所以,人们一般也不容易有直观的体验。


但是,如果要找出成千上万甚至几十万行记录时,那感觉就明显了,100 毫秒执行 1 万次就要 1000 秒了。上面说的用户应用效果差也是这种情况。

用键值取数时,可以通过索引直接跳到数据所在地,这样硬盘访问量非常小,所以能做到很快。而如果键值非常多时,涉及的数据到处都是,硬盘访问量就会加大很多。而且数据在外存中是按块存储的,你不可能只读取一条记录本身的数据,而要把这个记录周边的数据都读出来,多读出的内容常常比要读的数据量还大很多倍。在总共只取一条记录时,即使这样,用户体验也不会有多差(10 毫秒和 100 毫秒的差异);而要取出很多记录时,这个多读的内容也就跟着翻倍了,用户体验也就很糟糕了。

如果这些键值是连续的,那么适当设计存储,让数据的物理存储也按键值有序,这样就不会有浪费的读取内容,性能损失也就很少。商用关系数据库一般会按插入次序存储数据,基本可以保证这一点。在存储块中会留有一部分空间应付少量改写,这样有些数据改动了也能大体保证连续性,按键值区间查找的性能也还不错。但 HDFS 没有改写能力,HBase 在有数据改写时只能先扔到后面(LSM 树也是这么设计的),这样会导致数据存储的不连续性,增加多余的读取,降低性能。

如果键值不连续(这是更常见的情况),那这种多余读就无论如何不能避免,这时候想再优化的办法就是压缩,直接减少物理存储量。但是在这方面,HBase 这种 key-value 数据库的表现也不如人意。这些 NoSQL 允许同一表中不同记录有不同字段,它不象关系数据库那样对每个表有一个所有记录统一的数据结构定义,这样带来了写入的灵活性,但势必要将数据结构信息附在记录上,导致存储量加大很多,给读取造成巨大的负担。而且,这种 key-value 方式也没法采用列存(严格地说,就没有列的概念),而列存 + 排序后可以极大提升压缩率(这个问题以后可以再专门讲)。HBase 有个列族的概念,可以充当列的作用,这方面问题一定程度会有所缓解,但用起来并不方便。

总结下来,大多数 **key-value 数据库是为了高频写入而设计的,而不是为了高速读取!** 用来做高性能查询完全是个方向性错误。用于键值查找都不合适,而其它非键值查询的效果就更为恶劣(以前文中也说过这个问题)。

明明不合适,为什么还有这么多人用或想用 HBase 来解决这个问题呢?可能是 Hadoop 名声太大吧,只要有大数据就会想到用 Hadoop。而且,很多传统关系数据库也确实搞不定太大量的数据,数据量大到一定程度,存储都是问题,查询就无从提起了。
不过,有些新的数据技术方案已经能够解决这些问题,延续了传统数据仓库的某些技术手段,比如事先确定数据结构、为读而优化的索引、列存及压缩等,再有合理的存储机制以支撑巨大数据量,这样就能得到比 HBase 好得多的性能体验。