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表。表结构如下:
Hbase探究——如何避免一行数据过大导致加载至内存出现out of memory的情况
文章图片





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”
【Hbase探究——如何避免一行数据过大导致加载至内存出现out of memory的情况】对象删除时戳
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
Hbase探究——如何避免一行数据过大导致加载至内存出现out of memory的情况
文章图片





观察上面两张表的结构我们可以知道:
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 = newColumnRangeFilter(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);
}
}





    推荐阅读