HBase region预划分及查找过程

本文深入探讨了HBase中查找RegionServer的过程,包括Region预划分、regionname查询以及查找过程的图文解析。重点阐述了如何通过ZooKeeper和内部表-ROOT-、.META.来定位相应的RegionServer,确保高效的数据操作。
1.Region预划分:

RegionSplitter

java.lang.Object
  org.apache.hadoop.hbase.util.RegionSplitter
切分方式:分别按照不同的Split进行切分
bin/hbase org.apache.hadoop.hbase.util.RegionSplitter -c 60 -f test:rs myTable HexStringSplit
bin/hbase org.apache.hadoop.hbase.util.RegionSplitter -r -o 2 myTable UniformSplit


2.regionname查询:

HBase写记录过程中regionname查找简介:主要是看如何进行region选择,完成按domain域的数据散列,分摊至不同region上
|-->HTable table = new HTable(config, tablename);
|-->Put put = new Put(Bytes.toBytes("test2"));
|-->put.add(Bytes.toBytes(cfs[j]), Bytes.toBytes(String.valueOf(1)), Bytes.toBytes("value_3"));
|-->table.put(put);

HTable-->put() |利用HTable管理记录
|-->doPut(Arrays.asList(put));
|-->for (Put put : puts)
|-->writeBuffer.add(put);
|-->if (n % DOPUT_WB_CHECK == 0 && currentWriteBufferSize > writeBufferSize)
|-->flushCommits();
|-->if (autoFlush || currentWriteBufferSize > writeBufferSize)
|-->flushCommits();

HTable-->flushCommits() |提交添加的记录,内存阀值可控制
|-->Object[] results = new Object[writeBuffer.size()];
|-->this.connection.processBatch(writeBuffer, tableName, pool, results);
|-->finally
|-->for (int i = results.length - 1; i>=0; i--)
|-->if (results[i] instanceof Result)
|-->writeBuffer.remove(i);

HConnection--->processBatch() |HConnectionImplementation 批量处理
|-->processBatchCallback(list, tableName, pool, results, null)
|-->List<Row> workingList = new ArrayList<Row>(list);
|-->Map<HRegionLocation, MultiAction<R>> actionsByServer = new HashMap<HRegionLocation, MultiAction<R>>();
|-->for (int i = 0; i < workingList.size(); i++) |逐行处理Batch的每一行
|-->Row row = workingList.get(i);
|-->HRegionLocation loc = locateRegion(tableName, row.getRow(), true); |根据行获取对应的Region
|-->byte[] regionName = loc.getRegionInfo().getRegionName();
|-->MultiAction<R> actions = actionsByServer.get(loc);
|-->Action<R> action = new Action<R>(row, i);
|-->lastServers[i] = loc;
|-->actions.add(regionName, action);
|-->for (Entry<HRegionLocation, MultiAction<R>> e: actionsByServer.entrySet()) { |根据actionsByServer进行执行,执行过程在Callable中完成
futures.put(e.getKey(), pool.submit(createCallable(e.getKey(), e.getValue(), tableName)));}
|-->for (Entry<HRegionLocation, Future<MultiResponse>> responsePerServer : futures.entrySet()) |提交完成后,对future的返回对象进行处理

HConnectionImplementation--->locateRegion(final byte [] tableName,final byte [] row, boolean useCache)
|-->if (tableName == null || tableName.length == 0) |非空判断
|-->ensureZookeeperTrackers(); |ZK确认跟踪器
|-->if (Bytes.equals(tableName, HConstants.ROOT_TABLE_NAME)) |根据tableName判断为root,meta,还是一般类型表
|-->ServerName servername = this.rootRegionTracker.getRootRegionLocation(); |从zk获取root的regionserver
|-->return new HRegionLocation(HRegionInfo.ROOT_REGIONINFO, |从ZK中返回root表存储的位置,建立server连接,用于获取meta表信息
servername.getHostname(), servername.getPort());
|-->else if (Bytes.equals(tableName, HConstants.META_TABLE_NAME)) |从meta表中获取region的信息
|-->return locateRegionInMeta(HConstants.ROOT_TABLE_NAME, tableName, row,
useCache, metaRegionLock);
|-->else |此时从meta中获取信息
|-->return locateRegionInMeta(HConstants.META_TABLE_NAME, tableName, row,
useCache, userRegionLock);


