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

本文探讨了HBase表的设计原则,特别是RowKey的选择及其对查询性能的影响,并介绍了一种利用列过滤器优化大量数据查询的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由于最近项目中需要使用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<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);
}
}



### HBase 文件描述符表分配失败导致内存不足的解决方案 当遇到 `unable to allocate file descriptor table - out of memory` 的错误时,这通常表明操作系统级别的文件句柄数量不足以支持当前运行的应用程序需求。以下是针对此问题的具体解决方法: #### 增加操作系统的最打开文件数限制 可以通过修改 Linux 系统配置来增加允许的最文件句柄数。编辑 `/etc/security/limits.conf` 文件并添加以下内容: ```bash * soft nofile 65535 * hard nofile 65535 ``` 上述命令将软硬限制都设置为 65535,可以根据实际需要进一步提高该数值。 此外,还需要确认系统范围内的文件句柄限制是否足够高。通过调整 `/proc/sys/fs/file-max` 来实现这一点: ```bash echo 100000 > /proc/sys/fs/file-max ``` 为了使这些更改永久生效,可以在 `/etc/sysctl.conf` 中加入如下行: ```bash fs.file-max = 100000 ``` #### 调整 HBase 配置参数 除了操作系统级优化外,还可以对 HBase 自身的相关配置进行微调以减少资源消耗。例如,适当减小 Region 小能够有效降低个 region server 承载的压力以及相应的文件句柄占用量。具体做法是在 hbase-site.xml 设置较小的初始值给 **hbase.hregion.max.filesize** 属性[^1]: ```xml <property> <name>hbase.hregion.max.filesize</name> <value>268435456</value><!-- 即256MB --> </property> ``` 另外,如果某些列族被频繁访问,则可考虑将其定义成 in-memory 列族形式从而减轻磁盘 I/O 和文件句柄压力[^2] : ```xml <property> <name>hbase.table.specific.inmemory.cf</name> <value>true</value> </property> ``` 最后需要注意的是,在构建 Kylin 或其他 OLAP 工具模型过程中应特别关注分区字段的选择准确性,避免由于不恰当的时间戳选取造成数据丢失或者重复计算等问题发生[^3]. ```python def set_hbase_config(): config = { "hbase.hregion.max.filesize": "268435456", # 设定Region小为256M "hbase.table.specific.inmemory.cf": "true" } return config ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值