HBase实现分析:HFile

hbase过往记忆 发表了文1章 • 1 个评论 • 955 次浏览 • 2018-08-16 11:40 • 来自相关话题

在这里主要分析一下HFile V2的各个组成部分的一些细节,重点分析了HFile V2的多级索引的机制,接下去有时间的话会分析源码中对HFile的读写扫描操作。HFile和流程:
如下图,HFile的组成分成四部分,分别是Scanned Block(数据block)、Non-Scanned block(元数据block)、Load-on-open(在hbase运行时,HFile需要加载到内存中的索引、bloom filter和文件信息)以及trailer(文件尾)。

在HFile中根据一个key搜索一个data的过程:
1、先内存中对HFile的root index进行二分查找。如果支持多级索引的话,则定位到的是leaf/intermediate index,如果是单级索引,则定位到的是data block
2、如果支持多级索引,则会从缓存/hdfs(分布式文件系统)中读取leaf/intermediate index chunk,在leaf/intermediate chunk根据key值进行二分查找(leaf/intermediate index chunk支持二分查找),找到对应的data block。
3、从缓存/hdfs中读取data block
4、在data block中遍历查找key。
接下去我们分析一个HFile的各个组成部分的详细细节,重点会分析一下HFile V2的多级索引。1 Hbase的KeyValue结构
KeyValue结构是hbase存储的核心,每个数据都是以keyValue结构在hbase中进行存储。KeyValue结构是一个有固定格式的byte数组,其结构在内存和磁盘中的格式如下:

The KeyValue格式:
Keylengthvaluelengthkeyvalue
其中keylength和valuelength都是整型,表示长度。
而key和value都是byte数据,key是有固定的数据,而value是raw data。Key的格式如下。
The Key format:
rowlengthrow (i.e., the rowkey)columnfamilylengthcolumnfamilycolumnqualifiertimestampkeytype
keytype有四种类型,分别是Put、Delete、 DeleteColumn和DeleteFamily。
特别说明:在key的所有组成成员中,columnquallfier的长度不固定,不需要用qualifier_len字段来标示其长度,因为可以通过key_len - ((key固定长度) + row_len + columnFamily_len)获得,其中Key固定长度为 sizeof(Row_len) + sizeof(columnFamily_len) + sizeof(timestamp) + sizeof(keytype)。KeyValue是字节流形式,所以不需要考虑字节对齐。2 File Trailer
fixedFileTrailer记录了HFile的基本信息、各个部分的偏移值和寻址信息。fileTailer拥有固定的长度,下图是HFile V1和HFile V2的差别。在FileTrailer中存储着加载一个HFile的所有信息。FileTrailer在磁盘中的分布如下图所示:

HFile V2见图右边。下面列举一下各个字段的含义和作用
BlockType:block类型。
FileInfoOffset:fileInfo的起始偏移地址。
LoadOnOpenDataOffset:需要被加载到内存中的Hfile部分的起始地址。
DataIndexEntriesNum:data index的root index chunk包含的index entry数目。
UncompressedDataIndexSize:所有的未经压缩的data index的大小 。
TmetaIndexEntriesNum:meta index entry的数目。
totalUncompressedBytes:key value对象未经压缩的总大小。
numEntries:key value对象的数目。
compressionCodec:编解码算法。
numDataIndexLeves:data block的index level。
firstDataBlockOffset:第一个data block的起始偏移地址。Scan操作的起始。
lastDataBlockOffset:最后一个data block的之后的第一个byte地址。记录scan的边界。
version:版本号。读取一个HFile的流程如下:
1、 首先读取文件尾的4字节Version信息(FileTrailer的version字段)。
2、 根据Version信息得到Trailer的长度(不同版本有不同的长度),然后根据trailer长度,加载FileTrailer。
3、 加载load-on-open部分到内存中,起始的文件偏移地址是trailer中的loadOnOpenDataOffset,load-on-open部分长度等于(HFile文件长度 - HFileTrailer长度)
如下图所示:

Load-on-open各个部分的加载顺序如下:
依次加载各部分的HFileBlock(load-on-open所有部分都是以HFileBlock格式存储):data index block、meta index block、FileInfo block、generate bloom filter index、和delete bloom filter。HFileBlock的格式会在下面介绍。3 Load on open
这部分数据在HBase的region server启动时,需要加载到内存中。包括FileInfo、Bloom filter block、data block index和meta block index。3.1 FileInfo
FileInfo中保存一些HFile的基本信息,并以PB格式写入到磁盘中。在0.96中是以PB格式进行保存。3.2 HFileBlock
在hfile中,所有的索引和数据都是以HFileBlock的格式存在在hdfs中,
HFile version2的Block格式如下两图所示,有两种类型,第一种类型是没有checksum;第二种是包含checksum。对于block,下图中的绿色和浅绿色的内存是block header;深红部分是block data;粉红部分是checksum。
第一种block的header长度= 8 + 2 * 4 + 8;
第二种block的header长度=8 + 2 * 4 + 8 + 1 + 4 * 2;

                      图3.1 不支持checksum的block

                    图 3.2 支持checksum的block
BlockType:8个字节的magic,表示不同的block 类型。
CompressedBlockSize:表示压缩的block 数据大小(也就是在HDFS中的HFileBlock数据长度),不包括header长度。
UncompressedBlockSize:表示未经压缩的block数据大小,不包括header长度。
PreBlockOffset:前一个block的在hfile中的偏移地址;用于访问前一个block而不用跳到前一个block中,实现类似于链表的功能。
CheckSumType:在支持block checksum中,表示checksum的类型。
bytePerCheckSum:在支持checksum的block中,记录了在checksumChunk中的字节数;records the number of bytes in a checksum chunk。
SizeDataOnDisk:在支持checksum的block中,记录了block在disk中的数据大小,不包括checksumChunk。DataBlock
DataBlock是用于存储具体kv数据的block,相对于索引和meta(这里的meta是指bloom filter)DataBlock的格式比较简单。
在DataBlock中,KeyValue的分布如下图,在KeyValue后面跟一个timestamp。
3.3 HFileIndex
HFile中的index level是不固定的,根据不同的数据类型和数据大小有不同的选择,主要有两类,一类是single-level(单级索引),另一类是multi-level(多级索引,索引block无法在内存中存放,所以采用多级索引)。
HFile中的index chunk有两大类,分别是root index chunk、nonRoot index chunk。而nonRoot index chunk又分为interMetadiate index chunk和leaf index chunk,但intermetadiate index chunk和leaf index chunk在内存中的分布是一样的。
对于meta block和bloom block,采用的索引是single-level形式,采用single-level时,只用root index chunk来保存指向block的索引信息(root_index-->xxx_block)。
而对于data,当HFile的data block数量较少时,采用的是single level(root_index-->data_block)。当data block数量较多时,采用的是multi-level,一般情况下是两级索引,使用root index chunk和leaf index chunk来保存索引信息(root_index-->leaf_index-->data_block);但当data block数量很多时,采用的是三级索引,使用root index chunk、intermetadiate index chunk和leaf index chunk来保存指向数据的索引(root_index-->intermediate_index-->leaf_index-->data_block)。
所有的index chunk都是以HFileBlock格式进行存放的,首先是一个HFileBlock Header,然后才是index chunk的内容。Root Index
Root index适用于两种情况:
1、作为data索引的根索引。
2、作为meta和bloom的索引。
在Hfile Version2中,Meta index和bloom index都是single-level,也都采用root索引的格式。Data index可以single-level和multi-level的这形式。Root index可以表示single-level index也可以表示multi-level的first level。但这两种表示方式在内存中的存储方式是由一定差别,见图3.3和3.4。

                         图3.3 single-level root index

                              图3.4 multi-level root indexSingle-level
root索引是会被加载到内存中。在磁盘的格式见图3.4。
index entry的组成含义如下:
1、Offset (long):表示索引对应的block在Hfile文件中的偏移值。
2、On-disk size (int):表示索引对应的block在disk(Hfile文件)中的长度。
3、Key:Key是在内存中存储的byte array,分成两部分,其中一部分是key长度,另一部分是key数据。 Key值应该是index entry对应的data block的first row key。不论这个block是leaf index chunk还是data block或者是meta block。Mid-key and multi-level
对于multi-level root index,除了上面index entry数组之外还带有格外的数据mid-key的信息,这个mid-key是用于在对hfile进行split时,快速定位HFile的中间位置所使用。Multi-level root index在硬盘中的格式见图3.4。
Mid-key的含义:如果HFile总共有n个data block,那么mid-key就是能定位到第(n - 1)/2个data block的信息。
Mid-key的信息组成如下:
1、Offset:所在的leaf index chunk的起始偏移量
2、On-disk size:所在的leaf index chunk的长度
3、Key:在leaf index chunk中的位置。
如下图所示:第(n – 1)/2个data block位于第i个LeafIndexChunk,如果LeafIndexChunk的第一个data block的序号为k,那么offset、on-disk size以及key的值如下:
Offset为 LeafIndexChunk[i] 的offset
On-disk size为LeafIndexChunk[i] 的size
Key为(n – 1)/2 – k

                                              图 3.6 mid-key示意图NonRoot index
当HFile以multi-level来索引数据block时,会引入nonRoot index与root index一起构建整个索引。Nonroot索引包括Intermediate index和leaf index这两种类型,这两种索引在disk中的格式一致,都统一使用NonRoot格式进行存放,但用途和存放的位置不同。
Intermediate index是在当HFile的数据block太多或内存存在限制时,使用两级数据索引时导致root index chunk超过其最大值,所以通过增加索引的级数,将intermediate index作为second level,以此来保证root index chunk的大小在一定限制内,减少加载到内存中时的内存消耗。
intermediate index chunk中的每个index Entry都指向一个leaf index chunk。Intermediate index chunk在加载时不会被加载到内存中。Intermediate index chunk在HFile中存储的位置是紧挨着root index chunk。在写入root index chunk时,会检查root index chunk的容量是否超过最大值,如果超过,那么将root index chunk划分成多个intermediate index chunk,然后重新生成一个root index entry,新root index entry中的每一个index entry都是指向intermediate index chunk。先将各个intermediate index chunk写入到disk中,然后再写入root index chunk,如下图所示。

HFile使用multi-level index来索引data block时,Leaf index chunk是作为最末一级,leaf index chunk中的index entry是保存指向datablock的数据。Leaf index chunk也是以nonRoot格式来进行存储的,见图3.4,与intermediate index chunk一样,都样不会在加载hfile时被加载到内存中。
nonRoot索引增加了secondIndexOffset,作为二级索引,用于实现二分查找;而而nonRoot索引不会加载到内存中。增加nonRoot索引的目的就是解决在存储数据过大时导致索引的数量量也增加,无法加载到内存中,从而增加了seek和read时的开销。NonRoot index在磁盘中的格式如下图:

                                             图 non Root index
1、BlockNumber:索引条目的数目。
2、secondaryIndexOffset:每一个secondaryIndexOffset都是表示index entry在leaf索引block中的相对偏移值(相对于第一个index entry),它是作为index entry的二级索引,用于实现快速搜索(二分法查找)。如下图所示,第一个secondaryIndexOffset的偏移值为0,往后都是index entry在disk中的长度相加。
3、curTotalNonRootEntrySize:leaf索引块中所有index entry在disk中总的大小。
4、Index Entries:每一个条目都包含三个部分
Offset:entry引用的block在文件中的偏移地址
On-disk size:block在硬盘中的大小
Key:block中的first row key. key不需要像在root索引中按照key length和keyvalue进行保存,因为有secondaryIndexOffset的存在,已经不需要通过key length来识别各个index entry的边界。NonRoot索引的二分查找实现
1、首先NonRoot索引中的Index_entry需要按照顺序排列,这个顺序是通过key值的大小来决定的。key值应该就是row的key值。
2、使用secondaryIndexOffset实现二分查找。二分查找的原理
如果要查找一个InputKey所处的位置
    首先将位置初始化,row为0,high为BlockNumber - 1
   循环:
       mid= (low + high) / 2
       定位到中间位置的index Entry
       将index Entry的key值与InputKey进行比较
           如果 key > inputKey
               Row = mid + 1
           如果 key == inputKey
               找到正确对象,返回
           如果 key < inputKey
               High = mid – 1快速定位

                          3.9 indexEntry偏移值计算
每一个secondaryIndexOffset是四个字节,secondaryIndexOffset的值是index Entry的相对偏移。见上图,对于一个序号为i的index entry,其在leaf索引chunk中的绝对偏移值为
“( BlockNumber + 2 ) * sizeof( int ) + secondaryIndexOffset[i]”

       图 3.10 key值相对于index Entry的偏移
见上图,那么key的长度等于indexEntryOffset[i + 1] - (indexEntryOffset[i] + 4 * 8)Bloom filter
在HFile中,bloom filter的meta index也是作为load-on-open的一部分保存,bloom fiter有两种类型,一种是generate bloom filter,用于快速确定key是否存储在hbase中;另一种是delete bloom filter,用于快速确定key是否已经被删除。
Bloom filter meta index在硬盘中的格式如下:

Bloom meta index在磁盘中的格式如上图所示。
Version:表示版本;
totalByteSize:表示bloom filter的位组的bit数。
HashCount:表示一个key在位组中用几个bit位来进行定位。
HashType:表示hash函数的类型。
totalKeyCount:表示bloom filter当前已经包含的key的数目.
totalKeyMaxs:表示bloom filter当前最多包含的key的数目.
numChunks:表示bloom filter中包含的bloom filter block的数目.
comparatorName:表示比较器的名字.
接下去是每一个Bloom filter block的索引条目。[/i][/i][/i][/i] 查看全部
在这里主要分析一下HFile V2的各个组成部分的一些细节,重点分析了HFile V2的多级索引的机制,接下去有时间的话会分析源码中对HFile的读写扫描操作。HFile和流程:
如下图,HFile的组成分成四部分,分别是Scanned Block(数据block)、Non-Scanned block(元数据block)、Load-on-open(在hbase运行时,HFile需要加载到内存中的索引、bloom filter和文件信息)以及trailer(文件尾)。

在HFile中根据一个key搜索一个data的过程
1、先内存中对HFile的root index进行二分查找。如果支持多级索引的话,则定位到的是leaf/intermediate index,如果是单级索引,则定位到的是data block
2、如果支持多级索引,则会从缓存/hdfs(分布式文件系统)中读取leaf/intermediate index chunk,在leaf/intermediate chunk根据key值进行二分查找(leaf/intermediate index chunk支持二分查找),找到对应的data block。
3、从缓存/hdfs中读取data block
4、在data block中遍历查找key。
接下去我们分析一个HFile的各个组成部分的详细细节,重点会分析一下HFile V2的多级索引。1 Hbase的KeyValue结构
KeyValue结构是hbase存储的核心,每个数据都是以keyValue结构在hbase中进行存储。KeyValue结构是一个有固定格式的byte数组,其结构在内存和磁盘中的格式如下:

The KeyValue格式:
  • Keylength
  • valuelength
  • key
  • value

其中keylength和valuelength都是整型,表示长度。
而key和value都是byte数据,key是有固定的数据,而value是raw data。Key的格式如下。
The Key format:
  • rowlength
  • row (i.e., the rowkey)
  • columnfamilylength
  • columnfamily
  • columnqualifier
  • timestamp
  • keytype

keytype有四种类型,分别是Put、Delete、 DeleteColumn和DeleteFamily。
特别说明:在key的所有组成成员中,columnquallfier的长度不固定,不需要用qualifier_len字段来标示其长度,因为可以通过key_len - ((key固定长度) + row_len + columnFamily_len)获得,其中Key固定长度为 sizeof(Row_len) + sizeof(columnFamily_len) + sizeof(timestamp) + sizeof(keytype)。KeyValue是字节流形式,所以不需要考虑字节对齐。2 File Trailer
fixedFileTrailer记录了HFile的基本信息、各个部分的偏移值和寻址信息。fileTailer拥有固定的长度,下图是HFile V1和HFile V2的差别。在FileTrailer中存储着加载一个HFile的所有信息。FileTrailer在磁盘中的分布如下图所示:

HFile V2见图右边。下面列举一下各个字段的含义和作用
BlockType:block类型。
FileInfoOffset:fileInfo的起始偏移地址。
LoadOnOpenDataOffset:需要被加载到内存中的Hfile部分的起始地址。
DataIndexEntriesNum:data index的root index chunk包含的index entry数目。
UncompressedDataIndexSize:所有的未经压缩的data index的大小 。
TmetaIndexEntriesNum:meta index entry的数目。
totalUncompressedBytes:key value对象未经压缩的总大小。
numEntries:key value对象的数目。
compressionCodec:编解码算法。
numDataIndexLeves:data block的index level。
firstDataBlockOffset:第一个data block的起始偏移地址。Scan操作的起始。
lastDataBlockOffset:最后一个data block的之后的第一个byte地址。记录scan的边界。
version:版本号。读取一个HFile的流程如下:
1、 首先读取文件尾的4字节Version信息(FileTrailer的version字段)。
2、 根据Version信息得到Trailer的长度(不同版本有不同的长度),然后根据trailer长度,加载FileTrailer。
3、 加载load-on-open部分到内存中,起始的文件偏移地址是trailer中的loadOnOpenDataOffset,load-on-open部分长度等于(HFile文件长度 - HFileTrailer长度)
如下图所示:

Load-on-open各个部分的加载顺序如下:
依次加载各部分的HFileBlock(load-on-open所有部分都是以HFileBlock格式存储):data index block、meta index block、FileInfo block、generate bloom filter index、和delete bloom filter。HFileBlock的格式会在下面介绍。3 Load on open
这部分数据在HBase的region server启动时,需要加载到内存中。包括FileInfo、Bloom filter block、data block index和meta block index。3.1 FileInfo
FileInfo中保存一些HFile的基本信息,并以PB格式写入到磁盘中。在0.96中是以PB格式进行保存。3.2 HFileBlock
在hfile中,所有的索引和数据都是以HFileBlock的格式存在在hdfs中,
HFile version2的Block格式如下两图所示,有两种类型,第一种类型是没有checksum;第二种是包含checksum。对于block,下图中的绿色和浅绿色的内存是block header;深红部分是block data;粉红部分是checksum。
第一种block的header长度= 8 + 2 * 4 + 8;
第二种block的header长度=8 + 2 * 4 + 8 + 1 + 4 * 2;

                      图3.1 不支持checksum的block

                    图 3.2 支持checksum的block
BlockType:8个字节的magic,表示不同的block 类型。
CompressedBlockSize:表示压缩的block 数据大小(也就是在HDFS中的HFileBlock数据长度),不包括header长度。
UncompressedBlockSize:表示未经压缩的block数据大小,不包括header长度。
PreBlockOffset:前一个block的在hfile中的偏移地址;用于访问前一个block而不用跳到前一个block中,实现类似于链表的功能。
CheckSumType:在支持block checksum中,表示checksum的类型。
bytePerCheckSum:在支持checksum的block中,记录了在checksumChunk中的字节数;records the number of bytes in a checksum chunk。
SizeDataOnDisk:在支持checksum的block中,记录了block在disk中的数据大小,不包括checksumChunk。DataBlock
DataBlock是用于存储具体kv数据的block,相对于索引和meta(这里的meta是指bloom filter)DataBlock的格式比较简单。
在DataBlock中,KeyValue的分布如下图,在KeyValue后面跟一个timestamp。
3.3 HFileIndex
HFile中的index level是不固定的,根据不同的数据类型和数据大小有不同的选择,主要有两类,一类是single-level(单级索引),另一类是multi-level(多级索引,索引block无法在内存中存放,所以采用多级索引)。
HFile中的index chunk有两大类,分别是root index chunk、nonRoot index chunk。而nonRoot index chunk又分为interMetadiate index chunk和leaf index chunk,但intermetadiate index chunk和leaf index chunk在内存中的分布是一样的。
对于meta block和bloom block,采用的索引是single-level形式,采用single-level时,只用root index chunk来保存指向block的索引信息(root_index-->xxx_block)。
而对于data,当HFile的data block数量较少时,采用的是single level(root_index-->data_block)。当data block数量较多时,采用的是multi-level,一般情况下是两级索引,使用root index chunk和leaf index chunk来保存索引信息(root_index-->leaf_index-->data_block);但当data block数量很多时,采用的是三级索引,使用root index chunk、intermetadiate index chunk和leaf index chunk来保存指向数据的索引(root_index-->intermediate_index-->leaf_index-->data_block)。
所有的index chunk都是以HFileBlock格式进行存放的,首先是一个HFileBlock Header,然后才是index chunk的内容。Root Index
Root index适用于两种情况:
1、作为data索引的根索引。
2、作为meta和bloom的索引。
在Hfile Version2中,Meta index和bloom index都是single-level,也都采用root索引的格式。Data index可以single-level和multi-level的这形式。Root index可以表示single-level index也可以表示multi-level的first level。但这两种表示方式在内存中的存储方式是由一定差别,见图3.3和3.4。

                         图3.3 single-level root index

                              图3.4 multi-level root indexSingle-level
root索引是会被加载到内存中。在磁盘的格式见图3.4。
index entry的组成含义如下:
1、Offset (long):表示索引对应的block在Hfile文件中的偏移值。
2、On-disk size (int):表示索引对应的block在disk(Hfile文件)中的长度。
3、Key:Key是在内存中存储的byte array,分成两部分,其中一部分是key长度,另一部分是key数据。 Key值应该是index entry对应的data block的first row key。不论这个block是leaf index chunk还是data block或者是meta block。Mid-key and multi-level
对于multi-level root index,除了上面index entry数组之外还带有格外的数据mid-key的信息,这个mid-key是用于在对hfile进行split时,快速定位HFile的中间位置所使用。Multi-level root index在硬盘中的格式见图3.4。
Mid-key的含义:如果HFile总共有n个data block,那么mid-key就是能定位到第(n - 1)/2个data block的信息。
Mid-key的信息组成如下:
1、Offset:所在的leaf index chunk的起始偏移量
2、On-disk size:所在的leaf index chunk的长度
3、Key:在leaf index chunk中的位置。
如下图所示:第(n – 1)/2个data block位于第i个LeafIndexChunk,如果LeafIndexChunk的第一个data block的序号为k,那么offset、on-disk size以及key的值如下:
Offset为 LeafIndexChunk[i] 的offset
On-disk size为LeafIndexChunk[i] 的size
Key为(n – 1)/2 – k

                                              图 3.6 mid-key示意图NonRoot index
当HFile以multi-level来索引数据block时,会引入nonRoot index与root index一起构建整个索引。Nonroot索引包括Intermediate index和leaf index这两种类型,这两种索引在disk中的格式一致,都统一使用NonRoot格式进行存放,但用途和存放的位置不同。
Intermediate index是在当HFile的数据block太多或内存存在限制时,使用两级数据索引时导致root index chunk超过其最大值,所以通过增加索引的级数,将intermediate index作为second level,以此来保证root index chunk的大小在一定限制内,减少加载到内存中时的内存消耗。
intermediate index chunk中的每个index Entry都指向一个leaf index chunk。Intermediate index chunk在加载时不会被加载到内存中。Intermediate index chunk在HFile中存储的位置是紧挨着root index chunk。在写入root index chunk时,会检查root index chunk的容量是否超过最大值,如果超过,那么将root index chunk划分成多个intermediate index chunk,然后重新生成一个root index entry,新root index entry中的每一个index entry都是指向intermediate index chunk。先将各个intermediate index chunk写入到disk中,然后再写入root index chunk,如下图所示。

HFile使用multi-level index来索引data block时,Leaf index chunk是作为最末一级,leaf index chunk中的index entry是保存指向datablock的数据。Leaf index chunk也是以nonRoot格式来进行存储的,见图3.4,与intermediate index chunk一样,都样不会在加载hfile时被加载到内存中。
nonRoot索引增加了secondIndexOffset,作为二级索引,用于实现二分查找;而而nonRoot索引不会加载到内存中。增加nonRoot索引的目的就是解决在存储数据过大时导致索引的数量量也增加,无法加载到内存中,从而增加了seek和read时的开销。NonRoot index在磁盘中的格式如下图:

                                             图 non Root index
1、BlockNumber:索引条目的数目。
2、secondaryIndexOffset:每一个secondaryIndexOffset都是表示index entry在leaf索引block中的相对偏移值(相对于第一个index entry),它是作为index entry的二级索引,用于实现快速搜索(二分法查找)。如下图所示,第一个secondaryIndexOffset的偏移值为0,往后都是index entry在disk中的长度相加。
3、curTotalNonRootEntrySize:leaf索引块中所有index entry在disk中总的大小。
4、Index Entries:每一个条目都包含三个部分
Offset:entry引用的block在文件中的偏移地址
On-disk size:block在硬盘中的大小
Key:block中的first row key. key不需要像在root索引中按照key length和keyvalue进行保存,因为有secondaryIndexOffset的存在,已经不需要通过key length来识别各个index entry的边界。NonRoot索引的二分查找实现
1、首先NonRoot索引中的Index_entry需要按照顺序排列,这个顺序是通过key值的大小来决定的。key值应该就是row的key值。
2、使用secondaryIndexOffset实现二分查找。二分查找的原理
如果要查找一个InputKey所处的位置
    首先将位置初始化,row为0,high为BlockNumber - 1
   循环:
       mid= (low + high) / 2
       定位到中间位置的index Entry
       将index Entry的key值与InputKey进行比较
           如果 key > inputKey
               Row = mid + 1
           如果 key == inputKey
               找到正确对象,返回
           如果 key < inputKey
               High = mid – 1快速定位

                          3.9 indexEntry偏移值计算
每一个secondaryIndexOffset是四个字节,secondaryIndexOffset的值是index Entry的相对偏移。见上图,对于一个序号为i的index entry,其在leaf索引chunk中的绝对偏移值为
“( BlockNumber + 2 ) * sizeof( int ) + secondaryIndexOffset[i]”

       图 3.10 key值相对于index Entry的偏移
见上图,那么key的长度等于indexEntryOffset[i + 1] - (indexEntryOffset[i] + 4 * 8)Bloom filter
在HFile中,bloom filter的meta index也是作为load-on-open的一部分保存,bloom fiter有两种类型,一种是generate bloom filter,用于快速确定key是否存储在hbase中;另一种是delete bloom filter,用于快速确定key是否已经被删除。
Bloom filter meta index在硬盘中的格式如下:

Bloom meta index在磁盘中的格式如上图所示。
Version:表示版本;
totalByteSize:表示bloom filter的位组的bit数。
HashCount:表示一个key在位组中用几个bit位来进行定位。
HashType:表示hash函数的类型。
totalKeyCount:表示bloom filter当前已经包含的key的数目.
totalKeyMaxs:表示bloom filter当前最多包含的key的数目.
numChunks:表示bloom filter中包含的bloom filter block的数目.
comparatorName:表示比较器的名字.
接下去是每一个Bloom filter block的索引条目。
[/i][/i][/i][/i]

HBase 高可用原理与实践

hbase过往记忆 发表了文1章 • 0 个评论 • 646 次浏览 • 2018-08-14 10:20 • 来自相关话题

前言

前段时间有套线上HBase出了点小问题,导致该套HBase集群服务停止了2个小时,从而造成使用该套HBase作为数据存储的应用也出现了服务异常。在排查问题之余,我们不禁也在思考,以后再出现类似的问题怎么办?这种问题该如何避免?用惯了MySQL,于是乎想到了HBase是否跟MySQL一样,也有其高可用方案?

答案当然是肯定的,几乎所有的数据库(无论是关系型还是分布式的),都采用WAL的方式来保障服务异常时候的数据恢复,HBase同样也是通过WAL来保障数据不丢失。HBase在写数据前会先写HLog,HLog中记录的是所有数据的变动, HBase的高可用也正是通过HLog来实现的。

进阶
 
HBase是一个没有单点故障的分布式系统,上层(HBase层)和底层(HDFS层)都通过一定的技术手段,保障了服务的可用性。上层HMaster一般都是高可用部署,而RegionServer如果出现宕机,region迁移的代价并不大,一般都在毫秒级别完成,所以对应用造成的影响也很有限;底层存储依赖于HDFS,数据本身默认也有3副本,数据存储上做到了多副本冗余,而且Hadoop 2.0以后NameNode的单点故障也被消除。所以,对于这样一个本身没有单点故障,数据又有多副本冗余的系统,再进行高可用的配置是否有这个必要?会不会造成资源的极大浪费?

高可用部署是否有必要,这个需要根据服务的重要性来定,这里先简单介绍下没有高可用的HBase服务会出现哪些问题:

1.      数据库管理人员失误,进行了不可逆的DDL操作

不管是什么数据库,DDL操作在执行的时候都需要慎之又慎,很可能一条简单的drop操作,会导致所有数据的丢失,并且无法恢复,对于HBase来说也是这样,如果管理员不小心drop了一个表,该表的数据将会被丢失。

2.      离线MR消耗过多的资源,造成线上服务受到影响
 
HBase经过这么多年的发展,已经不再是只适合离线业务的数据存储分析平台,许多公司的线上业务也相继迁移到了HBase上,比较典型的如:facebook的iMessage系统、360的搜索业务、小米米聊的历史数据等等。但不可避免在这些数据上做些统计分析类操作,大型MR跑起来,会有很大的资源消耗,可能会影响线上业务。

3.      不可预计的另外一些情况

比如核心交换机故障,机房停电等等情况都会造成HBase服务中断

 对于上述的那些问题,可以通过配置HBase的高可用来解决:

1.     不可逆DDL问题

HBase的高可用不支持DDL操作,换句话说,在master上的DDL操作,不会影响到slave上的数据,所以即使在master上进行了DDL操作,slave上的数据依然没有变化。这个跟MySQL有很大不同,MySQL的DDL可以通过statement格式的Binlog进行复制。

2.     离线MR影响线上业务问题

高可用的最大好处就是可以进行读写分离,离线MR可以直接跑在slave上,master继续对外提供写服务,这样也就不会影响到线上的业务,当然HBase的高可用复制是异步进行的,在slave上进行MR分析,数据可能会有稍微延迟。

3.     意外情况

对于像核心交换机故障、断电等意外情况,slave跨机架或者跨机房部署都能解决该种情况。
 
基于以上原因,如果是核心服务,对于可用性要求非常高,可以搭建HBase的高可用来保障服务较高的可用性,在HBase的Master出现异常时,只需简单把流量切换到Slave上,即可完成故障转移,保证服务正常运行。

原理
 
HBase高可用保证在出现异常时,快速进行故障转移。下面让我们先来看看HBase高可用的实现,首先看下官方的一张图:





HBase Replication
 
需要声明的是,HBase的replication是以Column Family为单位的,每个Column Family都可以设置是否进行replication。

 上图中,一个Master对应了3个Slave,Master上每个RegionServer都有一份HLog,在开启Replication的情况下,每个RegionServer都会开启一个线程用于读取该RegionServer上的HLog,并且发送到各个Slave,Zookeeper用于保存当前已经发送的HLog的位置。Master与Slave之间采用异步通信的方式,保障Master上的性能不会受到Slave的影响。用Zookeeper保存已经发送HLog的位置,主要考虑在Slave复制过程中如果出现问题后重新建立复制,可以找到上次复制的位置。





HBase Replication步骤

 

1.         HBase Client向Master写入数据

 

2.         对应RegionServer写完HLog后返回Client请求

 

3.         同时replication线程轮询HLog发现有新的数据,发送给Slave

 

4.         Slave处理完数据后返回给Master

 

5.         Master收到Slave的返回信息,在Zookeeper中标记已经发送到Slave的HLog位置

 

注:在进行replication时,Master与Slave的配置并不一定相同,比如Master上可以有3台RegionServer,Slave上并不一定是3台,Slave上的RegionServer数量可以不一样,数据如何分布这个HBase内部会处理。

种类

 

HBase通过HLog进行数据复制,那么HBase支持哪些不同种类的复制关系?

 

从复制模式上来讲,HBase支持主从、主主两种复制模式,也就是经常说的Master-Slave、Master-Master复制。

 

1.         Master-Slave

 

Master-Slave复制比较简单,所有在Master集群上写入的数据都会被同步到Slave上。

 

2.         Master-Master

 

Master-Master复制与Master-Slave类似,主要的不同在于,在Master-Master复制中,两个Master地位相同,都可以进行读取和写入。

 

既然Master-Master两个Master都可以进行写入,万一出现一种情况:两个Master上都进行了对同一表的相同Column Family的同一个rowkey进行写入,会出现什么情况?

 

create  ‘t’,  {NAME=>’cf’, REPLICATION_SCOPE=>’1’}

 

Master1                                                        Master2

 

put ‘t’, ‘r1’, ‘cf’, ‘aaaaaaaaaaaaaaa’                       put ‘t’, ‘r1’, ‘cf’, ‘bbbbbbbbbbbbbbb’

 

