全面总结Hbase优化方案

Hbase应用
HBase是一个高可靠、高性能、面向列、可伸缩的分布式数据库,是谷歌BigTable的开源实现,主要用来存储非结构化和半结构化的松散数据。HBase的目标是处理非常庞大的表,可以通过水平扩展的方式,利用廉价计算机集群处理由超过10亿行数据和数百万列元素组成的数据表。

因此,对HBase性能提出了一定的要求,那么如何进行HBase性能优化呢?

对表的设计进行优化

1、RowKey 设计

在HBase中,rowkey可以是任意字符串,最大长度64KB,实际应用中一般为10~100bytes,存为byte[]字节数组,一般设计成定长的。
row key是按照字典序存储,因此,设计row key时,要充分利用这个排序特点,
将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。

举个例子:
如果最近写入HBase表中的数据是最可能被访问的,可以考虑将时间戳作为row key的一部分,由于是字典序排序,所以可以使用Long.MAX_VALUE - timestamp作为row key,这样能保证新写入的数据在读取时可以被快速命中。

一条数据的唯一标识就是 rowkey,那么这条数据存储于哪个分区,取决于
rowkey 处于哪个一个预分区的区间内,设计 rowkey 的主要目的 ,就是让数据
均匀的分布于所有的 region 中,在一定程度上防止数据倾斜(数据热点)。
下面来介绍几个rowkey 常用的设计方案。

(1).生成随机数、hash、散列值
比如:
原本 rowKey 为 1001 的,SHA1 后变成:
dd01903921ea24941c26a48f2cec24e0bb0e8cc7
原本 rowKey 为 3001 的,SHA1 后变成:
49042c54de64a1e9bf0b33e00245660ef92dc7bd
原本 rowKey 为 5001 的,SHA1 后变成:
7b61dec07e02c188790670af43e717f0f46e8913
在做此操作之前,一般我们会选择从数据集中抽取样本,来决定什么样的 rowKey
来 Hash 后作为每个分区的临界值。

(2).字符串反转
比如:
20170524000001 转成 10000042507102
20170524000002 转成 20000042507102
这样也可以在一定程度上散列逐步 put 进来的数据。

(3).字符串拼接
比如:
20170524000001_a12e
20170524000001_93i7

2、列簇的设计

不要在一张表里定义太多的column family。目前Hbase并不能很好的处理超过2~3个column family的表。
因为某个column family在flush的时候,它邻近的column family也会因关联效应被触发flush,最终导致系统产生更多的I/O。

3、In Memory

创建表的时候,可以通过HColumnDescriptor.setInMemory(true)将表放到RegionServer的缓存中,保证在读取的时候被cache命中。

4、Max Version

创建表的时候,可以通过HColumnDescriptor.setMaxVersions(int maxVersions)设置表中数据的最大版本,如果只需要保存最新版本的数据,那么可以设置setMaxVersions(1)。

5、Time To Live

创建表的时候,可以通过HColumnDescriptor.setTimeToLive(int timeToLive)设置表中数据的存储生命期,过期数据将自动被删除;
例如如果只需要存储最近两天的数据,那么可以设置setTimeToLive(2 * 24 * 60 * 60)。(相当于Linux中的Crontab任务)。

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的发生。

compaction介绍
hbase为了防止小文件(被刷到磁盘的menstore)过多,以保证保证查询效率,hbase需要在必要的时候将这些小的store file合并成相对较大的store file,这个过程就称之为compaction。
在hbase中,主要存在两种类型的compaction:minor compaction 和 major compaction。

minor compaction:对较小、数量很少文件的合并。

major compaction 的功能是将所有的store file合并成一个
触发major compaction的可能条件有:major_compact 命令、majorCompact() API、region server自动运行(相关参数:hbase.hregion.majoucompaction 默认为24 小时hbase.hregion.majorcompaction.jetter 默认值为0.2 防止region server 在同一时间进行major compaction)。

hbase.hregion.majorcompaction.jetter参数的作用是:对参数hbase.hregion.majoucompaction 规定的值起到浮动的作用;
假如两个参数都为默认值24和0,2,那么major compact最终使用的数值为:19.2~28.8 这个范围。

minor compaction的运行机制要复杂一些,它由一下几个参数共同决定:

hbase.hstore.compaction.min :默认值为 3,表示至少需要三个满足条件的store file时,minor compaction才会启动

hbase.hstore.compaction.max 默认值为10,表示一次minor compaction中最多选取10个store file

hbase.hstore.compaction.min.size 表示文件大小小于该值的store file 一定会加入到minor compaction的store file中

hbase.hstore.compaction.max.size 表示文件大小大于该值的store file 一定会被minor compaction排除

hbase.hstore.compaction.ratio 将store file 按照文件年龄排序(older to younger),minor compaction总是从older store file开始选择

多线程并发写

在客户端开启多个HTable写线程,每个写线程负责一个HTable对象的flush操作,这样结合定时flush和写buffer(writeBufferSize),可以既保证在数据量小的时候,数据可以在较短时间内被flush(如1秒内),同时又保证在数据量大的时候,写buffer一满就及时进行flush。