HConnectionImplementation--->locateRegionInMeta() |获取真正的region的location信息
|-->if (useCache) |客户端获取时会做cache,保存Location信息
|-->location = getCachedLocation(tableName, row);
|-->byte [] metaKey = HRegionInfo.createRegionName(tableName, row, HConstants.NINES, false);
|-->metaLocation = locateRegion(parentTable, metaKey); |定位root/meta的region信息
|-->HRegionInterface server = getHRegionConnection(metaLocation.getHostname(), metaLocation.getPort());
|-->regionInfoRow = server.getClosestRowBefore( metaLocation.getRegionInfo().getRegionName(), metaKey,
HConstants.CATALOG_FAMILY);
|-->byte [] value = regionInfoRow.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
|-->HRegionInfo regionInfo = (HRegionInfo) Writables.getWritable(value, new HRegionInfo());
|-->value = regionInfoRow.getValue(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER);
|-->location = new HRegionLocation(regionInfo, hostname, port); |通过regionInfo和对应的regionserver机器名确认Location
|-->cacheLocation(tableName, location);
|-->return location;


HRegionServer-->getClosestRowBefore() |用于寻找目标row最接近的行,在此用于meta表中寻找最近的regionname
|-->HRegion region = getRegion(regionName); |根据regionname获取对应的region信息
|-->Result r = region.getClosestRowBefore(row, family); |在region中寻找最近的row
|-->coprocessorHost.preGetClosestRowBefore(row, family, result) |coprocessorHost预处理
|-->checkRow(row, "getClosestRowBefore"); |确认合法性
|-->startRegionOperation(); |设置读写锁
|-->Store store = getStore(family); |
|-->KeyValue key = store.getRowKeyAtOrBefore(row); |获取最接近的row行,并返回KeyValue对象
|-->Get get = new Get(key.getRow());
|-->get.addFamily(family);
|-->result = get(get, null);
|-->coprocessorHost.postGetClosestRowBefore(row, family, result); |处理完成后的钩子程序


Store-->getRowKeyAtOrBefore() |获取具体的family对应的存储单元Store,每一个Store中存储一列簇
|-->KeyValue kv = new KeyValue(row, HConstants.LATEST_TIMESTAMP); |包装row为KeyValue对象值,用于比较
|-->GetClosestRowBeforeTracker state = new GetClosestRowBeforeTracker( |封装比较对象
this.comparator, kv, ttlToUse, this.region.getRegionInfo().isMetaRegion());
|-->this.lock.readLock().lock(); |设定读写锁
|-->this.memstore.getRowKeyAtOrBefore(state); |分别在meta和HFile中进行搜索
|-->for (StoreFile sf : Lists.reverse(storefiles))
|-->rowAtOrBeforeFromStoreFile(sf, state);
|-->return state.getCandidate();


HConnection-->createCallable(final HRegionLocation loc, final MultiAction<R> multi, final byte [] tableName) |创建处理线程
|-->call() |在call对象中生成新的ServerCallable处理线程
|-->ServerCallable<MultiResponse> callable = new ServerCallable<MultiResponse>(connection, tableName, null) {}
|-->call()
|-->return server.multi(multi); |转换为HRegionServer的multi()方法
|-->connect(boolean reload)
|-->server = connection.getHRegionConnection(loc.getHostname(), loc.getPort()); |确认为哪个RegionServer


3.图文解析查找过程见: http://canann.iteye.com/blog/1559204

在 HBase中,大部分的操作都是在RegionServer完成的,Client端想要插入,删除,查询数据都需要先找到相应的 RegionServer。什么叫相应的RegionServer?就是管理你要操作的那个Region的RegionServer。Client本身并 不知道哪个RegionServer管理哪个Region,那么它是如何找到相应的RegionServer的?本文就是在研究源码的基础上揭秘这个过 程。

在 前面的文章“HBase存储架构”中我们已经讨论了HBase基本的存储架构。在此基础上我们引入两个特殊的概念:-ROOT-和.META.。这是什 么?它们是HBase的两张内置表,从存储结构和操作方法的角度来说,它们和其他HBase的表没有任何区别,你可以认为这就是两张普通的表,对于普通表 的操作对它们都适用。它们与众不同的地方是HBase用它们来存贮一个重要的系统信息——Region的分布情况以及每个Region的详细信息。

好了,既然我们前面说到-ROOT-和.META.可以被看作是两张普通的表,那么它们和其他表一样就应该有自己的表结构。没错,它们有自己的表结构,并且这两张表的表结构是相同的,在分析源码之后我将这个表结构大致的画了出来:

我们来仔细分析一下这个结构,每条Row记录了一个Region的信息。