如上操作,Master1上对t的cf列簇写入rowkey为r1,value为aaaaaaaaaaaaaaa的数据,Master2上同时对t的cf列簇写入rowkey为r1, value为bbbbbbbbbbbbbbb的数据,由于是Master-Master复制,Master1和Master2上在写入数据的同时都会把更新发送给对方,这样最终的数据就变成了:

 






从上述表格中可以看到,最终Master1和Master2上cf列簇rowkey为r1的数据两边不一致。

 

所以,在做Master-Master高可用时,确保两边写入的表都是不同的,这样能防止上述数据不一致问题。

 

异常

 

HBase复制时,都是通过RegionServer开启复制线程进行HLog的发送,那么当其中某个RegionServer出现异常时,HBase是如何处理的?这里需要区别两种不同的情况,即Master上RegionServer异常和Slave上RegionServer异常。

 

1.     Slave上RegionServer异常

 

对于该种异常HBase处理比较简单,Slave上出现某个RegionServer异常,该RegionServer直接会被标记为异常状态,后续所有的更新都不会被发送到该台RegionServer,Slave会重新选取一台RegionServer来接收这部分数据。

 

2.   Master上RegionServer异常

 

Master上RegionServer出现异常,由于HLog都是通过RegionServer开启复制线程进行发送,如果RegionServer出现异常,这个时候,属于该台RegionServer的HLog就没有相关处理线程,这个时候,这部分数据又该如何处理?

 

Master上某台RegionServer异常,其他RegionServer会对该台RegionServer在zookeeper中的信息尝试加锁操作,当然这个操作是互斥的,同一时间只有一台RegionServer能获取到锁,然后,会把HLog信息拷贝到自己的目录下,这样就完成了异常RegionServer的HLog信息的转移,通过新的RegionServer把HLog的信息发送到Slave。

Master regionserver crash

操作

 

上面介绍的都是HBase高可用的理论实现和异常处理等问题,下面就动手实践下,如何配置一个HBase的Replication(假设已经部署好了两套HBase系统,并且在配置文件中已经开启了replication配置),首先尝试配置下Master-Slave模式的高可用:

 

1.         选取一套系统作为Master,另外一套作为Slave

 

2.         在Master上通过add_peer 命令添加复制关系,如下

 

add_peer ‘1’, “db-xxx.photo.163.org:2181:/hbase”

 

3.         在Master上新建表t,该表拥有一个列簇名为cf,并且该列簇开启replication,如下:

 

create ‘t’, {NAME=>’cf’, REPLICATION_SCOPE=>’1’}

 

上面REPLICATION_SCOPE的值需要跟步骤2中的对应

 

4.         在slave建立相同的表(HBase不支持DDL的复制),在master-slave模式中,slave不需要开启复制,如下:

 

create ‘t’, {NAME=>’cf’ }

 

这样,我们就完成了整个master-slave模式高可用的搭建,后续可以在master上通过put操作插入一条记录,查看slave上是否会复制该记录,最终结果如下:

Master上操作

Slave上结果

上述结果显示,在添加完复制关系后,Master上插入rowkey=r1, value=’aaaaaaaaa’的记录,slave上可以获取该记录,Master-Slave模式数据复制成功。

接下来我们再看下Master-Master模式的复制,配置的时候与Master-Slave模式不同的是,在Master上添加完复制关系后,需要在另外一台Master也添加复制关系,而且两边的cluster_id必须相同,并且在另外一台Master上建表的时候,需要加上列簇的REPLICATION_SCOPE=>’1’配置,最终结果如下:

Master1上操作

Master2上操作

 

上述结果显示,添加完了Master-Master复制关系,在Master1上插入一条记录rowkey=r1, value=“aaaaaaaaaa”,Master2上通过scan操作发现该记录已经被复制到Master2上,接着我们在Master2上添加一条记录rowkey=r2, value=’bbbbbbbbbbbb’,查看Master1上的数据,该条记录也已经被复制到Master2上,Master-Master模式的replication验证成功。 查看全部
前言

前段时间有套线上HBase出了点小问题,导致该套HBase集群服务停止了2个小时,从而造成使用该套HBase作为数据存储的应用也出现了服务异常。在排查问题之余,我们不禁也在思考,以后再出现类似的问题怎么办?这种问题该如何避免?用惯了MySQL,于是乎想到了HBase是否跟MySQL一样,也有其高可用方案?

答案当然是肯定的,几乎所有的数据库(无论是关系型还是分布式的),都采用WAL的方式来保障服务异常时候的数据恢复,HBase同样也是通过WAL来保障数据不丢失。HBase在写数据前会先写HLog,HLog中记录的是所有数据的变动, HBase的高可用也正是通过HLog来实现的。

进阶
 
HBase是一个没有单点故障的分布式系统,上层(HBase层)和底层(HDFS层)都通过一定的技术手段,保障了服务的可用性。上层HMaster一般都是高可用部署,而RegionServer如果出现宕机,region迁移的代价并不大,一般都在毫秒级别完成,所以对应用造成的影响也很有限;底层存储依赖于HDFS,数据本身默认也有3副本,数据存储上做到了多副本冗余,而且Hadoop 2.0以后NameNode的单点故障也被消除。所以,对于这样一个本身没有单点故障,数据又有多副本冗余的系统,再进行高可用的配置是否有这个必要?会不会造成资源的极大浪费?

高可用部署是否有必要,这个需要根据服务的重要性来定,这里先简单介绍下没有高可用的HBase服务会出现哪些问题:

1.      数据库管理人员失误,进行了不可逆的DDL操作

不管是什么数据库,DDL操作在执行的时候都需要慎之又慎,很可能一条简单的drop操作,会导致所有数据的丢失,并且无法恢复,对于HBase来说也是这样,如果管理员不小心drop了一个表,该表的数据将会被丢失。

2.      离线MR消耗过多的资源,造成线上服务受到影响
 
HBase经过这么多年的发展,已经不再是只适合离线业务的数据存储分析平台,许多公司的线上业务也相继迁移到了HBase上,比较典型的如:facebook的iMessage系统、360的搜索业务、小米米聊的历史数据等等。但不可避免在这些数据上做些统计分析类操作,大型MR跑起来,会有很大的资源消耗,可能会影响线上业务。

3.      不可预计的另外一些情况

比如核心交换机故障,机房停电等等情况都会造成HBase服务中断

 对于上述的那些问题,可以通过配置HBase的高可用来解决:

1.     不可逆DDL问题

HBase的高可用不支持DDL操作,换句话说,在master上的DDL操作,不会影响到slave上的数据,所以即使在master上进行了DDL操作,slave上的数据依然没有变化。这个跟MySQL有很大不同,MySQL的DDL可以通过statement格式的Binlog进行复制。

2.     离线MR影响线上业务问题

高可用的最大好处就是可以进行读写分离,离线MR可以直接跑在slave上,master继续对外提供写服务,这样也就不会影响到线上的业务,当然HBase的高可用复制是异步进行的,在slave上进行MR分析,数据可能会有稍微延迟。

3.     意外情况

对于像核心交换机故障、断电等意外情况,slave跨机架或者跨机房部署都能解决该种情况。
 
基于以上原因,如果是核心服务,对于可用性要求非常高,可以搭建HBase的高可用来保障服务较高的可用性,在HBase的Master出现异常时,只需简单把流量切换到Slave上,即可完成故障转移,保证服务正常运行。

原理
 
HBase高可用保证在出现异常时,快速进行故障转移。下面让我们先来看看HBase高可用的实现,首先看下官方的一张图:

20180705104611cfc33072-8bdb-4916-bc02-6c8a4db752f6.jpg

HBase Replication
 
需要声明的是,HBase的replication是以Column Family为单位的,每个Column Family都可以设置是否进行replication。

 上图中,一个Master对应了3个Slave,Master上每个RegionServer都有一份HLog,在开启Replication的情况下,每个RegionServer都会开启一个线程用于读取该RegionServer上的HLog,并且发送到各个Slave,Zookeeper用于保存当前已经发送的HLog的位置。Master与Slave之间采用异步通信的方式,保障Master上的性能不会受到Slave的影响。用Zookeeper保存已经发送HLog的位置,主要考虑在Slave复制过程中如果出现问题后重新建立复制,可以找到上次复制的位置。

201807051046218dd436b9-d42d-4243-ae58-b88d533e9910.jpg

HBase Replication步骤

 

1.         HBase Client向Master写入数据

 

2.         对应RegionServer写完HLog后返回Client请求

 

3.         同时replication线程轮询HLog发现有新的数据,发送给Slave

 

4.         Slave处理完数据后返回给Master

 

5.         Master收到Slave的返回信息,在Zookeeper中标记已经发送到Slave的HLog位置

 

注:在进行replication时,Master与Slave的配置并不一定相同,比如Master上可以有3台RegionServer,Slave上并不一定是3台,Slave上的RegionServer数量可以不一样,数据如何分布这个HBase内部会处理。

种类

 

HBase通过HLog进行数据复制,那么HBase支持哪些不同种类的复制关系?

 

从复制模式上来讲,HBase支持主从、主主两种复制模式,也就是经常说的Master-Slave、Master-Master复制。

 

1.         Master-Slave

 

Master-Slave复制比较简单,所有在Master集群上写入的数据都会被同步到Slave上。

 

2.         Master-Master

 

Master-Master复制与Master-Slave类似,主要的不同在于,在Master-Master复制中,两个Master地位相同,都可以进行读取和写入。

 

既然Master-Master两个Master都可以进行写入,万一出现一种情况:两个Master上都进行了对同一表的相同Column Family的同一个rowkey进行写入,会出现什么情况?

 

create  ‘t’,  {NAME=>’cf’, REPLICATION_SCOPE=>’1’}

 

Master1                                                        Master2

 

put ‘t’, ‘r1’, ‘cf’, ‘aaaaaaaaaaaaaaa’                       put ‘t’, ‘r1’, ‘cf’, ‘bbbbbbbbbbbbbbb’

 

如上操作,Master1上对t的cf列簇写入rowkey为r1,value为aaaaaaaaaaaaaaa的数据,Master2上同时对t的cf列簇写入rowkey为r1, value为bbbbbbbbbbbbbbb的数据,由于是Master-Master复制,Master1和Master2上在写入数据的同时都会把更新发送给对方,这样最终的数据就变成了:

 
menu.saveimg_.savepath20180814101838_.jpg



从上述表格中可以看到,最终Master1和Master2上cf列簇rowkey为r1的数据两边不一致。

 

所以,在做Master-Master高可用时,确保两边写入的表都是不同的,这样能防止上述数据不一致问题。

 

异常

 

HBase复制时,都是通过RegionServer开启复制线程进行HLog的发送,那么当其中某个RegionServer出现异常时,HBase是如何处理的?这里需要区别两种不同的情况,即Master上RegionServer异常和Slave上RegionServer异常。

 

1.     Slave上RegionServer异常

 

对于该种异常HBase处理比较简单,Slave上出现某个RegionServer异常,该RegionServer直接会被标记为异常状态,后续所有的更新都不会被发送到该台RegionServer,Slave会重新选取一台RegionServer来接收这部分数据。

 

2.   Master上RegionServer异常

 

Master上RegionServer出现异常,由于HLog都是通过RegionServer开启复制线程进行发送,如果RegionServer出现异常,这个时候,属于该台RegionServer的HLog就没有相关处理线程,这个时候,这部分数据又该如何处理?

 

Master上某台RegionServer异常,其他RegionServer会对该台RegionServer在zookeeper中的信息尝试加锁操作,当然这个操作是互斥的,同一时间只有一台RegionServer能获取到锁,然后,会把HLog信息拷贝到自己的目录下,这样就完成了异常RegionServer的HLog信息的转移,通过新的RegionServer把HLog的信息发送到Slave。

Master regionserver crash

操作

 

上面介绍的都是HBase高可用的理论实现和异常处理等问题,下面就动手实践下,如何配置一个HBase的Replication(假设已经部署好了两套HBase系统,并且在配置文件中已经开启了replication配置),首先尝试配置下Master-Slave模式的高可用:

 

1.         选取一套系统作为Master,另外一套作为Slave

 

2.         在Master上通过add_peer 命令添加复制关系,如下

 

add_peer ‘1’, “db-xxx.photo.163.org:2181:/hbase”

 

3.         在Master上新建表t,该表拥有一个列簇名为cf,并且该列簇开启replication,如下:

 

create ‘t’, {NAME=>’cf’, REPLICATION_SCOPE=>’1’}

 

上面REPLICATION_SCOPE的值需要跟步骤2中的对应

 

4.         在slave建立相同的表(HBase不支持DDL的复制),在master-slave模式中,slave不需要开启复制,如下:

 

create ‘t’, {NAME=>’cf’ }

 

这样,我们就完成了整个master-slave模式高可用的搭建,后续可以在master上通过put操作插入一条记录,查看slave上是否会复制该记录,最终结果如下:

Master上操作

Slave上结果

上述结果显示,在添加完复制关系后,Master上插入rowkey=r1, value=’aaaaaaaaa’的记录,slave上可以获取该记录,Master-Slave模式数据复制成功。

接下来我们再看下Master-Master模式的复制,配置的时候与Master-Slave模式不同的是,在Master上添加完复制关系后,需要在另外一台Master也添加复制关系,而且两边的cluster_id必须相同,并且在另外一台Master上建表的时候,需要加上列簇的REPLICATION_SCOPE=>’1’配置,最终结果如下:

Master1上操作

Master2上操作

 

上述结果显示,添加完了Master-Master复制关系,在Master1上插入一条记录rowkey=r1, value=“aaaaaaaaaa”,Master2上通过scan操作发现该记录已经被复制到Master2上,接着我们在Master2上添加一条记录rowkey=r2, value=’bbbbbbbbbbbb’,查看Master1上的数据,该条记录也已经被复制到Master2上,Master-Master模式的replication验证成功。

Hbase 统计表行数的3种方式

hbase过往记忆 发表了文1章 • 4 个评论 • 1419 次浏览 • 2018-08-13 19:24 • 来自相关话题

有些时候需要我们去统计某一个hbase表的行数,由于hbase本身不支持SQL语言,只能通过其他方式实现。可以通过一下几种方式实现hbase表的行数统计工作:

1.count命令

最直接的方式是在hbase shell中执行count的命令可以统计行数。hbase> count ‘t1′
hbase> count ‘t1′, INTERVAL => 100000
hbase> count ‘t1′, CACHE => 1000
hbase> count ‘t1′, INTERVAL => 10, CACHE => 1000
其中,INTERVAL为统计的行数间隔,默认为1000,CACHE为统计的数据缓存。这种方式效率很低,如果表行数很大的话不建议采用这种方式。
2. 调用Mapreduce$HBASE_HOME/bin/hbase org.apache.hadoop.hbase.mapreduce.RowCounter 'tablename'
这种方式效率比上一种要高很多,调用的hbase jar中自带的统计行数的类。
3.hive over hbase
如果已经见了hive和hbase的关联表的话,可以直接在hive中执行sql语句统计hbase表的行数。
hive over hbase 表的建表语句为:
/*创建hive与hbase的关联表*/
CREATE TABLE hive_hbase_1(key INT,value STRING)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping"=":key,cf:val")
TBLPROPERTIES("hbase.table.name"="t_hive","hbase.table.default.storage.type"="binary");

/*hive关联已经存在的hbase*/
CREATE EXTERNAL TABLE hive_hbase_1(key INT,value STRING)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping"=":key,cf:val")
TBLPROPERTIES("hbase.table.name"="t_hive","hbase.table.default.storage.type"="binary");
  查看全部
有些时候需要我们去统计某一个hbase表的行数,由于hbase本身不支持SQL语言,只能通过其他方式实现。可以通过一下几种方式实现hbase表的行数统计工作:

1.count命令

最直接的方式是在hbase shell中执行count的命令可以统计行数。
hbase> count ‘t1′
hbase> count ‘t1′, INTERVAL => 100000
hbase> count ‘t1′, CACHE => 1000
hbase> count ‘t1′, INTERVAL => 10, CACHE => 1000

其中,INTERVAL为统计的行数间隔,默认为1000,CACHE为统计的数据缓存。这种方式效率很低,如果表行数很大的话不建议采用这种方式。
2. 调用Mapreduce
$HBASE_HOME/bin/hbase org.apache.hadoop.hbase.mapreduce.RowCounter 'tablename'

这种方式效率比上一种要高很多,调用的hbase jar中自带的统计行数的类。
3.hive over hbase
如果已经见了hive和hbase的关联表的话,可以直接在hive中执行sql语句统计hbase表的行数。
hive over hbase 表的建表语句为:
/*创建hive与hbase的关联表*/
CREATE TABLE hive_hbase_1(key INT,value STRING)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping"=":key,cf:val")
TBLPROPERTIES("hbase.table.name"="t_hive","hbase.table.default.storage.type"="binary");

/*hive关联已经存在的hbase*/
CREATE EXTERNAL TABLE hive_hbase_1(key INT,value STRING)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping"=":key,cf:val")
TBLPROPERTIES("hbase.table.name"="t_hive","hbase.table.default.storage.type"="binary");

 

HBase读写设计与实践

hbase过往记忆 发表了文1章 • 0 个评论 • 602 次浏览 • 2018-08-13 17:28 • 来自相关话题

背景介绍
本项目主要解决 check 和 opinion2 张历史数据表(历史数据是指当业务发生过程中的完整中间流程和结果数据)的在线查询。原实现基于 Oracle 提供存储查询服务,随着数据量的不断增加,在写入和读取过程中面临性能问题,且历史数据仅供业务查询参考,并不影响实际流程,从系统结构上来说,放在业务链条上游比较重。本项目将其置于下游数据处理 Hadoop 分布式平台来实现此需求。下面列一些具体的需求指标:

数据量:目前 check 表的累计数据量为 5000w+ 行,11GB;opinion 表的累计数据量为 3 亿 +,约 100GB。每日增量约为每张表 50 万 + 行,只做 insert,不做 update。

查询要求:check 表的主键为 id(Oracle 全局 id),查询键为 check_id,一个 check_id 对应多条记录,所以需返回对应记录的 list; opinion 表的主键也是 id,查询键是 bussiness_no 和 buss_type,同理返回 list。单笔查询返回 List 大小约 50 条以下,查询频率为 100 笔 / 天左右,查询响应时间 2s。
技术选型
从数据量及查询要求来看,分布式平台上具备大数据量存储,且提供实时查询能力的组件首选 HBase。根据需求做了初步的调研和评估后,大致确定 HBase 作为主要存储组件。将需求拆解为写入和读取 HBase 两部分。

读取 HBase 相对来说方案比较确定,基本根据需求设计 RowKey,然后根据 HBase 提供的丰富 API(get,scan 等)来读取数据,满足性能要求即可。

写入 HBase 的方法大致有以下几种:

1、 Java 调用 HBase 原生 API,HTable.add(List(Put))。
2、 MapReduce 作业,使用 TableOutputFormat 作为输出。
3、 Bulk Load,先将数据按照 HBase 的内部数据格式生成持久化的 HFile 文件,然后复制到合适的位置并通知 RegionServer ,即完成海量数据的入库。其中生成 Hfile 这一步可以选择 MapReduce 或 Spark。

本文采用第 3 种方式,Spark + Bulk Load 写入 HBase。该方法相对其他 2 种方式有以下优势:

① BulkLoad 不会写 WAL,也不会产生 flush 以及 split。
②如果我们大量调用 PUT 接口插入数据,可能会导致大量的 GC 操作。除了影响性能之外,严重时甚至可能会对 HBase 节点的稳定性造成影响,采用 BulkLoad 无此顾虑。
③过程中没有大量的接口调用消耗性能。
④可以利用 Spark 强大的计算能力。

图示如下:





设计
环境信息
Hadoop 2.5-2.7
HBase 0.98.6
Spark 2.0.0-2.1.1
Sqoop 1.4.6
表设计

本段的重点在于讨论 HBase 表的设计,其中 RowKey 是最重要的部分。为了方便说明问题,我们先来看看数据格式。以下以 check 举例,opinion 同理。

check 表(原表字段有 18 个,为方便描述,本文截选 5 个字段示意)





如上图所示,主键为 id,32 位字母和数字随机组成,业务查询字段 check_id 为不定长字段(不超过 32 位),字母和数字组成,同一 check_id 可能对应多条记录,其他为相关业务字段。众所周知,HBase 是基于 RowKey 提供查询,且要求 RowKey 是唯一的。RowKey 的设计主要考虑的是数据将怎样被访问。初步来看,我们有 2 种设计方法。

① 拆成 2 张表,一张表 id 作为 RowKey,列为 check 表对应的各列;另一张表为索引表,RowKey 为 check_id,每一列对应一个 id。查询时,先找到 check_id 对应的 id list,然后根据 id 找到对应的记录。均为 HBase 的 get 操作。

②将本需求可看成是一个范围查询,而不是单条查询。将 check_id 作为 RowKey 的前缀,后面跟 id。查询时设置 Scan 的 startRow 和 stopRow,找到对应的记录 list。

第一种方法优点是表结构简单,RowKey 容易设计,缺点为 1)数据写入时,一行原始数据需要写入到 2 张表,且索引表写入前需要先扫描该 RowKey 是否存在,如果存在,则加入一列,否则新建一行,2)读取的时候,即便是采用 List, 也至少需要读取 2 次表。第二种设计方法,RowKey 设计较为复杂,但是写入和读取都是一次性的。综合考虑,我们采用第二种设计方法。
 RowKey 设计
 
热点问题

HBase 中的行是以 RowKey 的字典序排序的,其热点问题通常发生在大量的客户端直接访问集群的一个或极少数节点。默认情况下,在开始建表时,表只会有一个 region,并随着 region 增大而拆分成更多的 region,这些 region 才能分布在多个 regionserver 上从而使负载均分。对于我们的业务需求,存量数据已经较大,因此有必要在一开始就将 HBase 的负载均摊到每个 regionserver,即做 pre-split。常见的防治热点的方法为加盐,hash 散列,自增部分(如时间戳)翻转等。

RowKey 设计

Step1:确定预分区数目,创建 HBase Table

不同的业务场景及数据特点确定数目的方式不一样,我个人认为应该综合考虑数据量大小和集群大小等因素。比如 check 表大小约为 11G,测试集群大小为 10 台机器,hbase.hregion.max.filesize=3G(当 region 的大小超过这个数时,将拆分为 2 个),所以初始化时尽量使得一个 region 的大小为 1~2G(不会一上来就 split),region 数据分到 11G/2G=6 个,但为了充分利用集群资源,本文中 check 表划分为 10 个分区。如果数据量为 100G,且不断增长,集群情况不变,则 region 数目增大到 100G/2G=50 个左右较合适。Hbase check 表建表语句如下: 
create 'tinawang:check',
{ NAME => 'f', COMPRESSION => 'SNAPPY',DATA_BLOCK_ENCODING => 'FAST_DIFF',BLOOMFILTER=>'ROW'},
{SPLITS => [ '1','2','3', '4','5','6','7','8','9']}其中,Column Family =‘f’,越短越好。

COMPRESSION => 'SNAPPY',HBase 支持 3 种压缩 LZO, GZIP and Snappy。GZIP 压缩率高,但是耗 CPU。后两者差不多,Snappy 稍微胜出一点,cpu 消耗的比 GZIP 少。一般在 IO 和 CPU 均衡下,选择 Snappy。

DATA_BLOCK_ENCODING => 'FAST_DIFF',本案例中 RowKey 较为接近,通过以下命令查看 key 长度相对 value 较长。
./hbase org.apache.hadoop.hbase.io.hfile.HFile -m -f /apps/hbase/data/data/tinawang/check/a661f0f95598662a53b3d8b1ae469fdf/f/a5fefc880f87492d908672e1634f2eed_SeqId_2_




Step2:RowKey 组成

Salt

让数据均衡的分布到各个 Region 上,结合 pre-split,我们对查询键即 check 表的 check_id 求 hashcode 值,然后 modulus(numRegions) 作为前缀,注意补齐数据。
StringUtils.leftPad(Integer.toString(Math.abs(check_id.hashCode() % numRegion)),1,’0’)
说明:如果数据量达上百 G 以上,则 numRegions 自然到 2 位数,则 salt 也为 2 位。

Hash 散列

因为 check_id 本身是不定长的字符数字串,为使数据散列化,方便 RowKey 查询和比较,我们对 check_id 采用 SHA1 散列化,并使之 32 位定长化。
MD5Hash.getMD5AsHex(Bytes.toBytes(check_id))
唯一性

以上 salt+hash 作为 RowKey 前缀,加上 check 表的主键 id 来保障 RowKey 唯一性。综上,check 表的 RowKey 设计如下:(check_id=A208849559)





为增强可读性,中间还可以加上自定义的分割符,如’+’,’|’等。
7+7c9498b4a83974da56b252122b9752bf+56B63AB98C2E00B4E053C501380709AD
以上设计能保证对每次查询而言,其 salt+hash 前缀值是确定的,并且落在同一个 region 中。需要说明的是 HBase 中 check 表的各列同数据源 Oracle 中 check 表的各列存储。

WEB 查询设计

RowKey 设计与查询息息相关,查询方式决定 RowKey 设计,反之基于以上 RowKey 设计,查询时通过设置 Scan 的 [startRow,stopRow], 即可完成扫描。以查询 check_id=A208849559 为例,根据 RowKey 的设计原则,对其进行 salt+hash 计算,得前缀。
startRow = 7+7c9498b4a83974da56b252122b9752bf
stopRow = 7+7c9498b4a83974da56b252122b9752bg
代码实现关键流程
Spark write to HBase

Step0: prepare work

因为是从上游系统承接的业务数据,存量数据采用 sqoop 抽到 hdfs;增量数据每日以文件的形式从 ftp 站点获取。因为业务数据字段中包含一些换行符,且 sqoop1.4.6 目前只支持单字节,所以本文选择’0x01’作为列分隔符,’0x10’作为行分隔符。

Step1: Spark read hdfs text file




SparkContext.textfile() 默认行分隔符为”\n”,此处我们用“0x10”,需要在 Configuration 中配置。应用配置,我们调用 newAPIHadoopFile 方法来读取 hdfs 文件,返回 JavaPairRDD,其中 LongWritable 和 Text 分别为 Hadoop 中的 Long 类型和 String 类型(所有 Hadoop 数据类型和 java 的数据类型都很相像,除了它们是针对网络序列化而做的特殊优化)。我们需要的数据文件放在 pairRDD 的 value 中,即 Text 指代。为后续处理方便,可将 JavaPairRDD转换为 JavaRDD< String >。

Step2: Transfer and sort RDD

① 将 avaRDD< String>转换成 JavaPairRDD<tuple2,String>,其中参数依次表示为,RowKey,col,value。做这样转换是因为 HBase 的基本原理是基于 RowKey 排序的,并且当采用 bulk load 方式将数据写入多个预分区(region)时,要求 Spark 各 partition 的数据是有序的,RowKey,column family(cf),col name 均需要有序。在本案例中因为只有一个列簇,所以将 RowKey 和 col name 组织出来为 Tuple2格式的 key。请注意原本数据库中的一行记录(n 个字段),此时会被拆成 n 行。

② 基于 JavaPairRDD<tuple2,String>进行 RowKey,col 的二次排序。如果不做排序,会报以下异常:
java.io.IOException: Added a key notlexically larger than previous key
③ 将数据组织成 HFile 要求的 JavaPairRDDhfileRDD。

Step3:create hfile and bulk load to HBase

①主要调用 saveAsNewAPIHadoopFile 方法:
hfileRdd.saveAsNewAPIHadoopFile(hfilePath,ImmutableBytesWritable.class,
KeyValue.class,HFileOutputFormat2.class,config);② hfilebulk load to HBase
final Job job = Job.getInstance();
job.setMapOutputKeyClass(ImmutableBytesWritable.class);
job.setMapOutputValueClass(KeyValue.class);
HFileOutputFormat2.configureIncrementalLoad(job,htable);
LoadIncrementalHFiles bulkLoader = newLoadIncrementalHFiles(config);
bulkLoader.doBulkLoad(newPath(hfilePath),htable);注:如果集群开启了 kerberos,step4 需要放置在 ugi.doAs()方法中,在进行如下验证后实现UserGroupInformation ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(keyUser,keytabPath);
UserGroupInformation.setLoginUser(ugi);访问 HBase 集群的 60010 端口 web,可以看到 region 分布情况。





Read from HBase

本文基于 spring boot 框架来开发 web 端访问 HBase 内数据。

use connection pool(使用连接池)

创建连接是一个比较重的操作,在实际 HBase 工程中,我们引入连接池来共享 zk 连接,meta 信息缓存,region server 和 master 的连接。
HConnection connection = HConnectionManager.createConnection(config);
HTableInterface table = connection.getTable("table1");
try {
// Use the table as needed, for a single operation and a single thread
} finally {
table.close();
}也可以通过以下方法,覆盖默认线程池。
HConnection createConnection(org.apache.hadoop.conf.Configuration conf,ExecutorService pool);
process query

Step1: 根据查询条件,确定 RowKey 前缀

根据 3.3 RowKey 设计介绍,HBase 的写和读都遵循该设计规则。此处我们采用相同的方法,将 web 调用方传入的查询条件,转化成对应的 RowKey 前缀。例如,查询 check 表传递过来的 check_id=A208849559,生成前缀 7+7c9498b4a83974da56b252122b9752bf。

Step2:确定 scan 范围

A208849559 对应的查询结果数据即在 RowKey 前缀为 7+7c9498b4a83974da56b252122b9752bf 对应的 RowKey 及 value 中。
scan.setStartRow(Bytes.toBytes(rowkey_pre)); //scan, 7+7c9498b4a83974da56b252122b9752bf
byte[] stopRow = Bytes.toBytes(rowkey_pre);
stopRow[stopRow.length-1]++;
scan.setStopRow(stopRow);// 7+7c9498b4a83974da56b252122b9752bgStep3:查询结果组成返回对象

遍历 ResultScanner 对象,将每一行对应的数据封装成 table entity,组成 list 返回。

测试

从原始数据中随机抓取 1000 个 check_id,用于模拟测试,连续发起 3 次请求数为 2000(200 个线程并发,循环 10 次),平均响应时间为 51ms,错误率为 0。










如上图,经历 N 次累计测试后,各个 region 上的 Requests 数较为接近,符合负载均衡设计之初。

踩坑记录
1、kerberos 认证问题

如果集群开启了安全认证,那么在进行 Spark 提交作业以及访问 HBase 时,均需要进行 kerberos 认证。

本文采用 yarn cluster 模式,像提交普通作业一样,可能会报以下错误。
ERROR StartApp: job failure,
java.lang.NullPointerException
at com.tinawang.spark.hbase.utils.HbaseKerberos.<init>(HbaseKerberos.java:18)
at com.tinawang.spark.hbase.job.SparkWriteHbaseJob.run(SparkWriteHbaseJob.java:60)定位到 HbaseKerberos.java:18,代码如下:
this.keytabPath = (Thread.currentThread().getContextClassLoader().getResource(prop.getProperty("hbase.keytab"))).getPath();
这是因为 executor 在进行 HBase 连接时,需要重新认证,通过 --keytab 上传的 tina.keytab 并未被 HBase 认证程序块获取到,所以认证的 keytab 文件需要另外通过 --files 上传。示意如下
--keytab /path/tina.keytab \
--principal tina@GNUHPC.ORG \
--files "/path/tina.keytab.hbase"其中 tina.keytab.hbase 是将 tina.keytab 复制并重命名而得。因为 Spark 不允许同一个文件重复上传。

2、序列化
 
org.apache.spark.SparkException: Task not serializable
at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:298)
at org.apache.spark.util.ClosureCleaner$.org$apache$spark$util$ClosureCleaner$$clean(ClosureCleaner.scala:288)
at org.apache.spark.util.ClosureCleaner$.clean(ClosureCleaner.scala:108)
at org.apache.spark.SparkContext.clean(SparkContext.scala:2101)
at org.apache.spark.rdd.RDD$$anonfun$map$1.apply(RDD.scala:370)
at org.apache.spark.rdd.RDD$$anonfun$map$1.apply(RDD.scala:369)
...
org.apache.spark.deploy.yarn.ApplicationMaster$$anon$2.run(ApplicationMaster.scala:637)
Caused by: java.io.NotSerializableException: org.apache.spark.api.java.JavaSparkContext
Serialization stack:
- object not serializable (class: org.apache.spark.api.java.JavaSparkContext, value: org.apache.spark.api.java.JavaSparkContext@24a16d8c)
- field (class: com.tinawang.spark.hbase.processor.SparkReadFileRDD, name: sc, type: class org.apache.spark.api.java.JavaSparkContext)
...解决方法一:

如果 sc 作为类的成员变量,在方法中被引用,则加 transient 关键字,使其不被序列化。
private transient JavaSparkContext sc;
解决方法二:

将 sc 作为方法参数传递,同时使涉及 RDD 操作的类 implements Serializable。 代码中采用第二种方法。详见代码。

3、批量请求测试
Exception in thread "http-nio-8091-Acceptor-0" java.lang.NoClassDefFoundError: org/apache/tomcat/util/ExceptionUtils
或者
Exception in thread "http-nio-8091-exec-34" java.lang.NoClassDefFoundError: ch/qos/logback/classic/spi/ThrowableProxy

查看下面 issue 以及一次排查问题的过程,可能是 open file 超过限制。

https://github.com/spring-proj ... /1106

http://mp.weixin.qq.com/s/34GVlaYDOdY1OQ9eZs-iXg

使用 ulimit-a 查看每个用户默认打开的文件数为 1024。

在系统文件 /etc/security/limits.conf 中修改这个数量限制,在文件中加入以下内容, 即可解决问题。

soft nofile 65536
hard nofile 65536

作者介绍

汪婷,中国民生银行大数据开发工程师,专注于 Spark 大规模数据处理和 Hbase 系统设计。
 
参考文献

http://hbase.apache.org/book.html#perf.writing
http://www.opencore.com/blog/2 ... park/
http://hbasefly.com/2016/03/23/hbase_writer/
https://github.com/spring-proj ... /1106
http://mp.weixin.qq.com/s/34GVlaYDOdY1OQ9eZs-iXg 查看全部
背景介绍
本项目主要解决 check 和 opinion2 张历史数据表(历史数据是指当业务发生过程中的完整中间流程和结果数据)的在线查询。原实现基于 Oracle 提供存储查询服务,随着数据量的不断增加,在写入和读取过程中面临性能问题,且历史数据仅供业务查询参考,并不影响实际流程,从系统结构上来说,放在业务链条上游比较重。本项目将其置于下游数据处理 Hadoop 分布式平台来实现此需求。下面列一些具体的需求指标:

数据量:目前 check 表的累计数据量为 5000w+ 行,11GB;opinion 表的累计数据量为 3 亿 +,约 100GB。每日增量约为每张表 50 万 + 行,只做 insert,不做 update。

查询要求:check 表的主键为 id(Oracle 全局 id),查询键为 check_id,一个 check_id 对应多条记录,所以需返回对应记录的 list; opinion 表的主键也是 id,查询键是 bussiness_no 和 buss_type,同理返回 list。单笔查询返回 List 大小约 50 条以下,查询频率为 100 笔 / 天左右,查询响应时间 2s。
技术选型
从数据量及查询要求来看,分布式平台上具备大数据量存储,且提供实时查询能力的组件首选 HBase。根据需求做了初步的调研和评估后,大致确定 HBase 作为主要存储组件。将需求拆解为写入和读取 HBase 两部分。

读取 HBase 相对来说方案比较确定,基本根据需求设计 RowKey,然后根据 HBase 提供的丰富 API(get,scan 等)来读取数据,满足性能要求即可。

写入 HBase 的方法大致有以下几种:

1、 Java 调用 HBase 原生 API,HTable.add(List(Put))。
2、 MapReduce 作业,使用 TableOutputFormat 作为输出。
3、 Bulk Load,先将数据按照 HBase 的内部数据格式生成持久化的 HFile 文件,然后复制到合适的位置并通知 RegionServer ,即完成海量数据的入库。其中生成 Hfile 这一步可以选择 MapReduce 或 Spark。

本文采用第 3 种方式,Spark + Bulk Load 写入 HBase。该方法相对其他 2 种方式有以下优势:

① BulkLoad 不会写 WAL,也不会产生 flush 以及 split。
②如果我们大量调用 PUT 接口插入数据,可能会导致大量的 GC 操作。除了影响性能之外,严重时甚至可能会对 HBase 节点的稳定性造成影响,采用 BulkLoad 无此顾虑。
③过程中没有大量的接口调用消耗性能。
④可以利用 Spark 强大的计算能力。

图示如下:

671-1513698266272.jpg

设计
环境信息
Hadoop 2.5-2.7
HBase 0.98.6
Spark 2.0.0-2.1.1
Sqoop 1.4.6

表设计

本段的重点在于讨论 HBase 表的设计,其中 RowKey 是最重要的部分。为了方便说明问题,我们先来看看数据格式。以下以 check 举例,opinion 同理。

check 表(原表字段有 18 个,为方便描述,本文截选 5 个字段示意)

下载.jpg

如上图所示,主键为 id,32 位字母和数字随机组成,业务查询字段 check_id 为不定长字段(不超过 32 位),字母和数字组成,同一 check_id 可能对应多条记录,其他为相关业务字段。众所周知,HBase 是基于 RowKey 提供查询,且要求 RowKey 是唯一的。RowKey 的设计主要考虑的是数据将怎样被访问。初步来看,我们有 2 种设计方法。

① 拆成 2 张表,一张表 id 作为 RowKey,列为 check 表对应的各列;另一张表为索引表,RowKey 为 check_id,每一列对应一个 id。查询时,先找到 check_id 对应的 id list,然后根据 id 找到对应的记录。均为 HBase 的 get 操作。

②将本需求可看成是一个范围查询,而不是单条查询。将 check_id 作为 RowKey 的前缀,后面跟 id。查询时设置 Scan 的 startRow 和 stopRow,找到对应的记录 list。

第一种方法优点是表结构简单,RowKey 容易设计,缺点为 1)数据写入时,一行原始数据需要写入到 2 张表,且索引表写入前需要先扫描该 RowKey 是否存在,如果存在,则加入一列,否则新建一行,2)读取的时候,即便是采用 List, 也至少需要读取 2 次表。第二种设计方法,RowKey 设计较为复杂,但是写入和读取都是一次性的。综合考虑,我们采用第二种设计方法。
 RowKey 设计
 
热点问题

HBase 中的行是以 RowKey 的字典序排序的,其热点问题通常发生在大量的客户端直接访问集群的一个或极少数节点。默认情况下,在开始建表时,表只会有一个 region,并随着 region 增大而拆分成更多的 region,这些 region 才能分布在多个 regionserver 上从而使负载均分。对于我们的业务需求,存量数据已经较大,因此有必要在一开始就将 HBase 的负载均摊到每个 regionserver,即做 pre-split。常见的防治热点的方法为加盐,hash 散列,自增部分(如时间戳)翻转等。

RowKey 设计

Step1:确定预分区数目,创建 HBase Table

不同的业务场景及数据特点确定数目的方式不一样,我个人认为应该综合考虑数据量大小和集群大小等因素。比如 check 表大小约为 11G,测试集群大小为 10 台机器,hbase.hregion.max.filesize=3G(当 region 的大小超过这个数时,将拆分为 2 个),所以初始化时尽量使得一个 region 的大小为 1~2G(不会一上来就 split),region 数据分到 11G/2G=6 个,但为了充分利用集群资源,本文中 check 表划分为 10 个分区。如果数据量为 100G,且不断增长,集群情况不变,则 region 数目增大到 100G/2G=50 个左右较合适。Hbase check 表建表语句如下: 
create 'tinawang:check',
{ NAME => 'f', COMPRESSION => 'SNAPPY',DATA_BLOCK_ENCODING => 'FAST_DIFF',BLOOMFILTER=>'ROW'},
{SPLITS => [ '1','2','3', '4','5','6','7','8','9']}
其中,Column Family =‘f’,越短越好。

COMPRESSION => 'SNAPPY',HBase 支持 3 种压缩 LZO, GZIP and Snappy。GZIP 压缩率高,但是耗 CPU。后两者差不多,Snappy 稍微胜出一点,cpu 消耗的比 GZIP 少。一般在 IO 和 CPU 均衡下,选择 Snappy。

DATA_BLOCK_ENCODING => 'FAST_DIFF',本案例中 RowKey 较为接近,通过以下命令查看 key 长度相对 value 较长。
./hbase org.apache.hadoop.hbase.io.hfile.HFile -m -f /apps/hbase/data/data/tinawang/check/a661f0f95598662a53b3d8b1ae469fdf/f/a5fefc880f87492d908672e1634f2eed_SeqId_2_
下载.png

Step2:RowKey 组成

Salt

让数据均衡的分布到各个 Region 上,结合 pre-split,我们对查询键即 check 表的 check_id 求 hashcode 值,然后 modulus(numRegions) 作为前缀,注意补齐数据。
StringUtils.leftPad(Integer.toString(Math.abs(check_id.hashCode() % numRegion)),1,’0’) 
说明:如果数据量达上百 G 以上,则 numRegions 自然到 2 位数,则 salt 也为 2 位。

Hash 散列

因为 check_id 本身是不定长的字符数字串,为使数据散列化,方便 RowKey 查询和比较,我们对 check_id 采用 SHA1 散列化,并使之 32 位定长化。
MD5Hash.getMD5AsHex(Bytes.toBytes(check_id))
唯一性

以上 salt+hash 作为 RowKey 前缀,加上 check 表的主键 id 来保障 RowKey 唯一性。综上,check 表的 RowKey 设计如下:(check_id=A208849559)

1084-1513698265991.png

为增强可读性,中间还可以加上自定义的分割符,如’+’,’|’等。
7+7c9498b4a83974da56b252122b9752bf+56B63AB98C2E00B4E053C501380709AD

以上设计能保证对每次查询而言,其 salt+hash 前缀值是确定的,并且落在同一个 region 中。需要说明的是 HBase 中 check 表的各列同数据源 Oracle 中 check 表的各列存储。

WEB 查询设计

RowKey 设计与查询息息相关,查询方式决定 RowKey 设计,反之基于以上 RowKey 设计,查询时通过设置 Scan 的 [startRow,stopRow], 即可完成扫描。以查询 check_id=A208849559 为例,根据 RowKey 的设计原则,对其进行 salt+hash 计算,得前缀。
startRow = 7+7c9498b4a83974da56b252122b9752bf 
stopRow = 7+7c9498b4a83974da56b252122b9752bg

代码实现关键流程
Spark write to HBase

Step0: prepare work

因为是从上游系统承接的业务数据,存量数据采用 sqoop 抽到 hdfs;增量数据每日以文件的形式从 ftp 站点获取。因为业务数据字段中包含一些换行符,且 sqoop1.4.6 目前只支持单字节,所以本文选择’0x01’作为列分隔符,’0x10’作为行分隔符。

Step1: Spark read hdfs text file
下载_(1).jpg

SparkContext.textfile() 默认行分隔符为”\n”,此处我们用“0x10”,需要在 Configuration 中配置。应用配置,我们调用 newAPIHadoopFile 方法来读取 hdfs 文件,返回 JavaPairRDD,其中 LongWritable 和 Text 分别为 Hadoop 中的 Long 类型和 String 类型(所有 Hadoop 数据类型和 java 的数据类型都很相像,除了它们是针对网络序列化而做的特殊优化)。我们需要的数据文件放在 pairRDD 的 value 中,即 Text 指代。为后续处理方便,可将 JavaPairRDD转换为 JavaRDD< String >。

Step2: Transfer and sort RDD

① 将 avaRDD< String>转换成 JavaPairRDD<tuple2,String>,其中参数依次表示为,RowKey,col,value。做这样转换是因为 HBase 的基本原理是基于 RowKey 排序的,并且当采用 bulk load 方式将数据写入多个预分区(region)时,要求 Spark 各 partition 的数据是有序的,RowKey,column family(cf),col name 均需要有序。在本案例中因为只有一个列簇,所以将 RowKey 和 col name 组织出来为 Tuple2格式的 key。请注意原本数据库中的一行记录(n 个字段),此时会被拆成 n 行。

② 基于 JavaPairRDD<tuple2,String>进行 RowKey,col 的二次排序。如果不做排序,会报以下异常:
java.io.IOException: Added a key notlexically larger than previous key
③ 将数据组织成 HFile 要求的 JavaPairRDDhfileRDD。

Step3:create hfile and bulk load to HBase

①主要调用 saveAsNewAPIHadoopFile 方法:
hfileRdd.saveAsNewAPIHadoopFile(hfilePath,ImmutableBytesWritable.class,
KeyValue.class,HFileOutputFormat2.class,config);
② hfilebulk load to HBase
final Job job = Job.getInstance();
job.setMapOutputKeyClass(ImmutableBytesWritable.class);
job.setMapOutputValueClass(KeyValue.class);
HFileOutputFormat2.configureIncrementalLoad(job,htable);
LoadIncrementalHFiles bulkLoader = newLoadIncrementalHFiles(config);
bulkLoader.doBulkLoad(newPath(hfilePath),htable);
注:如果集群开启了 kerberos,step4 需要放置在 ugi.doAs()方法中,在进行如下验证后实现
UserGroupInformation ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(keyUser,keytabPath);
UserGroupInformation.setLoginUser(ugi);
访问 HBase 集群的 60010 端口 web,可以看到 region 分布情况。

896-1513698266412.png

Read from HBase

本文基于 spring boot 框架来开发 web 端访问 HBase 内数据。

use connection pool(使用连接池)

创建连接是一个比较重的操作,在实际 HBase 工程中,我们引入连接池来共享 zk 连接,meta 信息缓存,region server 和 master 的连接。
HConnection connection = HConnectionManager.createConnection(config);
HTableInterface table = connection.getTable("table1");
try {
// Use the table as needed, for a single operation and a single thread
} finally {
table.close();
}
也可以通过以下方法,覆盖默认线程池。
HConnection createConnection(org.apache.hadoop.conf.Configuration conf,ExecutorService pool);
process query

Step1: 根据查询条件,确定 RowKey 前缀

根据 3.3 RowKey 设计介绍,HBase 的写和读都遵循该设计规则。此处我们采用相同的方法,将 web 调用方传入的查询条件,转化成对应的 RowKey 前缀。例如,查询 check 表传递过来的 check_id=A208849559,生成前缀 7+7c9498b4a83974da56b252122b9752bf。

Step2:确定 scan 范围

A208849559 对应的查询结果数据即在 RowKey 前缀为 7+7c9498b4a83974da56b252122b9752bf 对应的 RowKey 及 value 中。
scan.setStartRow(Bytes.toBytes(rowkey_pre)); //scan, 7+7c9498b4a83974da56b252122b9752bf
byte[] stopRow = Bytes.toBytes(rowkey_pre);
stopRow[stopRow.length-1]++;
scan.setStopRow(stopRow);// 7+7c9498b4a83974da56b252122b9752bg
Step3:查询结果组成返回对象

遍历 ResultScanner 对象,将每一行对应的数据封装成 table entity,组成 list 返回。

测试

从原始数据中随机抓取 1000 个 check_id,用于模拟测试,连续发起 3 次请求数为 2000(200 个线程并发,循环 10 次),平均响应时间为 51ms,错误率为 0。

667-1513699401621.png


558-1513699401302.png

如上图,经历 N 次累计测试后,各个 region 上的 Requests 数较为接近,符合负载均衡设计之初。

踩坑记录
1、kerberos 认证问题

如果集群开启了安全认证,那么在进行 Spark 提交作业以及访问 HBase 时,均需要进行 kerberos 认证。

本文采用 yarn cluster 模式,像提交普通作业一样,可能会报以下错误。
ERROR StartApp: job failure,
java.lang.NullPointerException
at com.tinawang.spark.hbase.utils.HbaseKerberos.<init>(HbaseKerberos.java:18)
at com.tinawang.spark.hbase.job.SparkWriteHbaseJob.run(SparkWriteHbaseJob.java:60)
定位到 HbaseKerberos.java:18,代码如下:
this.keytabPath = (Thread.currentThread().getContextClassLoader().getResource(prop.getProperty("hbase.keytab"))).getPath();
这是因为 executor 在进行 HBase 连接时,需要重新认证,通过 --keytab 上传的 tina.keytab 并未被 HBase 认证程序块获取到,所以认证的 keytab 文件需要另外通过 --files 上传。示意如下
--keytab /path/tina.keytab \
--principal tina@GNUHPC.ORG \
--files "/path/tina.keytab.hbase"
其中 tina.keytab.hbase 是将 tina.keytab 复制并重命名而得。因为 Spark 不允许同一个文件重复上传。

2、序列化
 
org.apache.spark.SparkException: Task not serializable
at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:298)
at org.apache.spark.util.ClosureCleaner$.org$apache$spark$util$ClosureCleaner$$clean(ClosureCleaner.scala:288)
at org.apache.spark.util.ClosureCleaner$.clean(ClosureCleaner.scala:108)
at org.apache.spark.SparkContext.clean(SparkContext.scala:2101)
at org.apache.spark.rdd.RDD$$anonfun$map$1.apply(RDD.scala:370)
at org.apache.spark.rdd.RDD$$anonfun$map$1.apply(RDD.scala:369)
...
org.apache.spark.deploy.yarn.ApplicationMaster$$anon$2.run(ApplicationMaster.scala:637)
Caused by: java.io.NotSerializableException: org.apache.spark.api.java.JavaSparkContext
Serialization stack:
- object not serializable (class: org.apache.spark.api.java.JavaSparkContext, value: org.apache.spark.api.java.JavaSparkContext@24a16d8c)
- field (class: com.tinawang.spark.hbase.processor.SparkReadFileRDD, name: sc, type: class org.apache.spark.api.java.JavaSparkContext)
...
解决方法一:

如果 sc 作为类的成员变量,在方法中被引用,则加 transient 关键字,使其不被序列化。
private transient JavaSparkContext sc;

解决方法二:

将 sc 作为方法参数传递,同时使涉及 RDD 操作的类 implements Serializable。 代码中采用第二种方法。详见代码。

3、批量请求测试
Exception in thread "http-nio-8091-Acceptor-0" java.lang.NoClassDefFoundError: org/apache/tomcat/util/ExceptionUtils
或者
Exception in thread "http-nio-8091-exec-34" java.lang.NoClassDefFoundError: ch/qos/logback/classic/spi/ThrowableProxy

查看下面 issue 以及一次排查问题的过程,可能是 open file 超过限制。

https://github.com/spring-proj ... /1106

http://mp.weixin.qq.com/s/34GVlaYDOdY1OQ9eZs-iXg

使用 ulimit-a 查看每个用户默认打开的文件数为 1024。

在系统文件 /etc/security/limits.conf 中修改这个数量限制,在文件中加入以下内容, 即可解决问题。

soft nofile 65536
hard nofile 65536

作者介绍

汪婷,中国民生银行大数据开发工程师,专注于 Spark 大规模数据处理和 Hbase 系统设计。
 
参考文献

http://hbase.apache.org/book.html#perf.writing
http://www.opencore.com/blog/2 ... park/
http://hbasefly.com/2016/03/23/hbase_writer/
https://github.com/spring-proj ... /1106
http://mp.weixin.qq.com/s/34GVlaYDOdY1OQ9eZs-iXg

opentsdb一般在什么场景下使用?

opentsdb时序LittleLawson 回复了问题 • 3 人关注 • 3 个回复 • 3829 次浏览 • 2018-08-11 21:27 • 来自相关话题

HBase 优化实战

hbase过往记忆 发表了文1章 • 0 个评论 • 942 次浏览 • 2018-08-10 12:44 • 来自相关话题

背景

Datastream一直以来在使用HBase分流日志,每天的数据量很大,日均大概在80亿条,10TB的数据。对于像Datastream这种数据量巨大、对写入要求非常高,并且没有复杂查询需求的日志系统来说,选用HBase作为其数据存储平台,无疑是一个非常不错的选择。

HBase是一个相对较复杂的分布式系统,并发写入的性能非常高。然而,分布式系统从结构上来讲,也相对较复杂,模块繁多,各个模块之间也很容易出现一些问题,所以对像HBase这样的大型分布式系统来说,优化系统运行,及时解决系统运行过程中出现的问题也变得至关重要。正所谓:“你”若安好,便是晴天;“你”若有恙,我便没有星期天。

历史现状

HBase交接到我们团队手上时,已经在线上运行有一大段时间了,期间也偶尔听到过系统不稳定的、时常会出现一些问题的言论,但我们认为:一个能被大型互联网公司广泛采用的系统(包括Facebook,twitter,淘宝,小米等),其在性能和可用性上是毋庸置疑的,何况像Facebook这种公司,是在经过严格选型后,放弃了自己开发的Cassandra系统,用HBase取而代之。既然这样,那么,HBase的不稳定、经常出问题一定有些其他的原因,我们所要做的,就是找出这些HBase的不稳定因素,还HBase一个“清白”。“查案”之前,先来简单回顾一下我们接手HBase时的现状(我们运维着好几个HBase集群,这里主要介绍问题最多那个集群的调优):





应用反应经常会过段时间出现数据写入缓慢,导致应用端数据堆积现象,是否可以通过增加机器数量来解决?

  其实,那个时候,我们本身对HBase也不是很熟悉,对HBase的了解,也仅仅在做过一些测试,了解一些性能,对内部结构,实现原理之类的基本上都不怎么清楚。于是刚开始几天,各种问题,每天晚上拉着一男一起摸索,顺利的时候,晚上8,9点就可以暂时搞定线上问题,更多的时候基本要到22点甚至更晚(可能那个时候流量也下去了),通过不断的摸索,慢慢了解HBase在使用上的一些限制,也就能逐渐解决这一系列过程中发现的问题。后面挑几个相对比较重要,效果较为明显的改进点,做下简单介绍。

调优

首先根据目前17台机器,50000+的QPS,并且观察磁盘的I/O利用率和CPU利用率都相当低来判断:当前的请求数量根本没有达到系统的性能瓶颈,不需要新增机器来提高性能。如果不是硬件资源问题,那么性能的瓶颈究竟是什么?

Rowkey设计问题

现象

打开HBase的Web端,发现HBase下面各个RegionServer的请求数量非常不均匀,第一个想到的就是HBase的热点问题,具体到某个具体表上的请求分布如下:




HBase表请求分布

上面是HBase下某张表的region请求分布情况,从中我们明显可以看到,部分region的请求数量为0,而部分的请求数量可以上百万,这是一个典型的热点问题。

原因

HBase出现热点问题的主要原因无非就是rowkey设计的合理性,像上面这种问题,如果rowkey设计得不好,很容易出现,比如:用时间戳生成rowkey,由于时间戳在一段时间内都是连续的,导致在不同的时间段,访问都集中在几个RegionServer上,从而造成热点问题。

解决

知道了问题的原因,对症下药即可,联系应用修改rowkey规则,使rowkey数据随机均匀分布,效果如下:




Rowkey重定义后请求分布

建议

  对于HBase来说,rowkey的范围划定了RegionServer,每一段rowkey区间对应一个RegionServer,我们要保证每段时间内的rowkey访问都是均匀的,所以我们在设计的时候,尽量要以hash或者md5等开头来组织rowkey。

Region重分布

现象

HBase的集群是在不断扩展的,分布式系统的最大好处除了性能外,不停服横向扩展也是其中之一,扩展过程中有一个问题:每次扩展的机器的配置是不一样的,一般,后面新加入的机器性能会比老的机器好,但是后面加入的机器经常被分配很少的region,这样就造成了资源分布不均匀,随之而来的就是性能上的损失,如下:




HBase各个RegionServer请求

上图中我们可以看到,每台RegionServer上的请求极为不均匀,多的好几千,少的只有几十

原因

资源分配不均匀,造成部分机器压力较大,部分机器负载较低,并且部分Region过大过热,导致请求相对较集中。

解决

迁移部分老的RegionServer上的region到新加入的机器上,使每个RegionServer的负载均匀。通过split切分部分较大region,均匀分布热点region到各个RegionServer上。




HBase region请求分布

对比前后两张截图我们可以看到,Region总数量从1336增加到了1426,而增加的这90个region就是通过split切分大的region得到的。而对region重新分布后,整个HBase的性能有了大幅度提高。

建议

Region迁移的时候不能简单开启自动balance,因为balance主要的问题是不会根据表来进行balance,HBase的自动balance只会根据每个RegionServer上的Region数量来进行balance,所以自动balance可能会造成同张表的region会被集中迁移到同一个台RegionServer上,这样就达不到分布式的效果。

基本上,新增RegionServer后的region调整,可以手工进行,尽量使表的Region都平均分配到各个RegionServer上,另外一点,新增的RegionServer机器,配置最好与前面的一致,否则资源无法更好利用。

对于过大,过热的region,可以通过切分的方法生成多个小region后均匀分布(注意:region切分会触发major compact操作,会带来较大的I/O请求,请务必在业务低峰期进行)

HDFS写入超时

现象

HBase写入缓慢,查看HBase日志,经常有慢日志如下:

WARN org.apache.hadoop.ipc.HBaseServer- (responseTooSlow): {"processingtimems":36096, "call":"multi(org.apache.hadoop.hbase.client.MultiAction@7884377e), rpc version=1, client version=29, methodsFingerPrint=1891768260", "client":"xxxx.xxx.xxx.xxxx:44367", "starttimems":1440239670790, "queuetimems":42081, "class":"HRegionServer", "responsesize":0, "method":"multi"}

并且伴有HDFS创建block异常如下:

INFO  org.apache.hadoop.hdfs.DFSClient - Exception in createBlockOutputStream

org.apache.hadoop.hdfs.protocol.HdfsProtoUtil.vintPrefixed(HdfsProtoUtil.java:171)

org.apache.hadoop.hdfs.DFSOutputStream$DataStreamer.createBlockOutputStream(DFSOutputStream.java:1105)

org.apache.hadoop.hdfs.DFSOutputStream$DataStreamer.nextBlockOutputStream(DFSOutputStream.java:1039)

org.apache.hadoop.hdfs.DFSOutputStream$DataStreamer.run(DFSOutputStream.java:487)

一般地,HBase客户端的写入到RegionServer下某个region的memstore后就返回,除了网络外,其他都是内存操作,应该不会有长达30多秒的延迟,外加HDFS层抛出的异常,我们怀疑很可能跟底层数据存储有关。

原因

定位到可能是HDFS层出现了问题,那就先从底层开始排查,发现该台机器上10块盘的空间利用率都已经达到100%。按理说,作为一个成熟的分布式文件系统,对于部分数据盘满的情况,应该有其应对措施。的确,HDFS本身可以设置数据盘预留空间,如果部分数据盘的预留空间小于该值时,HDFS会自动把数据写入到另外的空盘上面,那么我们这个又是什么情况?

  最终通过多方面的沟通确认,发现了主要原因:我们这批机器,在上线前SA已经经过处理,每块盘默认预留100G空间,所以当通过df命令查看盘使用率为100%时,其实盘还有100G的预留空间,而HDFS层面我们配置的预留空间是50G,那么问题就来了:HDFS认为盘还有100G空间,并且多于50G的预留,所以数据可以写入本地盘,但是系统层面却禁止了该写入操作,从而导致数据写入异常。

解决

解决的方法可以让SA释放些空间出来便于数据写入。当然,最直接有效的就是把HDFS的预留空间调整至100G以上,我们也正是这样做的,通过调整后,异常不再出现,HBase层面的slow log也没有再出现。同时我们也开启了HDFS层面的balance,使数据自动在各个服务器之间保持平衡。

建议

磁盘满了导致的问题很难预料,HDFS可能会导致部分数据写入异常,MySQL可能会出现直接宕机等等,所以最好的办法就是:不要使盘的利用率达到100%。

网络拓扑

现象

通过rowkey调整,HDFS数据balance等操作后,HBase的确稳定了许多,在很长一段时间都没有出现写入缓慢问题,整体的性能也上涨了很多。但时常会隔一段时间出现些slow log,虽然对整体的性能影响不大,但性能上的抖动还是很明显。

原因

由于该问题不经常出现,对系统的诊断带来不小的麻烦,排查了HBase层和HDFS层,几乎一无所获,因为在大多数情况下,系统的吞吐量都是正常的。通过脚本收集RegionServer所在服务器的系统资源信息,也看不出问题所在,最后怀疑到系统的物理拓扑上,HBase集群的最大特点是数据量巨大,在做一些操作时,很容易把物理机的千兆网卡都吃满,这样如果网络拓扑结构存在问题,HBase的所有机器没有部署在同一个交换机上,上层交换机的进出口流量也有可能存在瓶颈。网络测试还是挺简单的,直接ping就可以,我们得到以下结果:共17台机器,只有其中一台的延迟存在问题,如下:




网络延迟测试:Ping结果

同一个局域网内的机器,延迟达到了毫秒级别,这个延迟是比较致命的,因为分布式存储系统HDFS本身对网络有要求,HDFS默认3副本存在不同的机器上,如果其中某台机器的网络存在问题,这样就会影响到该机器上保存副本的写入,拖慢整个HDFS的写入速度。

解决

  网络问题,联系机房解决,机房的反馈也验证了我们的想法:由于HBase的机器后面进行了扩展,后面加入的机器有一台跟其他机器不在同一个交换机下,而这台机器正是我们找出的有较大ping延时这台,整个HBase物理结构如下:




HBase物理拓扑结构

跟机房协调,调整机器位置,使所有的HBase机器都位于同一个交换机下,问题迎刃而解。

建议

  对于分布式大流量的系统,除了系统本身,物理机的部署和流量规划也相当重要,尽量使集群中所有的机器位于相同的交换机下(有容灾需求的应用除外),集群较大,需要跨交换机部署时,也要充分考虑交换机的出口流量是否够用,网络硬件上的瓶颈诊断起来相对更为困难。

JVM参数调整

解决了网络上面的不稳定因素,HBase的性能又得到进一步的提高,随之也带来了另外的问题。

现象

  根据应用反应,HBase会阶段性出现性能下降,导致应用数据写入缓慢,造成应用端的数据堆积,这又是怎么回事?经过一系列改善后HBase的系统较之以前有了大幅度增长,怎么还会出现数据堆积的问题?为什么会阶段性出现?




从上图看,HBase平均流量QPS基本能达到12w,但是每过一段时间,流量就会下降到接近零点,同时这段时间,应用会反应数据堆积。

原因

  这个问题定位相对还是比较简单,结合HBase的日志,很容易找到问题所在:

org.apache.hadoop.hbase.util.Sleeper - We slept 41662ms instead of 3000ms, this is likely due to a long garbage collecting pause and it's usually bad

通过上述日志,基本上可以判定是HBase的某台RegionServer出现GC问题,导致了服务在很长一段时间内禁止访问。

  HBase通过一系列的调整后,整个系统的吞吐量增加了好几倍,然而JVM的堆大小没有进行相应的调整,整个系统的内存需求变大,而虚拟机又来不及回收,最终导致出现Full GC

解决

   GC问题导致HBase整个系统的请求下降,通过适当调整JVM参数的方式,解决HBase RegionServer的GC问题。

建议

  对于HBase来说,本身不存在单点故障,即使宕掉1,2台RegionServer,也只是使剩下几台的压力有所增加,不会导致整个集群服务能力下降很多。但是,如果其中某台RegionServer出现Full GC问题,那么这台机器上所有的访问都会被挂起,客户端请求一般都是batch发送的,rowkey的随机分布导致部分请求会落到该台RegionServer上,这样该客户端的请求就会被阻塞,导致客户端无法正常写数据到HBase。所以,对于HBase来说,宕机并不可怕,但长时间的Full GC是比较致命的,配置JVM参数的时候,尽量要考虑避免Full GC的出现。

后记

  经过前面一系列的优化,目前Datastream的这套HBase线上环境已经相当稳定,连续运行几个月都没有任何HBase层面由于系统性能不稳定导致的报警,平均性能在各个时间段都比较稳定,没有出现过大幅度的波动或者服务不可用等现象。

本文来自网易实践者社区 查看全部
背景

Datastream一直以来在使用HBase分流日志,每天的数据量很大,日均大概在80亿条,10TB的数据。对于像Datastream这种数据量巨大、对写入要求非常高,并且没有复杂查询需求的日志系统来说,选用HBase作为其数据存储平台,无疑是一个非常不错的选择。

HBase是一个相对较复杂的分布式系统,并发写入的性能非常高。然而,分布式系统从结构上来讲,也相对较复杂,模块繁多,各个模块之间也很容易出现一些问题,所以对像HBase这样的大型分布式系统来说,优化系统运行,及时解决系统运行过程中出现的问题也变得至关重要。正所谓:“你”若安好,便是晴天;“你”若有恙,我便没有星期天。

历史现状

HBase交接到我们团队手上时,已经在线上运行有一大段时间了,期间也偶尔听到过系统不稳定的、时常会出现一些问题的言论,但我们认为:一个能被大型互联网公司广泛采用的系统(包括Facebook,twitter,淘宝,小米等),其在性能和可用性上是毋庸置疑的,何况像Facebook这种公司,是在经过严格选型后,放弃了自己开发的Cassandra系统,用HBase取而代之。既然这样,那么,HBase的不稳定、经常出问题一定有些其他的原因,我们所要做的,就是找出这些HBase的不稳定因素,还HBase一个“清白”。“查案”之前,先来简单回顾一下我们接手HBase时的现状(我们运维着好几个HBase集群,这里主要介绍问题最多那个集群的调优):

