原创文章,转载请注明原作地址:http://www.jianshu.com/p/1e1646e1b38d
提高HBase响应速度的技巧有许多,将个人经验大致整理成了以下三个模块。
一. 建表技巧
1. 合理设计列族
一张HBase表的列族数量最好控制在三个以内,因为当一个列族的MemStore中的数据量达到阈值时,会引起同一个region的所有columnFamily的MemStore进行flush操作,即使其中某些列族MemStore中的数据量还很小。因此,如果有很多列族的话,会产生许多小文件,可能会引起很多不必要的flush和compact操作,导致不必要的I/O负载。因此,在设计表结构时,尽可能使用一个列族,除非每次查询的时候,查询粒度是列,而不是行。
如果有一个数据量小的列族经常要做全表扫描,那么这个列族最好不要和数据量大的列族放在同一张表。因为不同列族的分区数是一致的,如果有一个列族的数据量很大,导致表被分割成了多个分区,那么数据量很小列族的数据会分布在很多分区,导致做该列族做全表扫描的时候,效率低下。如果大部分是随机取的情况,数据量小的列和数据量大的列尽量不要放在同一列族中,尤其在我们经常做列查找,而不是行查找的情况下。举个例子,列A和列B属于同一列族,其中列B的数据量远大于列A,当客户端查找列A中某一行的所有记录,迫使HBase扫描所有底层文件以找到所有属于该行的记录,因为同一列族中的数据会被写入同一个底层文件,HBase在扫描列A的同时,实际上也扫描了列B的所有数据,做了大量无用功。
2. 使用块缓存
HBase的所有存储文件都被划分成了若干个小存储块,存储块的默认大小是64KB[1]。当HBase顺序地读取一个数据块到内存缓存中时,其读取相邻的数据时就可以在内存中读取,而不需要再次读取磁盘,可以有效减少磁盘I/O的次数,提高了I/O效率。这个参数默认为true,意味着每次读取的块都会缓存到内存中。但是,如果用户需要顺序读取列族,最好将这个属性设置为false,从而禁止其使用块缓存,以免有用的缓存流失。
3. 在内存中
除了利用缓存块来提高连续访问的效率以外,还有一个在内存中(in-memory)标志,默认值为false。当这个参数设置true时,并不意味着整个列族的所有存储块都会被加载到内存中,也不意味这内存中的数据会被长期保留,而代表一种高优先级的承诺。在正常的数据读取过程中,块数据会被加载到缓存区并长期驻留在内存中,除非堆压力过大,才会从内存中强制卸载这部分数据。需要注意的是,这个参数通常适合数据量较小的列族,例如保存登陆账号和密码的用户表,将这个参数设置为true有利于提升这个环节的处理速度。
4. 布隆过滤器
布隆过滤器属于HBase系统中的高级功能,能够减少特定访问模式下的查询时间,有需要的话,可以参考我的另一篇博文《布隆过滤器在HBase中的应用》。但是,布隆过滤器加重了内存和存储的负担,因此默认情况下是关闭的状态。
5. 生存期TTL
HBase不仅可以设置每个值能保存的最大版本数,也支持处理版本数据保存时间。在major合并过程中,时间戳被判定为超过TTL的数据会被删除。
6. 使用压缩
压缩的作用是减小存储文件的大小,一方面可以节省存储空间,更重要的是,因为文件变小了,文件读入内存的速度也提高了。当然,压缩和解压也会耗费一定的时间(也会导致compact操作的时间变长),因此需要小心衡量性能瓶颈是在IO还是在CPU。不同的算法压缩率也有所不同,压缩率越高的算法,需要耗费的时间也越长,因此应该根据实际情况,选择合适的压缩算法。
在这里补充一点,对一个已经存在的表,可以通过alter table的方式,开启压缩或者更改压缩算法。然而,这个操作并不会立刻起作用,因为之前产生的存储文件依然保持更改之前的状态。若想强制重写这些文件,可以通过major_compact强制进行major合并,新产生的文件自然是我们想要的格式。
7. 预拆分Region
HBase可以自动管理region拆分,当region中的数据量达到一定阈值,HBase会将其拆分成两个region继续工作。然而,设想当用户所有的region以相同的速率增长,最后它们会在同一时间发生region拆分,拆分过程中需要重写底层文件(compaction),引起磁盘I/O上升,影响集群响应速度,这就是俗称的“split/compaction storms”(拆分/合并风暴)。
为了避免这个问题,解决方案之一是,关闭拆分功能(将拆分阈值设成无限大),然后由用户手动调用命令进行region拆分。这样做的好处是,用户可以控制拆分的时间,拆分可以在集群或者表空闲的时间进行,并且如果不同的region在不同时间拆分,能够分散I/O负载。第二种方法是,在创建表的时候,就预先将表按照给定的行键,划分成多个分区。这样做的好处是,当用户能预先估计到表会增长到很大规模时,预先拆分成多个分区,可以有效避免拆分合并风暴;另一方面,多个分区会交给多个regionServer管理,通过在行键前面添加分区号,可以将读写压力均匀地分配到多个server,可以解决读写热点问题。
二. 查询技巧
1. 多并发读写提升吞吐量
HBase的优势不在于对单条请求的响应速度,而在于整个集群的吞吐量高。因此,倘若想提高客户端读写速度,最直接的一个方法就是多并发读写。
2. 批量处理请求
和其它数据库类似,HBase提供了批量处理操作的API,批量处理请求可以利用好RPC时间,提高单个客户端的处理效率。
3. 全表扫描时关闭块缓存功能
HBase提供了读缓存,当读取一条记录时,会将对应的块读到读内存中。对于某些频繁访问的行,这个功能可以提高读取速度。然而,当用户需要做全表扫描时,应记得关闭这个功能,避免读缓存扰动、缓存命中率下降。
4. 扫描时使用扫描缓存
HBase的扫描器在获取数据时,会为每行数据生成一个单独的RPC请求,即使用户显式指定了要获取n行的数据,扫描器也会向服务器发送n个RPC请求。为了一次RPC请求可以获取多行数据,用户必须显式开启扫描器的扫描缓存功能。
5. 严格限制查找范围
使用行键查找对应的值无疑是效率最高的查找方式。不过,如果在查找请求中添加一些额外的条件,也有助于提高查找效率:限定列族可以避免扫描其它列族的存储文件;限定时间戳在4小时以内,可以跳过一些最后修改时间在4小时之前的文件;限定列名可以控制返回给客户端的数据量,降低传输时间和网络流量。
三. 其它
1. 定期触发major_compact
太频繁地major_compact会给集群带来I/O压力,不过当表中有太多的已删除数据影响了查找速度的话,执行major_compact彻底删除这些数据无疑是最佳选择。目前,我们在使用的是hbase-1.0.0-cdh5.4.5版本,默认的major_compact周期是7天,用户也可以通过命令行或者客户端API手动触发compact操作。
2. 尽量使用简短的列名
HBase的数据存储是key-value的形式,也就是<行键-列族-列-时间戳,值>。换句话说,为每一个单元格,HBase都存储了列名,因此使用较简短的列名也是一个好习惯。
*注:[ 1 ] * 注意,HFile的块和HDFS的块没有直接关系。HDFS的块用于拆分大文件以提供分布式存储,另外便于MR框架进行并行计算;而HBase的块主要用于高效加载和缓存数据,并不依赖于HDFS的块大小,并且只用于HBase内部。