接下来添加部分HBase优化相关的选择,先添加部分,后续实际工作中再补充完善。优化主要针对外界条件以及自身条件。
外界条件包括硬件配置、GC、JVM等。
HBase是吃内存的,很多地方都有涉及到,如blockcache读缓存,memStore写缓存,LSM树状结构和日志记录机制等都用到了内存,因此内存当然是越大越好,实际应用中会给HBase配置60~65%的物理内存。
当HBase中频繁使用过滤器,会涉及到计算,搜索和过滤等操作,这会比较耗费CPU,此外多条件组合扫描、使用压缩算法操作等,会给CPU带来压力,如果CPU性能低将有可能导致工作线程在阻塞等待状态。CPU主流2到8路,推荐使用双路四核CPU,一般的单核CPU至少需要四核。
HBase是基于java编写的,GC回收器的选择也将影响整个程序的吞吐能力和停顿时间。以HotPot提供的虚拟机为例,它提供了多种新生代GC收集器+老年代GC收集器的组合选择,不同的组合,会有不同的应用场景。
对于HBase来说,它的出发点是低延迟的查询,因此需要有能提供低停顿时间的GC收集器,在老年代中CMS是一个不错的选择,而新生代中能跟它搭配使用的就只剩下Serial和ParNew,前者是老版本JDK的选择,是单线程的,而ParNew是基于多线程的,考虑更好性能,新生代中选择ParNew。
在hbase中通过hbase-env.sh来设置。
# 打开后,将使用ParNew+CMS+Serial Old的组合进行内存回收,Serial Old是备用的,当使用CMS出现大量Concurent Model Failure后,它会作为backup使用
export HBASE_OPTS="-XX:+UseConcMarkSweepGC"
# 设置CMS收集器在老年代空间被使用多少百分比后触发垃圾回收
# -XX:CMSInitiatingOccupancyFraction=70
# 设置CMS收集器在完成垃圾回收后是否要进行一次内存碎片整理
# -XX:+UseCMSCompactAtFullCollection"
堆大小一般设置不超过物理内存的一半,还要留下一部分内存给操作系统和Yarn。它也可以通过hbase.env.sh里来添加设置。
# 16384MB,默认16G export HBASE_HEAPSIZE=16384
自身就是配置hbase相关参数,根据使用场景进行必要调整。
datablock大小默认为64KB,可以在建表时指定,在列族层面进行设置,如下图设置为65536字节,即64KB。设置大,datablock有利于顺序读,设置小,则有利于字段度,根据实际的需要来调整。
根据HFile的结构,它包含datablock以及对应的dataindex和trailer,如果datablock设置小,则一个HFile索引内容就更多会更占空间,如果datablock设置大,则索引内容就少,相对来说HFile中能包含更多的数据。
# 建表时指定大小
hbase(main):002:0> create ‘star‘,{NAME => ‘basic‘, BLOCKSIZE => ‘65536‘}
blockcache默认是开启的,但是在顺序读的场景,如果读取的内容很多将很容易挤爆读缓存,如要读取1G的数据,这样的情况下开启blockcache不是最好的选择,它可能因此将本应该缓存以提高读取性能的数据挤出去,因此这种情况可以关闭它。
可以在新建表或者修改表时关闭blockcache。
hbase(main):002:0> create ‘star‘, {NAME => ‘basic‘, BLOCKCACHE => ‘false‘}
布隆过滤器开启会提高读取速度,当查询某行是否在某个datablock时询问布隆过滤器,它将返回一个‘确定不在‘或者‘不确定‘的答案。除此之外,布隆过滤器还能精细到行级别和列标识符级别,如果使用行级别布隆过滤器,它可以判断某个行键是否在datablock中不存在,如果是列标识符级别布隆过滤器,它可以判断某个行和列标识符联合体是否在datablock中不存在。
但是使用它也是有代价的,因为布隆过滤器底层维系着一个byte数组,使用它势必会占用一定的空间,其中列标识符布隆过滤器的空间开销高于行级布隆过滤器。
可以在列族层面打开布隆过滤器。
# BLOOMFILTER默认为NONE,行级为COL,列标识符级为ROWCOL create ‘star‘, {NAME => ‘basic‘, BLOOMFILTER => ‘ROWCOL‘}
一般在CPU压力不大的情况下建议开启,因为数据的压缩和解压缩本身需消耗部分CPU资源。开启数据压缩后可以节省网络传输压力,主流的压缩算法为snappy、LZO、GZIP等,其中snappy是其中的佼佼者,它因为冠绝的压缩比和压缩速率让其他在座的压缩算法都变得‘垃圾‘。
在建表时可以在列族上打开压缩。
# 指定snappy算法
create ‘star‘, {NAME => ‘basic‘, COMPRESSION => ‘SNAPPY‘}
另外,数据只在硬盘上是压缩的,在内存中(memStore或blockcache)或在网络传输时是没有压缩的。要配置snappy等外来算法,需要编译配置,否则是不能使用的。
scan查询可以缓存,使用setCaching方法,这样可以减少客户端和服务端的交互,提升扫描查询的性能,下面是示意代码。
//设置scan缓存 @Test public void test() throws IOException { HTable table = new HTable(conf, TableName.valueOf("employer")); Scan scanner = new Scan(); /* 设置cache,减少客户端和服务端的交互 */ scanner.setBatch(0); scanner.setCaching(10000); ResultScanner rs = table.getScanner(scanner); //TODO,自定义 rs.close(); }
hbase能提供百万列,因此如果查询行键对应的列数据很多时,服务端通过网络传输到客户端将耗费大量网络资源,如果能指定查询的列,将可以减少网络IO开销,下面是使用示意代码。
//扫描时显示指定列 @Test public void readColumn() throws IOException { HTable table = new HTable(conf, Bytes.toBytes("employer")); Scan scanner = new Scan(); /* 指定列 */ scanner.addColumn(Bytes.toBytes("basic"), Bytes.toBytes("name"));//name为列,basic为列族 ResultScanner rsScanner = table.getScanner(scanner); //TODO,自定义 rsScanner.close(); }
上面示意代码中都需要关闭ResultScanner,因为它本质上是一个流,使用完后应该关闭释放资源,这点和JDBC类似。
前面在记录hbase基本API使用中(https://www.cnblogs.com/youngchaolin/p/12179320.html),就有批量插入的的代码,hbase为我们提供了批量读和批量写的方法。HTable对象的put(List)和get(List)方法,都可以传入一个List集合,实现批量操作,这可以降低网络IO开销。
WAL日志设计的作用,是用来规避因为HRegionserver宕机而产生的数据丢失的风险,如果业务场景能承受这种风险,可以关闭写WAL日志。代码中可以通过Put实例对象的setWriteToWAL(false)来关闭。
@Test public void putData() throws IOException { HTable table=new HTable(conf,TableName.valueOf("employer")); //添加数据 //将每一条数据封装为put对象 Put put=new Put("e1".getBytes());//给定rowKey //关闭写WAL日志 put.setWriteToWAL(false); put.add("basic".getBytes(),"name".getBytes(),"clyang".getBytes()); put.add("basic".getBytes(),"age".getBytes(),"28".getBytes()); //添加数据 table.put(put); //关流 table.close(); }
HTable有一个属性AutoFlush,默认是true,即客户端每收到一条数据,立刻发送给服务端,这样是不高效的,可以将其关闭,先put请求缓存到客户端,最后再批量提交到服务端。有两种情况会批量提交,一是达到了write buffer size的阈值,一种是执行table.flushCommit()方法。
关闭AutoFlush有风险,当批量缓存到客户端后,在提交到服务端之前客户端宕机了,会导致缓存丢失,因此关闭它也是需要业务场景能容忍丢失数据。
//关闭AutoFlush @Test public void autoFlush() throws IOException { HTable table=new HTable(conf,TableName.valueOf("employer")); //关闭AutoFlush功能,后面批量提交 table.setAutoFlush(false); table.setWriteBufferSize(10*1024*1024);//10M //添加数据 //将每一条数据封装为put对象 Put put=new Put("e1".getBytes());//给定rowKey put.add("basic".getBytes(),"name".getBytes(),"clyang".getBytes()); put.add("basic".getBytes(),"age".getBytes(),"28".getBytes()); //TODO put不断添加数据,略了 //添加数据 table.put(put); //批量提交 table.flushCommits(); //关流 table.close(); }
创建表时默认只有一个HRegion(默认10G),在导入大容量数据(假设200G)到hbase的场景下,可以预先创建HRegion,这样比自动分裂要更加的节省资源,因为自动分裂产生的HRegion数目会比预先创建的数目要多。可以通过hbase提供的RegionSplitter来执行操作,如下所示。
# star2是表名,HexStringSplit表示划分的算法,参数-c 10表示预创建10个Region,-f basic表示创建一个名字为basic的列族 [root@hadoop01 /home/software/hbase-0.98.17-hadoop2/bin] sh hbase org.apache.hadoop.hbase.util.RegionSplitter star2 HexStringSplit -c 10 -f basic
zookeeper session的有效时长,是在zookeeper.session.timeout中设置的,默认是180s,这种时间一般需要修改。hbase的节点注册在zookeeper上,如果一个HRegionserver宕机,HMaster需要180s才能监听到,zookeeper会将这个HRegionserver从/hbase/rs节点中移除,HMaster会对被移除HRegionserver管理的HRegion进行balance,交给其他提供服务的HRegionserver来管理。一般会将这个时间设置小一些,参考《HBase权威指南》,可以在hbase-site.xml中添加如下设置。
<!--设置为30s,单位是ms,默认是180000ms-->
<property> <name>zookeeper.session.timeout</name> <value>30000</value> </property>
以上是对HBase优化的初步学习,后续持续添加。
参考博文:
(1)《深入理解Java虚拟机》周志明
(2)https://www.cnblogs.com/youngchaolin/p/12187151.html
(3)https://www.cnblogs.com/ukouryou/articles/2679559.html zookeeper session设置相关
(4)《HBase权威指南》zookeeper配置session相关
原文:https://www.cnblogs.com/youngchaolin/p/12201045.html