menu.saveimg_.savepath20180810124058_.jpg

应用反应经常会过段时间出现数据写入缓慢,导致应用端数据堆积现象,是否可以通过增加机器数量来解决?

  其实,那个时候,我们本身对HBase也不是很熟悉,对HBase的了解,也仅仅在做过一些测试,了解一些性能,对内部结构,实现原理之类的基本上都不怎么清楚。于是刚开始几天,各种问题,每天晚上拉着一男一起摸索,顺利的时候,晚上8,9点就可以暂时搞定线上问题,更多的时候基本要到22点甚至更晚(可能那个时候流量也下去了),通过不断的摸索,慢慢了解HBase在使用上的一些限制,也就能逐渐解决这一系列过程中发现的问题。后面挑几个相对比较重要,效果较为明显的改进点,做下简单介绍。

调优

首先根据目前17台机器,50000+的QPS,并且观察磁盘的I/O利用率和CPU利用率都相当低来判断:当前的请求数量根本没有达到系统的性能瓶颈,不需要新增机器来提高性能。如果不是硬件资源问题,那么性能的瓶颈究竟是什么?

Rowkey设计问题

现象

打开HBase的Web端,发现HBase下面各个RegionServer的请求数量非常不均匀,第一个想到的就是HBase的热点问题,具体到某个具体表上的请求分布如下:
201806281646495b8a5674-3a09-4c12-8f81-5ff43ebc0fa9.jpg

HBase表请求分布

上面是HBase下某张表的region请求分布情况,从中我们明显可以看到,部分region的请求数量为0,而部分的请求数量可以上百万,这是一个典型的热点问题。

原因

HBase出现热点问题的主要原因无非就是rowkey设计的合理性,像上面这种问题,如果rowkey设计得不好,很容易出现,比如:用时间戳生成rowkey,由于时间戳在一段时间内都是连续的,导致在不同的时间段,访问都集中在几个RegionServer上,从而造成热点问题。

解决

知道了问题的原因,对症下药即可,联系应用修改rowkey规则,使rowkey数据随机均匀分布,效果如下:
20180628164659ff3e72b7-9be5-4671-ba17-6a37aafd0cdd.jpg

Rowkey重定义后请求分布

建议

  对于HBase来说,rowkey的范围划定了RegionServer,每一段rowkey区间对应一个RegionServer,我们要保证每段时间内的rowkey访问都是均匀的,所以我们在设计的时候,尽量要以hash或者md5等开头来组织rowkey。

Region重分布

现象

HBase的集群是在不断扩展的,分布式系统的最大好处除了性能外,不停服横向扩展也是其中之一,扩展过程中有一个问题:每次扩展的机器的配置是不一样的,一般,后面新加入的机器性能会比老的机器好,但是后面加入的机器经常被分配很少的region,这样就造成了资源分布不均匀,随之而来的就是性能上的损失,如下:
201806281647298f5c069c-bc91-4706-bb78-ad825e4a15d4.jpg

HBase各个RegionServer请求

上图中我们可以看到,每台RegionServer上的请求极为不均匀,多的好几千,少的只有几十

原因

资源分配不均匀,造成部分机器压力较大,部分机器负载较低,并且部分Region过大过热,导致请求相对较集中。

解决

迁移部分老的RegionServer上的region到新加入的机器上,使每个RegionServer的负载均匀。通过split切分部分较大region,均匀分布热点region到各个RegionServer上。
20180628164752abef950e-8d45-42ca-a5fe-0b1396a96b5f.jpg

HBase region请求分布

对比前后两张截图我们可以看到,Region总数量从1336增加到了1426,而增加的这90个region就是通过split切分大的region得到的。而对region重新分布后,整个HBase的性能有了大幅度提高。

建议

Region迁移的时候不能简单开启自动balance,因为balance主要的问题是不会根据表来进行balance,HBase的自动balance只会根据每个RegionServer上的Region数量来进行balance,所以自动balance可能会造成同张表的region会被集中迁移到同一个台RegionServer上,这样就达不到分布式的效果。

基本上,新增RegionServer后的region调整,可以手工进行,尽量使表的Region都平均分配到各个RegionServer上,另外一点,新增的RegionServer机器,配置最好与前面的一致,否则资源无法更好利用。

对于过大,过热的region,可以通过切分的方法生成多个小region后均匀分布(注意:region切分会触发major compact操作,会带来较大的I/O请求,请务必在业务低峰期进行)

HDFS写入超时

现象

HBase写入缓慢,查看HBase日志,经常有慢日志如下:

WARN org.apache.hadoop.ipc.HBaseServer- (responseTooSlow): {"processingtimems":36096, "call":"multi(org.apache.hadoop.hbase.client.MultiAction@7884377e), rpc version=1, client version=29, methodsFingerPrint=1891768260", "client":"xxxx.xxx.xxx.xxxx:44367", "starttimems":1440239670790, "queuetimems":42081, "class":"HRegionServer", "responsesize":0, "method":"multi"}

并且伴有HDFS创建block异常如下:

INFO  org.apache.hadoop.hdfs.DFSClient - Exception in createBlockOutputStream

org.apache.hadoop.hdfs.protocol.HdfsProtoUtil.vintPrefixed(HdfsProtoUtil.java:171)

org.apache.hadoop.hdfs.DFSOutputStream$DataStreamer.createBlockOutputStream(DFSOutputStream.java:1105)

org.apache.hadoop.hdfs.DFSOutputStream$DataStreamer.nextBlockOutputStream(DFSOutputStream.java:1039)

org.apache.hadoop.hdfs.DFSOutputStream$DataStreamer.run(DFSOutputStream.java:487)

一般地,HBase客户端的写入到RegionServer下某个region的memstore后就返回,除了网络外,其他都是内存操作,应该不会有长达30多秒的延迟,外加HDFS层抛出的异常,我们怀疑很可能跟底层数据存储有关。

原因

定位到可能是HDFS层出现了问题,那就先从底层开始排查,发现该台机器上10块盘的空间利用率都已经达到100%。按理说,作为一个成熟的分布式文件系统,对于部分数据盘满的情况,应该有其应对措施。的确,HDFS本身可以设置数据盘预留空间,如果部分数据盘的预留空间小于该值时,HDFS会自动把数据写入到另外的空盘上面,那么我们这个又是什么情况?

  最终通过多方面的沟通确认,发现了主要原因:我们这批机器,在上线前SA已经经过处理,每块盘默认预留100G空间,所以当通过df命令查看盘使用率为100%时,其实盘还有100G的预留空间,而HDFS层面我们配置的预留空间是50G,那么问题就来了:HDFS认为盘还有100G空间,并且多于50G的预留,所以数据可以写入本地盘,但是系统层面却禁止了该写入操作,从而导致数据写入异常。

解决

解决的方法可以让SA释放些空间出来便于数据写入。当然,最直接有效的就是把HDFS的预留空间调整至100G以上,我们也正是这样做的,通过调整后,异常不再出现,HBase层面的slow log也没有再出现。同时我们也开启了HDFS层面的balance,使数据自动在各个服务器之间保持平衡。

建议

磁盘满了导致的问题很难预料,HDFS可能会导致部分数据写入异常,MySQL可能会出现直接宕机等等,所以最好的办法就是:不要使盘的利用率达到100%。

网络拓扑

现象

通过rowkey调整,HDFS数据balance等操作后,HBase的确稳定了许多,在很长一段时间都没有出现写入缓慢问题,整体的性能也上涨了很多。但时常会隔一段时间出现些slow log,虽然对整体的性能影响不大,但性能上的抖动还是很明显。

原因

由于该问题不经常出现,对系统的诊断带来不小的麻烦,排查了HBase层和HDFS层,几乎一无所获,因为在大多数情况下,系统的吞吐量都是正常的。通过脚本收集RegionServer所在服务器的系统资源信息,也看不出问题所在,最后怀疑到系统的物理拓扑上,HBase集群的最大特点是数据量巨大,在做一些操作时,很容易把物理机的千兆网卡都吃满,这样如果网络拓扑结构存在问题,HBase的所有机器没有部署在同一个交换机上,上层交换机的进出口流量也有可能存在瓶颈。网络测试还是挺简单的,直接ping就可以,我们得到以下结果:共17台机器,只有其中一台的延迟存在问题,如下:
20180628164811ef38f3c7-dcc1-492f-83f8-fe7bb8ba94fa.jpg

网络延迟测试:Ping结果

同一个局域网内的机器,延迟达到了毫秒级别,这个延迟是比较致命的,因为分布式存储系统HDFS本身对网络有要求,HDFS默认3副本存在不同的机器上,如果其中某台机器的网络存在问题,这样就会影响到该机器上保存副本的写入,拖慢整个HDFS的写入速度。

解决

  网络问题,联系机房解决,机房的反馈也验证了我们的想法:由于HBase的机器后面进行了扩展,后面加入的机器有一台跟其他机器不在同一个交换机下,而这台机器正是我们找出的有较大ping延时这台,整个HBase物理结构如下:
20180628164824ce396dad-ae37-49b5-8fa7-de67ad187b5c.jpg

HBase物理拓扑结构

跟机房协调,调整机器位置,使所有的HBase机器都位于同一个交换机下,问题迎刃而解。

建议

  对于分布式大流量的系统,除了系统本身,物理机的部署和流量规划也相当重要,尽量使集群中所有的机器位于相同的交换机下(有容灾需求的应用除外),集群较大,需要跨交换机部署时,也要充分考虑交换机的出口流量是否够用,网络硬件上的瓶颈诊断起来相对更为困难。

JVM参数调整

解决了网络上面的不稳定因素,HBase的性能又得到进一步的提高,随之也带来了另外的问题。

现象

  根据应用反应,HBase会阶段性出现性能下降,导致应用数据写入缓慢,造成应用端的数据堆积,这又是怎么回事?经过一系列改善后HBase的系统较之以前有了大幅度增长,怎么还会出现数据堆积的问题?为什么会阶段性出现?
201806281648395c7bae34-f068-41f5-9c8d-a865f5f8f70c.jpg

从上图看,HBase平均流量QPS基本能达到12w,但是每过一段时间,流量就会下降到接近零点,同时这段时间,应用会反应数据堆积。

原因

  这个问题定位相对还是比较简单,结合HBase的日志,很容易找到问题所在:

org.apache.hadoop.hbase.util.Sleeper - We slept 41662ms instead of 3000ms, this is likely due to a long garbage collecting pause and it's usually bad

通过上述日志,基本上可以判定是HBase的某台RegionServer出现GC问题,导致了服务在很长一段时间内禁止访问。

  HBase通过一系列的调整后,整个系统的吞吐量增加了好几倍,然而JVM的堆大小没有进行相应的调整,整个系统的内存需求变大,而虚拟机又来不及回收,最终导致出现Full GC

解决

   GC问题导致HBase整个系统的请求下降,通过适当调整JVM参数的方式,解决HBase RegionServer的GC问题。

建议

  对于HBase来说,本身不存在单点故障,即使宕掉1,2台RegionServer,也只是使剩下几台的压力有所增加,不会导致整个集群服务能力下降很多。但是,如果其中某台RegionServer出现Full GC问题,那么这台机器上所有的访问都会被挂起,客户端请求一般都是batch发送的,rowkey的随机分布导致部分请求会落到该台RegionServer上,这样该客户端的请求就会被阻塞,导致客户端无法正常写数据到HBase。所以,对于HBase来说,宕机并不可怕,但长时间的Full GC是比较致命的,配置JVM参数的时候,尽量要考虑避免Full GC的出现。

后记

  经过前面一系列的优化,目前Datastream的这套HBase线上环境已经相当稳定,连续运行几个月都没有任何HBase层面由于系统性能不稳定导致的报警,平均性能在各个时间段都比较稳定,没有出现过大幅度的波动或者服务不可用等现象。

本文来自网易实践者社区

如何从solr和hbase中删除所有数据

hbasehbasegroup 回复了问题 • 2 人关注 • 3 个回复 • 3194 次浏览 • 2018-10-09 14:01 • 来自相关话题

HBase查询优化

hbase过往记忆 发表了文1章 • 2 个评论 • 1430 次浏览 • 2018-08-06 14:35 • 来自相关话题

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;
}
2.2 服务端优化

HBase服务端配置或集群有问题,也会导致客户端读取耗时较大,集群出现问题,影响的是整个集群的业务应用。

2.2.1 负载均衡优化

客户端的请求实际上是与HBase集群的每个RegionServer进行数据交互,在细分一下,就是与每个RegionServer上的某些Region进行数据交互,每个RegionServer上的Region个数上的情况下,可能这种耗时情况影响不大,体现不够明显。但是,如果每个RegionServer上的Region个数较大的话,这种影响就会很严重。笔者这里做过统计的数据统计,当每个RegionServer上的Region个数超过800+,如果发生负载不均衡,这样的影响就会很严重。

可能有同学会有疑问,为什么会发送负载不均衡?负载不均衡为什么会造成这样耗时严重的影响?

1.为什么会发送负载不均衡?

负载不均衡的影响通常由以下几个因素造成:

没有开启自动负载均衡
集群维护,扩容或者缩减RegionServer节点
集群有RegionServer节点发生宕机或者进程停止,随后守护进程又自动拉起宕机的RegionServer进程

针对这些因素,可以通过以下解决方案来解决:

开启自动负载均衡,执行命令:echo "balance_switch true" | hbase shell
在维护集群,或者守护进程拉起停止的RegionServer进程时,定时调度执行负载均衡命令:echo "balancer" | hbase shell

2.负载不均衡为什么会造成这样耗时严重的影响?

这里笔者用一个例子来说,集群每个RegionServer包含由800+的Region数,但是,由于集群维护,有几台RegionServer节点的Region全部集中到一台RegionServer,分布如下图所示:






这样之前请求在RegionServer2和RegionServer3上的,都会集中到RegionServer1上去请求。这样就不能发挥整个集群的并发处理能力,另外,RegionServer1上的资源使用将会翻倍(比如网络、磁盘IO、HBase RPC的Handle数等)。而原先其他正常业务到RegionServer1的请求也会因此受到很大的影响。因此,读取请求不均衡不仅会造成本身业务性能很长,还会严重影响其他正常业务的查询。同理,写请求不均衡,也会造成类似的影响。故HBase负载均衡是HBase集群性能的重要体现。

2.2.2 BlockCache优化

BlockCache作为读缓存,合理设置对于提高读性能非常重要。默认情况下,BlockCache和Memstore的配置各站40%,可以通过在hbase-site.xml配置以下属性来实现:

hfile.block.cache.size,默认0.4,用来提高读性能
hbase.regionserver.global.memstore.size,默认0.4,用来提高写性能

本篇博客主要介绍提高读性能,这里我们可以将BlockCache的占比设置大一些,Memstore的占比设置小一些(总占比保持在0.8即可)。另外,BlockCache的策略选择也是很重要的,不同的策略对于读性能来说影响不大,但是对于GC的影响却比较明显,在设置hbase.bucketcache.ioengine属性为offheap时,GC表现的很优秀。缓存结构如下图所示:






设置BlockCache可以在hbase-site.xml文件中,配置如下属性:<!-- 分配的内存大小尽可能的多些,前提是不能超过 (机器实际物理内存-JVM内存) -->
<property>
<name>hbase.bucketcache.size</name>
<value>16384</value>
</property>
<property>
<name>hbase.bucketcache.ioengine</name>
<value>offheap</value>
</property>设置块内存大小,可以参考入下表格:






另外,BlockCache策略,能够有效的提高缓存命中率,这样能够间接的提高热数据覆盖率,从而提升读取性能。

2.2.3 HFile优化

HBase读取数据时会先从BlockCache中进行检索(热数据),如果查询不到,才会到HDFS上去检索。而HBase存储在HDFS上的数据以HFile的形式存在的,文件如果越多,检索所花费的IO次数也就必然增加,对应的读取耗时也就增加了。文件数量取决于Compaction的执行策略,有以下2个属性有关系:

hbase.hstore.compactionThreshold,默认为3,表示store中文件数超过3个就开始进行合并操作
hbase.hstore.compaction.max.size,默认为9223372036854775807,合并的文件最大阀值,超过这个阀值的文件不能进行合并

 另外,hbase.hstore.compaction.max.size值可以通过实际的Region总数来计算,公式如下:hbase.hstore.compaction.max.size = RegionTotal / hbase.hstore.compactionThreshold
 
2.2.4 Compaction优化

Compaction操作是将小文件合并为大文件,提高后续业务随机读取的性能,但是在执行Compaction操作期间,节点IO、网络带宽等资源会占用较多,那么什么时候执行Compaction才最好?什么时候需要执行Compaction操作?

1.什么时候执行Compaction才最好?

实际应用场景中,会关闭Compaction自动执行策略,通过属性hbase.hregion.majorcompaction来控制,将hbase.hregion.majorcompaction=0,就可以禁止HBase自动执行Compaction操作。一般情况下,选择集群负载较低,资源空闲的时间段来定时调度执行Compaction。

如果合并的文件较多,可以通过设置如下属性来提生Compaction的执行速度,配置如下:<property>
<name>hbase.regionserver.thread.compaction.large</name>
<value>8</value>
<description></description>
</property>
<property>
<name>hbase.regionserver.thread.compaction.small</name>
<value>5</value>
<description></description>
</property>
2.什么时候需要执行Compaction操作?

一般维护HBase集群后,由于集群发生过重启,HBase数据本地性较低,通过HBase页面可以观察,此时如果不执行Compaction操作,那么客户端查询的时候,需要跨副本节点去查询,这样来回需要经过网络带宽,对比正常情况下,从本地节点读取数据,耗时是比较大的。在执行Compaction操作后,HBase数据本地性为1,这样能够有效的提高查询效率。

3.总结

本篇博客HBase查询优化从客户端和服务端角度,列举一些常见有效地额优化手段。当然,优化还需要从自己实际应用场景出发,例如代码实现逻辑、物理机的实际配置等方面来设置相关参数。大家可以根据实际情况来参考本篇博客进行优化。 查看全部
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;
}

2.2 服务端优化

HBase服务端配置或集群有问题,也会导致客户端读取耗时较大,集群出现问题,影响的是整个集群的业务应用。

2.2.1 负载均衡优化

客户端的请求实际上是与HBase集群的每个RegionServer进行数据交互,在细分一下,就是与每个RegionServer上的某些Region进行数据交互,每个RegionServer上的Region个数上的情况下,可能这种耗时情况影响不大,体现不够明显。但是,如果每个RegionServer上的Region个数较大的话,这种影响就会很严重。笔者这里做过统计的数据统计,当每个RegionServer上的Region个数超过800+,如果发生负载不均衡,这样的影响就会很严重。

可能有同学会有疑问,为什么会发送负载不均衡?负载不均衡为什么会造成这样耗时严重的影响?

1.为什么会发送负载不均衡?

负载不均衡的影响通常由以下几个因素造成:

没有开启自动负载均衡
集群维护,扩容或者缩减RegionServer节点
集群有RegionServer节点发生宕机或者进程停止,随后守护进程又自动拉起宕机的RegionServer进程

针对这些因素,可以通过以下解决方案来解决:

开启自动负载均衡,执行命令:echo "balance_switch true" | hbase shell
在维护集群,或者守护进程拉起停止的RegionServer进程时,定时调度执行负载均衡命令:echo "balancer" | hbase shell

2.负载不均衡为什么会造成这样耗时严重的影响?

这里笔者用一个例子来说,集群每个RegionServer包含由800+的Region数,但是,由于集群维护,有几台RegionServer节点的Region全部集中到一台RegionServer,分布如下图所示:

666745-20180805123042543-512818204.png


这样之前请求在RegionServer2和RegionServer3上的,都会集中到RegionServer1上去请求。这样就不能发挥整个集群的并发处理能力,另外,RegionServer1上的资源使用将会翻倍(比如网络、磁盘IO、HBase RPC的Handle数等)。而原先其他正常业务到RegionServer1的请求也会因此受到很大的影响。因此,读取请求不均衡不仅会造成本身业务性能很长,还会严重影响其他正常业务的查询。同理,写请求不均衡,也会造成类似的影响。故HBase负载均衡是HBase集群性能的重要体现。

2.2.2 BlockCache优化

BlockCache作为读缓存,合理设置对于提高读性能非常重要。默认情况下,BlockCache和Memstore的配置各站40%,可以通过在hbase-site.xml配置以下属性来实现:

hfile.block.cache.size,默认0.4,用来提高读性能
hbase.regionserver.global.memstore.size,默认0.4,用来提高写性能

本篇博客主要介绍提高读性能,这里我们可以将BlockCache的占比设置大一些,Memstore的占比设置小一些(总占比保持在0.8即可)。另外,BlockCache的策略选择也是很重要的,不同的策略对于读性能来说影响不大,但是对于GC的影响却比较明显,在设置hbase.bucketcache.ioengine属性为offheap时,GC表现的很优秀。缓存结构如下图所示:

666745-20180805125621644-552171040.png


设置BlockCache可以在hbase-site.xml文件中,配置如下属性:
<!-- 分配的内存大小尽可能的多些,前提是不能超过 (机器实际物理内存-JVM内存) -->
<property>
<name>hbase.bucketcache.size</name>
<value>16384</value>
</property>
<property>
<name>hbase.bucketcache.ioengine</name>
<value>offheap</value>
</property>
设置块内存大小,可以参考入下表格:

搜狗截图20180806142247.png


另外,BlockCache策略,能够有效的提高缓存命中率,这样能够间接的提高热数据覆盖率,从而提升读取性能。

2.2.3 HFile优化

HBase读取数据时会先从BlockCache中进行检索(热数据),如果查询不到,才会到HDFS上去检索。而HBase存储在HDFS上的数据以HFile的形式存在的,文件如果越多,检索所花费的IO次数也就必然增加,对应的读取耗时也就增加了。文件数量取决于Compaction的执行策略,有以下2个属性有关系:

hbase.hstore.compactionThreshold,默认为3,表示store中文件数超过3个就开始进行合并操作
hbase.hstore.compaction.max.size,默认为9223372036854775807,合并的文件最大阀值,超过这个阀值的文件不能进行合并

 另外,hbase.hstore.compaction.max.size值可以通过实际的Region总数来计算,公式如下:hbase.hstore.compaction.max.size = RegionTotal / hbase.hstore.compactionThreshold
 
2.2.4 Compaction优化

Compaction操作是将小文件合并为大文件,提高后续业务随机读取的性能,但是在执行Compaction操作期间,节点IO、网络带宽等资源会占用较多,那么什么时候执行Compaction才最好?什么时候需要执行Compaction操作?

1.什么时候执行Compaction才最好?

实际应用场景中,会关闭Compaction自动执行策略,通过属性hbase.hregion.majorcompaction来控制,将hbase.hregion.majorcompaction=0,就可以禁止HBase自动执行Compaction操作。一般情况下,选择集群负载较低,资源空闲的时间段来定时调度执行Compaction。

如果合并的文件较多,可以通过设置如下属性来提生Compaction的执行速度,配置如下:
<property>
<name>hbase.regionserver.thread.compaction.large</name>
<value>8</value>
<description></description>
</property>
<property>
<name>hbase.regionserver.thread.compaction.small</name>
<value>5</value>
<description></description>
</property>

2.什么时候需要执行Compaction操作?

一般维护HBase集群后,由于集群发生过重启,HBase数据本地性较低,通过HBase页面可以观察,此时如果不执行Compaction操作,那么客户端查询的时候,需要跨副本节点去查询,这样来回需要经过网络带宽,对比正常情况下,从本地节点读取数据,耗时是比较大的。在执行Compaction操作后,HBase数据本地性为1,这样能够有效的提高查询效率。

3.总结

本篇博客HBase查询优化从客户端和服务端角度,列举一些常见有效地额优化手段。当然,优化还需要从自己实际应用场景出发,例如代码实现逻辑、物理机的实际配置等方面来设置相关参数。大家可以根据实际情况来参考本篇博客进行优化。

HBase数据模型解析和基本的表设计分析

hbase过往记忆 发表了文1章 • 0 个评论 • 784 次浏览 • 2018-08-06 11:02 • 来自相关话题

HBase是一个开源可伸缩的针对海量数据存储的分布式nosql数据库,它根据Google Bigtable数据模型来建模并构建在hadoop的hdfs存储系统之上。它和关系型数据库Mysql, Oracle等有明显的区别,HBase的数据模型牺牲了关系型数据库的一些特性但是却换来了极大的可伸缩性和对表结构的灵活操作。

在一定程度上,Hbase又可以看成是以行键(Row Key),列标识(column qualifier),时间戳(timestamp)标识的有序Map数据结构的数据库,具有稀疏,分布式,持久化,多维度等特点。

Base的数据模型介绍
HBase的数据模型也是由一张张的表组成,每一张表里也有数据行和列,但是在HBase数据库中的行和列又和关系型数据库的稍有不同。下面统一介绍HBase数据模型中一些名词的概念:
      
表(Table): HBase会将数据组织进一张张的表里面,但是需要注意的是表名必须是能用在文件路径里的合法名字,因为HBase的表是映射成hdfs上面的文件。行(Row): 在表里面,每一行代表着一个数据对象,每一行都是以一个行键(Row Key)来进行唯一标识的,行键并没有什么特定的数据类型,以二进制的字节来存储。 列族(Column Family): 在定义HBase表的时候需要提前设置好列族, 表中所有的列都需要组织在列族里面,列族一旦确定后,就不能轻易修改,因为它会影响到HBase真实的物理存储结构,但是列族中的列标识(Column Qualifier)以及其对应的值可以动态增删。表中的每一行都有相同的列族,但是不需要每一行的列族里都有一致的列标识(Column Qualifier)和值,所以说是一种稀疏的表结构,这样可以一定程度上避免数据的冗余。例如:{row1, userInfo: telephone —> 137XXXXX869 }{row2, userInfo: fax phone —> 0898-66XXXX } 行1和行2都有同一个列族userinfo,但是行1中的列族只有列标识(Column Qualifier):移动电话号码,而行2中的列族中只有列标识(Column Qualifier):传真号码。列标识(Column Qualifier): 列族中的数据通过列标识来进行映射,其实这里大家可以不用拘泥于“列”这个概念,也可以理解为一个键值对,Column Qualifier就是Key。列标识也没有特定的数据类型,以二进制字节来存储。单元(Cell): 每一个 行键,列族和列标识共同组成一个单元,存储在单元里的数据称为单元数据,单元和单元数据也没有特定的数据类型,以二进制字节来存储。时间戳(Timestamp): 默认下每一个单元中的数据插入时都会用时间戳来进行版本标识。读取单元数据时,如果时间戳没有被指定,则默认返回最新的数据,写入新的单元数据时,如果没有设置时间戳,默认使用当前时间。每一个列族的单元数据的版本数量都被HBase单独维护,默认情况下HBase保留3个版本数据。





        有时候,你也可以把HBase看成一个多维度的Map模型去理解它的数据模型。正如下图,一个行键映射一个列族数组,列族数组中的每个列族又映射一个列标识数组,列标识数组中的每一个列标识(Column Qualifier)又映射到一个时间戳数组,里面是不同时间戳映射下不同版本的值,但是默认取最近时间的值,所以可以看成是列标识(Column Qualifier)和它所对应的值的映射。用户也可以通过HBase的API去同时获取到多个版本的单元数据的值。Row Key在HBase中也就相当于关系型数据库的主键,并且Row Key在创建表的时候就已经设置好,用户无法指定某个列作为Row Key。






又有的时候,你也可以把HBase看成是一个类似Redis那样的Key-Value数据库。如下图,当你要查询某一行的所有数据时,Row Key就相当于Key,而Value就是单元中的数据(列族,列族里的列和列中时间戳所对应的不同版本的值);当深入到HBase底层的存储机制时,用户要查询指定行里某一条单元数据时,HBase会去读取一个数据块,里面除了有要查询的单元数据,可能同时也会获取到其它单元数据,因为这个数据块还包含着这个Row Key所对应的其它列族或其它的列信息,这些信息实际也代表着另一个单元数据,这也是HBase的API内部实际的工作原理






HBase提供了丰富的API接口让用户去操作这些数据。主要的API接口有3个,Put,Get,Scan。Put和Get是操作指定行的数据的,所以需要提供行键来进行操作。Scan是操作一定范围内的数据,通过指定开始行键和结束行键来获取范围,如果没有指定开始行键和结束行键,则默认获取所有行数据。


HBase的表设计中需要注意的问题


   当开始设计HBase中的表的时候需要考虑以下的几个问题:
        1. Row Key的结构该如何设置,而Row Key中又该包含什么样的信息(这个很重要,下面的例子会有说明)
        2. 表中应该有多少的列族
        3. 列族中应该存储什么样的数据
        4. 每个列族中存储多少列数据
        5. 列的名字分别是什么,因为操作API的时候需要这些信息
        6. 单元中(cell)应该存储什么样的信息
        7. 每个单元中存储多少个版本信息
     在HBase表设计中最重要的就是定义Row-Key的结构,要定义Row-Key的结构时就不得不考虑表的接入样本,也就是在真真实应用中会对这张表出现什么样的读写场景。

除此之外,在设计表的时候我们也应该要考虑HBase数据库的一些特性。

       1. HBase中表的索引是通过Key来实现的
       2. 在表中是通过Row Key的字典序来对一行行的数据来进行排序的,表中每一块区域的划分都是通过开始Row Key和结束Row Key来决定的。
       3. 所有存储在HBase表中的数据都是二进制的字节,并没有数据类型。
       4. 原子性只在行内保证,HBase表中并没有多行事务。
       5. 列族(Column Family)在表创建之前就要定义好
       6. 列族中的列标识(Column Qualifier)可以在表创建完以后动态插入数据时添加。

        接下来我们考虑一个这样的场景,我们要设计一张表,用来保存微博上用户互粉的信息。所以设计表之前,我们要考虑业务中的读写场景。

读场景中我们要考虑:
1. 每个用户都关注了谁
2. 用户A有没有关注用户B
3. 谁关注了用户A

写场景中我们要考虑:
1. 用户关注了另一个用户
2. 用户取消关注某个用户

下面我们来看几种表结构的设计:





        第一种表结构设计中,在这种表结构设计中,每一行代表着某个用户和所有他所关注的其它用户。这个用户ID就是Row Key,而每一个列标识(Column Qualifier)就是这个用户所关注的其他用户在列族里的序号,单元数据就是这个用户所关注的其他用户的用户ID。在这种表结构的设计下,“每个用户都关注了谁”这个问题很好解决,但对于“用户A有没有关注用户B”这个问题在列很多的时候,需要遍历所有单元数据去找到用户B,这样的开销会十分大。并且当添加新的被关注用户时,因为不知道给这个新用户分配什么样的列族序号,需要遍历整个列族中的所有列找出最后一个列,并将最后一个列的序号+1给新的被关注用户作为列族内的序号,这样的开销也十分大。







所以衍生出了第二种表结构设计,如下图,添加一个counter记录列族中所有列的总数量,当添加新的被关注用户时,这个新用户的序号就是counter+1。但是当要取消关注某个用户时,一样得遍历所有的列数据,而且最大的问题是在于HBase不支持事务处理,这种通过counter来添加被关注用户的操作逻辑得写在客户端中。






回想一下,列标识(Column Qualifier)存储的时候是二进制的字节,所以列标识可以存储任何数据,而且列标识还是动态增添的,基于这个特性我们再改进表的设计,如下图。这次以被关注的用户ID做为列标识(Column Qualifier),然后单元数据可以是任意数字,比如全部统一成1。

在这种表结构的设计下,添加新的被关注者,以及取消关注都会变得很简单。但是对于读场景中,谁关注了用户A这个问题,因为HBase数据库的索引只建立在Row Key上,这里不得不扫描全表去统计所有关注了用户A的用户数量,所以下面的这个表结构设计也存在一定的性能问题。这里也引出一个思路,被关注者需要以某种方式添加索引。






针对上面的表结构有三种优化方案,第一种是新建另一张表,里面保存某个用户和所有关注他的用户。第二种解决方案就是在同一张表中也存储某个用户和所有关注他的用户的信息,并从Row Key中区分开来,比如:Row key为Jame_001_following的这行保存着所有Jame关注的人的信息,而Row_Key为Jame_001_followed的这行保存着所有关注Jame的人的信息。

