Apache HBase是一个高性能、面向列、可伸缩的开源分布式NoSQL数据库,基于google三大论文中的《Bigtable:一个分布式的结构化数据存储系统》,它是Google Bigtable的开源实现。
用户使用它能够在廉价的PC server上搭建大规模的结构化存储集群;因为Hbase是构建在hadoop之上,具有很好的横向扩展能力。
此图来源于cloudera
拿之前我做电商商品站外推荐举例,前期因为客户比较少,对接的广告主的商品库比较少,所以我们当时是用mysql来存储广告主的商品信息和商品的变更信息;但是,随着我们的广告主越来越多,对接的商品库也越来越多,尤其是一些大广告主商品库中SKU几百万甚至上千万的情况下,mysql数据库中单表数据超过1亿时,商品信息的查询速度越来越慢。而Hbase一个表可以有数十亿行,上百列。
基于具体的业务场景,因为当时我们的业务对于数据存储事务的要求不像金融行业和财务系统这么高,只要保证最终一致性就行。再一个关系型数据库一般都具有强数据类型,具有严格的schema,而Hbase是无模式的,每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一张表中不同的行可以有截然不同的列;Hbase中的数据都是字符串,没有类型。所以在这种特定的场景下,我们放弃使用mysql的深入优化,直接选择把所有的信息存储在HBase上。
下图为Hbase的逻辑视图:
RowKey:是Byte array,是表中每条记录的“主键”,方便快速查找,Rowkey的设计非常重要,后面在重点讲讲我们在RowKey的设计上遇到过的坑。
Column Family:列族,拥有一个名称(string),包含一个或者多个相关列
Column:属于某一个columnfamily,familyName:columnName,每条记录可动态添加
Version Number:类型为Long,默认值是系统时间戳,可由用户自定义
Value(Cell):Byte array
为了满足每天上亿级别的访问,而hbase根据rowkey进行查询的速度相当快,仅次于redis等缓存数据库。具体业务场景如下图:
用户的推荐结果保存在redis中,但是redis中保存的推荐结果只有商品的ID,所以到具体的广告展现的时候需要根据id从hbase中获取具体的商品信息。此设计方案经过了双十一的洗礼,在仅有的几台机器资源下,单天访问超过10亿级别没什么压力。
在这之前的设计如下图:
虽然说这个设计得有点鸡肋,但是基于hbase的倒排索引实现,还是能够及时的更新redis缓存,与上一个方案比有一些更新上的延迟,因为必须定时去执行。
在这之前的之前是这样的,如下图:
这就是一开始的方案,简单的跑着玩玩,上几个小广告主毫无压力。
当然,不仅仅是我们这种中小团队使用hbase,在很多的大团队使用hbase的就更加多了,比如说facebook,他们看中hbase的什么呢?
Hign write throughput
Good random read performance
Horizontal scalability
Automatic Failover
Strong consistency
Benefits of HDFS
Fault tolerant,scalable,checksums,MapReduce
Internal dev & ops expertise
关于Hbase具体的架构解析,参考下面文章:
https://www.mapr.com/blog/in-depth-look-hbase-architecture#.VdMxvWSqqko
Rowkey是一段二进制码流,最大长度为64KB,内容可以由使用的用户自定义。数据加载时,一般也是根据Rowkey的二进制序由小到大进行的。之前根据广告主id加上广告主的商品id作为rowkey,因为有的广告主sku非常多,有些广告主的sku比较少,这样导致导致数据hash不均匀,有的hbase节点上存储的数据量很大,有的数据量很少,这样的数据倾斜,导致整个hbase的集群性能下降。
1,基于单个rowkey查询,即按照具体的rowkey键值进行get操作;
2,通过Rowkey的range进行scan,即通过设置startRowKey和endRowKey,在这个范围内进行扫描。这样可以按指定的条件获取一批记录;
3,全表扫描所有数据(不建议这干,因为效率特别低)
Rowkey长度设计原则:Rowkey是一个二进制码流,Rowkey的长度被很多开发者建议说设计在10~100个字节,不过建议是越短越好,不要超过16个字节。
因为:
1,数据的持久化文件HFile中是按照KeyValue存储的,如果Rowkey过长比如100个字节,1000万列数据光Rowkey就要占用100*1000万=10亿个字节,将近1G数据,这会极大影响HFile的存储效率;
2,MemStore将缓存部分数据到内存,如果Rowkey字段过长内存的有效利用率会降低,系统将无法缓存更多的数据,这会降低检索效率。因此Rowkey的字节长度越短越好。
3,目前操作系统是都是64位系统,内存8字节对齐。控制在16个字节,8字节的整数倍利用操作系统的最佳特性。
必须在设计上保证其唯一性。
必须要保证所有的rowkey那个均匀的分布在各个hbase节点上,后来我们把rowkey加上时间戳然后做了md5的加密来解决此问题。
如果Rowkey是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将Rowkey的高位作为散列字段,由程序循环生成,低位放时间字段,这样将提高数据均衡分布在每个Regionserver实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息将产生所有新数据都在一个 RegionServer上堆积的热点现象,这样在做数据检索的时候负载将会集中在个别RegionServer,降低查询效率。
1,在准备使用hbase之前设计好rowkey是关键,一般rowkey上都会存一些比较关键的检索信息;
2,必须提前想好数据应该查,已经数据具体需要如何使用,根据查询方式进行数据存储格式的设计;
3, 列名,表名等尽量使用短名字,这样可以节省空间,提高性能;
4,使用预分区;
默认情况下,在创建HBase表的时候会自动创建一个region分区,当导入数据的时候,所有的HBase客户端都向这一个region写数据,直到这个region足够大了才进行切分。一种可以加快批量写入速度的方法是通过预先创建一些空的regions,这样当数据写入HBase时,会按照region分区情况,在集群内做数据的负载均衡。
5,创建表的时候,可以通过HColumnDescriptor.setTimeToLive(int timeToLive)设置表中数据的存储生命期,过期数据将自动被删除,这样可以保证不活跃的数据或者过期的数据占用磁盘和内存空间;具体有效期设置多长时间需要根据具体的业务场景进行判断后设置。
6,Compact & Split
在HBase中,数据在更新时首先写入WAL 日志(HLog)和内存(MemStore)中,MemStore中的数据是排序的,当MemStore累计到一定阈值时,就会创建一个新的MemStore,并且将老的MemStore添加到flush队列,由单独的线程flush到磁盘上,成为一个StoreFile。于此同时, 系统会在zookeeper中记录一个redo point,表示这个时刻之前的变更已经持久化了(minor compact)。
StoreFile是只读的,一旦创建后就不可以再修改。因此Hbase的更新其实是不断追加的操作。当一个Store中的StoreFile达到一定的阈值后,就会进行一次合并(major compact),将对同一个key的修改合并到一起,形成一个大的StoreFile,当StoreFile的大小达到一定阈值后,又会对 StoreFile进行分割(split),等分为两个StoreFile。
由于对表的更新是不断追加的,处理读请求时,需要访问Store中全部的StoreFile和MemStore,将它们按照row key进行合并,由于StoreFile和MemStore都是经过排序的,并且StoreFile带有内存中索引,通常合并过程还是比较快的。
实际应用中,可以考虑必要时手动进行major compact,将同一个row key的修改进行合并形成一个大的StoreFile。同时,可以将StoreFile设置大些,减少split的发生。
当然,这里我只列出了在我的实践过程中感触比较深的情况,更多的hbase优化方式可以参考如下文章:
HBase性能优化方法总结
HBase 在淘宝的应用和优化
在这里没有黑mysql的意思,关系型数据库和nosql是一个很好的补充,谁也替代不了谁,只是我们需要根据具体的业务场景进行判断,选择适合我们的工具。关于正反两面的对立性,有时间优点往往也是缺点。
当然,hbase目前我觉得最大的缺点就是不支持条件查询(不知道最新的版本怎么样)。
还是那句话适用就好,遇到什么问题解决什么问题,更加完善的总结以及最新的实践经验后续继续补充。
本篇文章只是本人一个阶段性的关于hbase实践的总结,主要的作用还是基于自己思路梳理为出发点,此文中引用了很多别人之前已经整理过的一些现成资料,但是这些都是我在之前在实践过程中深刻体会到的。仅用于和大家交流使用。