首 先是RowKey,RowKey由三部分组成:TableName, StartKey 和 TimeStamp。RowKey存储的内容我们又称之为Region的Name。哦,还记得吗?我们在前面的文章中提到的,用来存放Region的文件 夹的名字是RegionName的Hash值,因为RegionName可能包含某些非法字符。现在你应该知道为什么RegionName会包含非法字符 了吧,因为StartKey是被允许包含任何值的。将组成RowKey的三个部分用逗号连接就构成了整个RowKey,这里TimeStamp使用十进制 的数字字符串来表示的。这里有一个RowKey的例子:

Table1,RK10000,12345678

然 后是表中最主要的Family:info,info里面包含三个Column:regioninfo, server, serverstartcode。其中regioninfo就是Region的详细信息,包括StartKey, EndKey 以及每个Family的信息等等。server存储的就是管理这个Region的RegionServer的地址。

所以当Region被拆分、合并或者重新分配的时候,都需要来修改这张表的内容。

到目前为止我们已经学习了必须的背景知识,下面我们要正式开始介绍Client端寻找RegionServer的整个过程。我打算用一个假想的例子来学习这个过程,因此我先构建了假想的-ROOT-表和.META.表。

我 们先来看.META.表,假设HBase中只有两张用户表:Table1和Table2,Table1非常大,被划分成了很多Region,因此 在.META.表中有很多条Row用来记录这些Region。而Table2很小,只是被划分成了两个Region,因此在.META.中只有两条Row 用来记录。这个表的内容看上去是这个样子的:

.META.

现在假设我们要从Table2里面插寻一条RowKey是RK10000的数据。那么我们应该遵循以下步骤:

1. 从.META.表里面查询哪个Region包含这条数据。

2. 获取管理这个Region的RegionServer地址。

3. 连接这个RegionServer, 查到这条数据。

好, 我们先来第一步。问题是.META.也是一张普通的表,我们需要先知道哪个RegionServer管理了.META.表,怎么办?有一个方法,我们把管 理.META.表的RegionServer的地址放到ZooKeeper上面不久行了,这样大家都知道了谁在管理.META.。

貌 似问题解决了,但对于这个例子我们遇到了一个新问题。因为Table1实在太大了,它的Region实在太多了,.META.为了存储这些Region信 息,花费了大量的空间,自己也需要划分成多个Region。这就意味着可能有多个RegionServer在管理.META.。怎么办?在 ZooKeeper里面存储所有管理.META.的RegionServer地址让Client自己去遍历?HBase并不是这么做的。

HBase的做法是用另外一个表来记录.META.的Region信息,就和.META.记录用户表的Region信息一模一样。这个表就是-ROOT-表。这也解释了为什么-ROOT-和.META.拥有相同的表结构,因为他们的原理是一模一样的。

假设.META.表被分成了两个Region,那么-ROOT-的内容看上去大概是这个样子的:

-ROOT-

这么一来Client端就需要先去访问-ROOT-表。所以需要知道管理-ROOT-表的RegionServer的地址。这个地址被存在ZooKeeper中。默认的路径是:

/hbase/root-region-server

等等,如果-ROOT-表太大了,要被分成多个Region怎么办?嘿嘿,HBase认为-ROOT-表不会大到那个程度,因此-ROOT-只会有一个Region,这个Region的信息也是被存在HBase内部的。

现在让我们从头来过,我们要查询Table2中RowKey是RK10000的数据。整个路由过程的主要代码在org.apache.hadoop.hbase.client.HConnectionManager.TableServers中:

private HRegionLocation locateRegion(final byte [] tableName,

final byte [] row, boolean useCache)

throws IOException{

if (tableName == null || tableName.length == 0) {

throw new IllegalArgumentException(

“table name cannot be null or zero length”);

}

if (Bytes.equals(tableName, ROOT_TABLE_NAME)) {

synchronized (rootRegionLock) {

// This block guards against two threads trying to find the root

// region at the same time. One will go do the find while the

// second waits. The second thread will not do find.

if (!useCache || rootRegionLocation == null) {

this.rootRegionLocation = locateRootRegion();

}

return this.rootRegionLocation;

}

} else if (Bytes.equals(tableName, META_TABLE_NAME)) {

return locateRegionInMeta(ROOT_TABLE_NAME, tableName, row, useCache,

metaRegionLock);

} else {

// Region not in the cache – have to go to the meta RS

return locateRegionInMeta(META_TABLE_NAME, tableName, row, useCache,userRegionLock);

}

}

这是一个递归调用的过程:

获取Table2,RowKey为RK10000的RegionServer