最后一种优化方案就是,如下图,将Row Key设计成“followerID+followedID”的形式,比如:“Jame+Emma”,这里的Row Key值就代表着Jame关注了Emma(其实这里应该是“Jame的ID+Emma的ID”,只是为了解释方便而直接用名字),同时包含了关注者和被关注者两个信息;还需要注意的一点就是列族的名字被设计成只有一个字母f,这样设计的好处就是减少了HBase对数据的I/O操作压力,同时减少了返回到客户端的数据字节,提高响应速度,因为每一个返回给客户端的KeyValue对象都会包含列族名字。

同时将被关注人的用户名称也保存在了表中作为Column Qualifier,这样做的好处就是节省了去用户表查找用户名的资源。在这种表结构设计下,“用户A取消关注某个用户B”,“用户A有没有关注用户B?”的业务处理就会变得简单高效。






还有一个需要注意的问题,就是在实际的生产环境中,还需要将Row Key使用MD5加密,一方面是使Row Key的长度都一致,能提高数据的存取性能。这方面的优化不在本文的讨论范围内。





总结:
整篇文章概述了HBase的数据模型和基本的表设计思路。下面是HBase一些关键特性的总结:
   1. Row Key是HBase表结构设计中很重要的一环,它设计的好坏直接影响程序和HBase交互的效率和数据存储的性能。
   2. Base的表结构比传统关系型数据库更灵活,你能存储任何二进制数据在表中,而且无关数据类型。
   3. 在相同的列族中所有数据都具有相同的接入模式
   4. 主要是通过Row Key来建立索引
   5. 以纵向扩张为主设计的表结构能快速简单的获取数据,但牺牲了一定的原子性,就比如上文中最后一种表结构;而以横向扩张为主设计的表结构,也就是列族中有很多列,比如上文中第一种表结构,能在行里面保持一定的原子性。
   6. HBase并不支持事务,所有尽量在一次API请求操作中获取到结果
   7. 对Row Key的Hash优化能获得固定长度的Row Key并使数据分布更加均匀一些,而不是集中在一台服务器上,但是也牺牲了一定的数据排序和读取性能。
   8. 可以利用列标识(Column Qualifier)来存储数据。
   9. 列标识(Column Qualifier)名字的长度和列族名字的长度都会影响I/O的读写性能和发送给客户端的数据量,所以它们的命名应该简洁! 查看全部
HBase是一个开源可伸缩的针对海量数据存储的分布式nosql数据库,它根据Google Bigtable数据模型来建模并构建在hadoop的hdfs存储系统之上。它和关系型数据库Mysql, Oracle等有明显的区别,HBase的数据模型牺牲了关系型数据库的一些特性但是却换来了极大的可伸缩性和对表结构的灵活操作。

在一定程度上,Hbase又可以看成是以行键(Row Key),列标识(column qualifier),时间戳(timestamp)标识的有序Map数据结构的数据库,具有稀疏,分布式,持久化,多维度等特点。

Base的数据模型介绍
HBase的数据模型也是由一张张的表组成,每一张表里也有数据行和列,但是在HBase数据库中的行和列又和关系型数据库的稍有不同。下面统一介绍HBase数据模型中一些名词的概念:
      
  • 表(Table): HBase会将数据组织进一张张的表里面,但是需要注意的是表名必须是能用在文件路径里的合法名字,因为HBase的表是映射成hdfs上面的文件。
  • 行(Row): 在表里面,每一行代表着一个数据对象,每一行都是以一个行键(Row Key)来进行唯一标识的,行键并没有什么特定的数据类型,以二进制的字节来存储。
  •  列族(Column Family): 在定义HBase表的时候需要提前设置好列族, 表中所有的列都需要组织在列族里面,列族一旦确定后,就不能轻易修改,因为它会影响到HBase真实的物理存储结构,但是列族中的列标识(Column Qualifier)以及其对应的值可以动态增删。表中的每一行都有相同的列族,但是不需要每一行的列族里都有一致的列标识(Column Qualifier)和值,所以说是一种稀疏的表结构,这样可以一定程度上避免数据的冗余。例如:{row1, userInfo: telephone —> 137XXXXX869 }{row2, userInfo: fax phone —> 0898-66XXXX } 行1和行2都有同一个列族userinfo,但是行1中的列族只有列标识(Column Qualifier):移动电话号码,而行2中的列族中只有列标识(Column Qualifier):传真号码。
  • 列标识(Column Qualifier): 列族中的数据通过列标识来进行映射,其实这里大家可以不用拘泥于“列”这个概念,也可以理解为一个键值对,Column Qualifier就是Key。列标识也没有特定的数据类型,以二进制字节来存储。
  • 单元(Cell): 每一个 行键,列族和列标识共同组成一个单元,存储在单元里的数据称为单元数据,单元和单元数据也没有特定的数据类型,以二进制字节来存储。
  • 时间戳(Timestamp): 默认下每一个单元中的数据插入时都会用时间戳来进行版本标识。读取单元数据时,如果时间戳没有被指定,则默认返回最新的数据,写入新的单元数据时,如果没有设置时间戳,默认使用当前时间。每一个列族的单元数据的版本数量都被HBase单独维护,默认情况下HBase保留3个版本数据。


下载.jpg

        有时候,你也可以把HBase看成一个多维度的Map模型去理解它的数据模型。正如下图,一个行键映射一个列族数组,列族数组中的每个列族又映射一个列标识数组,列标识数组中的每一个列标识(Column Qualifier)又映射到一个时间戳数组,里面是不同时间戳映射下不同版本的值,但是默认取最近时间的值,所以可以看成是列标识(Column Qualifier)和它所对应的值的映射。用户也可以通过HBase的API去同时获取到多个版本的单元数据的值。Row Key在HBase中也就相当于关系型数据库的主键,并且Row Key在创建表的时候就已经设置好,用户无法指定某个列作为Row Key。

下载_(1).jpg


又有的时候,你也可以把HBase看成是一个类似Redis那样的Key-Value数据库。如下图,当你要查询某一行的所有数据时,Row Key就相当于Key,而Value就是单元中的数据(列族,列族里的列和列中时间戳所对应的不同版本的值);当深入到HBase底层的存储机制时,用户要查询指定行里某一条单元数据时,HBase会去读取一个数据块,里面除了有要查询的单元数据,可能同时也会获取到其它单元数据,因为这个数据块还包含着这个Row Key所对应的其它列族或其它的列信息,这些信息实际也代表着另一个单元数据,这也是HBase的API内部实际的工作原理

下载_(2).jpg


HBase提供了丰富的API接口让用户去操作这些数据。主要的API接口有3个,Put,Get,Scan。Put和Get是操作指定行的数据的,所以需要提供行键来进行操作。Scan是操作一定范围内的数据,通过指定开始行键和结束行键来获取范围,如果没有指定开始行键和结束行键,则默认获取所有行数据。


HBase的表设计中需要注意的问题


   当开始设计HBase中的表的时候需要考虑以下的几个问题:
        1. Row Key的结构该如何设置,而Row Key中又该包含什么样的信息(这个很重要,下面的例子会有说明)
        2. 表中应该有多少的列族
        3. 列族中应该存储什么样的数据
        4. 每个列族中存储多少列数据
        5. 列的名字分别是什么,因为操作API的时候需要这些信息
        6. 单元中(cell)应该存储什么样的信息
        7. 每个单元中存储多少个版本信息
     在HBase表设计中最重要的就是定义Row-Key的结构,要定义Row-Key的结构时就不得不考虑表的接入样本,也就是在真真实应用中会对这张表出现什么样的读写场景。

除此之外,在设计表的时候我们也应该要考虑HBase数据库的一些特性。

       1. HBase中表的索引是通过Key来实现的
       2. 在表中是通过Row Key的字典序来对一行行的数据来进行排序的,表中每一块区域的划分都是通过开始Row Key和结束Row Key来决定的。
       3. 所有存储在HBase表中的数据都是二进制的字节,并没有数据类型。
       4. 原子性只在行内保证,HBase表中并没有多行事务。
       5. 列族(Column Family)在表创建之前就要定义好
       6. 列族中的列标识(Column Qualifier)可以在表创建完以后动态插入数据时添加。

        接下来我们考虑一个这样的场景,我们要设计一张表,用来保存微博上用户互粉的信息。所以设计表之前,我们要考虑业务中的读写场景。

读场景中我们要考虑:
1. 每个用户都关注了谁
2. 用户A有没有关注用户B
3. 谁关注了用户A

写场景中我们要考虑:
1. 用户关注了另一个用户
2. 用户取消关注某个用户

下面我们来看几种表结构的设计:





        第一种表结构设计中,在这种表结构设计中,每一行代表着某个用户和所有他所关注的其它用户。这个用户ID就是Row Key,而每一个列标识(Column Qualifier)就是这个用户所关注的其他用户在列族里的序号,单元数据就是这个用户所关注的其他用户的用户ID。在这种表结构的设计下,“每个用户都关注了谁”这个问题很好解决,但对于“用户A有没有关注用户B”这个问题在列很多的时候,需要遍历所有单元数据去找到用户B,这样的开销会十分大。并且当添加新的被关注用户时,因为不知道给这个新用户分配什么样的列族序号,需要遍历整个列族中的所有列找出最后一个列,并将最后一个列的序号+1给新的被关注用户作为列族内的序号,这样的开销也十分大。

下载_(3).jpg



所以衍生出了第二种表结构设计,如下图,添加一个counter记录列族中所有列的总数量,当添加新的被关注用户时,这个新用户的序号就是counter+1。但是当要取消关注某个用户时,一样得遍历所有的列数据,而且最大的问题是在于HBase不支持事务处理,这种通过counter来添加被关注用户的操作逻辑得写在客户端中。

下载_(4).jpg


回想一下,列标识(Column Qualifier)存储的时候是二进制的字节,所以列标识可以存储任何数据,而且列标识还是动态增添的,基于这个特性我们再改进表的设计,如下图。这次以被关注的用户ID做为列标识(Column Qualifier),然后单元数据可以是任意数字,比如全部统一成1。

在这种表结构的设计下,添加新的被关注者,以及取消关注都会变得很简单。但是对于读场景中,谁关注了用户A这个问题,因为HBase数据库的索引只建立在Row Key上,这里不得不扫描全表去统计所有关注了用户A的用户数量,所以下面的这个表结构设计也存在一定的性能问题。这里也引出一个思路,被关注者需要以某种方式添加索引。

下载_(5).jpg


针对上面的表结构有三种优化方案,第一种是新建另一张表,里面保存某个用户和所有关注他的用户。第二种解决方案就是在同一张表中也存储某个用户和所有关注他的用户的信息,并从Row Key中区分开来,比如:Row key为Jame_001_following的这行保存着所有Jame关注的人的信息,而Row_Key为Jame_001_followed的这行保存着所有关注Jame的人的信息。

最后一种优化方案就是,如下图,将Row Key设计成“followerID+followedID”的形式,比如:“Jame+Emma”,这里的Row Key值就代表着Jame关注了Emma(其实这里应该是“Jame的ID+Emma的ID”,只是为了解释方便而直接用名字),同时包含了关注者和被关注者两个信息;还需要注意的一点就是列族的名字被设计成只有一个字母f,这样设计的好处就是减少了HBase对数据的I/O操作压力,同时减少了返回到客户端的数据字节,提高响应速度,因为每一个返回给客户端的KeyValue对象都会包含列族名字。

同时将被关注人的用户名称也保存在了表中作为Column Qualifier,这样做的好处就是节省了去用户表查找用户名的资源。在这种表结构设计下,“用户A取消关注某个用户B”,“用户A有没有关注用户B?”的业务处理就会变得简单高效。

下载_(6).jpg


还有一个需要注意的问题,就是在实际的生产环境中,还需要将Row Key使用MD5加密,一方面是使Row Key的长度都一致,能提高数据的存取性能。这方面的优化不在本文的讨论范围内。

下载_(7).jpg

总结:
整篇文章概述了HBase的数据模型和基本的表设计思路。下面是HBase一些关键特性的总结:
   1. Row Key是HBase表结构设计中很重要的一环,它设计的好坏直接影响程序和HBase交互的效率和数据存储的性能。
   2. Base的表结构比传统关系型数据库更灵活,你能存储任何二进制数据在表中,而且无关数据类型。
   3. 在相同的列族中所有数据都具有相同的接入模式
   4. 主要是通过Row Key来建立索引
   5. 以纵向扩张为主设计的表结构能快速简单的获取数据,但牺牲了一定的原子性,就比如上文中最后一种表结构;而以横向扩张为主设计的表结构,也就是列族中有很多列,比如上文中第一种表结构,能在行里面保持一定的原子性。
   6. HBase并不支持事务,所有尽量在一次API请求操作中获取到结果
   7. 对Row Key的Hash优化能获得固定长度的Row Key并使数据分布更加均匀一些,而不是集中在一台服务器上,但是也牺牲了一定的数据排序和读取性能。
   8. 可以利用列标识(Column Qualifier)来存储数据。
   9. 列标识(Column Qualifier)名字的长度和列族名字的长度都会影响I/O的读写性能和发送给客户端的数据量,所以它们的命名应该简洁!

HBase最佳实践-读性能优化策略

hbase过往记忆 发表了文1章 • 0 个评论 • 707 次浏览 • 2018-08-06 10:32 • 来自相关话题

本文作者范欣欣,其就职于网易杭州研究院后台技术中心数据库技术组,从事HBase开发、运维,对HBase相关技术有浓厚的兴趣。

任何系统都会有各种各样的问题,有些是系统本身设计问题,有些却是使用姿势问题。HBase也一样,在真实生产线上大家或多或少都会遇到很多问题,有些是HBase还需要完善的,有些是我们确实对它了解太少。总结起来,大家遇到的主要问题无非是Full GC异常导致宕机问题、RIT问题、写吞吐量太低以及读延迟较大。

Full GC问题之前在一些文章里面已经讲过它的来龙去脉,主要的解决方案目前主要有两方面需要注意,一方面需要查看GC日志确认是哪种Full GC,根据Full GC类型对JVM参数进行调优,另一方面需要确认是否开启了BucketCache的offheap模式,建议使用LRUBlockCache的童鞋尽快转移到BucketCache来。当然我们还是很期待官方2.0.0版本发布的更多offheap模块。

RIT问题,我相信更多是因为我们对其不了解,具体原理可以戳这里,解决方案目前有两个,优先是使用官方提供的HBCK进行修复(HBCK本人一直想拿出来分享,但是目前案例还不多,等后面有更多案例的话再拿出来说),使用之后还是解决不了的话就需要手动修复文件或者元数据表。

而对于写吞吐量太低以及读延迟太大的优化问题,笔者也和很多朋友进行过探讨,这篇文章就以读延迟优化为核心内容展开,具体分析HBase进行读延迟优化的那些套路,以及这些套路之后的具体原理。希望大家在看完之后能够结合这些套路剖析自己的系统。

一般情况下,读请求延迟较大通常存在三种场景,分别为:

1. 仅有某业务延迟较大,集群其他业务都正常

2. 整个集群所有业务都反映延迟较大

3. 某个业务起来之后集群其他部分业务延迟较大

这三种场景是表象,通常某业务反应延迟异常,首先需要明确具体是哪种场景,然后针对性解决问题。下图是对读优化思路的一点总结,主要分为四个方面:客户端优化、服务器端优化、列族设计优化以及HDFS相关优化,下面每一个小点都会按照场景分类,文章最后进行归纳总结。下面分别进行详细讲解:





HBase客户端优化

和大多数系统一样,客户端作为业务读写的入口,姿势使用不正确通常会导致本业务读延迟较高实际上存在一些使用姿势的推荐用法,这里一般需要关注四个问题:

1. scan缓存是否设置合理?

优化原理:在解释这个问题之前,首先需要解释什么是scan缓存,通常来讲一次scan会返回大量数据,因此客户端发起一次scan请求,实际并不会一次就将所有数据加载到本地,而是分成多次RPC请求进行加载,这样设计一方面是因为大量数据请求可能会导致网络带宽严重消耗进而影响其他业务,另一方面也有可能因为数据量太大导致本地客户端发生OOM。在这样的设计体系下用户会首先加载一部分数据到本地,然后遍历处理,再加载下一部分数据到本地处理,如此往复,直至所有数据都加载完成。数据加载到本地就存放在scan缓存中,默认100条数据大小。

通常情况下,默认的scan缓存设置就可以正常工作的。但是在一些大scan(一次scan可能需要查询几万甚至几十万行数据)来说,每次请求100条数据意味着一次scan需要几百甚至几千次RPC请求,这种交互的代价无疑是很大的。因此可以考虑将scan缓存设置增大,比如设为500或者1000就可能更加合适。笔者之前做过一次试验,在一次scan扫描10w+条数据量的条件下,将scan缓存从100增加到1000,可以有效降低scan请求的总体延迟,延迟基本降低了25%左右。

优化建议:大scan场景下将scan缓存从100增大到500或者1000,用以减少RPC次数

2. get请求是否可以使用批量请求?


优化原理:HBase分别提供了单条get以及批量get的API接口,使用批量get接口可以减少客户端到RegionServer之间的RPC连接数,提高读取性能。另外需要注意的是,批量get请求要么成功返回所有请求数据,要么抛出异常。

优化建议:使用批量get进行读取请求

3. 请求是否可以显示指定列族或者列?


优化原理:HBase是典型的列族数据库,意味着同一列族的数据存储在一起,不同列族的数据分开存储在不同的目录下。如果一个表有多个列族,只是根据Rowkey而不指定列族进行检索的话不同列族的数据需要独立进行检索,性能必然会比指定列族的查询差很多,很多情况下甚至会有2倍~3倍的性能损失。

优化建议:可以指定列族或者列进行精确查找的尽量指定查找

4. 离线批量读取请求是否设置禁止缓存?


优化原理:通常离线批量读取数据会进行一次性全表扫描,一方面数据量很大,另一方面请求只会执行一次。这种场景下如果使用scan默认设置,就会将数据从HDFS加载出来之后放到缓存。可想而知,大量数据进入缓存必将其他实时业务热点数据挤出,其他业务不得不从HDFS加载,进而会造成明显的读延迟毛刺

优化建议:离线批量读取请求设置禁用缓存,scan.setBlockCache(false)

HBase服务器端优化

一般服务端端问题一旦导致业务读请求延迟较大的话,通常是集群级别的,即整个集群的业务都会反映读延迟较大。可以从4个方面入手:

5. 读请求是否均衡?


优化原理:极端情况下假如所有的读请求都落在一台RegionServer的某几个Region上,这一方面不能发挥整个集群的并发处理能力,另一方面势必造成此台RegionServer资源严重消耗(比如IO耗尽、handler耗尽等),落在该台RegionServer上的其他业务会因此受到很大的波及。可见,读请求不均衡不仅会造成本身业务性能很差,还会严重影响其他业务。当然,写请求不均衡也会造成类似的问题,可见负载不均衡是HBase的大忌。

观察确认:观察所有RegionServer的读请求QPS曲线,确认是否存在读请求不均衡现象

优化建议:RowKey必须进行散列化处理(比如MD5散列),同时建表必须进行预分区处理

6. BlockCache是否设置合理?


优化原理:BlockCache作为读缓存,对于读性能来说至关重要。默认情况下BlockCache和Memstore的配置相对比较均衡(各占40%),可以根据集群业务进行修正,比如读多写少业务可以将BlockCache占比调大。另一方面,BlockCache的策略选择也很重要,不同策略对读性能来说影响并不是很大,但是对GC的影响却相当显著,尤其BucketCache的offheap模式下GC表现很优越。另外,HBase 2.0对offheap的改造(HBASE-11425)将会使HBase的读性能得到2~4倍的提升,同时GC表现会更好!

观察确认:观察所有RegionServer的缓存未命中率、配置文件相关配置项一级GC日志,确认BlockCache是否可以优化

优化建议:JVM内存配置量 < 20G,BlockCache策略选择LRUBlockCache;否则选择BucketCache策略的offheap模式;期待HBase 2.0的到来!

7. HFile文件是否太多?


优化原理:HBase读取数据通常首先会到Memstore和BlockCache中检索(读取最近写入数据&热点数据),如果查找不到就会到文件中检索。HBase的类LSM结构会导致每个store包含多数HFile文件,文件越多,检索所需的IO次数必然越多,读取延迟也就越高。文件数量通常取决于Compaction的执行策略,一般和两个配置参数有关:hbase.hstore.compactionThreshold和hbase.hstore.compaction.max.size,前者表示一个store中的文件数超过多少就应该进行合并,后者表示参数合并的文件大小最大是多少,超过此大小的文件不能参与合并。这两个参数不能设置太’松’(前者不能设置太大,后者不能设置太小),导致Compaction合并文件的实际效果不明显,进而很多文件得不到合并。这样就会导致HFile文件数变多。

观察确认:观察RegionServer级别以及Region级别的storefile数,确认HFile文件是否过多

优化建议:hbase.hstore.compactionThreshold设置不能太大,默认是3个;设置需要根据Region大小确定,通常可以简单的认为hbase.hstore.compaction.max.size = RegionSize / hbase.hstore.compactionThreshold

8. Compaction是否消耗系统资源过多?

优化原理:Compaction是将小文件合并为大文件,提高后续业务随机读性能,但是也会带来IO放大以及带宽消耗问题(数据远程读取以及三副本写入都会消耗系统带宽)。正常配置情况下Minor Compaction并不会带来很大的系统资源消耗,除非因为配置不合理导致Minor Compaction太过频繁,或者Region设置太大情况下发生Major Compaction。

观察确认:观察系统IO资源以及带宽资源使用情况,再观察Compaction队列长度,确认是否由于Compaction导致系统资源消耗过多

优化建议:

(1)Minor Compaction设置:hbase.hstore.compactionThreshold设置不能太小,又不能设置太大,因此建议设置为5~6;hbase.hstore.compaction.max.size = RegionSize / hbase.hstore.compactionThreshold

(2)Major Compaction设置:大Region读延迟敏感业务( 100G以上)通常不建议开启自动Major Compaction,手动低峰期触发。小Region或者延迟不敏感业务可以开启Major Compaction,但建议限制流量;

(3)期待更多的优秀Compaction策略,类似于stripe-compaction尽早提供稳定服务

HBase列族设计优化

HBase列族设计对读性能影响也至关重要,其特点是只影响单个业务,并不会对整个集群产生太大影响。列族设计主要从两个方面检查:

9. Bloomfilter是否设置?是否设置合理?

优化原理:Bloomfilter主要用来过滤不存在待检索RowKey或者Row-Col的HFile文件,避免无用的IO操作。它会告诉你在这个HFile文件中是否可能存在待检索的KV,如果不存在,就可以不用消耗IO打开文件进行seek。很显然,通过设置Bloomfilter可以提升随机读写的性能。

Bloomfilter取值有两个,row以及rowcol,需要根据业务来确定具体使用哪种。如果业务大多数随机查询仅仅使用row作为查询条件,Bloomfilter一定要设置为row,否则如果大多数随机查询使用row+cf作为查询条件,Bloomfilter需要设置为rowcol。如果不确定业务查询类型,设置为row。

优化建议:任何业务都应该设置Bloomfilter,通常设置为row就可以,除非确认业务随机查询类型为row+cf,可以设置为rowcol

HDFS相关优化

HDFS作为HBase最终数据存储系统,通常会使用三副本策略存储HBase数据文件以及日志文件。从HDFS的角度望上层看,HBase即是它的客户端,HBase通过调用它的客户端进行数据读写操作,因此HDFS的相关优化也会影响HBase的读写性能。这里主要关注如下三个方面:

10. Short-Circuit Local Read功能是否开启?


优化原理:当前HDFS读取数据都需要经过DataNode,客户端会向DataNode发送读取数据的请求,DataNode接受到请求之后从硬盘中将文件读出来,再通过TPC发送给客户端。Short Circuit策略允许客户端绕过DataNode直接读取本地数据。(具体原理参考此处)

优化建议:开启Short Circuit Local Read功能,具体配置戳这里

11. Hedged Read功能是否开启?

优化原理:HBase数据在HDFS中一般都会存储三份,而且优先会通过Short-Circuit Local Read功能尝试本地读。但是在某些特殊情况下,有可能会出现因为磁盘问题或者网络问题引起的短时间本地读取失败,为了应对这类问题,社区开发者提出了补偿重试机制 – Hedged Read。该机制基本工作原理为:客户端发起一个本地读,一旦一段时间之后还没有返回,客户端将会向其他DataNode发送相同数据的请求。哪一个请求先返回,另一个就会被丢弃。 

优化建议:开启Hedged Read功能,具体配置参考这里

12. 数据本地率是否太低?


数据本地率:HDFS数据通常存储三份,假如当前RegionA处于Node1上,数据a写入的时候三副本为(Node1,Node2,Node3),数据b写入三副本是(Node1,Node4,Node5),数据c写入三副本(Node1,Node3,Node5),可以看出来所有数据写入本地Node1肯定会写一份,数据都在本地可以读到,因此数据本地率是100%。现在假设RegionA被迁移到了Node2上,只有数据a在该节点上,其他数据(b和c)读取只能远程跨节点读,本地率就为33%(假设a,b和c的数据大小相同)。

优化原理:数据本地率太低很显然会产生大量的跨网络IO请求,必然会导致读请求延迟较高,因此提高数据本地率可以有效优化随机读性能。数据本地率低的原因一般是因为Region迁移(自动balance开启、RegionServer宕机迁移、手动迁移等),因此一方面可以通过避免Region无故迁移来保持数据本地率,另一方面如果数据本地率很低,也可以通过执行major_compact提升数据本地率到100%。

优化建议:避免Region无故迁移,比如关闭自动balance、RS宕机及时拉起并迁回飘走的Region等;在业务低峰期执行major_compact提升数据本地率

HBase读性能优化归纳

在本文开始的时候提到读延迟较大无非三种常见的表象,单个业务慢、集群随机读慢以及某个业务随机读之后其他业务受到影响导致随机读延迟很大。了解完常见的可能导致读延迟较大的一些问题之后,我们将这些问题进行如下归类,读者可以在看到现象之后在对应的问题列表中进行具体定位:













HBase读性能优化总结

性能优化是任何一个系统都会遇到的话题,每个系统也都有自己的优化方式。 HBase作为分布式KV数据库,优化点又格外不同,更多得融入了分布式特性以及存储系统优化特性。文中总结了读优化的基本突破点,有什么不对的地方还望指正,有补充的也可以一起探讨交流! 查看全部
本文作者范欣欣,其就职于网易杭州研究院后台技术中心数据库技术组,从事HBase开发、运维,对HBase相关技术有浓厚的兴趣。

任何系统都会有各种各样的问题,有些是系统本身设计问题,有些却是使用姿势问题。HBase也一样,在真实生产线上大家或多或少都会遇到很多问题,有些是HBase还需要完善的,有些是我们确实对它了解太少。总结起来,大家遇到的主要问题无非是Full GC异常导致宕机问题、RIT问题、写吞吐量太低以及读延迟较大。

Full GC问题之前在一些文章里面已经讲过它的来龙去脉,主要的解决方案目前主要有两方面需要注意,一方面需要查看GC日志确认是哪种Full GC,根据Full GC类型对JVM参数进行调优,另一方面需要确认是否开启了BucketCache的offheap模式,建议使用LRUBlockCache的童鞋尽快转移到BucketCache来。当然我们还是很期待官方2.0.0版本发布的更多offheap模块。

RIT问题,我相信更多是因为我们对其不了解,具体原理可以戳这里,解决方案目前有两个,优先是使用官方提供的HBCK进行修复(HBCK本人一直想拿出来分享,但是目前案例还不多,等后面有更多案例的话再拿出来说),使用之后还是解决不了的话就需要手动修复文件或者元数据表。

而对于写吞吐量太低以及读延迟太大的优化问题,笔者也和很多朋友进行过探讨,这篇文章就以读延迟优化为核心内容展开,具体分析HBase进行读延迟优化的那些套路,以及这些套路之后的具体原理。希望大家在看完之后能够结合这些套路剖析自己的系统。

一般情况下,读请求延迟较大通常存在三种场景,分别为:

1. 仅有某业务延迟较大,集群其他业务都正常

2. 整个集群所有业务都反映延迟较大

3. 某个业务起来之后集群其他部分业务延迟较大

这三种场景是表象,通常某业务反应延迟异常,首先需要明确具体是哪种场景,然后针对性解决问题。下图是对读优化思路的一点总结,主要分为四个方面:客户端优化、服务器端优化、列族设计优化以及HDFS相关优化,下面每一个小点都会按照场景分类,文章最后进行归纳总结。下面分别进行详细讲解:
hbase1.png


HBase客户端优化

和大多数系统一样,客户端作为业务读写的入口,姿势使用不正确通常会导致本业务读延迟较高实际上存在一些使用姿势的推荐用法,这里一般需要关注四个问题:

1. scan缓存是否设置合理?

优化原理:在解释这个问题之前,首先需要解释什么是scan缓存,通常来讲一次scan会返回大量数据,因此客户端发起一次scan请求,实际并不会一次就将所有数据加载到本地,而是分成多次RPC请求进行加载,这样设计一方面是因为大量数据请求可能会导致网络带宽严重消耗进而影响其他业务,另一方面也有可能因为数据量太大导致本地客户端发生OOM。在这样的设计体系下用户会首先加载一部分数据到本地,然后遍历处理,再加载下一部分数据到本地处理,如此往复,直至所有数据都加载完成。数据加载到本地就存放在scan缓存中,默认100条数据大小。

通常情况下,默认的scan缓存设置就可以正常工作的。但是在一些大scan(一次scan可能需要查询几万甚至几十万行数据)来说,每次请求100条数据意味着一次scan需要几百甚至几千次RPC请求,这种交互的代价无疑是很大的。因此可以考虑将scan缓存设置增大,比如设为500或者1000就可能更加合适。笔者之前做过一次试验,在一次scan扫描10w+条数据量的条件下,将scan缓存从100增加到1000,可以有效降低scan请求的总体延迟,延迟基本降低了25%左右。

优化建议:大scan场景下将scan缓存从100增大到500或者1000,用以减少RPC次数

2. get请求是否可以使用批量请求?


优化原理:HBase分别提供了单条get以及批量get的API接口,使用批量get接口可以减少客户端到RegionServer之间的RPC连接数,提高读取性能。另外需要注意的是,批量get请求要么成功返回所有请求数据,要么抛出异常。

优化建议:使用批量get进行读取请求

3. 请求是否可以显示指定列族或者列?


优化原理:HBase是典型的列族数据库,意味着同一列族的数据存储在一起,不同列族的数据分开存储在不同的目录下。如果一个表有多个列族,只是根据Rowkey而不指定列族进行检索的话不同列族的数据需要独立进行检索,性能必然会比指定列族的查询差很多,很多情况下甚至会有2倍~3倍的性能损失。

优化建议:可以指定列族或者列进行精确查找的尽量指定查找

4. 离线批量读取请求是否设置禁止缓存?


优化原理:通常离线批量读取数据会进行一次性全表扫描,一方面数据量很大,另一方面请求只会执行一次。这种场景下如果使用scan默认设置,就会将数据从HDFS加载出来之后放到缓存。可想而知,大量数据进入缓存必将其他实时业务热点数据挤出,其他业务不得不从HDFS加载,进而会造成明显的读延迟毛刺

优化建议:离线批量读取请求设置禁用缓存,scan.setBlockCache(false)

HBase服务器端优化

一般服务端端问题一旦导致业务读请求延迟较大的话,通常是集群级别的,即整个集群的业务都会反映读延迟较大。可以从4个方面入手:

5. 读请求是否均衡?


优化原理:极端情况下假如所有的读请求都落在一台RegionServer的某几个Region上,这一方面不能发挥整个集群的并发处理能力,另一方面势必造成此台RegionServer资源严重消耗(比如IO耗尽、handler耗尽等),落在该台RegionServer上的其他业务会因此受到很大的波及。可见,读请求不均衡不仅会造成本身业务性能很差,还会严重影响其他业务。当然,写请求不均衡也会造成类似的问题,可见负载不均衡是HBase的大忌。

观察确认:观察所有RegionServer的读请求QPS曲线,确认是否存在读请求不均衡现象

优化建议:RowKey必须进行散列化处理(比如MD5散列),同时建表必须进行预分区处理

6. BlockCache是否设置合理?


优化原理:BlockCache作为读缓存,对于读性能来说至关重要。默认情况下BlockCache和Memstore的配置相对比较均衡(各占40%),可以根据集群业务进行修正,比如读多写少业务可以将BlockCache占比调大。另一方面,BlockCache的策略选择也很重要,不同策略对读性能来说影响并不是很大,但是对GC的影响却相当显著,尤其BucketCache的offheap模式下GC表现很优越。另外,HBase 2.0对offheap的改造(HBASE-11425)将会使HBase的读性能得到2~4倍的提升,同时GC表现会更好!

观察确认:观察所有RegionServer的缓存未命中率、配置文件相关配置项一级GC日志,确认BlockCache是否可以优化

