1.HFile详解
HFile文件分为以下六大部分
序号 |
名称 |
描述 |
1 |
数据块 |
由多个block(块)组成,每个块的格式为:
[块头] + [key长] + [value长] + [key] + [value]。 |
2 |
元数据块 |
元数据是key-value类型的值,但元数据快只保存元数据的value值,元数据的key值保存在第五项(元数据索引块)中。
该块由多个元数据值组成。
|
3 |
fileInfo块 |
该块保存与HFile相关的一些信息。
fileInfo是以key值排序key-value类型的值。基本格式为:
keyValue元素的个数 +
(key + value类型id + value) +
(key + value类型id + value) +
……
|
4 |
数据索引块 |
该块的组成为:
索引块头 +
(数据块在文件中的偏移 + 数据块长 + 数据块的第一个key) +
(数据块在文件中的偏移 + 数据块长 + 数据块的第一个key) +
……
|
5 |
元数据索引块 |
该块组成格式同数据块索引,只是某部分的意义不一样,组成格式:
索引块头 +
(元数据在文件中的偏移 + 元数据value长 + 元数据key) +
(元数据在文件中的偏移 + 元数据value长 + 元数据key) +
……
|
6 |
HFile文件尾 |
该块记录了其他各块在hfile文件中的偏移信息和其他一些元信息。组成格式如下:
文件尾 +
Fileinfo偏移 +
数据块索引偏移 +
数据块索引个数 +
元数据索引偏移 +
元数据索引个数 +
数据块中未压缩数据字节数 +
数据块中全部数据的key-value个数 +
压缩代码标识 +
版本标识
|
1.1.数据块详解
数据块组成图如下:
Data Block Magic
{‘D’, ‘A’, ‘T’, ‘A’, ‘B’, ‘L’, ‘K’, 42 }
|
key长 |
value长 |
key |
value |
key长 |
value长 |
key |
value |
…… |
key长 |
value长 |
key |
value |
Data Block Magic
{‘D’, ‘A’, ‘T’, ‘A’, ‘B’, ‘L’, ‘K’, 42 }
|
key长 |
value长 |
key |
value |
key长 |
value长 |
key |
value |
…… |
key长 |
value长 |
key |
value |
Blocks …… |
数据块部分由多个block块组成,每个数据块由块头 + 多个cell(key-value对)集合组成,如上图。每个数据块的大小在创建表的列族的时候可以指定,默认为(64 * 1024)。
1. 块大小的设定由HColumnDescriptor.setBlockSize(int)指定,默认(64 * 1024)。
2. 块大小设置,块设置的越小,访问速度越快,但数据块索引越大,消耗的内存越多。因为在加载HFile时会把数据块索引全部加载到内存中。
数据块组成说明:
1. Data Block Magic – 数据块头,8字节,固定字节如下:{‘D’, ‘A’, ‘T’, ‘A’, ‘B’, ‘L’, ‘K’, 42 }。
2. key长 – 4字节整型,记录每个cell的key的长度。
3. value长 – 4字节整型,记录每个cell的value的长度。
4. key – cell的key值,byte[]类型,组成如下:
rowKey的长(2字节)+ rowKey + family的长(1字节) + family + qualify + timestampe(8字节) + keyType类型(1字节)
1)rowKey的长度不能大于0x7fff(32767).
2)rowKey不能为空。
3)family(列族)的长度不能大于0x7f(127)
4)qualify(限定符)的长度不能大于(0x7fffffff(2147483647) – row长度 – family长度)。
5. value – cell的value值,byte[]类型,value值不能为空。
例如:在hbase中有一个表(student),其中有一个列族(info),该列族不压缩。其中的rowkey用学号表示,现在插入一个记录(rowkey=’033′, qualify=’age’, value=’19′)。那么该记录将被表示成一个cell(key-value对)保存到HFile中,那么该cell在HFile中的内容如下:
项 |
字节表示 |
key长 |
{0,0,0,24} |
value长 |
{0,0,0,2} |
key |
{0,3}+{’0′, ’3′, ’3′}+{4}+{‘i’, ‘n’, ‘f’, ‘o’}+{‘a’, ‘g’, ‘e’}+{0,0,0,0,0,0,0,8}+{4} |
value |
{’1′, ’9′} |
问题:
1. 块大小的设置策略?
2. keyType的说明?
3. compress压缩的说明?
1.2.元数据块详解
每个元数据是key-value类型的值,新增的元数据会按照从小到大的顺序排序。
在StoreFile中,如果使用BloomFilter,则StoreFile将会把BloomFilter的信息保存到HFile中的元数据中, 元数据块中只保存元数据的value值,key值保存在元数据索引块中。格式如下:
Meta Block Magic
{‘M’, ‘E’, ‘T’, ‘A’, ‘B’, ‘L’, ‘K’, 99 }
|
Meta Data Value |
Meta Block Magic
{‘M’, ‘E’, ‘T’, ‘A’, ‘B’, ‘L’, ‘K’, 99 }
|
Meta Data Value |
…… |
每个元数据由元数据头+元数据值组成。
1.3.FileInfo详解
fileInfo中保存的信息为key-value类型的值,其中key与value都是byte[]类型。每个新增的值在内部都以值key顺序从小到大进行排序。fileInfo保存了与该HFile相关的一些信息,其中有系统保留的一些固定的值,这些值的key以”hfile.”为前缀。也可以保存用户自定义的一些值,但这些值的key不能以”hfile.”开头。其中系统内部保留的一些值如下:
项 |
key(字符串表示,实际以二进制存储) |
value |
LASTKEY |
hfile.LASTKEY |
该HFile中的数据块中的最后一个值的key, 该值如果为空则不进行保存 |
AVG_KEY_LEN |
hfile.AVG_KEY_LEN |
该HFile中的数据块中的所有值key的平均长度。 |
AVG_VALUE_LEN |
hfile.AVG_VALUE_LEN |
该HFile中的数据块中的所有值value的平均长度。 |
COMPARATOR |
hfile.COMPARATOR |
在HFile中的数据块中的值都是以值的key进行排序来存放的,而key的组成比较复杂,这就需要一个key的比较器类,而该值保存了key值比较器的类的名称。 |
fileInfo在HFile中的格式如下:
filInfo中所有值(key-value对)的个数,整型 |
key |
value类型标识 |
value |
key |
value类型标识 |
value |
…… |
fileInfo各项说明:
1. filInfo中所有值(key-value对)的个数,整型,四字节。
2. key值,保存fileInfo中值得key值。在HFile中的组成为
key长+key
其中key长以压缩的整型保存,整型类型包括(byte,short,int,long),如果该整数用i表示,详细说明如下:
1. 当-112 <= i <= 127 时,用一个字节保存实际值。
2. 其他情况下,第一个字节表示该整型与正负与该整数占字节长度,随后存储的是从该整数补码的高位算起的第一个非0字节的所有值。如果第一个字节为v,详细说明如下:
a) 当-120<=i<=-113时,表示该值为正数,该数所占字节为-(v+112)
b) 当-128<=i<=-121时,表示该值为负数,该数所占字节为-(v+120)
例如:
原始值 |
压缩后,以字节表示 |
说明 |
-87 |
{-87} |
第一种情况 |
127 |
{127} |
第一种情况 |
-1246 |
{-122} + {4, -35} |
第二种情况的b类型。{-122}表示该数为负数,并且所占字节长度为-(-122+120)=2字节。其中{4,-35}保存的是-1246的补码1245的第一个非0字节开始的所有字节。1245的16进制为0x04DD,非0字节共2个,第一个为0×04(4),第二个为0xDD(-35),组合一起为{-122, 4,-35} |
130 |
{-113} + {-126} |
第二种情况的a类型。{-113}表示该数为正数,并且所占字节长度为-(-113+112)=1字节。其中{-126}保存的是130的补码130的第一个非0字节开始的所有字节。130的16进制为0x04DD,非0字节共2个,第一个为0×04(4),第二个为0×82(-126),组合一起为{-113, -126} |
3. value值,保存fileInfo中值的value值。在HFile中的组成为
value长+value
其中value长以压缩的整型保存,压缩整型具体格式参考key值中关于压缩整型的说明。
1.4.数据块索引
数据块索引保存的是每一个数据块在HFile文件中的位置、大小信息以及每个块的第一个cell的key值。格式如下:
Index Block Magic
{‘I’, ‘D’, ‘X’, ‘B’, ‘L’, ‘K’, 41, 43 }
|
block offset |
block size |
block first key |
block offset |
block size |
block first key |
…… |
block offset |
block size |
block first key |
格式各项说明:
1. block offset 块在HFile中偏移,long(8字节)。
2. block size 块大小,int(4字节)。
3. block first key 块中第一个cell(key-value)值得key.该值的组成为(key的长(压缩整型表示)+key值)
1.5.元数据块索引
该数据块的格式与数据库索引相同,元数据块索引保存的是每一个元数据在HFile文件中的位置、大小信息以及每个元数据的key值。格式如下:
Index Block Magic
{‘I’, ‘D’, ‘X’, ‘B’, ‘L’, ‘K’, 41, 43 }
|
meta offset |
meta size |
meta name |
meta offset |
meta size |
meta name |
…… |
meta offset |
meta size |
meta name |
格式各项说明:
1. meta offset 元信息在HFile中偏移,long(8字节)。
2. meta size 元信息数据大小,int(4字节)。
3. meta name 元信息中的key值.该值的组成为(key的长(压缩整型表示)+key值)
1.6.文件尾
文件尾主要保存了该HFile的一些基本信息。格式比较简单,如下:
Trailer Block Magic
{‘T’, ‘R’, ‘A’, ‘B’, ‘L’, ‘K’, 34, 36 }
|
FileInfo Offset (long) |
Data Index Offset (long) |
Data Index Count (int) |
Meta Index Offset (long) |
Meta Index Count (int) |
Total Uncompressed Bytes (long) |
Entry Count (int) |
Compression Codec (int) |
Version (int) |
说明如下:
1. FileInfo Offset – FileInfo信息在HFile中的偏移。long(8字节)。
2. DataIndex Offset – 数据块索引在HFile中的偏移。long(8字节)。
3. DataIndex Count – 数据块索引的个数。int(4字节)。
4. MetaIndex Offset – 元数据索引块在HFile中的偏移。long(8字节)。
5. MetaIndex Count – 元数据索引块的个数。int(4字节)。
6. TotalUncompressedBytes – 未压缩的数据块部分的总大小。long(8字节)。
7. Entry Count – 数据块中所有cell(key-value)的个数。int(4字节)
8. Compression Codec – 压缩算法为enum类型,该值表示压缩算法代码。(LZO-0,GZ-1,NONE-2),int(4字节)
9. Version – 版本信息。当前该版本值为1. int(4字节)。
--------------------------------------
Sumary:
Protobuf
BinarySearch
本篇主要讲HFileV2的相关内容,包括HFile的构成、解析及怎么样从HFile中快速找到相关的KeyValue.基于Hbase 0.98.1-hadoop2,本文大部分参考了官方的资源,大家可以先阅读下这篇官方文档,Reference Guide:http://hbase.apache.org/book/apes03.html。其实也就是跟我们发行包内dos/book下的其中一篇。dos下有很多有用的文章,有时间的时候建议大家还是细读一下。
研究HFile也有一些时间了,源码也大概研究了下,做了不少试验,庖丁解牛远远谈不上,但是还是很详细地分享一下HFile的方方面面,像拆零件一样,把它一件一件地拆开看看,究竟是什么东西,怎么组织在一起的。
图1
这张图也是摘自上面那篇文章,主要分四部分:Scanned block section,Non-scanned block section,Load-on-open-section,以及Trailer.
Scanned block section : 即存储数据block部分
Non-scanned block section :元数据block部分,主要存放meta信息,即BloomFilter信息。
Load-on-open-section :这部分数据在RegionServer启动时,实例化Region并创建HStore时会将所有StoreFile的Load-on-open-section加载进内存,主要存放了Root Data Index,meta Index,File Info及BooleamFilter的metadata等。除了Fields for midkey外,每部分都是一个HFileBlock.下面会详细去讲这块。
Trailer :文件尾,主要记录version版本,不同的版本Trailer的字段不一样,及Trailer的字段相关信息。
在拆解HFile过程中,我们从下而上地开始分析,HBase本身也是这样,首先要知道Version版本,才知道怎么去加载它们。在开始讲解之前,我们应先获得一份HFile数据,其实很简单,直接从hdfs上下载到本地即可,我使用的数据是我上一篇文章中做测试生成的,10W rows, 70W KeyValue,26M左右。
Trailer :
文件最后4位,即一个整型数字,为version信息,我们知道是V2.而V2的Trailer长度为212字节。除去MagicCode(BlockType) 8字节及 Version 4字节外,剩余206字节记录了整个文件的一些重要的字段信息,而这些字段信息是由protobuf组成的,下面我们尝试山寨一把,自主解析下Trailer的所有信息。
实践1:
step1: 准备一份描述Trailer的Protobuf.
Hbase的源码包下,有一个hbase-protocol sub module.它包含了HBase的所有Protobuf,包括序列化要用到的实体及RPC的定义。我们找到HFile.proto,我们只选取一小部分
新建我们自已的Protobuf文件 : HFile.proto
option java_package = "com.bdifn.hbase.hfile.proto";
option java_outer_classname = "HFileProtos";
option java_generic_services = true;
option java_generate_equals_and_hash = true;
option optimize_for = SPEED;
message FileTrailerProto {
optional uint64 file_info_offset = 1;
optional uint64 load_on_open_data_offset = 2;
optional uint64 uncompressed_data_index_size = 3;
optional uint64 total_uncompressed_bytes = 4;
optional uint32 data_index_count = 5;
optional uint32 meta_index_count = 6;
optional uint64 entry_count = 7;
optional uint32 num_data_index_levels = 8;
optional uint64 first_data_block_offset = 9;
optional uint64 last_data_block_offset = 10;
optional string comparator_class_name = 11;
optional uint32 compression_codec = 12;
optional bytes encryption_key = 13;
}
从proto文件可以看出,Trailer主要记录了Load-on-open-section相关的信息,应该花点时间去做些结合和对比。
step2:使用Protobuf命令生成java代码.(刚好我之前在hadoop环境中编译过源码,安装了protobuf)
protoc HFile.proto --java_out=.
将生成的java类拷到我们的项目中.
step3. 编写java代码解析Trailer.
public static void main(String[] args) throws Exception {
Configuration config = new Configuration();
String pathStr = "file:///f:/0a99d83b2b0a49c0adbc371d4bfe021e";
Path path = new Path(pathStr);
FileSystem fs = FileSystem.get(URI.create(pathStr), config);
FSDataInputStream input = fs.open(path);
long length = input.available();
int trailerSize = 212;
input.seek(length - trailerSize);
byte[] trailerBytes = new byte[trailerSize];
input.read(trailerBytes);
ByteBuffer trailerBuf = ByteBuffer.wrap(trailerBytes);
trailerBuf.position(trailerSize - 4);
int version = trailerBuf.getInt();
int majorVersion = version & 0x00ffffff;
int minorVersion = version >>> 24;
String magicCode = Bytes.toString(Arrays.copyOfRange(trailerBytes, 0, 8));
FileTrailerProto hfileProtos = FileTrailerProto.PARSER
.parseDelimitedFrom(new ByteArrayInputStream(trailerBytes, 8,
trailerSize - 4));
System.out.println(String.format("MagicCode:%s,majorVersion:%d,:minorVersion:%d",magicCode,majorVersion,minorVersion));
System.out.println(hfileProtos);
}
输出结果:
至此,Trailer已经完全解析完成,接下来开始下一部分:
Load-on-open-section:
RegionServer托管着0...n个Region,Region管理着一个或多个HStore,其中HStore就管理着一个MemStore及多个StoreFile.
所在RegionServer启动时,会扫描所StoreFile,加载StoreFile的相关信息到内存,而这部分内容就是Load-on-open-section,主要包括 Root数据索引,miidKyes(optional),Meta索引,File Info,及BloomFilter metadata等.
数据索引:
数据索引是分层的,可以1-3层,其中第一层,即Root level Data Index,这部分数据是处放在内存区的。一开始,文件比较小,只有single-level,rootIndex直接定位到了DataBlock。当StoreFile变大时,rootIndex越来越大,随之所耗内存增大,会以多层结构存储数据索引.当采用multi-level方式,level=2时,使用root index和leaf index chunk,即内存区的rootIndex定位到的是 leafIndex,再由leafIndex定位到Datablock。当一个文件的datablock非常多,采用的是三级索引,即rootIndex定位到intermediate index,再由intermediate index定位到leaf index,最后定位到data block.可以看看上面图1所示,各个level的index都是分布在不同的区域的。但每部分index是以HFileBlock格式存放的,后面会比较详细地讲HFileBlock,说白了,就是HFile中的一个块。
Fileds for midKey:
这部分数据是Optional的,保存了一些midKey信息,可以快速地定位到midKey,常常在HFileSplit的时候非常有用。
MetaIndex:
即meta的索引数据,和data index类似,但是meta存放的是BloomFilter的信息,关于BloomFilter由于篇幅就不深入讨论了.
FileInfo:
保存了一些文件的信息,如lastKey,avgKeylen,avgValueLen等等,一会我们将会写程序将这部分内容解析出来并打印看看是什么东西。同样,FileInfo使用了Protobuf来进行序列化。
Bloom filter metadata:
分为GENERAL_BLOOM_META及DELETE_FAMILY_BLOOM_META二种。
OK,下面开始操刀分割下Load-on-open-section的各个小块,看看究竟有什么东西。在开始分析之前,上面提到了一个HFileBlock想先看看。从上面可以看出来,其实基本每个小块都叫HFileBlock(除field for midkey),在Hbase中有一个类叫HFileBlock与之对应。从V2开始,即我们当前用的HFile版本,HFileBlock是支持checksum的,默认地使用CRC32,由此HFileBlock由header,data,checksum三部分内容组成,如下图所示。其中Header占了33个字节,字段是一样的,而每个block的组织会有些小差异.
图2
了解了HFileBlock的结构,我们下面开始正式解析内存区中的各个index的block内容。首先我们根据图2我们抽象出一个简单的HFileBlock实体。
实验2: HFileBlock的解析.及BlockReader内部类
public class MyHFileBlock {public static class Header {private String magicCode ;
int onDiskSizeWithoutHeader;
int unCompressBlockSize;
long prevBlockOffset;
byte checkSum;
int bytesPerChecksum;
int onDiskDataSizeWithHeader;
}
private Header header;
private ByteBuffer blockBuf;
private byte [] checkSum ;
....
public static class BlockIndexReader {public BlockIndexReader(MyHFileBlock block) {
....
}
public BlockIndexReader parseMultiLevel(int numEntries, String expectedMagicCode, int level) throws Exception {
.....
}
.......
}
}
2.编写HFileBlock遍历器,代码有点长,折叠起来吧,有兴趣可以看看,详细完整代码还是下载附件项目吧,
public class MyHFileBlockIterator {private ByteBuffer loadOnOpenBuffer;
public MyHFileBlockIterator(FSDataInputStream data, long offset, int length) {
try {
data.seek(offset);
byte[] loadOnOpenBytes = new byte[length];
data.read(loadOnOpenBytes);
loadOnOpenBuffer = ByteBuffer.wrap(loadOnOpenBytes);
} catch (IOException e) {
e.printStackTrace();
}
}
public MyHFileBlockIterator(byte [] data) {
loadOnOpenBuffer = ByteBuffer.wrap(data);
}
public MyHFileBlock nextBlock() {
MyHFileBlock block = new MyHFileBlock(loadOnOpenBuffer);
Header header = block.getHeader();
int currentBlockLength = block.getHeader()
.getOnDiskDataSizeWithHeader();
int dataSize = currentBlockLength - MyHFileBlock.HARDER_SIZE;
byte[] dataBlockArray = new byte[dataSize];
loadOnOpenBuffer.get(dataBlockArray);
ByteBuffer dataBlock = ByteBuffer.wrap(dataBlockArray);
block.setBlockBuf(dataBlock);
int checkSumChunks = header.getOnDiskSizeWithoutHeader()
/ header.getBytesPerChecksum();
if (header.getOnDiskSizeWithoutHeader() % header.getBytesPerChecksum() != 0) {
checkSumChunks++;
}
int checkSumBytes = checkSumChunks * 4;
byte[] checkSum = new byte[checkSumBytes];
loadOnOpenBuffer.get(checkSum);
block.setCheckSum(checkSum);
return block;
}
public boolean hasNext(){
return loadOnOpenBuffer.position() < loadOnOpenBuffer.capacity();
}
}
View Code
开始解析Root Data Index和metaIndex .在Trailer解析后,我们可以得到Load-on-open-section内容的相关信息,可以构造字节数组,将这部分字节码load进内存进行解析,在解析之前先讲下FileInfo
FileInfo的内容是以ProtoBuf放式存放的,与Trailer类似,我们先创建FileInfo.proto
option java_package = "com.bdifn.hbase.hfile.proto";
option java_outer_classname = "FileInfoProtos";
option java_generic_services = true;
option java_generate_equals_and_hash = true;
option optimize_for = SPEED;
message BytesBytesPair {
required bytes first = 1;
required bytes second = 2;
}
message FileInfoProto {
repeated BytesBytesPair map_entry = 1;
}
编译: protoc FileInfo.proto --java_out=.
编写测试类:
....
FileTrailerProto hfileProtos = FileTrailerProto.PARSER.parseDelimitedFrom(new ByteArrayInputStream(trailerBytes, 8,trailerBytes.length - 4));
long loadOnOpenLength = length - trailerSize - hfileProtos.getLoadOnOpenDataOffset();
MyHFileBlockIterator inter = new MyHFileBlockIterator(input,hfileProtos.getLoadOnOpenDataOffset(), (int) loadOnOpenLength);
MyHFileBlock dataIndex = inter.nextBlock();
int dataIndexLevels = hfileProtos.getNumDataIndexLevels();
int dataIndexEntries = hfileProtos.getDataIndexCount();
MyHFileBlock.BlockIndexReader rootDataReader = dataIndex.createBlockIndexReader().parseMultiLevel(dataIndexEntries,"IDXROOT2", dataIndexLevels);
MyHFileBlock metaIndex = inter.nextBlock();
.....
MyHFileBlock fileInfo = inter.nextBlock();
ByteArrayInputStream in = new ByteArrayInputStream(fileInfo.getBlockBuf().array());
int pblen = ProtobufUtil.lengthOfPBMagic();
byte[] pbuf = new byte[pblen];
if (in.markSupported())
in.mark(pblen);
int read = in.read(pbuf);
FileInfoProtos.FileInfoProto fileInfoProto = FileInfoProtos.FileInfoProto.parseDelimitedFrom(in);
List<BytesBytesPair> entries = fileInfoProto.getMapEntryList();
for (BytesBytesPair entry : entries) {
System.out.println(entry.getFirst().toStringUtf8() + ":"+ entry.getSecond().toStringUtf8());
}
while (inter.hasNext()) {
MyHFileBlock block = inter.nextBlock();
System.out.println(block.getHeader());
}
以上就是解析HFile Load-on-open-section部分的各个fileblock内容,完整代码请下载附带的地址。
Scanned block section: 关于bloomfilter先不分析了。
Non-scanned block section:
这部分内容就是真正的数据块,从图1看出,这部分数据是分datablock存储的,默认地,每个datablock占64K,如果是多层的index的话,部分index block也会存放在这里,由于我的测试数据,是single-level的,所以只针对单级的index分析。
的single-level情况下,内存的rootDataIndex记录了每个datablock的偏移量,大小及startKey信息,主要是为了快速地定位到KeyValue的位置,在HFile中查找或者seek到某个KeyValue时,首先会在内存中,对rootDataIndex进行二分查找,单级的index可以直接定位DataBlock,然后通过迭代datablock定位到KeyValue所在的位置,而2-3层时,上面也略有提及,大家有时间的话,可以做多点研究这部分。
弱弱提句:在HStore中,会有cache将这些datablock缓存起来,使用LRU算法,这样会提高不少性能。
每个DataBlock同样也是一个HFileBlock,也包括header,data,checksum信息,可以用我们之前写的BlockIterator就可以搞定。下面使用代码,去遍历一个datablock看看。
实验3:
编写KeyValue遍历器
public class KeyValueIterator {public static final int KEY_LENGTH_SIZE = 4;
public static final int VALUE_LENGTH_SIZE = 4;
private byte [] data ;
private int currentOffset ;
public KeyValueIterator(byte [] data) {
this.data = data;
currentOffset = 0;
}
public KeyValue nextKeyValue(){
KeyValue kv = null;
int keyLen = Bytes.toInt(data,currentOffset,4);
incrementOffset(KEY_LENGTH_SIZE);
int valueLen = Bytes.toInt(data,currentOffset,4);
incrementOffset(VALUE_LENGTH_SIZE);
incrementOffset(keyLen,valueLen,1);
int kvSize = KEY_LENGTH_SIZE + VALUE_LENGTH_SIZE + keyLen + valueLen ;
kv = new KeyValue(data , currentOffset - kvSize - 1, kvSize);
return kv;
}
public void incrementOffset(int ... lengths) {
for(int length : lengths)
currentOffset = currentOffset + length;
}
public boolean hasNext() {
return currentOffset < data.length;
}
}
编写测试代码:
long offset = rootDataReader.getBlockOffsets()[0];
int size = rootDataReader.getBlockDataSizes()[0];
byte[] dataBlockArray = new byte[size];
input.seek(offset);
input.read(dataBlockArray);
MyHFileBlockIterator dataBlockIter = new MyHFileBlockIterator(dataBlockArray);
MyHFileBlock dataBlock1 = dataBlockIter.nextBlock();
KeyValueIterator kvIter = new KeyValueIterator(dataBlock1.getBlockBuf().array());
while (kvIter.hasNext()) {
KeyValue kv = kvIter.nextKeyValue();
System.out.println(kv);
}
OK,基本上是这些内容了。有点抱歉一开篇讲得有点大了,其实没有方方面面都讲得很详细。meta,bloomfilter部分没有详细分析,大家有时间可以研究后,分享一下。
源码我将我测试的Hfile也附带上传了,压缩后有3M多,完整代码请下载:下载源码
|
相关推荐
本文将深入探讨如何使用Hadoop MapReduce任务将文件数据转换为HBase所需的HFile格式,并进一步将其批量导入到HBase表中。 首先,我们需要了解HFile。HFile是HBase内部存储数据的二进制格式,它提供高效的随机读取...
HFile是HBase中的存储格式文件,它可以被HBase识别和读取,类似于传统数据库中的数据文件。在转换过程中,Hadoop MapReduce程序用于处理文本文件中的数据,并生成HBase能识别的HFile格式。这一步骤需要将原始文本...
HBase 配置文件 hbase-site.xml 详解 HBase 是一个基于 Hadoop 的分布式、面向列的 NoSQL 数据库,它提供了高性能、可扩展、可靠的数据存储解决方案。HBase 的配置文件 hbase-site.xml 是其核心配置文件,本文将...
1. **数据预处理**:首先,使用MapReduce作业将源数据转换为HBase的内部存储格式HFile。在这个阶段,Mapper处理输入数据,可能涉及解析、清洗、转换等操作。Reducer则负责将处理后的数据写入HDFS上的一个临时目录,...
1. **数据预处理**:HDFS上的文件可能是CSV、JSON或自定义格式,需要先进行解析和转换,使其符合HBase的表结构。这可能涉及到使用MapReduce或者Spark等工具进行数据清洗和转换。 2. **创建HBase表**:在HBase中创建...
7. HFile:HFile是HBase中的文件格式,负责存储Region中的数据块。 8. MemStore:MemStore是HRegion中的内存存储单元,负责管理Region中的数据、数据的读写操作等。 9. HLog:HLog是HBase中的日志系统,负责记录...
数据以HFile格式存储于HDFS上,HFile是一种优化过的数据文件格式,支持高效的数据检索和压缩。 #### 全量导入挑战与解决方案 在全量导入过程中,HBase的compaction机制可能会显著减慢写入速度。这是因为compaction...
例如,使用HFile V2或V3格式可以优化存储效率;通过Region Split策略调整数据分布,避免热点问题;还可以通过监控工具查看HBase的运行状态,及时发现和解决问题。 总的来说,HBase 2.0.1在CentOS 6.5上的单机安装...
`org.apache.hadoop.hbase.regionserver.StoreFile`类管理每个列族的HFile,当一个Region的大小达到预设阈值时,`org.apache.hadoop.hbase.regionserver.HRegion`会触发分裂过程,这涉及到`org.apache.hadoop.hbase....
5. **执行MapReduce作业**:编写一个MapReduce作业,该作业使用HFileOutputFormat作为输出格式,将上一步骤中写入HDFS的数据转化为HBase可读的HFile格式。 6. **加载HFiles**:最后,通过HBase的Admin API或者HBase...
HBase的存储引擎基于LSM-Like树,这使得一次范围查询可能涉及到多个Region(分片)、BlockCache(内存缓存)以及多个HFile(数据存储文件)。在读取过程中,HBase不仅要处理多版本的数据,还要处理已标记为删除但未...
- **HFile**:HFile 是 HBase 中的数据文件格式,用于存储已经刷盘的数据。 #### 三、HBase 安装与配置 **3.1 安装前准备** - **安装 Java**:HBase 要求运行在 Java 环境下,因此首先需要安装 Java。 - **安装 ...
在HFile数据格式中,KeyValue数据结构的Value部分通常是二进制数据。这是因为HBase主要用于存储和处理大量非结构化或半结构化的数据。 9. **分布式模式下的节点数** HBase在分布式模式下最好至少拥有3个节点,...
### HBase架构与运作机制详解 #### 一、HBase概览 HBase是一个分布式、可扩展的大数据存储系统,其设计目标是为了提供高可靠性、高性能的数据存储服务。HBase基于Google的Bigtable论文实现,运行于Hadoop之上,...
- **HFile**:HBase的底层文件格式,用于存储Region的数据。HFile支持高效的数据压缩、索引等特性,以提高读写性能。 #### HBase集群典型部署组网 - **HDFS(Hadoop Distributed File System)**:HBase的数据最终...
### Apache HBase™ 参考指南关键知识点解析 #### 一、引言 Apache HBase 是一个分布式的、可扩展的大规模数据存储系统,它基于Google的Bigtable论文设计实现。该参考指南作为HBase的官方文档,为用户提供了一个...
- **StoreFile**:Region中的数据存储为多个StoreFile文件,它们是不可变的,只读的HFile格式文件。 - **MemStore**:在内存中缓存新写入的数据,当数据达到一定大小时会刷盘到StoreFile。 - **HLog**:日志文件,...
### Java大数据作业_3HBase知识点解析 #### 1. 使用Java集合的代码描述HBase的表结构 在HBase中,表结构由一系列的行组成,每行都有唯一的row key,并且每个单元格都存储在列族下。为了描述这样一个表结构,我们...
本文将重点解析HBase数据读取中的scanner体系构建。 首先,HBase的读取流程需要处理多版本数据和已标记删除的数据。更新操作不覆盖原有数据,而是通过时间戳创建新版本。删除操作则仅标记数据为"deleted",实际删除...
8. `hbase.hregion.majorcompaction.interval`: 主要合并的间隔时间,用于定期合并HFile。 9. `hbase.hregion.replication`: 是否开启HBase的区域复制,提高数据可用性。 四、优化策略 1. 调整`hbase.regionserver....