2021SC@SDUSC
一、简述
HBase是一个KeyValue型的数据库,核心存储结构是KeyValue类,这个类定义了HBase的数据模型,并贯穿了HBase的整个读写链路。同时HBase自身的元数据管理也是使用了业务表相同的模式。
二、数据模型
Table(表格) 一个HBase表格由多行组成 Row(行) HBase中的行里面包含一个key和一个或者多个包含值的列。行按照行的key字母顺序存储在表格中。因为这个原因,行的key的设计就显得非常重要。数据的存储目标是相近的数据存储到一起。一个常用的行的key的格式是网站域名。 Column(列) HBase中的列包含用:分隔开的列族和列的限定符。 Column Family(列族) 因为性能的原因,列族物理上包含一组列和它们的值。每一个列族拥有一系列的存储属性,例如值是否缓存在内存中,数据是否要压缩或者他的行key是否要加密等等。表格中的每一行拥有相同的列族,尽管一个给定的行可能没有存储任何数据在一个给定的列族中。 Column Qualifier(列的限定符) 列的限定符是列族中数据的索引。例如给定了一个列族content,那么限定符可能是content:html,也可以是content:pdf。列族在创建表格时是确定的了,但是列的限定符是动态地并且行与行之间的差别也可能是非常大的。 Cell(单元) 单元是由行、列族、列限定符、值和代表值版本的时间戳组成的。 TimeStamp(时间戳) 时间戳是写在值旁边的一个用于区分值的版本的数据。默认情况下,时间戳表示的是当数据写入时RegionSever的时间点,但也可以在写入数据时指定一个不同的时间戳。
三、KeyValue
KeyValue本身就是一串二进制数据,即byte[],通过一些编码规则,将二进制数据映射为六元组或七元组。 KeyValue的byte数组由3部分组成: 1.2个长度字段,每个字段4字节:即key的长度,value的长度 2.key数据 3.value数据 其中,key包括了rowkey,family,qualifier,timestamp,type,这5个部分。 末尾是value字段,其长度由开始的ValueLength字段来定义。最大是Integer#MAX_VALUE,即4GB。
四、表定义和列族定义
HTableDescriptor tableDesc = new HTableDescriptor("test");
日志flush的时候是同步写,还是异步写
tableDesc.setDurability(Durability.SYNC_WAL);
tableDesc.setMemStoreFlushSize(256*1024*1024);
HColumnDescriptor colDesc = new HColumnDescriptor("f");
块缓存,保存着每个HFile数据块的startKey
colDesc.setBlockCacheEnabled(true);
colDesc.setBlocksize(64*1024);
bloom过滤器,有ROW和ROWCOL,ROWCOL除了过滤ROW还要过滤列族
colDesc.setBloomFilterType(BloomType.ROW);
写的时候缓存bloom并缓存索引
colDesc.setCacheBloomsOnWrite(true);
colDesc.setCacheIndexesOnWrite(true);
存储和进行compaction的时候使用压缩算法
colDesc.setCompressionType(Algorithm.SNAPPY);
colDesc.setCompactionCompressionType(Algorithm.SNAPPY);
colDesc.setDataBlockEncoding(DataBlockEncoding.PREFIX);
写入硬盘的时候是否进行编码;关闭的时候,是否剔除缓存的块;是否保存那些已经删除掉的kv;让数据块缓存在LRU缓存里面有更高的优先级
colDesc.setEncodeOnDisk(true);
colDesc.setEvictBlocksOnClose(true);
colDesc.setKeepDeletedCells(false);
colDesc.setInMemory(true);
colDesc.setMaxVersions(3);
colDesc.setMinVersions(1);
集群间复制的时候,如果被设置成REPLICATION_SCOPE_LOCAL就不能被复制了
colDesc.setScope(HConstants.REPLICATION_SCOPE_GLOBAL);
colDesc.setTimeToLive(18000);
tableDesc.addFamily(colDesc);
bloom过滤器,过滤加速colDesc.setBloomFilterType(BloomType.ROW),colDesc.setDataBlockEncoding(DataBlockEncoding.PREFIX); 压缩内存和存储中的数据,内存紧张时设置 colDesc.setInMemory(true); 让数据块缓存在LRU缓存里面有更高的优先级 KeyValue的存储:
public void write(Cell cell) throws IOException {
checkFlushed();
write(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
write(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength());
write(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength());
this.out.write(Bytes.toBytes(cell.getTimestamp()));
this.out.write(cell.getTypeByte());
write(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
}
分别定义了Row rowkey的起始位置、长度;Column family 列族的起始位置、长度;Qualifier 列名的起始位置、长度;Version 时间戳;Value 值的起始位置、长度
五、使用KeyValue
如果通过KeyValue的接口来获取KeyValue内部的各个字段的数据,那么,从KeyValue中获取任何一个字段的数据,本质上都是从这个byte[]中截取一段,然后返回。这必然涉及到两种数据获取方式: 拷贝一次:如KeyValue#getRow(), getValue()等方法,创建一个新的byte[],将内部的byte[]中对应的字段的二进制拷贝到新的数组中,然后返回新的数组 返回ptr:即返回一个3元组,(byte[], offset, length),让用户自己根据offset来在一个byte[]定位起始位置,读取指定长度的数据,即可得到需要的字段的内容(参见Cell接口的定义)。KeyValue为每个字段提供了3个接口来实现这个功能,这里以Value为例:
getValueArray() :返回一个byte[],这个数组中存储了valuegetValueOffset() :返回一个int,指示value字段的起始的字节偏移量getValueLength() :返回一个int,指示value字段的实际长度,单位是字节
六、总结
列存储的话存储的时候每个列都会重复前面的rowkey、列族这些信息,在列很多的情况下,rowkey和列族越长,消耗的内存和列族都会很大,所以它们都要尽量的短。 可以用colDesc.setDataBlockEncoding(DataBlockEncoding.PREFIX_TREE) 来压缩一下内存中的大小,这个后面后面会讲到
|