核心代码如下:


for (int i = 0; i < threadN; i++) {
    Thread th = new Thread() {
        public void run() {
            while (true) {
		try {
                    sleep(1000); //1 second
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
 synchronized (wTableLog[i]) {
                    try {
                        wTableLog[i].flushCommits();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
 }
    };
    th.setDaemon(true);
    th.start();
}

多HTable并发读

创建多个HTable客户端用于读操作,提高读数据的吞吐量

static final Configuration conf = HBaseConfiguration.create();
static final String table_log_name = “user_log”;
rTableLog = new HTable[tableN];
for (int i = 0; i < tableN; i++) {
      rTableLog[i] = new HTable(conf, table_log_name);
    rTableLog[i].setScannerCaching(50);
}

内存管理

HBase上的Regionserver的内存主要分为两部分,一部分作为Memstore,主要用来写;一部分作为BlockCache,主要用于读。
写请求会先写入Memstore,Regionserver会给每个region提供一个Memstore,当Memstore满128M(hbase.hregion.memstore.flush.size)以后,会启动flush刷新到磁盘,当Memstore的总大小超过限制时(heapsizehbase.regionserver.global.memstore.upperLimit0.9),会强行启动flush进程,从最大的Memstore开始flush知道低于限制。
读请求先到Memstore中查数据,查不到就到BlockCache中查,再查不到就会到磁盘上读,并把读的结果放入BlockCache。
由于ClockCache采用的是LRU(最近最少使用)策略,因此BlockCache达到上限(heapsizehfile.block.cache.size0.85)后,会启动淘汰机制,淘汰掉最老的一批数据。
在注重读响应时间的应用场景下,可以将BlockCache设置大些,Memstore设置小些,以加大缓存命中率。

如果不希望自动触发溢写,就将值调大
<name>hbase.hregion.memstore.flush.size</name>
    <value>134217728</value>
一般在企业中这个参数是禁用的
<name>hbase.hregion.majorcompaction</name>
   <value>604800000</value>
直接将值设置为0就可以了,表示禁用

何时执行split
<name>hbase.hregion.max.filesize</name>
    <value>10737418240</value>
一般建议将值调大,在期间手动去触发split
  

Memstore刷写数据到磁盘时,造成RegionServer内存碎片增多,当生存时间较长的数据从堆的老年代空间刷写到磁盘,就会产生内存孔洞。
由于碎片过多导致没有足够大的连续内存空间,JVM就会暂停工作进程,进行垃圾回收(GC),导致HBase的RegionServer对外服务停顿。
本地Memstore缓存机制:
启用本地memstore分配缓存区(Memstore-Local Allocation Buffers,MSLAB),也就是允许从堆中分配相同大小的对象,
一旦这些对象分配并且最终被回收,就会在堆中留下固定大小的孔洞,这些孔洞可被重复利用,GC就无需使应用程序进程停顿来回收内存空间,配置参数hbase.hregion.memstore.mslab.enabled,默认为true。

启动SNAPPY压缩

HBase列存储,比较占用空间,所以一般需要采用压缩算法,(从mysql导入hbase数据时发现,原本在mysql中30G数据,在hbase中占用150G),
其中snappy性能优异,而且CDH中,直接安装了snappy的库。

使用方式:
       1、创建时指定格式;
       create 'test', { NAME => 'c', COMPRESSION => 'SNAPPY' }
       2、修改已经创建好的列簇的压缩格式
       disable 'test'
       alter 'test', NAME => 'c', COMPRESSION => 'snappy'
       enable 'test'
       major_compact 'test'   --修改之后,需要做一个major合并才能养压缩格式生效
       describe 'test'  --查看有没有生效

注意:
snappy是需要单独下载并编译安装的

预创建Region

创建HBase时,就预先根据可能的RowKey划分出多个Region而不是默认的一个,从而可以将后续的读写操作负载均衡到不同的Region上,避免热点现象;
HBase表的预分区需结合业务场景来选择分区的key值,每个region都有一个startKey和一个endKey来表示该region存储的rowKey范围;

//有以下四种创建方式:
create 'ns1:t1' , 'f1' , SPLITS => ['10','20','30','40'] ;
create 't1','f1',SPLITS_FILE => 'splits.txt', OWNER=> 'johnode' ;
//其中splits.txt文件内容是每行一个rowkey值
create 't1','f1',{NUMREGIONS => 15, SPLITALGO =>'HexStringSplit'}

JavaAPI
        HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(weibo_content));
        HColumnDescriptor family = new HColumnDescriptor(Bytes.toBytes("cf"));
        // 开启列簇 -- store的块缓存
        family.setBlockCacheEnabled(true);
        family.setBlocksize(1024 * 1024 * 2);
        family.setCompressionType(Algorithm.SNAPPY);
        family.setMaxVersions(1);
        family.setMinVersions(1);

	desc.addFamily(family);
	byte[][] splitKeys = { Bytes.toBytes("100"), Bytes.toBytes("200"), Bytes.toBytes("300") };
        admin.createTable(desc, splitKeys);

负载均衡

balance_switch,master用来均衡各个regionserver上region数量

避免Region热点

热点现象:某个小的时段内,对HBase的读写请求集中到极少数的Region上,导致这些Region所在的RegionServer处理请求量骤增,负载量明显偏大,而其他的RegionServer明显空闲;

出现的原因:主要是因为Hbase表设计时,rowKey设计不合理造成的;
RowKey设计原则:

1、总的原则:避免热点现象,提高读写性能;
2、长度原则:最大长度64KB,开发通常10-16个字节,因为Hbase中每个单元格是以key-value进行存储的,因此每个value都会存储rowkey,所以rowkey越来越占空间;
3、散列原则:将时间上连续产生的rowkey散列化,以避免集中到极少数Region上;
4、唯一原则:必须在设计上保证rowkey的唯一性;
具体操作在前面没寂静讲过就不多做概述了!

基础优化

1.允许在 HDFS 的文件中追加内容
hdfs-site.xml、hbase-site.xml
属性:dfs.support.append
解释:开启 HDFS 追加同步,可以优秀的配合 HBase 的数据同步和持久化
Hadoop2.0 默认值为 true。

2.优化 DataNode 允许的最大文件打开数
hdfs-site.xml
属性:hdfs.datanode.max.transfer.threads
解释:HBase 一般都会同一时间操作大量的文件,根据集群的数量和规模以及数据动作,设置为 4096 或者更高。默认值:4096。该操作前提是 Linux 的文件最大数已经设置完成。

3.优化延迟高的数据操作的等待时间
hdfs-site.xml
属性:dfs.image.transfer.timeout
解释:如果对于某一次数据操作来讲,延迟非常高,socket 需要等待更长的时间,建议把该值设置为更大的值(默认 60000 毫秒),以确保 socket 不会被timeout 掉。

4.优化数据的写入效率
mapred-site.xml
属性
mapreduce.map.output.compress
mapreduce.map.output.compress.codec
解释:开启这两个数据可以大大提高文件的写入效率,减少写入时间。第一个属性值修改为 true ,
第 二 个 属 性 值 修 改 为 :
org.apache.hadoop.io.compress.GzipCodec 或者其他压缩方式

5.设置 RPC 监听数量
hbase-site.xml
属性:hbase.regionserver.handler.count
解释:默认值为 30,用于指定 RPC 监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值.

6.优化 HStore 文件大小
hbase-site.xml
属性:hbase.hregion.max.filesize
解释:默认值 10737418240(10GB),如果需要运行 HBase 的 MR 任务,可以减小此值,因为一个 region 对应一个 map 任务,如果单个 region 过大,会导致 map 任务执行时间过长。该值的意思就是,如果 HFile 的大小达到这个数值,则这个 region 会被切分为两个 Hfile。

7.优化 hbase 客户端缓存
客户端配置
属性:hbase.client.write.buffer
解释:用于指定 HBase 客户端缓存,增大该值可以减少 RPC 调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少 RPC 次数的目的。

8.指定 scan.next 扫描 HBase 所获取的行数
客户端配置
属性:hbase.client.scanner.caching
解释:用于指定 scan.next 方法获取的默认行数,值越大,消耗内存越大。

9.flush、compact、split 机制
当 MemStore 达到阈值,将 Memstore 中的数据 Flush 进 Storefile;compact机制则是把 flush 出来的小文件合并成大的 Storefile 文件。
split 则是当 Region 达到阈值,会把过大的 Region 一分为二。

涉及属性:
即:128M 就是 Memstore 的默认阈值
hbase.hregion.memstore.flush.size:134217728

即:这个参数的作用是当单个 HRegion 内所有的 Memstore 大小总和超过指定值时,flush 该 HRegion 的所有 memstore。

RegionServer 的 flush 是通过将请求添加一个队列,模拟生产消费模型来异步处理的。
那这里就有一个问题,当队列来不及消费,产生大量积压请求时,可能会导致内存陡增,最坏的情况是触发OOM。

hbase.regionserver.global.memstore.size:默认整个堆大小的 40%

regionServer 的全局 memstore 的大小,超过该大小会触发 flush 到磁盘的操作,默认是堆大小的 40%,而且 regionserver 级别的 flush 会阻塞客户端读写。

hbase.regionserver.global.memstore.size.lower.limit:默认堆大小0.40.95

有时候集群的“写负载”非常高,写入量一直超过 flush 的量,这时,我们
就希望 MemStore 不要超过一定的安全设置。

在这种情况下,写操作就要被阻塞一直到 MemStore 恢复到一个“可管理”的大小, 这个大小就是默认值是堆大小 * 0.4 * 0.95;
也就是当 HRegionserver 级别的 flush 操作发送后,会阻塞客户端写,一直阻塞到整个 HRegionserver 级别的 MemStore 的大小为 堆大小 * 0.4 *0.95 为止。

猜你喜欢

转载自blog.csdn.net/zp17834994071/article/details/107514857