优化建议:JVM内存配置量 < 20G,BlockCache策略选择LRUBlockCache;否则选择BucketCache策略的offheap模式;期待HBase 2.0的到来!

7. HFile文件是否太多?


优化原理:HBase读取数据通常首先会到Memstore和BlockCache中检索(读取最近写入数据&热点数据),如果查找不到就会到文件中检索。HBase的类LSM结构会导致每个store包含多数HFile文件,文件越多,检索所需的IO次数必然越多,读取延迟也就越高。文件数量通常取决于Compaction的执行策略,一般和两个配置参数有关:hbase.hstore.compactionThreshold和hbase.hstore.compaction.max.size,前者表示一个store中的文件数超过多少就应该进行合并,后者表示参数合并的文件大小最大是多少,超过此大小的文件不能参与合并。这两个参数不能设置太’松’(前者不能设置太大,后者不能设置太小),导致Compaction合并文件的实际效果不明显,进而很多文件得不到合并。这样就会导致HFile文件数变多。

观察确认:观察RegionServer级别以及Region级别的storefile数,确认HFile文件是否过多

优化建议:hbase.hstore.compactionThreshold设置不能太大,默认是3个;设置需要根据Region大小确定,通常可以简单的认为hbase.hstore.compaction.max.size = RegionSize / hbase.hstore.compactionThreshold

8. Compaction是否消耗系统资源过多?

优化原理:Compaction是将小文件合并为大文件,提高后续业务随机读性能,但是也会带来IO放大以及带宽消耗问题(数据远程读取以及三副本写入都会消耗系统带宽)。正常配置情况下Minor Compaction并不会带来很大的系统资源消耗,除非因为配置不合理导致Minor Compaction太过频繁,或者Region设置太大情况下发生Major Compaction。

观察确认:观察系统IO资源以及带宽资源使用情况,再观察Compaction队列长度,确认是否由于Compaction导致系统资源消耗过多

优化建议:

(1)Minor Compaction设置:hbase.hstore.compactionThreshold设置不能太小,又不能设置太大,因此建议设置为5~6;hbase.hstore.compaction.max.size = RegionSize / hbase.hstore.compactionThreshold

(2)Major Compaction设置:大Region读延迟敏感业务( 100G以上)通常不建议开启自动Major Compaction,手动低峰期触发。小Region或者延迟不敏感业务可以开启Major Compaction,但建议限制流量;

(3)期待更多的优秀Compaction策略,类似于stripe-compaction尽早提供稳定服务

HBase列族设计优化

HBase列族设计对读性能影响也至关重要,其特点是只影响单个业务,并不会对整个集群产生太大影响。列族设计主要从两个方面检查:

9. Bloomfilter是否设置?是否设置合理?

优化原理:Bloomfilter主要用来过滤不存在待检索RowKey或者Row-Col的HFile文件,避免无用的IO操作。它会告诉你在这个HFile文件中是否可能存在待检索的KV,如果不存在,就可以不用消耗IO打开文件进行seek。很显然,通过设置Bloomfilter可以提升随机读写的性能。

Bloomfilter取值有两个,row以及rowcol,需要根据业务来确定具体使用哪种。如果业务大多数随机查询仅仅使用row作为查询条件,Bloomfilter一定要设置为row,否则如果大多数随机查询使用row+cf作为查询条件,Bloomfilter需要设置为rowcol。如果不确定业务查询类型,设置为row。

优化建议:任何业务都应该设置Bloomfilter,通常设置为row就可以,除非确认业务随机查询类型为row+cf,可以设置为rowcol

HDFS相关优化

HDFS作为HBase最终数据存储系统,通常会使用三副本策略存储HBase数据文件以及日志文件。从HDFS的角度望上层看,HBase即是它的客户端,HBase通过调用它的客户端进行数据读写操作,因此HDFS的相关优化也会影响HBase的读写性能。这里主要关注如下三个方面:

10. Short-Circuit Local Read功能是否开启?


优化原理:当前HDFS读取数据都需要经过DataNode,客户端会向DataNode发送读取数据的请求,DataNode接受到请求之后从硬盘中将文件读出来,再通过TPC发送给客户端。Short Circuit策略允许客户端绕过DataNode直接读取本地数据。(具体原理参考此处)

优化建议:开启Short Circuit Local Read功能,具体配置戳这里

11. Hedged Read功能是否开启?

优化原理:HBase数据在HDFS中一般都会存储三份,而且优先会通过Short-Circuit Local Read功能尝试本地读。但是在某些特殊情况下,有可能会出现因为磁盘问题或者网络问题引起的短时间本地读取失败,为了应对这类问题,社区开发者提出了补偿重试机制 – Hedged Read。该机制基本工作原理为:客户端发起一个本地读,一旦一段时间之后还没有返回,客户端将会向其他DataNode发送相同数据的请求。哪一个请求先返回,另一个就会被丢弃。 

优化建议:开启Hedged Read功能,具体配置参考这里

12. 数据本地率是否太低?


数据本地率:HDFS数据通常存储三份,假如当前RegionA处于Node1上,数据a写入的时候三副本为(Node1,Node2,Node3),数据b写入三副本是(Node1,Node4,Node5),数据c写入三副本(Node1,Node3,Node5),可以看出来所有数据写入本地Node1肯定会写一份,数据都在本地可以读到,因此数据本地率是100%。现在假设RegionA被迁移到了Node2上,只有数据a在该节点上,其他数据(b和c)读取只能远程跨节点读,本地率就为33%(假设a,b和c的数据大小相同)。

优化原理:数据本地率太低很显然会产生大量的跨网络IO请求,必然会导致读请求延迟较高,因此提高数据本地率可以有效优化随机读性能。数据本地率低的原因一般是因为Region迁移(自动balance开启、RegionServer宕机迁移、手动迁移等),因此一方面可以通过避免Region无故迁移来保持数据本地率,另一方面如果数据本地率很低,也可以通过执行major_compact提升数据本地率到100%。

优化建议:避免Region无故迁移,比如关闭自动balance、RS宕机及时拉起并迁回飘走的Region等;在业务低峰期执行major_compact提升数据本地率

HBase读性能优化归纳

在本文开始的时候提到读延迟较大无非三种常见的表象,单个业务慢、集群随机读慢以及某个业务随机读之后其他业务受到影响导致随机读延迟很大。了解完常见的可能导致读延迟较大的一些问题之后,我们将这些问题进行如下归类,读者可以在看到现象之后在对应的问题列表中进行具体定位:
hbase2.png

hbase3.png

hbase4.png


HBase读性能优化总结

性能优化是任何一个系统都会遇到的话题,每个系统也都有自己的优化方式。 HBase作为分布式KV数据库,优化点又格外不同,更多得融入了分布式特性以及存储系统优化特性。文中总结了读优化的基本突破点,有什么不对的地方还望指正,有补充的也可以一起探讨交流!

网易大数据工程师学习之路分享

hbasehbasegroup 发表了文1章 • 0 个评论 • 790 次浏览 • 2018-08-05 19:36 • 来自相关话题

范欣欣,目前就职于网易杭州研究院后台技术中心数据库技术组,专注于HBase的开发运维,热衷于MySQL等相关数据库技术
之所以忽然提笔,是因为这段时间正好在业余时间系统地学习Spark,整个学习思路让我想起了大学期间学习《模拟电子电路》这门课的一些方法,个人觉得可以作为一个学习模板来和大家一起交流分享(本文只谈如何系统高效地学习一项技能或者一门课程,抱有突击学习目的的请绕道)。


无论是学习Spark技术还是学习《模拟电子电路》课程,总结起来,大体都经历了这么几个阶段:

1. 初识(10%):系统地过一遍整个内容,《模电》就是大体听一遍老师的课程,Spark就到处看看相关的资料,在测试环境写一点测试代码。这个过程不需要特别仔细,也不需要多么深入的理解,只需要有个基本的概念了解即可。通常初识阶段是没有办法建立起知识的体系结构的。

2. 搭建知识体系(20%):初步了解基本概念之后,需要再过一遍所有内容,这次同样不需要关注细节,但是需要重点关注章节体系以及章节核心点:

这门课程有哪些章节,比如Spark整体可以划分为Spark内核原理、Spark运帷优化、SparkSQL等几大部分。其中Spark内核原理可以划分为RDD介绍、DAG介绍、Spark任务调度、Spark存储管理系统、Spark资源管理系统等几个模块,Spark运维优化分为Spark部署、Web UI等

进一步,需要针对每个模块列出核心点,不需要多,每个章节只需要列出三四个核心点即可,比如Spark资源管理模块需要关注静态资源管理策略以及动态资源管理策略

这样处理之后,厚厚的Spark书籍/《模电》就可以变成薄薄的几页纸,一页纸对应一个具体模块,记录该模块的核心点。犹记得当年考试前别的同学在拿着一本厚厚的模电书翻来翻去的时候,我就只需要拿着几页纸复习(而且单科成绩全系前茅)。

第二步是整个方法论中的重中之重,建立知识体系结构,构建知识树谱对知识的深入理解至关重要。只有拥有了全局的视野,你才知道现在自己学习了哪些,还有哪些没学。就像盖别墅,你需要首先画图纸,有个全局的设计,没有人会今天想起来东边盖个花园,明天在西边搞个游泳池。很多同学在学习一门技能的时候总是这里看点资料,那里学点优化方法,缺乏全局的视野,缺乏系统性,学到的东西永远支离破碎。而此时的你,已经可以滔滔不绝、由浅及深地概括一本书了。下面两图是笔者学习Spark内核时为自己列的一个提纲以及优化章节的部分核心点:
 












3.  深入探索(20%):知识体系建立起来之后,需要更加深入。此时可以丢掉课本,只需要对照着每个章节的核心点一个一个重点击破,比如现在要深入学习Spark资源调度模块中的动态资源调度,那就利用所有可以利用到的资源(google、官方博客、spark文档、youtubo、jira、源码等)检索动态资源调度的相关信息,深入理解其工作原理。这样一个一个击破之后整个知识体系就更加丰满。这个阶段需要严苛的工匠精神,需要你搜集各种资料去了解,去思考why。至此,在理论层面你已经是所谓的”专家”了。这个阶段有两点建议:


(1) 经典内容检索:这个阶段拼的是各位的检索能力和阅读理解能力,在此强烈推荐技术人多关注国外技术大牛的博客以及官方博客、文档,比如Spark可以关注的材料主要有:

官方文档:http://spark.apache.org/docs/latest/index.html

官方博客:https://databricks.com/blog

Youtube视频(比较多):https://www.youtube.com/watch?v=cs3_3LdCny8

国内大神:https://github.com/JerryLead/S ... kdown

(2)画图整理:网络上关于Spark的内容有很多很多,经典的内容更不少,一般遇到经典内容之后都会一口气读完,再加入书签。然,久而久之,很多内容都会慢慢模糊,当你再想去查的时候已经不知道是哪个博客的内容了,相信很多人会有这样的苦恼。针对这样的问题,需要将一些自己体会非常深刻的内容记录下来,建议使用画图工具,俗话说一图胜千言,比如学习SparkSQL时为了理解SQL的整个解析过程,笔者就简单地将一个简单的SQL的执行计划通过几张图表示出来:






4. 实践探索(30%): 第三步完成之后,相信你已经可以就这项技能和别人谈笑风生了,但也就仅此而已。一旦别人问你一个线上问题,相信你就会从滔滔不绝变得支支吾吾,因为你缺少实践。当然,只有在知识体系构建完成后的实践才是真正意义上的实践。有理论依据作为支撑,实践才有更多意义。实践是一个遇坑填坑的过程,没有遇到坑也不能称为实践。因为只有遇到问题,你才会完整地将监控、日志信息利用起来追踪整个系统工作流程,你才会真真切切地去想如何通过修改配置、修改源码来进一步改造它。

这个阶段,主要考察你解决问题的能力,一般来说通常就三板斧:监控、日志和源码。监控分为硬件监控以及业务监控,两者都需要看懂并会分析。日志也有很多,比如业务日志、GC日志等,需要能力根据异常猜测问题原因并进行验证。如果前者都失效,就只能分析源码。问题分析是第一步,更重要的,你还要提出一个高效地解决方案,这个可能是领导/面试官更看重的

实践探索不可能一撮而就,需要不断的踩坑填坑,所以需要一颗大心脏。

5. 分享交流(20%): 上面四步都是你自己对知识的理解,你还需要看看同行是如何理解的。实践结束之后一定记得需要以博客的形式系统完整的将这个模块完完整整、成体系地、由浅及深地进行复盘整理、分享交流!这个阶段可以让你认识更多圈子里的朋友,一起交流探讨才能不寂寞

学习有无数种方法,适合自己的才是最好的。本文只是笔者对学习方法的一点思考,分享出来一方面是为了更好地完善自己,另一方面也是希望能够给大家提供参考。当然,学习从来不是一件轻松的事情,但也从来不是一件艰难的事情。共勉。
 
下面是我的公众号,如果想进入互联网行业,可协助帮忙内推,同时也欢迎对互联网行业感兴趣的同学一起交流学习。也想大家推荐一下hbase相关学习,大家工作学习遇到HBase技术问题,把问题发布到HBase技术社区论坛hbase.group,欢迎大家论坛上面提问留言讨论。想了解更多HBase技术关注HBase技术社区公众号:(hbasegroup),非常欢迎大家积极投稿。

长按下面的二维码关注HBase技术社区公众号





  查看全部
范欣欣,目前就职于网易杭州研究院后台技术中心数据库技术组,专注于HBase的开发运维,热衷于MySQL等相关数据库技术
之所以忽然提笔,是因为这段时间正好在业余时间系统地学习Spark,整个学习思路让我想起了大学期间学习《模拟电子电路》这门课的一些方法,个人觉得可以作为一个学习模板来和大家一起交流分享(本文只谈如何系统高效地学习一项技能或者一门课程,抱有突击学习目的的请绕道)。


无论是学习Spark技术还是学习《模拟电子电路》课程,总结起来,大体都经历了这么几个阶段:

1. 初识(10%):系统地过一遍整个内容,《模电》就是大体听一遍老师的课程,Spark就到处看看相关的资料,在测试环境写一点测试代码。这个过程不需要特别仔细,也不需要多么深入的理解,只需要有个基本的概念了解即可。通常初识阶段是没有办法建立起知识的体系结构的。

2. 搭建知识体系(20%):初步了解基本概念之后,需要再过一遍所有内容,这次同样不需要关注细节,但是需要重点关注章节体系以及章节核心点:

这门课程有哪些章节,比如Spark整体可以划分为Spark内核原理、Spark运帷优化、SparkSQL等几大部分。其中Spark内核原理可以划分为RDD介绍、DAG介绍、Spark任务调度、Spark存储管理系统、Spark资源管理系统等几个模块,Spark运维优化分为Spark部署、Web UI等

进一步,需要针对每个模块列出核心点,不需要多,每个章节只需要列出三四个核心点即可,比如Spark资源管理模块需要关注静态资源管理策略以及动态资源管理策略

这样处理之后,厚厚的Spark书籍/《模电》就可以变成薄薄的几页纸,一页纸对应一个具体模块,记录该模块的核心点。犹记得当年考试前别的同学在拿着一本厚厚的模电书翻来翻去的时候,我就只需要拿着几页纸复习(而且单科成绩全系前茅)。

第二步是整个方法论中的重中之重,建立知识体系结构,构建知识树谱对知识的深入理解至关重要。只有拥有了全局的视野,你才知道现在自己学习了哪些,还有哪些没学。就像盖别墅,你需要首先画图纸,有个全局的设计,没有人会今天想起来东边盖个花园,明天在西边搞个游泳池。很多同学在学习一门技能的时候总是这里看点资料,那里学点优化方法,缺乏全局的视野,缺乏系统性,学到的东西永远支离破碎。而此时的你,已经可以滔滔不绝、由浅及深地概括一本书了。下面两图是笔者学习Spark内核时为自己列的一个提纲以及优化章节的部分核心点:
 

微信图片_20180805185011.jpg


微信图片_20180805184928.jpg



3.  深入探索(20%):知识体系建立起来之后,需要更加深入。此时可以丢掉课本,只需要对照着每个章节的核心点一个一个重点击破,比如现在要深入学习Spark资源调度模块中的动态资源调度,那就利用所有可以利用到的资源(google、官方博客、spark文档、youtubo、jira、源码等)检索动态资源调度的相关信息,深入理解其工作原理。这样一个一个击破之后整个知识体系就更加丰满。这个阶段需要严苛的工匠精神,需要你搜集各种资料去了解,去思考why。至此,在理论层面你已经是所谓的”专家”了。这个阶段有两点建议:


(1) 经典内容检索:这个阶段拼的是各位的检索能力和阅读理解能力,在此强烈推荐技术人多关注国外技术大牛的博客以及官方博客、文档,比如Spark可以关注的材料主要有:

官方文档:http://spark.apache.org/docs/latest/index.html

官方博客:https://databricks.com/blog

Youtube视频(比较多):https://www.youtube.com/watch?v=cs3_3LdCny8

国内大神:https://github.com/JerryLead/S ... kdown

(2)画图整理:网络上关于Spark的内容有很多很多,经典的内容更不少,一般遇到经典内容之后都会一口气读完,再加入书签。然,久而久之,很多内容都会慢慢模糊,当你再想去查的时候已经不知道是哪个博客的内容了,相信很多人会有这样的苦恼。针对这样的问题,需要将一些自己体会非常深刻的内容记录下来,建议使用画图工具,俗话说一图胜千言,比如学习SparkSQL时为了理解SQL的整个解析过程,笔者就简单地将一个简单的SQL的执行计划通过几张图表示出来:

微信图片_20180805193446.jpg


4. 实践探索(30%): 第三步完成之后,相信你已经可以就这项技能和别人谈笑风生了,但也就仅此而已。一旦别人问你一个线上问题,相信你就会从滔滔不绝变得支支吾吾,因为你缺少实践。当然,只有在知识体系构建完成后的实践才是真正意义上的实践。有理论依据作为支撑,实践才有更多意义。实践是一个遇坑填坑的过程,没有遇到坑也不能称为实践。因为只有遇到问题,你才会完整地将监控、日志信息利用起来追踪整个系统工作流程,你才会真真切切地去想如何通过修改配置、修改源码来进一步改造它。

这个阶段,主要考察你解决问题的能力,一般来说通常就三板斧:监控、日志和源码。监控分为硬件监控以及业务监控,两者都需要看懂并会分析。日志也有很多,比如业务日志、GC日志等,需要能力根据异常猜测问题原因并进行验证。如果前者都失效,就只能分析源码。问题分析是第一步,更重要的,你还要提出一个高效地解决方案,这个可能是领导/面试官更看重的

实践探索不可能一撮而就,需要不断的踩坑填坑,所以需要一颗大心脏。

5. 分享交流(20%): 上面四步都是你自己对知识的理解,你还需要看看同行是如何理解的。实践结束之后一定记得需要以博客的形式系统完整的将这个模块完完整整、成体系地、由浅及深地进行复盘整理、分享交流!这个阶段可以让你认识更多圈子里的朋友,一起交流探讨才能不寂寞

学习有无数种方法,适合自己的才是最好的。本文只是笔者对学习方法的一点思考,分享出来一方面是为了更好地完善自己,另一方面也是希望能够给大家提供参考。当然,学习从来不是一件轻松的事情,但也从来不是一件艰难的事情。共勉。
 
下面是我的公众号,如果想进入互联网行业,可协助帮忙内推,同时也欢迎对互联网行业感兴趣的同学一起交流学习。也想大家推荐一下hbase相关学习,大家工作学习遇到HBase技术问题,把问题发布到HBase技术社区论坛hbase.group,欢迎大家论坛上面提问留言讨论。想了解更多HBase技术关注HBase技术社区公众号:(hbasegroup),非常欢迎大家积极投稿。

长按下面的二维码关注HBase技术社区公众号

微信图片_20180623105555.jpg

 

Phoenix4.14不读取hbase配置文件

Phoenixjiyz0323 回复了问题 • 4 人关注 • 3 个回复 • 1782 次浏览 • 2018-08-05 18:54 • 来自相关话题

HBase在贝壳找房的实践经验

hbase过往记忆 发表了文1章 • 0 个评论 • 579 次浏览 • 2018-08-02 13:37 • 来自相关话题

本文根据贝壳找房邓钫元老师在中国HBase技术社区第二届MeetUp:“HBase技术解析及应用实践”中分享的《HBase在贝壳找房的实践经验》编辑整理而成,在未改变原意的基础上稍做整理。

钫元老师

首先给大家介绍一下贝壳,贝壳是链家一个房地产品牌,链家的愿景是把贝壳打造为一个大的互联网房地产入口品牌,也允许第三方品牌接入我们的贝壳平台。类似于京东商城与京东自营的关系。我们的愿景是把链家的数据与行业的品牌结合起来,更好的服务整个行业的消费者。

本次分享从以下四个部分介绍:

第一部分 HBase简介

(1)数据模型

它的几个程式是一张表,有一个行键,叫做rowkey,它用来确定每一行,然后每一行里面会分为很多个列族,每一个列族下面有不限量的列。每一个列上可以存储数据,每一个数据都是有版本的,可以通过时间戳来区别。所以我们在一张表中,知道行键,列族,列,版本时间戳可以确定一个唯一的值。

(2)逻辑架构

任何一张表,他的rowkey是全局有序的,由于对物理存储上的考虑,我们把它放在多个机器上,我们按照大小或者其他策略,分为多个region。每一个region负责表示一份数据,region会在物理机器上,保证是一个均衡的状态。

(3)系统架构

首先它也是一套标准的存储架构。他的Hmaster主要负责简单的协调服务,比如region的转移,均衡,以及错误的恢复,实际上他并不参与查询,真正的查询是发生在region server。region server事负责存储的,刚才我们说过,每一个表会分为几个region。然后存储在region server。这里最重要的部分是hlog。为了保证数据一致性,首先会写一份日志文件,这是数据库系统里面以来的一种特性,创建了日志以后,我们才能写入成功。我们刚才提到HBase里面有很多column-family列族,没个列族在一个region里对应一个store,store分别包含storefile和menstore。为了后续对HBase可以优化,我们首先考虑把文件写入menstore里面,随着menstore里面的数据满了之后,会把数据分发到磁盘里,然后storefile和memstore整体的话,依赖一个数据模型,叫做lmstree。然后,数据是采用append方式写入的,无论是插入,修改,删除。实际上都是不断的append的。比如说你的更新,删除的操作方式,都是以打标记方式写入,所以它避免了磁盘的随机io,提高了写入性能,当然的话,它的底层的话是建立在hdfs之上。

第二部分 HBase在贝壳中的使用经验

(1)总体介绍

我们贝壳使用HBase是非常多的。这边也搭建了一套系统,部署在我们机器上,目前我们应用在房客源轨迹跟踪,我们制订了一个指标平台,这个平台依赖于另外一个引擎—kylin。借助HBase实现一套多维分析,比如用户的画像,还有日志统计分析等这几个方面。

(2)房客源轨迹产品介绍

这就是我们房客源轨迹的一个产品,其实这个产品主要使用者是经纪人,这个产品可以根据客户的一套房子去跟踪它的所有信息,包括它挂牌成交后的电话以及房源状态的跟新。实际上这个状态量是非常大。我们使用HBase来存储这些数据。

其实这个模型是非常适合HBase存储的模型,比如说,对于房源轨迹的存储我们做了如下设计,因为它就依赖于房价来做索引,我们把它的row-key设计成房源Id+时间+操作类型。value的话,就是具体操作的值。为了保证HBase序列的均衡性,我们对Id加一个md5,做一个哈希,这样可以保证数据的分配均衡。这是一个非常典型的使用场景。

(3)指标平台

第二个场景叫做指标平台,首先给大家展示一下平台。这是一个可视化的分析平台,在这上面可以选择我们在HBase存储好的数据,可以选择哪些维度,去查询哪些指标。比如这个成交数据,可以选择时间,城市。就会形成一张图,进而创建一张报表。然后这张报表可以分享给其他人使用。

这张表,应该从时间和组织架构这两个维度,我们现在看到的是一个大区的情况,这是每个大区的成交量。

多维分析的话,包括上卷和下钻。刚才我么刚到大区的情况,我想往上一层,看全国的区域,比如说东北区域或者华南区域的情况。

第二个是下钻,这就是看某个区域的情况,比如说我们想看某个区域作业的经纪人或者作业的店,可以往下查询,分析到每一个人。

(4)引擎选型

指标平台的话,前面是从一个产品方面展示。他后台依赖的一个引擎是Kylin。为什么会选择Kylin呢,因为Kylin是一个molap引擎,他是一个运算模型,他满足我们的需求,对页面的相应的话,需要亚秒级的响应。第二,他对并发有一定的要求,原始的数据达到了百亿的规模。另外需要具有一定的灵活性,最好有sql接口,以离线为主。综合考虑,我们使用的是Kylin。

(5)Kylin简介

Kylin给他家简单介绍一下,Apache Kylin™是一个开源的分布式分析引擎,提供Hadoop之上的SQL查询接口及多维分析(OLAP)能力以支持超大规模数据,最初由eBay Inc.开发并贡献至开源社区。它能在亚秒内查询巨大的Hive表。他的原理比较简单,基于一个并运算模型,我预先知道,我要从那几个维度去查询某个指标。在预定好的指标和维度的情况下去把所有的情况遍历一遍。利用molap把所有的结果都算出来,在存储到HBase中。然后根据sql查询的维度和指标直接到HBase中扫描就行了。为什么能够实现亚秒级的查询,这就依赖于HBase的计算。

(6)kylin架构

和刚才讲的逻辑是一致的,左边是数据仓库。所有的数据都在数据仓库中存储。中间是计算引擎,把每天的调度做好,转化为HBase的KY结构存储在HBase中,对外提供sql接口,提供路由功能,解析sql语句,转化为具体的HBase命令。

Kylin中有一个概念叫Cube和Cubold,其实这个逻辑也非常简单,比如已经知道查询的维度有A,b,c,d四个。那abcd查询的时候,可以取也可以不取。一共有16种组合,整体叫做,cube。其中每一种组合叫做Cuboid。

(7)Kylin如何在Hbase中进行物理存储的

首先定义一张原始表,有两个维度,year和city。在定义一个指标,比如总价。下面是所有的组合,刚才说到Kylin里面有很多cuboid组合,比如说前面三行有一个cuboid:00000011组合,他们在HBase中的RowKey就是cuboid加上各维度的取值。这里面会做一点小技巧,对维度的值做一些编码,如果把程式的原始值放到Rowkey里,会比较长。Rowkey也会存在一个sell里任何一个版本的值都会存在里面。如果rowkey变的非常长,对HBase的压力会非常大,所有通过一个字典编码去减少长度,通过这种方法,就可以把kylin中的计算数据存储到HBase中。

(7)平台架构

这就是我们链家多维分析的平台架构,左边是一套主集群,数据仓库和离线计算在上面,右边是Kylin的系统,分为查询和构件两部分,通过一个调度系统,定时去调度计算任务,把数据导入独立的HBase中,这套独立的HBase是为了保证线上查询不会受影响。最右边,我们做了一套Kylin中间件,因为Kylin本身会有很多问题,比如任务数有限制,任务之间没有优先级,错误重试等问题,通过自研了一套中间件来解决这些问题。最右边是使用这些系统和集群的平台。它底层就依赖于kylin的数据查询服务,同时也会做一些数据缓存。使用系统中的通用模块,比如权限管理,监控管理,元数据管理,调度系统。

(8)Kylin自研中间件

这就是我们自研的Kylin中间件,原生的话,本身有很多问题,我们做的工作支持如下功能:无限容量队列,针对特定cube的优先调度,权限的管理, 重试任务执行,实现任务的并发控制。

这是中间件的另一个功能:cube管理,Kylin原生也有一套cube管理,但是会有很多问题,比如说性能问题,cube会很卡,另外一个是权限问题,我们希望可以对接到链家的权限,优化前端页面,简化配置,提升管理效率。右边是我们自主创建cube的过程。

这里是我们Kylin使用的情况。

这里是我们链家对Kylin的定制改造。

第三部分 HBase的优化经验

(1)HBase部署上的链路

我们优化也是针对这个链路做优化,第一步:Kylin及各类应用,由HBase调用,HBase数据又存储在HDFS上。同时HBase和HDFS又运行在操作系统上。我们会从这几层做优化。

(2)性能优化工具

首先给大家介绍一个性能优化的工具,是Profile利器-火焰图。首先看右边的图,表示的是代码在我们CPU上运行的逻辑。AB就是两个函数,A会调用B,然后时序会从左到右。上面的每个箭头,是每个堆栈的切片,on-CPU表示你的代码正在运行,off-cpu表示你的代码调用其他读写功能,让出线程。把这些切片信息累加起来,就可以得到在右下角的一张图,叫做火焰图。

这是一张火焰图的样例,这张图是对Firefox进行采样的,一般分析火焰图,是从下往上的。我们可以看到非常多的“山峰”。一般我们关注的是,“山峰”从下往上一直没有缩减。

(3)优化实例

这里讲一个优化实例,之前某一个查询服务,当时发现,并发的时候CPU满载,用火焰图做一个采样分析,左上角是一个认证模块加密算法,他消耗百分之七八十的CPU资源,下面的弹窗显示的是具体处理过程,也是占用百分之八九十的CPU资源,他为了实现长加密,使用了一套Base的加密算法,这套算法对CPU的消费非常高,实际上在内网环境上,对安全的要求没有那么高,所以换成了MD5加密算法,大家可以看到,优化之后,处理的时间大大降低了。通过对比,提升了四五倍的qps。右边是火焰图的创造者。

(4)GC算法

HBase是内存消耗的大户,它分为两部分,一个是写入的时候,里那个一个是cache。他们对内存的消耗是非常大,我们这边对Java的堆开到了将近100g左右,如果使用CMS算法的话,对其进行调优。都会出现暂停时间比较长的情况,满足线上的重调业务,后期的话,我们改成了G1,G1就是把内存的堆分成region,然后每一个region有各自的功能,G1有预测算法,比如,我指定一个暂停时间100ms,他能够通过预测算法,能够预算到在这100ms内能够搜集多少垃圾,然后他会收集指定的region,这样就可以做到时间可控。

这是我们线上使用java的执行参数。第一个框我们指定暂停时间100ms,有几个重要的参数,比如有几个并行数,另外alwayspretouch这个参数比较有意思,当我们启动java程序的时候,他向操作系统申请96M的空间,实际上这个空间是虚拟内存,当你有反馈的时候,操作系统才会把虚拟内存映射到物理内存上,这样一个操作叫做区域中断,这一个负载比较高的系统中,会对你的处理有一些延迟,你开启这个参数,就会把所有的清零。因为每次写入新数据,会产生一个回升带,回升带设置比较大的话,造成暂停的时间比较长,所以建议设置一个比较小的数,百分之1就可以。右边是一个优化的图,上边是使用GMS的时间,下面是G1算法的。可以看到得到了很好的缩短。

(5)HBase在IO上优化

我们也了解到HBase在IO上可以做很多优化,但是对IO最好的优化,还是基于硬件。左边的话,最上方主要依赖Hbase对多存储的支持,叫做异构存储,可以标记存储页,同时,HBase上也会有有一些策略,比如ALL_SSD,(F副本全部放在SSD上)ONE_SSD(一个副本放在SSD上)。目前,我们把HBase核心业务和预写日志用ALL_SSD这种策略上,一些重要业务会使用ONE_SSD,普通的话,使用村纯硬盘就可以了。

下面说一下,在ONE_SSD测策略下,会有一些性能会出现问题,比如第一个文件块,会有三个副本,其中某一块SSD,当我发起读取的请求时,根据原生hadoop的策略,会优先去读本地,但实际上,在目前的架构下,远程去读SSD的性能是远超读取本地硬盘的。所以在架构上增加了SSD-FIRST策略,优先读取远程SSD。

另外就是HBase的读写分离,原生HBase可以生成队列树和线程树,想很多读写请求和scan请求都会统一并发,这样的话会出现一个问题,当我有一个scan请求,会扫面很多行,把队列都堵住了,后续的请求可能会无法响应,上边是1.2里的功能,我可以把队列和线程分组,比如80%负责读,15%负责写,5%负责scan。

这是HBase基于wasd的性能测试,左边是测试的设备和配置。可以看硬盘与SSD的IO差别很大,绿色的这条线,代表的是qps这条线。大家可以看到,最开始全部使用硬盘的时候,qps大概在一万左右,当我们使用ONE_SSD的时候,大概能够提升一倍的qps。最后引入SSDFIRST策略优化之后,提升大概一点五倍,开启读写分离以后,性能有提升了大概一点五倍。整体提升了10倍的qps,同时延迟也是大幅度下降。

