1.概述
HBase是一个实时的非关系型数据库,用来存储海量数据。但是,在实际使用场景中,在使用HBase API查询HBase中的数据时,有时会发现数据查询会很慢。本篇博客将从客户端优化和服务端优化两个方面来介绍,如何提高查询HBase的效率。
2.内容
这里,我们先给大家介绍如何从客户端优化查询速度。
2.1 客户端优化
客户端查询HBase,均通过HBase API的来获取数据,如果在实现代码逻辑时使用API不当,也会造成读取耗时严重的情况。
2.1.1 Scan优化
在使用HBase的Scan接口时,一次Scan会返回大量数据。客户端向HBase发送一次Scan请求,实际上并不会将所有数据加载到本地,而是通过多次RPC请求进行加载。这样设计的好处在于避免大量数据请求会导致网络带宽负载过高影响其他业务使用HBase,另外从客户端的角度来说可以避免数据量太大,从而本地机器发送OOM(内存溢出)。
默认情况下,HBase每次Scan会缓存100条,可以通过属性hbase.client.scanner.caching来设置。另外,最大值默认为-1,表示没有限制,具体实现见源代码:
/** * @return the maximum result size in bytes. See {@link #setMaxResultSize(long)} */ public long getMaxResultSize() { return maxResultSize; } /** * Set the maximum result size. The default is -1; this means that no specific * maximum result size will be set for this scan, and the global configured * value will be used instead. (Defaults to unlimited). * * @param maxResultSize The maximum result size in bytes. */ public Scan setMaxResultSize(long maxResultSize) { this.maxResultSize = maxResultSize; return this; }
一般情况下,默认缓存100就可以满足,如果数据量过大,可以适当增大缓存值,来减少RPC次数,从而降低Scan的总体耗时。另外,在做报表呈现时,建议使用HBase分页来返回Scan的数据。
2.1.2 Get优化
HBase系统提供了单条get数据和批量get数据,单条get通常是通过请求表名+rowkey,批量get通常是通过请求表名+rowkey集合来实现。客户端在读取HBase的数据时,实际是与RegionServer进行数据交互。在使用批量get时可以有效的较少客户端到各个RegionServer之间RPC连接数,从而来间接的提高读取性能。批量get实现代码见org.apache.hadoop.hbase.client.HTable类:
public Result[] get(List<Get> gets) throws IOException { if (gets.size() == 1) { return new Result[]{get(gets.get(0))}; } try { Object[] r1 = new Object[gets.size()]; batch((List<? extends Row>)gets, r1, readRpcTimeoutMs); // Translate. Result [] results = new Result[r1.length]; int i = 0; for (Object obj: r1) { // Batch ensures if there is a failure we get an exception instead results[i++] = (Result)obj; } return results; } catch (InterruptedException e) { throw (InterruptedIOException)new InterruptedIOException().initCause(e); } }
从实现的源代码分析可知,批量get请求的结果,要么全部返回,要么抛出异常。
2.1.3 列簇和列优化
通常情况下,HBase表设计我们一个指定一个列簇就可以满足需求,但也不排除特殊情况,需要指定多个列簇(官方建议最多不超过3个),其实官方这样建议也是有原因的,HBase是基于列簇的非关系型数据库,意味着相同的列簇数据会存放在一起,而不同的列簇的数据会分开存储在不同的目录下。如果一个表设计多个列簇,在使用rowkey查询而不限制列簇,这样在检索不同列簇的数据时,需要独立进行检索,查询效率固然是比指定列簇查询要低的,列簇越多,这样影响越大。
而同一列簇下,可能涉及到多个列,在实际查询数据时,如果一个表的列簇有上1000+的列,这样一个大表,如果不指定列,这样查询效率也是会很低。通常情况下,在查询的时候,可以查询指定我们需要返回结果的列,对于不需要的列,可以不需要指定,这样能够有效地的提高查询效率,降低延时。
2.1.4 禁止缓存优化
批量读取数据时会全表扫描一次业务表,这种提现在Scan操作场景。在Scan时,客户端与RegionServer进行数据交互(RegionServer的实际数据时存储在HDFS上),将数据加载到缓存,如果加载很大的数据到缓存时,会对缓存中的实时业务热数据有影响,由于缓存大小有限,加载的数据量过大,会将这些热数据“挤压”出去,这样当其他业务从缓存请求这些数据时,会从HDFS上重新加载数据,导致耗时严重。
在批量读取(T+1)场景时,建议客户端在请求是,在业务代码中调用setCacheBlocks(false)函数来禁止缓存,默认情况下,HBase是开启这部分缓存的。源代码实现为:
/** * Set whether blocks should be cached for this Get. * <p> * This is true by default. When true, default settings of the table and * family are used (this will never override caching blocks if the block * cache is disabled for that family or entirely). * * @param cacheBlocks if false, default settings are overridden and blocks * will not be cached */ public Get setCacheBlocks(boolean cacheBlocks) { this.cacheBlocks = cacheBlocks; return this; } /** * Get whether blocks should be cached for this Get. * @return true if default caching should be used, false if blocks should not * be cached */ public boolean getCacheBlocks() { return cacheBlocks; }

