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” |
【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 |
文章图片
观察上面两张表的结构我们可以知道:
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
LinkedList
LinkedList
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
try {
results = MetaStorageHBaseClient.scan(
MetaStorageHBaseClient.TABLE_BLOCK, scan, 1,
MetaStorageHBaseClient.makeStartKey(startKey),
MetaStorageHBaseClient.makeStopKey(startKey));
if (0 != results.size()) {
for (Result r : results) {
LinkedList
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);
}
}
推荐阅读
- 急于表达——往往欲速则不达
- 慢慢的美丽
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量
- 2019-02-13——今天谈梦想()
- 考研英语阅读终极解决方案——阅读理解如何巧拿高分
- Ⅴ爱阅读,亲子互动——打卡第178天
- 低头思故乡——只是因为睡不着
- 取名——兰
- 每日一话(49)——一位清华教授在朋友圈给大学生的9条建议
- 广角叙述|广角叙述 展众生群像——试析鲁迅《示众》的展示艺术