下面是优化的一些杂项。

下面说下,在操作系统层面上做的优化。下面的图运行之后,可以看到机器内存使用状况。Cached主要做文件系统缓存使用的。下面是解决问题的一个流程。

我们是基于一个systemptap分析的,它是一个内核注入工具。它的原理其实就是操作在内核的函数上,比如说你操作一个函数,在里面可以加入计数的代码等,下面是定义的cache命中率的公式。右边是systemtap在几个函数操作的数据统计。右边减去的部分,是当page发生写入的时候,它也会进入到缓存。剪掉的主要是在写入过程中的造成的page情况。左下角是计算结果。如果硬盘是SSD的话,效果会更好一点。

另外一个就是HBase在多处理器上的问题,这是大多数情况下都会遇到的问题,传统的多处理器引入的架构中,所有内存通过总线连接到CPU上。实际上所有内存对CPU都是对称的,这种模式叫做对称处理器。随着内存的规模越来越大,总线成为了瓶颈。目前大多使用NUMA架构,它的原理是每个CPU对内存访问的数量是有限的。所以会把内存分为多组,每个组附加在一个CPU上,只有当这一块不够的时候,才会通过总线去访问另外一个CPU,这种设计的话,在内存应用的话,比较友好。但是对HBase和redis这种大的架构下,一个内存会远远超过一个cpu所能支持本地内存。默认的话,是亲和模式。不积极的使用远程内存,会出现本地内存满了之后,导致内存分配失败。所以可以设置一个zone_reclaim_mode=0参数。

最后是操作系统的优化杂项。 查看全部
本文根据贝壳找房邓钫元老师在中国HBase技术社区第二届MeetUp:“HBase技术解析及应用实践”中分享的《HBase在贝壳找房的实践经验》编辑整理而成,在未改变原意的基础上稍做整理。

钫元老师

首先给大家介绍一下贝壳,贝壳是链家一个房地产品牌,链家的愿景是把贝壳打造为一个大的互联网房地产入口品牌,也允许第三方品牌接入我们的贝壳平台。类似于京东商城与京东自营的关系。我们的愿景是把链家的数据与行业的品牌结合起来,更好的服务整个行业的消费者。

本次分享从以下四个部分介绍:

第一部分 HBase简介

(1)数据模型

它的几个程式是一张表,有一个行键,叫做rowkey,它用来确定每一行,然后每一行里面会分为很多个列族,每一个列族下面有不限量的列。每一个列上可以存储数据,每一个数据都是有版本的,可以通过时间戳来区别。所以我们在一张表中,知道行键,列族,列,版本时间戳可以确定一个唯一的值。

(2)逻辑架构

任何一张表,他的rowkey是全局有序的,由于对物理存储上的考虑,我们把它放在多个机器上,我们按照大小或者其他策略,分为多个region。每一个region负责表示一份数据,region会在物理机器上,保证是一个均衡的状态。

(3)系统架构

首先它也是一套标准的存储架构。他的Hmaster主要负责简单的协调服务,比如region的转移,均衡,以及错误的恢复,实际上他并不参与查询,真正的查询是发生在region server。region server事负责存储的,刚才我们说过,每一个表会分为几个region。然后存储在region server。这里最重要的部分是hlog。为了保证数据一致性,首先会写一份日志文件,这是数据库系统里面以来的一种特性,创建了日志以后,我们才能写入成功。我们刚才提到HBase里面有很多column-family列族,没个列族在一个region里对应一个store,store分别包含storefile和menstore。为了后续对HBase可以优化,我们首先考虑把文件写入menstore里面,随着menstore里面的数据满了之后,会把数据分发到磁盘里,然后storefile和memstore整体的话,依赖一个数据模型,叫做lmstree。然后,数据是采用append方式写入的,无论是插入,修改,删除。实际上都是不断的append的。比如说你的更新,删除的操作方式,都是以打标记方式写入,所以它避免了磁盘的随机io,提高了写入性能,当然的话,它的底层的话是建立在hdfs之上。

第二部分 HBase在贝壳中的使用经验

(1)总体介绍

我们贝壳使用HBase是非常多的。这边也搭建了一套系统,部署在我们机器上,目前我们应用在房客源轨迹跟踪,我们制订了一个指标平台,这个平台依赖于另外一个引擎—kylin。借助HBase实现一套多维分析,比如用户的画像,还有日志统计分析等这几个方面。

(2)房客源轨迹产品介绍

这就是我们房客源轨迹的一个产品,其实这个产品主要使用者是经纪人,这个产品可以根据客户的一套房子去跟踪它的所有信息,包括它挂牌成交后的电话以及房源状态的跟新。实际上这个状态量是非常大。我们使用HBase来存储这些数据。

其实这个模型是非常适合HBase存储的模型,比如说,对于房源轨迹的存储我们做了如下设计,因为它就依赖于房价来做索引,我们把它的row-key设计成房源Id+时间+操作类型。value的话,就是具体操作的值。为了保证HBase序列的均衡性,我们对Id加一个md5,做一个哈希,这样可以保证数据的分配均衡。这是一个非常典型的使用场景。

(3)指标平台

第二个场景叫做指标平台,首先给大家展示一下平台。这是一个可视化的分析平台,在这上面可以选择我们在HBase存储好的数据,可以选择哪些维度,去查询哪些指标。比如这个成交数据,可以选择时间,城市。就会形成一张图,进而创建一张报表。然后这张报表可以分享给其他人使用。

这张表,应该从时间和组织架构这两个维度,我们现在看到的是一个大区的情况,这是每个大区的成交量。

多维分析的话,包括上卷和下钻。刚才我么刚到大区的情况,我想往上一层,看全国的区域,比如说东北区域或者华南区域的情况。

第二个是下钻,这就是看某个区域的情况,比如说我们想看某个区域作业的经纪人或者作业的店,可以往下查询,分析到每一个人。

(4)引擎选型

指标平台的话,前面是从一个产品方面展示。他后台依赖的一个引擎是Kylin。为什么会选择Kylin呢,因为Kylin是一个molap引擎,他是一个运算模型,他满足我们的需求,对页面的相应的话,需要亚秒级的响应。第二,他对并发有一定的要求,原始的数据达到了百亿的规模。另外需要具有一定的灵活性,最好有sql接口,以离线为主。综合考虑,我们使用的是Kylin。

(5)Kylin简介

Kylin给他家简单介绍一下,Apache Kylin™是一个开源的分布式分析引擎,提供Hadoop之上的SQL查询接口及多维分析(OLAP)能力以支持超大规模数据,最初由eBay Inc.开发并贡献至开源社区。它能在亚秒内查询巨大的Hive表。他的原理比较简单,基于一个并运算模型,我预先知道,我要从那几个维度去查询某个指标。在预定好的指标和维度的情况下去把所有的情况遍历一遍。利用molap把所有的结果都算出来,在存储到HBase中。然后根据sql查询的维度和指标直接到HBase中扫描就行了。为什么能够实现亚秒级的查询,这就依赖于HBase的计算。

(6)kylin架构

和刚才讲的逻辑是一致的,左边是数据仓库。所有的数据都在数据仓库中存储。中间是计算引擎,把每天的调度做好,转化为HBase的KY结构存储在HBase中,对外提供sql接口,提供路由功能,解析sql语句,转化为具体的HBase命令。

Kylin中有一个概念叫Cube和Cubold,其实这个逻辑也非常简单,比如已经知道查询的维度有A,b,c,d四个。那abcd查询的时候,可以取也可以不取。一共有16种组合,整体叫做,cube。其中每一种组合叫做Cuboid。

(7)Kylin如何在Hbase中进行物理存储的

首先定义一张原始表,有两个维度,year和city。在定义一个指标,比如总价。下面是所有的组合,刚才说到Kylin里面有很多cuboid组合,比如说前面三行有一个cuboid:00000011组合,他们在HBase中的RowKey就是cuboid加上各维度的取值。这里面会做一点小技巧,对维度的值做一些编码,如果把程式的原始值放到Rowkey里,会比较长。Rowkey也会存在一个sell里任何一个版本的值都会存在里面。如果rowkey变的非常长,对HBase的压力会非常大,所有通过一个字典编码去减少长度,通过这种方法,就可以把kylin中的计算数据存储到HBase中。

(7)平台架构

这就是我们链家多维分析的平台架构,左边是一套主集群,数据仓库和离线计算在上面,右边是Kylin的系统,分为查询和构件两部分,通过一个调度系统,定时去调度计算任务,把数据导入独立的HBase中,这套独立的HBase是为了保证线上查询不会受影响。最右边,我们做了一套Kylin中间件,因为Kylin本身会有很多问题,比如任务数有限制,任务之间没有优先级,错误重试等问题,通过自研了一套中间件来解决这些问题。最右边是使用这些系统和集群的平台。它底层就依赖于kylin的数据查询服务,同时也会做一些数据缓存。使用系统中的通用模块,比如权限管理,监控管理,元数据管理,调度系统。

(8)Kylin自研中间件

这就是我们自研的Kylin中间件,原生的话,本身有很多问题,我们做的工作支持如下功能:无限容量队列,针对特定cube的优先调度,权限的管理, 重试任务执行,实现任务的并发控制。

这是中间件的另一个功能:cube管理,Kylin原生也有一套cube管理,但是会有很多问题,比如说性能问题,cube会很卡,另外一个是权限问题,我们希望可以对接到链家的权限,优化前端页面,简化配置,提升管理效率。右边是我们自主创建cube的过程。

这里是我们Kylin使用的情况。

这里是我们链家对Kylin的定制改造。

第三部分 HBase的优化经验

(1)HBase部署上的链路

我们优化也是针对这个链路做优化,第一步:Kylin及各类应用,由HBase调用,HBase数据又存储在HDFS上。同时HBase和HDFS又运行在操作系统上。我们会从这几层做优化。

(2)性能优化工具

首先给大家介绍一个性能优化的工具,是Profile利器-火焰图。首先看右边的图,表示的是代码在我们CPU上运行的逻辑。AB就是两个函数,A会调用B,然后时序会从左到右。上面的每个箭头,是每个堆栈的切片,on-CPU表示你的代码正在运行,off-cpu表示你的代码调用其他读写功能,让出线程。把这些切片信息累加起来,就可以得到在右下角的一张图,叫做火焰图。

这是一张火焰图的样例,这张图是对Firefox进行采样的,一般分析火焰图,是从下往上的。我们可以看到非常多的“山峰”。一般我们关注的是,“山峰”从下往上一直没有缩减。

(3)优化实例

这里讲一个优化实例,之前某一个查询服务,当时发现,并发的时候CPU满载,用火焰图做一个采样分析,左上角是一个认证模块加密算法,他消耗百分之七八十的CPU资源,下面的弹窗显示的是具体处理过程,也是占用百分之八九十的CPU资源,他为了实现长加密,使用了一套Base的加密算法,这套算法对CPU的消费非常高,实际上在内网环境上,对安全的要求没有那么高,所以换成了MD5加密算法,大家可以看到,优化之后,处理的时间大大降低了。通过对比,提升了四五倍的qps。右边是火焰图的创造者。

(4)GC算法

HBase是内存消耗的大户,它分为两部分,一个是写入的时候,里那个一个是cache。他们对内存的消耗是非常大,我们这边对Java的堆开到了将近100g左右,如果使用CMS算法的话,对其进行调优。都会出现暂停时间比较长的情况,满足线上的重调业务,后期的话,我们改成了G1,G1就是把内存的堆分成region,然后每一个region有各自的功能,G1有预测算法,比如,我指定一个暂停时间100ms,他能够通过预测算法,能够预算到在这100ms内能够搜集多少垃圾,然后他会收集指定的region,这样就可以做到时间可控。

这是我们线上使用java的执行参数。第一个框我们指定暂停时间100ms,有几个重要的参数,比如有几个并行数,另外alwayspretouch这个参数比较有意思,当我们启动java程序的时候,他向操作系统申请96M的空间,实际上这个空间是虚拟内存,当你有反馈的时候,操作系统才会把虚拟内存映射到物理内存上,这样一个操作叫做区域中断,这一个负载比较高的系统中,会对你的处理有一些延迟,你开启这个参数,就会把所有的清零。因为每次写入新数据,会产生一个回升带,回升带设置比较大的话,造成暂停的时间比较长,所以建议设置一个比较小的数,百分之1就可以。右边是一个优化的图,上边是使用GMS的时间,下面是G1算法的。可以看到得到了很好的缩短。

(5)HBase在IO上优化

我们也了解到HBase在IO上可以做很多优化,但是对IO最好的优化,还是基于硬件。左边的话,最上方主要依赖Hbase对多存储的支持,叫做异构存储,可以标记存储页,同时,HBase上也会有有一些策略,比如ALL_SSD,(F副本全部放在SSD上)ONE_SSD(一个副本放在SSD上)。目前,我们把HBase核心业务和预写日志用ALL_SSD这种策略上,一些重要业务会使用ONE_SSD,普通的话,使用村纯硬盘就可以了。

下面说一下,在ONE_SSD测策略下,会有一些性能会出现问题,比如第一个文件块,会有三个副本,其中某一块SSD,当我发起读取的请求时,根据原生hadoop的策略,会优先去读本地,但实际上,在目前的架构下,远程去读SSD的性能是远超读取本地硬盘的。所以在架构上增加了SSD-FIRST策略,优先读取远程SSD。

另外就是HBase的读写分离,原生HBase可以生成队列树和线程树,想很多读写请求和scan请求都会统一并发,这样的话会出现一个问题,当我有一个scan请求,会扫面很多行,把队列都堵住了,后续的请求可能会无法响应,上边是1.2里的功能,我可以把队列和线程分组,比如80%负责读,15%负责写,5%负责scan。

这是HBase基于wasd的性能测试,左边是测试的设备和配置。可以看硬盘与SSD的IO差别很大,绿色的这条线,代表的是qps这条线。大家可以看到,最开始全部使用硬盘的时候,qps大概在一万左右,当我们使用ONE_SSD的时候,大概能够提升一倍的qps。最后引入SSDFIRST策略优化之后,提升大概一点五倍,开启读写分离以后,性能有提升了大概一点五倍。整体提升了10倍的qps,同时延迟也是大幅度下降。

下面是优化的一些杂项。

下面说下,在操作系统层面上做的优化。下面的图运行之后,可以看到机器内存使用状况。Cached主要做文件系统缓存使用的。下面是解决问题的一个流程。

我们是基于一个systemptap分析的,它是一个内核注入工具。它的原理其实就是操作在内核的函数上,比如说你操作一个函数,在里面可以加入计数的代码等,下面是定义的cache命中率的公式。右边是systemtap在几个函数操作的数据统计。右边减去的部分,是当page发生写入的时候,它也会进入到缓存。剪掉的主要是在写入过程中的造成的page情况。左下角是计算结果。如果硬盘是SSD的话,效果会更好一点。

另外一个就是HBase在多处理器上的问题,这是大多数情况下都会遇到的问题,传统的多处理器引入的架构中,所有内存通过总线连接到CPU上。实际上所有内存对CPU都是对称的,这种模式叫做对称处理器。随着内存的规模越来越大,总线成为了瓶颈。目前大多使用NUMA架构,它的原理是每个CPU对内存访问的数量是有限的。所以会把内存分为多组,每个组附加在一个CPU上,只有当这一块不够的时候,才会通过总线去访问另外一个CPU,这种设计的话,在内存应用的话,比较友好。但是对HBase和redis这种大的架构下,一个内存会远远超过一个cpu所能支持本地内存。默认的话,是亲和模式。不积极的使用远程内存,会出现本地内存满了之后,导致内存分配失败。所以可以设置一个zone_reclaim_mode=0参数。

最后是操作系统的优化杂项。

phoenix 连接超时,服务器操作正常

Phoenixhbase 回复了问题 • 2 人关注 • 1 个回复 • 4616 次浏览 • 2018-08-01 23:28 • 来自相关话题

【招聘】 阿里云-数据库团队-数据产品专家

招聘hbase 发表了文1章 • 1 个评论 • 1121 次浏览 • 2018-08-01 10:20 • 来自相关话题

级别:产品专家
地点:杭州、北京、深圳 均可
 

负责ApsaraDB数据库产品设计,ApsaraDB数据库服务是阿里云核心的数据类的产品,是阿里云最为重要的PASS平台之一。一起打造世界级的数据库产品,一起走102年。
你会接触一流的工程师,你也会接触阿里云服务的大客户,站在世界级的舞台与大家一起探讨未来。

工作内容:
0. 负责ApsaraDB HBase产品建设,包括延伸的多模式数据库solr、Phoenix、opentsdb、geomesa、janusgraph、spark等
1. 完成市场调研与竞争对手分析,准确定义数据库/大数据具有市场竞争力的产品
2. 规划产品的生命周期和交付形态,对项目结果负责
3. 关注用户体验负责,产出安全、稳定、易用的产品
4. 组织研发、测试、运维、运营的高效沟通
5. 支持业务团队完成业务目标
6. 负责多模式数据库构建,spark的分析等
7. 负责具体案例落实,打造物联网、互联网一体化数据库

要求:
1. 计算机科学领域(CS)学士或硕士学位
2. 对大数据架构基本了解,有一定的大数据架构经验
3. 熟悉Hadoop\Spark\HBase\MongoDB\Redis优先
4. 了解数据库基本理论、大数据基本理论、云计算
5. 程序员/DBA出身优先
6. 熟悉大数据方案架构优先
7. 相信自己,愿意挑战难度,勇登高峰

 
联系人:封神
邮件:dragon.caol@alibaba-inc.com
 
欢迎研发转产品 查看全部
级别:产品专家
地点:杭州、北京、深圳 均可
 

负责ApsaraDB数据库产品设计,ApsaraDB数据库服务是阿里云核心的数据类的产品,是阿里云最为重要的PASS平台之一。一起打造世界级的数据库产品,一起走102年。
你会接触一流的工程师,你也会接触阿里云服务的大客户,站在世界级的舞台与大家一起探讨未来。

工作内容:
0. 负责ApsaraDB HBase产品建设,包括延伸的多模式数据库solr、Phoenix、opentsdb、geomesa、janusgraph、spark等
1. 完成市场调研与竞争对手分析,准确定义数据库/大数据具有市场竞争力的产品
2. 规划产品的生命周期和交付形态,对项目结果负责
3. 关注用户体验负责,产出安全、稳定、易用的产品
4. 组织研发、测试、运维、运营的高效沟通
5. 支持业务团队完成业务目标
6. 负责多模式数据库构建,spark的分析等
7. 负责具体案例落实,打造物联网、互联网一体化数据库

要求:
1. 计算机科学领域(CS)学士或硕士学位
2. 对大数据架构基本了解,有一定的大数据架构经验
3. 熟悉Hadoop\Spark\HBase\MongoDB\Redis优先
4. 了解数据库基本理论、大数据基本理论、云计算
5. 程序员/DBA出身优先
6. 熟悉大数据方案架构优先
7. 相信自己,愿意挑战难度,勇登高峰

 
联系人:封神
邮件:dragon.caol@alibaba-inc.com
 
欢迎研发转产品

solr 查询hbase做二级索引除了使用Key-Value Store Indexer之外是否还有其它替代方案?

hbasehbase 回复了问题 • 5 人关注 • 5 个回复 • 4239 次浏览 • 2018-08-01 10:18 • 来自相关话题

HBase最佳实践-读性能优化策略

hbase过往记忆 发表了文1章 • 0 个评论 • 550 次浏览 • 2018-08-01 09:57 • 来自相关话题

任何系统都会有各种各样的问题,有些是系统本身设计问题,有些却是使用姿势问题。HBase也一样,在真实生产线上大家或多或少都会遇到很多问题,有些是HBase还需要完善的,有些是我们确实对它了解太少。总结起来,大家遇到的主要问题无非是Full GC异常导致宕机问题、RIT问题、写吞吐量太低以及读延迟较大。

Full GC问题之前在一些文章里面已经讲过它的来龙去脉,主要的解决方案目前主要有两方面需要注意,一方面需要查看GC日志确认是哪种Full GC,根据Full GC类型对JVM参数进行调优,另一方面需要确认是否开启了BucketCache的offheap模式,建议使用LRUBlockCache的童鞋尽快转移到BucketCache来。当然我们还是很期待官方2.0.0版本发布的更多offheap模块。

RIT问题,我相信更多是因为我们对其不了解,具体原理可以戳这里,解决方案目前有两个,优先是使用官方提供的HBCK进行修复(HBCK本人一直想拿出来分享,但是目前案例还不多,等后面有更多案例的话再拿出来说),使用之后还是解决不了的话就需要手动修复文件或者元数据表。

而对于写吞吐量太低以及读延迟太大的优化问题,笔者也和很多朋友进行过探讨,这篇文章就以读延迟优化为核心内容展开,具体分析HBase进行读延迟优化的那些套路,以及这些套路之后的具体原理。希望大家在看完之后能够结合这些套路剖析自己的系统。

一般情况下,读请求延迟较大通常存在三种场景,分别为:

1. 仅有某业务延迟较大,集群其他业务都正常

2. 整个集群所有业务都反映延迟较大

3. 某个业务起来之后集群其他部分业务延迟较大

这三种场景是表象,通常某业务反应延迟异常,首先需要明确具体是哪种场景,然后针对性解决问题。下图是对读优化思路的一点总结,主要分为四个方面:客户端优化、服务器端优化、列族设计优化以及HDFS相关优化,下面每一个小点都会按照场景分类,文章最后进行归纳总结。下面分别进行详细讲解:




HBase客户端优化


和大多数系统一样,客户端作为业务读写的入口,姿势使用不正确通常会导致本业务读延迟较高实际上存在一些使用姿势的推荐用法,这里一般需要关注四个问题:

1. scan缓存是否设置合理?


优化原理:在解释这个问题之前,首先需要解释什么是scan缓存,通常来讲一次scan会返回大量数据,因此客户端发起一次scan请求,实际并不会一次就将所有数据加载到本地,而是分成多次RPC请求进行加载,这样设计一方面是因为大量数据请求可能会导致网络带宽严重消耗进而影响其他业务,另一方面也有可能因为数据量太大导致本地客户端发生OOM。在这样的设计体系下用户会首先加载一部分数据到本地,然后遍历处理,再加载下一部分数据到本地处理,如此往复,直至所有数据都加载完成。数据加载到本地就存放在scan缓存中,默认100条数据大小。

通常情况下,默认的scan缓存设置就可以正常工作的。但是在一些大scan(一次scan可能需要查询几万甚至几十万行数据)来说,每次请求100条数据意味着一次scan需要几百甚至几千次RPC请求,这种交互的代价无疑是很大的。因此可以考虑将scan缓存设置增大,比如设为500或者1000就可能更加合适。笔者之前做过一次试验,在一次scan扫描10w+条数据量的条件下,将scan缓存从100增加到1000,可以有效降低scan请求的总体延迟,延迟基本降低了25%左右。

优化建议:大scan场景下将scan缓存从100增大到500或者1000,用以减少RPC次数

2. get请求是否可以使用批量请求?


优化原理:HBase分别提供了单条get以及批量get的API接口,使用批量get接口可以减少客户端到RegionServer之间的RPC连接数,提高读取性能。另外需要注意的是,批量get请求要么成功返回所有请求数据,要么抛出异常。

优化建议:使用批量get进行读取请求

3. 请求是否可以显示指定列族或者列?


优化原理:HBase是典型的列族数据库,意味着同一列族的数据存储在一起,不同列族的数据分开存储在不同的目录下。如果一个表有多个列族,只是根据Rowkey而不指定列族进行检索的话不同列族的数据需要独立进行检索,性能必然会比指定列族的查询差很多,很多情况下甚至会有2倍~3倍的性能损失。

优化建议:可以指定列族或者列进行精确查找的尽量指定查找

4. 离线批量读取请求是否设置禁止缓存?


优化原理:通常离线批量读取数据会进行一次性全表扫描,一方面数据量很大,另一方面请求只会执行一次。这种场景下如果使用scan默认设置,就会将数据从HDFS加载出来之后放到缓存。可想而知,大量数据进入缓存必将其他实时业务热点数据挤出,其他业务不得不从HDFS加载,进而会造成明显的读延迟毛刺

优化建议:离线批量读取请求设置禁用缓存,scan.setBlockCache(false)

HBase服务器端优化


一般服务端端问题一旦导致业务读请求延迟较大的话,通常是集群级别的,即整个集群的业务都会反映读延迟较大。可以从4个方面入手:

5. 读请求是否均衡?


优化原理:极端情况下假如所有的读请求都落在一台RegionServer的某几个Region上,这一方面不能发挥整个集群的并发处理能力,另一方面势必造成此台RegionServer资源严重消耗(比如IO耗尽、handler耗尽等),落在该台RegionServer上的其他业务会因此受到很大的波及。可见,读请求不均衡不仅会造成本身业务性能很差,还会严重影响其他业务。当然,写请求不均衡也会造成类似的问题,可见负载不均衡是HBase的大忌。

观察确认:观察所有RegionServer的读请求QPS曲线,确认是否存在读请求不均衡现象

优化建议:RowKey必须进行散列化处理(比如MD5散列),同时建表必须进行预分区处理

6. BlockCache是否设置合理?


优化原理:BlockCache作为读缓存,对于读性能来说至关重要。默认情况下BlockCache和Memstore的配置相对比较均衡(各占40%),可以根据集群业务进行修正,比如读多写少业务可以将BlockCache占比调大。另一方面,BlockCache的策略选择也很重要,不同策略对读性能来说影响并不是很大,但是对GC的影响却相当显著,尤其BucketCache的offheap模式下GC表现很优越。另外,HBase 2.0对offheap的改造(HBASE-11425)将会使HBase的读性能得到2~4倍的提升,同时GC表现会更好!

观察确认:观察所有RegionServer的缓存未命中率、配置文件相关配置项一级GC日志,确认BlockCache是否可以优化

优化建议:JVM内存配置量 < 20G,BlockCache策略选择LRUBlockCache;否则选择BucketCache策略的offheap模式;期待HBase 2.0的到来!

7. HFile文件是否太多?


优化原理:HBase读取数据通常首先会到Memstore和BlockCache中检索(读取最近写入数据&热点数据),如果查找不到就会到文件中检索。HBase的类LSM结构会导致每个store包含多数HFile文件,文件越多,检索所需的IO次数必然越多,读取延迟也就越高。文件数量通常取决于Compaction的执行策略,一般和两个配置参数有关:hbase.hstore.compactionThreshold和hbase.hstore.compaction.max.size,前者表示一个store中的文件数超过多少就应该进行合并,后者表示参数合并的文件大小最大是多少,超过此大小的文件不能参与合并。这两个参数不能设置太’松’(前者不能设置太大,后者不能设置太小),导致Compaction合并文件的实际效果不明显,进而很多文件得不到合并。这样就会导致HFile文件数变多。

观察确认:观察RegionServer级别以及Region级别的storefile数,确认HFile文件是否过多

优化建议:hbase.hstore.compactionThreshold设置不能太大,默认是3个;设置需要根据Region大小确定,通常可以简单的认为hbase.hstore.compaction.max.size = RegionSize / hbase.hstore.compactionThreshold

8. Compaction是否消耗系统资源过多?


优化原理:Compaction是将小文件合并为大文件,提高后续业务随机读性能,但是也会带来IO放大以及带宽消耗问题(数据远程读取以及三副本写入都会消耗系统带宽)。正常配置情况下Minor Compaction并不会带来很大的系统资源消耗,除非因为配置不合理导致Minor Compaction太过频繁,或者Region设置太大情况下发生Major Compaction。

观察确认:观察系统IO资源以及带宽资源使用情况,再观察Compaction队列长度,确认是否由于Compaction导致系统资源消耗过多

优化建议:

(1)Minor Compaction设置:hbase.hstore.compactionThreshold设置不能太小,又不能设置太大,因此建议设置为5~6;hbase.hstore.compaction.max.size = RegionSize / hbase.hstore.compactionThreshold

(2)Major Compaction设置:大Region读延迟敏感业务( 100G以上)通常不建议开启自动Major Compaction,手动低峰期触发。小Region或者延迟不敏感业务可以开启Major Compaction,但建议限制流量;

(3)期待更多的优秀Compaction策略,类似于stripe-compaction尽早提供稳定服务

HBase列族设计优化


HBase列族设计对读性能影响也至关重要,其特点是只影响单个业务,并不会对整个集群产生太大影响。列族设计主要从两个方面检查:

9. Bloomfilter是否设置?是否设置合理?


优化原理:Bloomfilter主要用来过滤不存在待检索RowKey或者Row-Col的HFile文件,避免无用的IO操作。它会告诉你在这个HFile文件中是否可能存在待检索的KV,如果不存在,就可以不用消耗IO打开文件进行seek。很显然,通过设置Bloomfilter可以提升随机读写的性能。

Bloomfilter取值有两个,row以及rowcol,需要根据业务来确定具体使用哪种。如果业务大多数随机查询仅仅使用row作为查询条件,Bloomfilter一定要设置为row,否则如果大多数随机查询使用row+cf作为查询条件,Bloomfilter需要设置为rowcol。如果不确定业务查询类型,设置为row。

优化建议:任何业务都应该设置Bloomfilter,通常设置为row就可以,除非确认业务随机查询类型为row+cf,可以设置为rowcol

HDFS相关优化


HDFS作为HBase最终数据存储系统,通常会使用三副本策略存储HBase数据文件以及日志文件。从HDFS的角度望上层看,HBase即是它的客户端,HBase通过调用它的客户端进行数据读写操作,因此HDFS的相关优化也会影响HBase的读写性能。这里主要关注如下三个方面:

10. Short-Circuit Local Read功能是否开启?


优化原理:当前HDFS读取数据都需要经过DataNode,客户端会向DataNode发送读取数据的请求,DataNode接受到请求之后从硬盘中将文件读出来,再通过TPC发送给客户端。Short Circuit策略允许客户端绕过DataNode直接读取本地数据。(具体原理参考此处)

优化建议:开启Short Circuit Local Read功能,具体配置戳这里

11. Hedged Read功能是否开启?


优化原理:HBase数据在HDFS中一般都会存储三份,而且优先会通过Short-Circuit Local Read功能尝试本地读。但是在某些特殊情况下,有可能会出现因为磁盘问题或者网络问题引起的短时间本地读取失败,为了应对这类问题,社区开发者提出了补偿重试机制 – Hedged Read。该机制基本工作原理为:客户端发起一个本地读,一旦一段时间之后还没有返回,客户端将会向其他DataNode发送相同数据的请求。哪一个请求先返回,另一个就会被丢弃。 

优化建议:开启Hedged Read功能,具体配置参考这里

12. 数据本地率是否太低?


数据本地率:HDFS数据通常存储三份,假如当前RegionA处于Node1上,数据a写入的时候三副本为(Node1,Node2,Node3),数据b写入三副本是(Node1,Node4,Node5),数据c写入三副本(Node1,Node3,Node5),可以看出来所有数据写入本地Node1肯定会写一份,数据都在本地可以读到,因此数据本地率是100%。现在假设RegionA被迁移到了Node2上,只有数据a在该节点上,其他数据(b和c)读取只能远程跨节点读,本地率就为33%(假设a,b和c的数据大小相同)。

优化原理:数据本地率太低很显然会产生大量的跨网络IO请求,必然会导致读请求延迟较高,因此提高数据本地率可以有效优化随机读性能。数据本地率低的原因一般是因为Region迁移(自动balance开启、RegionServer宕机迁移、手动迁移等),因此一方面可以通过避免Region无故迁移来保持数据本地率,另一方面如果数据本地率很低,也可以通过执行major_compact提升数据本地率到100%。

优化建议:避免Region无故迁移,比如关闭自动balance、RS宕机及时拉起并迁回飘走的Region等;在业务低峰期执行major_compact提升数据本地率

HBase读性能优化归纳


在本文开始的时候提到读延迟较大无非三种常见的表象,单个业务慢、集群随机读慢以及某个业务随机读之后其他业务受到影响导致随机读延迟很大。了解完常见的可能导致读延迟较大的一些问题之后,我们将这些问题进行如下归类,读者可以在看到现象之后在对应的问题列表中进行具体定位:













HBase读性能优化总结

性能优化是任何一个系统都会遇到的话题,每个系统也都有自己的优化方式。 HBase作为分布式KV数据库,优化点又格外不同,更多得融入了分布式特性以及存储系统优化特性。文中总结了读优化的基本突破点,有什么不对的地方还望指正,有补充的也可以一起探讨交流! 查看全部
任何系统都会有各种各样的问题,有些是系统本身设计问题,有些却是使用姿势问题。HBase也一样,在真实生产线上大家或多或少都会遇到很多问题,有些是HBase还需要完善的,有些是我们确实对它了解太少。总结起来,大家遇到的主要问题无非是Full GC异常导致宕机问题、RIT问题、写吞吐量太低以及读延迟较大。

