硬件和操作系统优化
配置内存
Hbase 对于内存的消耗是非常巨大的,主要是其LSM树状结构、缓存机制和日志记录机制决定的,所以物理内存当然是越大越好。
配置CPU
Hbase 给使用者的印象更偏于 "内存型" NoSql 数据库,从而忽略了CPU方面的需求,其实Hbase在某些应用上对CPU的消耗非常大,例如频繁使用过滤器,因为在过滤器中包含很多匹配、搜索和过滤的操作;多条件组合扫描的场景也是CPU密集型的;压缩操作很频繁等。如果服务器CPU不够强悍,会导致整个集群负载非常高,很多线程都在阻塞状态(非网络阻塞或死锁的情况)。
垃圾回收器的选择
对于运行 Hbase 相关进程JVM的垃圾回收器,不仅仅关注吞吐量,还关注停顿时间,而且两者之间停顿时间更为重要,因为Hbase设计的初衷就是解决大规模数据集下实时访问的问题。配置方式,需要在 hbase-env.sh 文件中
export HBASE_OPTS="$HBASE_OPTS -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=60 -XX:+UseParNewGC -XX:ParallelGCThreads=6"
简要说明一下,-XX:+UseConcMarkSweepGC 表示年老代并发收集;
对于老年代来说, 它可以更早的开始回收。当分配在老年代的空间比率超过了一个阀值,CMS 开始运行。如果 CMS 开始的太晚,HBase 或许会直接进行 full garbage collection。这种情况会导致block所有的线程,如果这个时间过长,就会导致hbase连接超时,结果就是regionserver集体下线。这是不能容忍额。为了避免这种情况的发生,我们建议设置 -XX:CMSInitiatingOccupancyFraction JVM 参数来精确指定在多少百分比 CMS 应该被开始,正如上面的配置中做的那样。在 百分之 60 或 70 开始是一个好的实践。当老年代使用 CMS,默认的年轻代 GC 将被设置成 Parallel New Collector。
再来看看hbase为什么可能进行full gc,如果我们不配置-XX:CMSInitiatingOccupancyFraction,jdk1.5以后会使用默认值90%,那么很可能,当老年代内存占用超过分配给他的内存大小的90%,会进行CMS(老年代的回收),但是不会阻止年轻代到老年代的迁移,如果迁移过快,CMS较慢,会出现老年代内存使用率100%,这时会导致full gc。如果我们把这个参数调整小一点,那么能给年轻带到老年代迁移的同时做CMS时一些时间,也就减少了full gc的发生。当然这可能会频繁的gc,但总比整个hbase挂掉的好不是么?
JVM堆大小设置
堆内存大小参数hbase-env.sh 文件中设置
export HBASE_HEAPSIZE=16284
上面代码中指定堆内存大小是16284,单位是MB,即16GB。当然,这个值需要根据节点实际的物理内存来决定。一般不超过实际物理内存的二分之一。
Hbase 调优
调节数据库(dataBlock)的大小
HFile 数据库大小可以在列族层次设置。这个数据库不同于之前谈到的HDFS数据块,其默认值是65536字节,或64KB。数据块索引存储每个HFile数据块的起始键。数据块大小的设置影响数据块索引的大小。数据块越小,索引越大,从而占用更大内存空间。同时加载进内存的数据块越小,随机查找性能更好。但是,如果需要更好的序列扫描性能,那么一次能够加载更多HFile数据进入内存更为合理,这意味着应该将数据块设置为更大的值。相应地,索引变小,将在随机读性能上付出更多的代价。可以在表实例化时设置数据块的大小:
create 'table_name',{NAME => 'colfam1',BLOCKSIZE => '65536'}
适当时机关闭数据块缓存
把数据块放进读缓存,并不是一定能提示性能。如果一个表或表的列族只被顺序化扫描访问或很少被访问,则Get或Scan操作花费时间长一点是可以接受的。在这种情况下,可以选择关闭列族的缓存。
关闭缓存的原因在于:如果只是执行很多顺序化扫描,会多次使用缓存,并且可能会滥用缓存,从而把应该放进缓存获得性能提升的数据给排挤出去。
所以如果关闭缓存,不仅可以避免上述情况发生,而且可以让出更多缓存给其他表和同一表的其他列族使用。数据块缓存默认打开的。
create 'table_name',{NAME => 'colfam1',BLOCKSIZE => 'false'}
开启布隆过滤器
数据块索引提供了一个有效的方法 getDataBlockIndexReader(),在访问某个特定的行时用来查找应该读取的HFile的数据块。但是刚方法的作用有限。HFile 数据块的默认大小是 64KB,一边情况下不能调整太多。
如果需要查找一个很短的行,只在整个数据的起始行键上建立索引是无法给出更细粒度的索引信息的。例如:某行占用100字节存储空间,一个64KB的数据包含(64*1024)/100=655.53,约700行,只能把起始行放在索引位上。要查找的行可能落在特定数据块上的行区间,但也不能肯定存放在那个数据块上,这就导致多种可能性;该行在表中不存在,或者存放在另一个HFile中,甚至在MemStore中。这些情况下,从硬盘读取数据块会带来IO开销,也会滥用数据块缓存,这会影响性能,尤其是当面对一个巨大的数据集且有很多并发读用户时。
布隆过滤器允许对存储在每个数据块的数据做一个反向检测。当查询某行时,先检查布隆过滤器,看看该行是否不在这个数据块。布隆过滤器要么确定回答该行不在,要么回答不知道。因此称之为反向检查。布隆过滤器也可以应用到行内的单元格上,当访问某列标识符时先使用同样的反向检测。
使用布隆过滤器也不是没有代价,相反,存储这个额外的索引层次占用空间。布隆过滤器的占用空间大小随着它们的索引对象数据增长而增长,所以行级布隆过滤器比列标识符级布隆过滤器占用空间要少。当空间不是问题,它们可以压榨整个系统的性能潜力。
create 'table_name',{NAME => 'colfam1',BLOOMFILTER=> 'ROWCOL'}
布隆过滤器默认时NONE。另外,还有两个值:ROW 表示行级布隆过滤器;ROWCOL表示列标识符布隆过滤器。行级布隆过滤器在数据块中检查特定行键是否不存在,列标识符布隆过滤器检查行和列标识符联合体是否不存在。ROWCOL布隆过滤器的空间开销高于ROW 布隆过滤器。
开启数据压缩
HFile 可以被压缩并存放在HDFS上,有助于节省磁盘IO,但是读写数据时压缩和解压缩会太高CPU利用率。压缩时表定义的一部分,可以在建表或模式改变时设定。除非确定压缩不会提升系统的性能,否则推荐打开表的压缩。只有在数据不能被压缩,或者因为某些原因服务器的CPU利用率有限制要求的情况下,有可能需要关闭压缩特性。
Hbase 可以有多种压缩编码,包括LZO、SNAPPY和GZIP,LZO和SNAPPY是其中最流行的两种。
create 'table_name',{NAME => 'colfam1',COMPRESSION=> 'SNAPPY'}
数据实在磁盘上压缩的,内存中(MemStore和BlockCache)或在网络传输时是没有压缩的。
设置Scan缓存
Hbase的Scan查询中可以设置缓存,定义一次交互从服务器端传输到客户端的行数,设置方法是使用Scan类中 setCaching() 方法,这样能有效地减少服务器端和客户端的交互,更好地提升扫描查询的性能。
显示地指定列
当使用Scan或Get处理大量的行时,最好确定一下所需要的列。因为服务器端处理完的结果,需要通过网络传输到客户端,而且此时,传输的数据量成为瓶颈,如果能有效地过滤部分数据,使用更精确的需求,能够很大程度上减少网络IO的花费,否则会造成很大的资源浪费。如果在查询中指定某列或者某几列,能够有效地减少网络传输量,在一定程度上提升查询性能。
关闭ResultScanner
ResultScanner类用于存储服务端扫描的最终结果,可以通过遍历该类获取查询结果。但是,如果不关闭该类,可能会出现服务端在一段时间内保存连接,资源无法释放,从而导致服务器端某些资源的不可用,还有可能引发 HRegionServer 的其他问题。所以在使用完该类后,需要执行关闭操作。这一点与JDBC操作MYSQL类似,需要关闭连接。
使用批量读
通过调用 HTable.get(Get) 方法可以根据一个指定的行键获取Hbase表中的一行记录。同样Hbase提供了一个方法,通过调用 HTable.get(List<Get>) 方法可以根据一个指定的行键列表,批量获取多行记录。使用该方法可以在服务器端执行完批量查询后返回结果,降低网络传输的速度,节省网络IO开销,对于数据实时性要求高且网络传输RTT高的场景,能带来明显的性能提升。
使用批量写
通过调用 HTable.put(Put) 方法可以将一个指定的行键记录写入Hbase。同样Hbase提供了一个方法,通过调用 HTable.put(List<Put) 方法可以将指定多个行键批量写入,这样做的好处是批量执行,减少网络IO开销。
关闭写WAL日志
在默认情况下,为了保证系统的高可用性,写WAL日志默认开启状态。写WAL开启或关闭,在一定程度上确实会对系统性能产生很大影响,根据Hbase内部设计,WAL 是规避数据丢失风险的一种补偿机制,如果应用可以容忍一定的数据丢失的风险,可以尝试在更新数据时,闭关写WAL。该方法存在的风险是,当 HRegionServer 宕机时,可能写入的数据会出现丢失的情况,且无法恢复。关闭写WAL操作通过Put类中的 writeToWAL() 设置。
设置AutoFlush
Htable 有一个属性是 AutoFlush,该属性用于支持客户端的批量更新。该属性默认值是true,即客户端每收到一条数据,立刻发送服务端。如果该属性值为false,当客户端提交Put请求时,将该请求在客户端缓存,直到数据达到某个阀值的容量时(该容量参数 hbase.client.write.buff 决定)或执行hbase.flushcommits() 时,才向 HRegionServer 提交请求。这种方式避免了每次跟服务端交互,采用批量提交的方式,所以更高效。但是,如果还没有达到该缓存而客户端崩溃,该部分数据将由于未发送到 HRegionServer 而丢失。这对于有些零容忍的在线服务是不可接受的。所以,设置该参数的时候要慎重。
table.setAutoFlush(false);
# 达到12M发出去
table.setWriteBufferSize(12*1024*1024);
# 或者手动发送
table.flushCommits();
预创建Region
在Hbase中创建表时,该表一开始只有一个Region,插入该表的所有数据会保存在该Region中。随着数据量不断增加,当该Region大小达到一定阀值时,就会发生分裂(Region Splitting)操作。并且在这个表创建后相当长的一段时间内,针对该表的所有写操作总是集中在某一台或者少数几台机器上,这不仅仅造成局部磁盘和网络资源紧张,同时也是对整个集群资源的浪费。这个问题在初始化表,即批量导入原始数据的时候,特别明显。为了解决这个问题,可以使用预创建Region的方法。
Hbase内部提供了 RegionSplitter 工具,使用命令如下
${HBASE_HOME}/bin/hbase org.apache.hadoop.hbase.util.RegionSplitter table_name HexStringSplit -c 10 -f cf1
其中,table_name是表名,HexStringSplit表示划分的算法,参数-c 10 表示预创建10个Region,-f cf1 表示创建一个名字为 cf1 的列族。
调整zookeeper Session的有效时长
参数zookeeper.session.timeout用于定义连接Zookeeper的Session的有效时长,这个默认值是180秒。这意味着一旦某个 HRegionServer 宕机,HMaster 至少需要180秒才能察觉到宕机,然后开始恢复。或者客户端读写过程中,如果服务端不能提供服务,客户端直到180秒后才能察觉到。在某些场景中,这样的时长可能对生产线业务来将不能容忍,需要调整这个值。在hbase-site.xml 中配置。
网友评论