Hbase探究——如何避免一行数据过大导致加载至内存出现out of memory的情况

由于最近项目中需要使用Hbase表,并且对其进行查询操作,因而我们先来了解下Hbase表的存储结构和原理。首先熟悉下hbase表的设计:

1 、hbase表设计:

    hbase使用三维有序存储,三维是指:rowkey(行主键),column key(columnFamily+qualifier),timestamp(时间戳)。我们知道rowkey是行的主键,而且hbase只能指定rowkey,或者一个rowkey范围(即scan)来查找数据。所以rowkey的设计是至关重要的,关系到你应用层的查询效率。我们知道,rowkey是以字典顺序排序的。比如,有两个rowkey:aa、bb,因为按字典排序,那么rowkey1是排在 rowkey2前面的。这个理解了,我们在根据rowkey范围查询的时候,我们一般是知道startRowkey和endRowkey的,这样查询的范围就确定下来了,就可以快速查询到这个范围内的所有数据,而且不需要遍历,这也是hbase的优势之一。比如说rowkey设计为:用户ID-日期,那么查某个用户某天的数据,startKEY为1234-20140729,endKey为:1234+20140730,那么你查到的就是用户为1234在20140729这一天的数据。

关于如何创建表等内容请参看:http://free9277.iteye.com/blog/2097964

2 、我们理解了Hbase表的存储原理之后,我们接下来谈谈我们项目中遇到的需求:有一张object表和block表。表结构如下:



Table Name

Object

RowKey

Object Id

“testbucket/pic/1.jpg”

Column Family

Column 

Description

Value 

M

“acl”

访问权限

字串

 

“version_owner”

对象拥有者

“admin” 

 

“copy_policy”

对象拷贝策略

“AZ1:3”

 

“encryption”

对象加密算法

“AES256”

 

“version_del_ts”

对象删除时戳

0:未删除; 

其他:删除时间;

 

“mime”

Object的MIME类型

“text/plain”

 

“meta”

自定义元数据列表

字串

 

“size”

实体数据大小

大小

 

“blockNum”

Object拥有的Block数目

数量

 

“lastModified”

版本最后修改时间

时间

 

“etag”

MD5算法生成的16字节数组

字串


Table Name

Block

Row Key

ObjectId

“testbucket/pic/1.jpg”

+version

+Block.partID

+partID.timestamp

Column Family

Column 

Description

Value 

M

Block.seqNum

+Block.timestamp

+ storageID

Block信息

Block.offset

+Block.size

+Block.checksum

+Block.Flag




观察上面两张表的结构我们可以知道:

object表中的的RowKey是不一样,因而可以通过设置startkey和endkey来进行多个block的选择;而且我们可以确定每一个object对象的行数据信息是一致的,不会出现增长的情况。但是我们看block表:同一个对象的每个block信息在hbase表中的RowKey是一样的,因而我们查找出的一行数据就是整个obj的所有block信息;这样子就会出现一个问题:如果我们的obj很大,达到TB级别,而我们的block块设置的是几M,这样子就会造成block表中的一行信息非常大,最大可以达到GB级别,所以即使是查询一条数据也会出现加载至内存中的数据过大。如何一次只显示一行信息的一部分呢?

我们知道scan查找的最小粒度是1行,所以从行上面我们是没有办法实现的,因而我们只能是从列方面考虑:经过查看Hbase的api接口,我们发现列向有过滤器,可以用过滤器进行过滤,具体包含如下几个类(具体类的作用及实现原理请查看Hbase的api接口:http://hbase.apache.org/apidocs/index.html):

ColumnCountGetFilter

ColumnPaginationFilter

ColumnRangeFilter

ColumnPrefixFilter

通过这几个类中的一个我们就可以实现该功能。以下是我的功能函数,当我只要给出column的范围就能找到这一行的相关数据了,从而避免了out of memory 情况的发生:

/**
 * 根据[minColumn,maxColumn)范围显示一条row中该范围内的column信息,目前设置的是 10*副本数
 * @param objId
 * @param version
 * @param minColumn
 * @param maxColumn
 * @param allBlockList
 * @param goodBlockList
 * @param badBlockList
 * @throws IOException
 */
public static void getTenBlock(String objId, long version, byte[] minColumn, byte[] maxColumn,
List allBlockList,
LinkedList> goodBlockList,
LinkedList> badBlockList) throws IOException {
if ((null == allBlockList) || (null == goodBlockList)
|| (null == badBlockList)) {
return;
}

ObjVersion ov = ObjDB.getObjVersion(objId, version);
Scan scan = new Scan();
scan.addFamily(COL_FAMILY_M);
Filter ColumnFilter = new  ColumnRangeFilter(minColumn, true, maxColumn, false); //设置过滤器
scan.setFilter(ColumnFilter);
byte[] startKey = BlockDB.parmToTwoBytes(objId, version);

List results = new ArrayList();
try {
results = MetaStorageHBaseClient.scan(
MetaStorageHBaseClient.TABLE_BLOCK, scan, 1,
MetaStorageHBaseClient.makeStartKey(startKey), 
MetaStorageHBaseClient.makeStopKey(startKey));
if (0 != results.size()) {
for (Result r : results) {
LinkedList subAllBlockList = new LinkedList();
LinkedList> subGoodBlockList = new LinkedList>();
BlockDB.parseBlock(r, subAllBlockList, subGoodBlockList);
if (subAllBlockList.size() > 0) {
allBlockList.addAll(subAllBlockList);
int partId = subAllBlockList.get(0).getPartId();
long partIdTs = subAllBlockList.get(0)
.getPartIdTs();
if ((null != ov)
&& (ov.isPartGood(partId, partIdTs))) {
goodBlockList.addAll(subGoodBlockList);
} else {
badBlockList.addAll(subGoodBlockList);
}
}
}
}
} catch (Exception e) {
LOG.error("Exception:", e);
}
}



你可能感兴趣的:(Hadoop)