由于最近项目中需要使用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
/**
* 根据[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<Block> allBlockList,
LinkedList<LinkedList<Block>> goodBlockList,
LinkedList<LinkedList<Block>> 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<Result> results = new ArrayList<Result>();
try {
results = MetaStorageHBaseClient.scan(
MetaStorageHBaseClient.TABLE_BLOCK, scan, 1,
MetaStorageHBaseClient.makeStartKey(startKey),
MetaStorageHBaseClient.makeStopKey(startKey));
if (0 != results.size()) {
for (Result r : results) {
LinkedList<Block> subAllBlockList = new LinkedList<Block>();
LinkedList<LinkedList<Block>> subGoodBlockList = new LinkedList<LinkedList<Block>>();
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);
}
}