Full GC问题之前在一些文章里面已经讲过它的来龙去脉,主要的解决方案目前主要有两方面需要注意,一方面需要查看GC日志确认是哪种Full GC,根据Full GC类型对JVM参数进行调优,另一方面需要确认是否开启了BucketCache的offheap模式,建议使用LRUBlockCache的童鞋尽快转移到BucketCache来。当然我们还是很期待官方2.0.0版本发布的更多offheap模块。

RIT问题,我相信更多是因为我们对其不了解,具体原理可以戳这里,解决方案目前有两个,优先是使用官方提供的HBCK进行修复(HBCK本人一直想拿出来分享,但是目前案例还不多,等后面有更多案例的话再拿出来说),使用之后还是解决不了的话就需要手动修复文件或者元数据表。

而对于写吞吐量太低以及读延迟太大的优化问题,笔者也和很多朋友进行过探讨,这篇文章就以读延迟优化为核心内容展开,具体分析HBase进行读延迟优化的那些套路,以及这些套路之后的具体原理。希望大家在看完之后能够结合这些套路剖析自己的系统。

一般情况下,读请求延迟较大通常存在三种场景,分别为:

1. 仅有某业务延迟较大,集群其他业务都正常

2. 整个集群所有业务都反映延迟较大

3. 某个业务起来之后集群其他部分业务延迟较大

这三种场景是表象,通常某业务反应延迟异常,首先需要明确具体是哪种场景,然后针对性解决问题。下图是对读优化思路的一点总结,主要分为四个方面:客户端优化、服务器端优化、列族设计优化以及HDFS相关优化,下面每一个小点都会按照场景分类,文章最后进行归纳总结。下面分别进行详细讲解:
hbase1.png

HBase客户端优化


和大多数系统一样,客户端作为业务读写的入口,姿势使用不正确通常会导致本业务读延迟较高实际上存在一些使用姿势的推荐用法,这里一般需要关注四个问题:

1. scan缓存是否设置合理?


优化原理:在解释这个问题之前,首先需要解释什么是scan缓存,通常来讲一次scan会返回大量数据,因此客户端发起一次scan请求,实际并不会一次就将所有数据加载到本地,而是分成多次RPC请求进行加载,这样设计一方面是因为大量数据请求可能会导致网络带宽严重消耗进而影响其他业务,另一方面也有可能因为数据量太大导致本地客户端发生OOM。在这样的设计体系下用户会首先加载一部分数据到本地,然后遍历处理,再加载下一部分数据到本地处理,如此往复,直至所有数据都加载完成。数据加载到本地就存放在scan缓存中,默认100条数据大小。

通常情况下,默认的scan缓存设置就可以正常工作的。但是在一些大scan(一次scan可能需要查询几万甚至几十万行数据)来说,每次请求100条数据意味着一次scan需要几百甚至几千次RPC请求,这种交互的代价无疑是很大的。因此可以考虑将scan缓存设置增大,比如设为500或者1000就可能更加合适。笔者之前做过一次试验,在一次scan扫描10w+条数据量的条件下,将scan缓存从100增加到1000,可以有效降低scan请求的总体延迟,延迟基本降低了25%左右。

优化建议:大scan场景下将scan缓存从100增大到500或者1000,用以减少RPC次数

2. get请求是否可以使用批量请求?


优化原理:HBase分别提供了单条get以及批量get的API接口,使用批量get接口可以减少客户端到RegionServer之间的RPC连接数,提高读取性能。另外需要注意的是,批量get请求要么成功返回所有请求数据,要么抛出异常。

优化建议:使用批量get进行读取请求

3. 请求是否可以显示指定列族或者列?


优化原理:HBase是典型的列族数据库,意味着同一列族的数据存储在一起,不同列族的数据分开存储在不同的目录下。如果一个表有多个列族,只是根据Rowkey而不指定列族进行检索的话不同列族的数据需要独立进行检索,性能必然会比指定列族的查询差很多,很多情况下甚至会有2倍~3倍的性能损失。

优化建议:可以指定列族或者列进行精确查找的尽量指定查找

4. 离线批量读取请求是否设置禁止缓存?


优化原理:通常离线批量读取数据会进行一次性全表扫描,一方面数据量很大,另一方面请求只会执行一次。这种场景下如果使用scan默认设置,就会将数据从HDFS加载出来之后放到缓存。可想而知,大量数据进入缓存必将其他实时业务热点数据挤出,其他业务不得不从HDFS加载,进而会造成明显的读延迟毛刺

优化建议:离线批量读取请求设置禁用缓存,scan.setBlockCache(false)

HBase服务器端优化


一般服务端端问题一旦导致业务读请求延迟较大的话,通常是集群级别的,即整个集群的业务都会反映读延迟较大。可以从4个方面入手:

5. 读请求是否均衡?


优化原理:极端情况下假如所有的读请求都落在一台RegionServer的某几个Region上,这一方面不能发挥整个集群的并发处理能力,另一方面势必造成此台RegionServer资源严重消耗(比如IO耗尽、handler耗尽等),落在该台RegionServer上的其他业务会因此受到很大的波及。可见,读请求不均衡不仅会造成本身业务性能很差,还会严重影响其他业务。当然,写请求不均衡也会造成类似的问题,可见负载不均衡是HBase的大忌。

观察确认:观察所有RegionServer的读请求QPS曲线,确认是否存在读请求不均衡现象

优化建议:RowKey必须进行散列化处理(比如MD5散列),同时建表必须进行预分区处理

6. BlockCache是否设置合理?


优化原理:BlockCache作为读缓存,对于读性能来说至关重要。默认情况下BlockCache和Memstore的配置相对比较均衡(各占40%),可以根据集群业务进行修正,比如读多写少业务可以将BlockCache占比调大。另一方面,BlockCache的策略选择也很重要,不同策略对读性能来说影响并不是很大,但是对GC的影响却相当显著,尤其BucketCache的offheap模式下GC表现很优越。另外,HBase 2.0对offheap的改造(HBASE-11425)将会使HBase的读性能得到2~4倍的提升,同时GC表现会更好!

观察确认:观察所有RegionServer的缓存未命中率、配置文件相关配置项一级GC日志,确认BlockCache是否可以优化

优化建议:JVM内存配置量 < 20G,BlockCache策略选择LRUBlockCache;否则选择BucketCache策略的offheap模式;期待HBase 2.0的到来!

7. HFile文件是否太多?


优化原理:HBase读取数据通常首先会到Memstore和BlockCache中检索(读取最近写入数据&热点数据),如果查找不到就会到文件中检索。HBase的类LSM结构会导致每个store包含多数HFile文件,文件越多,检索所需的IO次数必然越多,读取延迟也就越高。文件数量通常取决于Compaction的执行策略,一般和两个配置参数有关:hbase.hstore.compactionThreshold和hbase.hstore.compaction.max.size,前者表示一个store中的文件数超过多少就应该进行合并,后者表示参数合并的文件大小最大是多少,超过此大小的文件不能参与合并。这两个参数不能设置太’松’(前者不能设置太大,后者不能设置太小),导致Compaction合并文件的实际效果不明显,进而很多文件得不到合并。这样就会导致HFile文件数变多。

观察确认:观察RegionServer级别以及Region级别的storefile数,确认HFile文件是否过多

优化建议:hbase.hstore.compactionThreshold设置不能太大,默认是3个;设置需要根据Region大小确定,通常可以简单的认为hbase.hstore.compaction.max.size = RegionSize / hbase.hstore.compactionThreshold

8. Compaction是否消耗系统资源过多?


优化原理:Compaction是将小文件合并为大文件,提高后续业务随机读性能,但是也会带来IO放大以及带宽消耗问题(数据远程读取以及三副本写入都会消耗系统带宽)。正常配置情况下Minor Compaction并不会带来很大的系统资源消耗,除非因为配置不合理导致Minor Compaction太过频繁,或者Region设置太大情况下发生Major Compaction。

观察确认:观察系统IO资源以及带宽资源使用情况,再观察Compaction队列长度,确认是否由于Compaction导致系统资源消耗过多

优化建议:

(1)Minor Compaction设置:hbase.hstore.compactionThreshold设置不能太小,又不能设置太大,因此建议设置为5~6;hbase.hstore.compaction.max.size = RegionSize / hbase.hstore.compactionThreshold

(2)Major Compaction设置:大Region读延迟敏感业务( 100G以上)通常不建议开启自动Major Compaction,手动低峰期触发。小Region或者延迟不敏感业务可以开启Major Compaction,但建议限制流量;

(3)期待更多的优秀Compaction策略,类似于stripe-compaction尽早提供稳定服务

HBase列族设计优化


HBase列族设计对读性能影响也至关重要,其特点是只影响单个业务,并不会对整个集群产生太大影响。列族设计主要从两个方面检查:

9. Bloomfilter是否设置?是否设置合理?


优化原理:Bloomfilter主要用来过滤不存在待检索RowKey或者Row-Col的HFile文件,避免无用的IO操作。它会告诉你在这个HFile文件中是否可能存在待检索的KV,如果不存在,就可以不用消耗IO打开文件进行seek。很显然,通过设置Bloomfilter可以提升随机读写的性能。

Bloomfilter取值有两个,row以及rowcol,需要根据业务来确定具体使用哪种。如果业务大多数随机查询仅仅使用row作为查询条件,Bloomfilter一定要设置为row,否则如果大多数随机查询使用row+cf作为查询条件,Bloomfilter需要设置为rowcol。如果不确定业务查询类型,设置为row。

优化建议:任何业务都应该设置Bloomfilter,通常设置为row就可以,除非确认业务随机查询类型为row+cf,可以设置为rowcol

HDFS相关优化


HDFS作为HBase最终数据存储系统,通常会使用三副本策略存储HBase数据文件以及日志文件。从HDFS的角度望上层看,HBase即是它的客户端,HBase通过调用它的客户端进行数据读写操作,因此HDFS的相关优化也会影响HBase的读写性能。这里主要关注如下三个方面:

10. Short-Circuit Local Read功能是否开启?


优化原理:当前HDFS读取数据都需要经过DataNode,客户端会向DataNode发送读取数据的请求,DataNode接受到请求之后从硬盘中将文件读出来,再通过TPC发送给客户端。Short Circuit策略允许客户端绕过DataNode直接读取本地数据。(具体原理参考此处)

优化建议:开启Short Circuit Local Read功能,具体配置戳这里

11. Hedged Read功能是否开启?


优化原理:HBase数据在HDFS中一般都会存储三份,而且优先会通过Short-Circuit Local Read功能尝试本地读。但是在某些特殊情况下,有可能会出现因为磁盘问题或者网络问题引起的短时间本地读取失败,为了应对这类问题,社区开发者提出了补偿重试机制 – Hedged Read。该机制基本工作原理为:客户端发起一个本地读,一旦一段时间之后还没有返回,客户端将会向其他DataNode发送相同数据的请求。哪一个请求先返回,另一个就会被丢弃。 

优化建议:开启Hedged Read功能,具体配置参考这里

12. 数据本地率是否太低?


数据本地率:HDFS数据通常存储三份,假如当前RegionA处于Node1上,数据a写入的时候三副本为(Node1,Node2,Node3),数据b写入三副本是(Node1,Node4,Node5),数据c写入三副本(Node1,Node3,Node5),可以看出来所有数据写入本地Node1肯定会写一份,数据都在本地可以读到,因此数据本地率是100%。现在假设RegionA被迁移到了Node2上,只有数据a在该节点上,其他数据(b和c)读取只能远程跨节点读,本地率就为33%(假设a,b和c的数据大小相同)。

优化原理:数据本地率太低很显然会产生大量的跨网络IO请求,必然会导致读请求延迟较高,因此提高数据本地率可以有效优化随机读性能。数据本地率低的原因一般是因为Region迁移(自动balance开启、RegionServer宕机迁移、手动迁移等),因此一方面可以通过避免Region无故迁移来保持数据本地率,另一方面如果数据本地率很低,也可以通过执行major_compact提升数据本地率到100%。

优化建议:避免Region无故迁移,比如关闭自动balance、RS宕机及时拉起并迁回飘走的Region等;在业务低峰期执行major_compact提升数据本地率

HBase读性能优化归纳


在本文开始的时候提到读延迟较大无非三种常见的表象,单个业务慢、集群随机读慢以及某个业务随机读之后其他业务受到影响导致随机读延迟很大。了解完常见的可能导致读延迟较大的一些问题之后,我们将这些问题进行如下归类,读者可以在看到现象之后在对应的问题列表中进行具体定位:
hbase2.png

hbase3.png

hbase4.png


HBase读性能优化总结

性能优化是任何一个系统都会遇到的话题,每个系统也都有自己的优化方式。 HBase作为分布式KV数据库,优化点又格外不同,更多得融入了分布式特性以及存储系统优化特性。文中总结了读优化的基本突破点,有什么不对的地方还望指正,有补充的也可以一起探讨交流!

HBase in Practice-性能、监控及问题解决

hbase过往记忆 发表了文1章 • 0 个评论 • 3123 次浏览 • 2018-08-01 09:38 • 来自相关话题

本文根据阿里李钰老师在中国HBase技术社区第二届MeetUp:“HBase技术解析及应用实践”中分享的《HBase in Practice-性能、监控及问题解决》编辑整理而成,在未改变原意的基础上稍做整理。




李钰老师



今天分享主要从两个大的部分介绍,第一部分会讲一些性能优化的知识,针对IO的性能优化,不同版本值得注意的性能问题/优化;第二部分将监控应该关注哪些指标以及在日志里面如何做问题排查。




首先讲一下针对IO性能优化,每个厂商IO硬件性能都不一样,针对不同的硬件,需要利用的功能和注意的问题也不一样。如HDD,它的IO能力比较弱,很容易打爆,如果在云上应用HBase,有很多不同的硬盘可以使用。在HDD架设HBase要避免磁盘被打爆,HBase提供了很多方法,第一个就是Compaction限流,基本思想就是限制它每秒能写出的数据量,在1.1.0版本以上才能使用,对于1.3.0版本分界线以上以下配置不同,具体配置如上图所示。你可以设置其吞吐 的上限和下限,也可以设置平峰期的限制。我们进行限流肯定一般是在高峰期,在平峰期没有必要,也或者平峰期你有没有其他的应用,有时也会跑一些其他的应用,如spark等。Flush限流是在1.3.0版本以上支持的,其实主要的IO来源就是Compaction和Flush,配置与Compaction比较像。值得注意的是限流不能过低,如果过低Compaction的Hfile数就降不下来,block stoken file默认值是20,如果超过20,flush就会delay,内存会膨胀,如果膨胀超过一定区域就会block update,会出现写入延迟阻塞。因此两者限流需要依据实际限流,不能限流过低。




另外一个在HDD上比较适合是Per-CF Flush,在1.1.0版本以上就支持,默认配置是flush all stores,当main Store的大小达到设定阀值,就会将所有的CF都flush出来。但是有些CF文件比较小,会出现浪费io,另外刷出很多小文件需要compaction,对磁盘也会有影响。因此出现了HBase available,在1.1.0版本以上可以用,从1.1.0-2.0实质是设置了一个下限,如果CF文件在main Store时超过16M就将其flush,没有超过就不flush。后续在社区基础上做了改变,将默认值改为flush大小除以CF的个数,这样的问题有可能出现CF过多,因此也会有下限值控制,也是16M。使用这个功能也需要注意,开启这个功能有很多数据是不flush,但是如果出现故障,replay的数据会变多,在HBase中有个参数optional flush internal,可以设置过多长时间强制flush一次,还有一个flush prechanges,就是有多少change就flush一次;一个默认值是1小时,后面是3千万。




第三个方案是多盘,多盘在社区1.0版本就有多log的功能。如果在自己的机器上,服务器都是12块硬盘,如果用一个write tacklog,HDFS是三个副本,虽然能将吞吐打满,三个副本需要三个盘,无法使用完,IO能力没充分利用。解决方式就是用多个write tacklog,一个region server配置4个hlog,测试性能会提升20%。版本低于1.2.0:replication存在问题, hbase.wal.provider ->multiwall,hbase.wal.regiongrouping.strategy -> bounded ,根据盘数设置hbase.wal.regiongrouping.numgroups。需要注意写多少Hlog是依据你的盘确定,IO能力是否充足。




SSD在HBase里面也有很好的支持,SSD对性能的优化分为两个部分,一方面是读,另一方面是写。影响写的性能就是write height log的写,SSD能很大的降低其响应时间,在用SSD时也可以用Multi WAL,其写入性能比单WAL提升40%以上。从读方面来说,在CF可以设置Storage Policy,但该功能在2.0版本上才有。对不同的CF设置不同的Storage Policy(wan SSD,all SSD),设置多CF的原因是数据的冷热程度是不一样的。Bulkload也需要支持Storage Policy配置,如果生成的文件都是HDD,会影响读取的性能。




SSD需要注意的是如果是Wan SSD需要允许HDFS client优先读远程SSD上的副本,但是未合入社区版本,需要手动backport。对于Hybrid,WAL策略可以是WAL SSD,对CF级别也可以是WAL SSD。值得注意的是SSD也需要开启限流。Merge MVCCand SequenceId引发的性能问题:branch-1.0不要使用1.0.3以下版本,branch-1.1不要使用1.1.3以下版本。高负载下写入性能瓶颈: 如果线上发现wait在WALKey#get Write Entry,建议升级到1.4.0以上版本,对ASYNC_WAL写入性能提升尤其明显 。在读的性能,如果用的是Bucket Cache,在高并发读取单key的性能问题,在1.2.0以上版本可以解决。如果远程读SSD,需要考虑网络开销,Hybrid开销尤其大。








接下来讲一下问题排查,首先是RPC相关监控:Server响应时间,Server处理时间,请求排队时间。Total Call Time记录的是请求到达你的regionServer开始到server请求完结束,不包含发送结果的时间。业务在请求,但是server处理也好,这种情况需要去业务debug客户端网络是不是拥塞。Total Call Time肯定是等于ProcessCall Time+QueueCall Time,请求到达server是先进入一个队列,如果heightlog不繁忙,QueueCall Time会很短,但是如果server很繁忙active handler数很满,calltime会很长,请求是从队列出来后处理。Active Handler 在1.4.0版本以前是没有读写分离监控的。读写分离的好处就是Handler打满到底是读出问题还是写出问题就可以很容易监控。RPC队列长度也可以判断机器是否出问题了,RPC连接数很高也是消耗系统资源。上图是我们监控指标图,如图一峰值就可以推测这台机器可能有点问题。

请求相关指标对debug很重要,Put请求latency,Get请求latency,Scan请求latency等这些都会监控。我想说明就是对latency监控,Hbase出问题到底是文件系统出问题了还是regionServer出问题了,这个困扰了我们很久,因为底层会有一个文件系统,文件系统过肯定会受影响,因次对于put来说你要监控WAL sync latency,对于get要监控HDFS pread latency,Scan请求latency。对于HDFS pread latency需要1.4.0版本以上。如果发现Get请求latency很高,HDFS pread latency也很高,那么基本确定HDFS陡了,至少可以定位问题。否则就必须定位999的regionserver一一排查。




第三个就是内存相关的指标,GC对于排查问题作用未必很大,但是可以监控整体情况。对于GC更多的是查GC日志更有效果,pause Time Without GC不是进程GC导致的问题时,在1.4.0版本以上会发现一些日志的,这个时候需要关注一些系统的指标,如内存整理,那么你整个系统会停止,或者资源瓶颈或者CPU满了都会导致进程堵塞。再一个就是对Block Cache/MemStore 的监控,如何监控Hfile数过多,一方面可以监控blockupdate的字符和频率,另一方面是看MemStore数是否变大了。Block Cache在1.3.0版本以上可以区分data和meta命中率,meta block命中率一般都很高,访问频率也很高,如果不区分这个命中率有可能是假的。真实的data block命中率在65%,但是meta命中率基本是100%。




RegionServer单机指标,如果看到一个regionserver打满了,需要看regionServer单机指标。如region大小,这个指标引起cache的命中率,get和写操作数,看topN那些请求非常高,handler打满,get时间非常高,可以查询那个region访问过多,有些情况是访问值超过正常值导致的,因此可以通过表找到问题去解决。再一个就是compaction队列长度和flush队列长度。

如何发现stale的Region Server,如果出现坏盘,请求卡在IO上一直无法返回,响应时间相关指标无法捕捉,在total call time中无法体现。还有就是你的数值很好但是机器已经出问题,因为出问题的请求没有汇报给server,另外如果机器资源耗尽,新的请求无法连接server,从响应时间等server metrics上也体现不出来。因此做了一个增强health check,定期向自己发请求并设置超时时间,失败超过一定概率报警,有一个问题就是还没有Upstreaming in progress,但后续会完成。其实会出现一个情况就是系统资源耗尽,但是已经启动的线程可以运行,但是无法启动新的线程,就是一台机器已经不能服务,但是master还是可以服务。




接下来讲一下日志的排查,首先关于慢请求。如发现一个server的999时间很长,第一反应是登陆该台regionServer查看日志,尤其是responsetooslow日志,但是老版本是不会打印任何有关processingtime 、roll具体信息的,因此会关注这两个HBASE-16033/HBASE-16972 response Too Slow,会打印详细信息,前面一个jQuery是对普通请求,后面一个jQuery是对scan。经常会看到scan超时等这些信息都是很重要的。branch-1.1要求1.1.8以上,branch-1.2要求1.2.5以上,或1.3.0以上版本。在自己的版本还做了一些事情,但是还未放到社区,会区分如果是慢请求,到底long process time还是long call Time,因为一个long process time会导致一系列的long call Time。如果不区分会看到很多response TooSlow,但是你并不知道出现的问题是什么。当然还需要设置阈值,超过多长时间才算慢请求,我们是超过10秒才算慢请求,这个可以自己配置。如设置10秒其实8秒也不短,这时需要去region Server上解scandetail,去查看handler线程wait在什么地方,wait的地方出现了多少。




在Client端也有些日志也是非常重要的,对于single请求打印很全,但是要注意batch,branch-1.1要求1.1.6以上,branch-1.2要求1.2.3以上,或1.3.0以上版本。在有慢请求时,去打印具体是哪个region,还有目前运行在哪个server上,或者在batch请求异常时打印异常的batch栈。客户端还有其他好的机制,HBase客户端是由backoff policy,就是通过regionServer的region load判断是否需要sleep一段时间。后续有机会可以讨论如何排查GC、内存泄漏等复杂问题,如何定位疯狂访问RS的问题客户端应用,如何规避HDFS故障对HBase的影响,2.0黑科技在线上应用可能踩的坑,升级HBase版本有哪些必须的准备以及线上升级操作有哪些注意事项。




  查看全部
1.jpg
本文根据阿里李钰老师在中国HBase技术社区第二届MeetUp:“HBase技术解析及应用实践”中分享的《HBase in Practice-性能、监控及问题解决》编辑整理而成,在未改变原意的基础上稍做整理。
2.jpg

李钰老师
3.png
今天分享主要从两个大的部分介绍,第一部分会讲一些性能优化的知识,针对IO的性能优化,不同版本值得注意的性能问题/优化;第二部分将监控应该关注哪些指标以及在日志里面如何做问题排查。
4.png

首先讲一下针对IO性能优化,每个厂商IO硬件性能都不一样,针对不同的硬件,需要利用的功能和注意的问题也不一样。如HDD,它的IO能力比较弱,很容易打爆,如果在云上应用HBase,有很多不同的硬盘可以使用。在HDD架设HBase要避免磁盘被打爆,HBase提供了很多方法,第一个就是Compaction限流,基本思想就是限制它每秒能写出的数据量,在1.1.0版本以上才能使用,对于1.3.0版本分界线以上以下配置不同,具体配置如上图所示。你可以设置其吞吐 的上限和下限,也可以设置平峰期的限制。我们进行限流肯定一般是在高峰期,在平峰期没有必要,也或者平峰期你有没有其他的应用,有时也会跑一些其他的应用,如spark等。Flush限流是在1.3.0版本以上支持的,其实主要的IO来源就是Compaction和Flush,配置与Compaction比较像。值得注意的是限流不能过低,如果过低Compaction的Hfile数就降不下来,block stoken file默认值是20,如果超过20,flush就会delay,内存会膨胀,如果膨胀超过一定区域就会block update,会出现写入延迟阻塞。因此两者限流需要依据实际限流,不能限流过低。
5.png

另外一个在HDD上比较适合是Per-CF Flush,在1.1.0版本以上就支持,默认配置是flush all stores,当main Store的大小达到设定阀值,就会将所有的CF都flush出来。但是有些CF文件比较小,会出现浪费io,另外刷出很多小文件需要compaction,对磁盘也会有影响。因此出现了HBase available,在1.1.0版本以上可以用,从1.1.0-2.0实质是设置了一个下限,如果CF文件在main Store时超过16M就将其flush,没有超过就不flush。后续在社区基础上做了改变,将默认值改为flush大小除以CF的个数,这样的问题有可能出现CF过多,因此也会有下限值控制,也是16M。使用这个功能也需要注意,开启这个功能有很多数据是不flush,但是如果出现故障,replay的数据会变多,在HBase中有个参数optional flush internal,可以设置过多长时间强制flush一次,还有一个flush prechanges,就是有多少change就flush一次;一个默认值是1小时,后面是3千万。
6.png

第三个方案是多盘,多盘在社区1.0版本就有多log的功能。如果在自己的机器上,服务器都是12块硬盘,如果用一个write tacklog,HDFS是三个副本,虽然能将吞吐打满,三个副本需要三个盘,无法使用完,IO能力没充分利用。解决方式就是用多个write tacklog,一个region server配置4个hlog,测试性能会提升20%。版本低于1.2.0:replication存在问题, hbase.wal.provider ->multiwall,hbase.wal.regiongrouping.strategy -> bounded ,根据盘数设置hbase.wal.regiongrouping.numgroups。需要注意写多少Hlog是依据你的盘确定,IO能力是否充足。
7.jpg

SSD在HBase里面也有很好的支持,SSD对性能的优化分为两个部分,一方面是读,另一方面是写。影响写的性能就是write height log的写,SSD能很大的降低其响应时间,在用SSD时也可以用Multi WAL,其写入性能比单WAL提升40%以上。从读方面来说,在CF可以设置Storage Policy,但该功能在2.0版本上才有。对不同的CF设置不同的Storage Policy(wan SSD,all SSD),设置多CF的原因是数据的冷热程度是不一样的。Bulkload也需要支持Storage Policy配置,如果生成的文件都是HDD,会影响读取的性能。
8.png

SSD需要注意的是如果是Wan SSD需要允许HDFS client优先读远程SSD上的副本,但是未合入社区版本,需要手动backport。对于Hybrid,WAL策略可以是WAL SSD,对CF级别也可以是WAL SSD。值得注意的是SSD也需要开启限流。Merge MVCCand SequenceId引发的性能问题:branch-1.0不要使用1.0.3以下版本,branch-1.1不要使用1.1.3以下版本。高负载下写入性能瓶颈: 如果线上发现wait在WALKey#get Write Entry,建议升级到1.4.0以上版本,对ASYNC_WAL写入性能提升尤其明显 。在读的性能,如果用的是Bucket Cache,在高并发读取单key的性能问题,在1.2.0以上版本可以解决。如果远程读SSD,需要考虑网络开销,Hybrid开销尤其大。
9.jpg

10.jpg

接下来讲一下问题排查,首先是RPC相关监控:Server响应时间,Server处理时间,请求排队时间。Total Call Time记录的是请求到达你的regionServer开始到server请求完结束,不包含发送结果的时间。业务在请求,但是server处理也好,这种情况需要去业务debug客户端网络是不是拥塞。Total Call Time肯定是等于ProcessCall Time+QueueCall Time,请求到达server是先进入一个队列,如果heightlog不繁忙,QueueCall Time会很短,但是如果server很繁忙active handler数很满,calltime会很长,请求是从队列出来后处理。Active Handler 在1.4.0版本以前是没有读写分离监控的。读写分离的好处就是Handler打满到底是读出问题还是写出问题就可以很容易监控。RPC队列长度也可以判断机器是否出问题了,RPC连接数很高也是消耗系统资源。上图是我们监控指标图,如图一峰值就可以推测这台机器可能有点问题。

请求相关指标对debug很重要,Put请求latency,Get请求latency,Scan请求latency等这些都会监控。我想说明就是对latency监控,Hbase出问题到底是文件系统出问题了还是regionServer出问题了,这个困扰了我们很久,因为底层会有一个文件系统,文件系统过肯定会受影响,因次对于put来说你要监控WAL sync latency,对于get要监控HDFS pread latency,Scan请求latency。对于HDFS pread latency需要1.4.0版本以上。如果发现Get请求latency很高,HDFS pread latency也很高,那么基本确定HDFS陡了,至少可以定位问题。否则就必须定位999的regionserver一一排查。
11.jpg

第三个就是内存相关的指标,GC对于排查问题作用未必很大,但是可以监控整体情况。对于GC更多的是查GC日志更有效果,pause Time Without GC不是进程GC导致的问题时,在1.4.0版本以上会发现一些日志的,这个时候需要关注一些系统的指标,如内存整理,那么你整个系统会停止,或者资源瓶颈或者CPU满了都会导致进程堵塞。再一个就是对Block Cache/MemStore 的监控,如何监控Hfile数过多,一方面可以监控blockupdate的字符和频率,另一方面是看MemStore数是否变大了。Block Cache在1.3.0版本以上可以区分data和meta命中率,meta block命中率一般都很高,访问频率也很高,如果不区分这个命中率有可能是假的。真实的data block命中率在65%,但是meta命中率基本是100%。
12.png

RegionServer单机指标,如果看到一个regionserver打满了,需要看regionServer单机指标。如region大小,这个指标引起cache的命中率,get和写操作数,看topN那些请求非常高,handler打满,get时间非常高,可以查询那个region访问过多,有些情况是访问值超过正常值导致的,因此可以通过表找到问题去解决。再一个就是compaction队列长度和flush队列长度。

如何发现stale的Region Server,如果出现坏盘,请求卡在IO上一直无法返回,响应时间相关指标无法捕捉,在total call time中无法体现。还有就是你的数值很好但是机器已经出问题,因为出问题的请求没有汇报给server,另外如果机器资源耗尽,新的请求无法连接server,从响应时间等server metrics上也体现不出来。因此做了一个增强health check,定期向自己发请求并设置超时时间,失败超过一定概率报警,有一个问题就是还没有Upstreaming in progress,但后续会完成。其实会出现一个情况就是系统资源耗尽,但是已经启动的线程可以运行,但是无法启动新的线程,就是一台机器已经不能服务,但是master还是可以服务。
13.jpg

接下来讲一下日志的排查,首先关于慢请求。如发现一个server的999时间很长,第一反应是登陆该台regionServer查看日志,尤其是responsetooslow日志,但是老版本是不会打印任何有关processingtime 、roll具体信息的,因此会关注这两个HBASE-16033/HBASE-16972 response Too Slow,会打印详细信息,前面一个jQuery是对普通请求,后面一个jQuery是对scan。经常会看到scan超时等这些信息都是很重要的。branch-1.1要求1.1.8以上,branch-1.2要求1.2.5以上,或1.3.0以上版本。在自己的版本还做了一些事情,但是还未放到社区,会区分如果是慢请求,到底long process time还是long call Time,因为一个long process time会导致一系列的long call Time。如果不区分会看到很多response TooSlow,但是你并不知道出现的问题是什么。当然还需要设置阈值,超过多长时间才算慢请求,我们是超过10秒才算慢请求,这个可以自己配置。如设置10秒其实8秒也不短,这时需要去region Server上解scandetail,去查看handler线程wait在什么地方,wait的地方出现了多少。
14.jpg

在Client端也有些日志也是非常重要的,对于single请求打印很全,但是要注意batch,branch-1.1要求1.1.6以上,branch-1.2要求1.2.3以上,或1.3.0以上版本。在有慢请求时,去打印具体是哪个region,还有目前运行在哪个server上,或者在batch请求异常时打印异常的batch栈。客户端还有其他好的机制,HBase客户端是由backoff policy,就是通过regionServer的region load判断是否需要sleep一段时间。后续有机会可以讨论如何排查GC、内存泄漏等复杂问题,如何定位疯狂访问RS的问题客户端应用,如何规避HDFS故障对HBase的影响,2.0黑科技在线上应用可能踩的坑,升级HBase版本有哪些必须的准备以及线上升级操作有哪些注意事项。
15.jpg

 

中国HBase技术社区微信公众号:
hbasegroup

欢迎加入HBase生态+Spark社区钉钉大群