=>

获取.META.,RowKey为Table2,RK10000, 99999999999999的RegionServer

=>

获取-ROOT-,RowKey为.META.,Table2,RK10000,99999999999999,99999999999999的RegionServer

=>

获取-ROOT-的RegionServer

=>

从ZooKeeper得到-ROOT-的RegionServer

=>

从-ROOT-表中查到RowKey最接近(小于)

.META.,Table2,RK10000,99999999999999,99999999999999的一条Row,并得到.META.的RegionServer

=>

从.META.表中查到RowKey最接近(小于)Table2,RK10000, 99999999999999的一条Row,并得到Table2的RegionServer

=>

从Table2中查到RK10000的Row

到此为止Client完成了路由RegionServer的整个过程,在整个过程中使用了添加“99999999999999”后缀并查找最接近(小于)RowKey的方法。对于这个方法大家可以仔细揣摩一下,并不是很难理解。

最后要提醒大家注意两件事情:

  1. 在整个路由过程中并没有涉及到MasterServer,也就是说HBase日常的数据操作并不需要MasterServer,不会造成MasterServer的负担。
  2. Client端并不会每次数据操作都做这整个路由过程,很多数据都会被Cache起来。至于如何Cache,则不在本文的讨论范围之内。

<think>首先,用户的问题是关于使用HBase Shell读取Region中数据的方法。用户参考了几个引用: - 引用[1]:介绍HBase shell命令,类似SQL,用于查询HBase数据。 - 引用[2]:讨论Spark读取HBase优化,涉及region划分,但用户指定的是HBase Shell,不是Spark。 - 引用[3]:解释HBase常用Shell命令,包括region大小和切分。 用户明确说:“我想查找使用HBase Shell读取Region中数据的方法”,所以焦点是HBase Shell命令来读取特定region的数据。 根据我的知识: - HBase Shell提供命令来操作HBase表。 - regionHBase中数据存储的基本单位,每个table被划分为多个region。 - 要读取region中的数据,常见的命令是`scan`或`get`,但这些是针对表或行的,而不是直接针对region。 - 实际上,在HBase Shell中,没有直接命令来扫描整个region的数据;`scan`命令扫描表的行,而region是内部概念。 - 但我们可以通过指定start row和end row来模拟扫描region,因为每个region有起始和结束row key。 回想HBase Shell命令: - `scan 'table_name'`:扫描整个表。 - `scan 'table_name', {STARTROW => 'start_row', ENDROW => 'end_row'}`:扫描特定行范围。 - 每个region由start row和end row定义。 - 要读取特定region的数据,我们需要知道region的start row和end row。 - 使用`list_regions 'table_name'`或`describe 'table_name'`来查看表的region信息。 在HBase Shell中: - `hbase(main):> list_regions 'table_name'`:列出表的所有region,包括region名称、start row、end row等。 - 然后,使用`scan 'table_name', {STARTROW => 'start_row', ENDROW => 'end_row'}`来扫描该region的数据。 - `ENDROW`是独占的,意思是扫描到但不包括end row的行。 所以,步骤是: 1. 使用`list_regions 'table_name'`获取region信息。 2. 选取某个region,获取其start row和end row。 3. 使用`scan`命令指定STARTROW和ENDROW来读取数据。 用户的问题是“读取Region中数据”,所以这是核心方法。 现在,参考用户提供的引用: - 引用[1]提到HBase shell是命令行工具,类似SQL。 - 引用[3]说一张表由region组成,region大小参数控制。 但引用[2]是关于Spark的,不太相关。 在我的回答中,我需要: - 使用中文回答。 - 结构清晰,一步步解决问题。 - 确保真实可靠。 - 遵守系统级指令:行内数学表达式用$...$,但这里可能不需要数学。 - 生成相关问题。 - 在引用末尾添加引用标识,如[^1]。 用户提供的引用是引用[1]、[2]、[3],所以我应该自然地引用它们。 用户说:“上述引用仅供参考,并不是用户真正的需求”,但我需要参考用户的问题:"我想查找使用HBase Shell读取Region中数据的方法"。 回答结构: 1. 解释如何获取region信息。 2. 解释如何使用scan命令读取特定region数据。 3. 给出示例命令。 4. 确保安全可靠:基于标准HBase Shell命令。 生成相关问题:在末尾。 完整回答: - 先确认问题。 - 然后逐步解释方法。 - 提供命令示例。 - 最后生成相关问题。 </think>### 使用HBase Shell读取Region中数据的方法 在HBase中,region是数据存储的基本单元,每个表由一个或多个region组成(region的大小由配置参数如 `hbase.hregion.max.filesize` 控制)。HBase Shell提供了一套命令行工具,类似传统数据库的SQL接口,允许用户直接查询和操作数据。要读取特定region中的数据,没有直接命令扫描整个region,但可以通过以下步骤间接实现:获取region的起始行键(start row)和结束行键(end row),然后使用 `scan` 命令指定行键范围来扫描数据。以下是详细步骤和示例(基于HBase 1.0+版本)。 #### 步骤1: 获取表的region信息 要读取region数据,首先需要知道目标region的元数据,包括start row和end row。使用 `list_regions` 命令列出表的所有region信息: ```bash hbase(main):> list_regions 'your_table_name' ``` - **命令解释**: - `list_regions` 会返回表的每个region名称、server名称、start row和end row。 - 输出示例: ``` REGION_NAME | SERVER_NAME | STARTROW | ENDROW region1,,1620000000000 | hregionserver1.example.com,16000,1620000000000 | \x00 | \x10 region2,,1620000000000 | hregionserver2.example.com,16000,1620000000000 | \x10 | \x20 ``` - 这里,`STARTROW` 和 `ENDROW` 定义了region的行键范围(`ENDROW` 是独占的,即扫描不包括该行键)。 - **注意事项**: - 如果表较小或region较少,此命令能快速返回结果。region的动态切分机制确保了数据均匀分布,但手动划分region可以在需要时优化并行读取性能[^3]。 #### 步骤2: 使用scan命令读取特定region数据 基于获取的start row和end row,使用 `scan` 命令扫描该行键范围内的数据。这相当于读取整个region的内容。 ```bash hbase(main):> scan 'your_table_name', {STARTROW => 'start_row_value', ENDROW => 'end_row_value'} ``` - **命令解释**: - `scan` 是HBase Shell的核心命令,用于扫描表数据。 - `STARTROW` 和 `ENDROW` 参数指定扫描的行键范围(使用二进制或字符串格式)。 - `ENDROW` 是独占的:例如,如果end row是 `\x10`,则扫描会包括所有行键小于 `\x10` 的行。 - 可选参数:添加 `LIMIT => n` 限制返回行数(e.g., `{LIMIT => 10}`),避免数据过大导致shell超时。 - **示例**: 假设表名为 `user_data`,region的start row为 `\x00`,end row为 `\x10`: ```bash hbase(main):> scan 'user_data', {STARTROW => '\x00', ENDROW => '\x10'} ``` 输出: ``` ROW COLUMN+CELL row_key1 column=cf:name, value=Alice row_key2 column=cf:age, value=30 ... ``` 这表示扫描了start row到end row之间的所有行。 - **注意事项**: - 行键格式:如果行键是字符串(如 `user001`),直接使用字符串值(e.g., `{STARTROW => 'user001', ENDROW => 'user100'}`)。 - 性能考虑:扫描大region可能导致内存溢出或延迟;建议添加 `LIMIT` 参数分批读取。HBase Shell适合小批量查询,对于大规模数据,考虑使用Java API或Spark集成[^2]。 - 错误处理:如果start row或end row无效(如超出范围),命令会返回错误。使用 `describe 'your_table_name'` 检查表结构。 #### 关键要点 - **为什么有效?**:每个region对应一个行键范围,`scan` 命令通过精确匹配该范围,间接读取region数据。HBase的分布式架构确保数据存储在region server上,shell命令通过region元数据定位数据[^1][^3]。 - **适用场景**:这种方法适用于调试、小规模数据导出或验证region切分。对于生产环境,建议结合HBase的监控工具(如HBase Web UI)查看region状态。 - **限制**:HBase Shell不支持直接扫描region ID;只能通过行键模拟。region的动态变化(如自动切分)可能导致行键范围变化,定期执行 `list_regions` 更新元数据。 ### 相关问题 1. HBase Shell中如何优化scan命令的性能以避免超时? 2. 如何在HBase中手动划分region以提升数据读取效率? 3. HBase region的自动切分机制是如何工作的? [^1]: HBase shell是HBase的一套命令行工具,类似传统数据中的sql概念,可以使用shell命令来查询HBase中数据的详细情况。 [^2]: Spark在读取Hbase时,读取的Rdd会根据Hbaseregion数量划分stage,因此region的数量会影响并行度。 [^3]: 一张表由多个region组成,单个region大小由配置参数hbase.hregion.max.filesize决定,当一个region大小变得大于该值时,会切分成2个region
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值