HBase查找一条数据的过程

HBase中的Client如何路由到正确的RegionServer

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

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

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

<iframe id="iframe_0.5558239947614803" style="border: medium; border-image: none; width: 578px; height: 196px;" src="data:text/html;charset=utf8,%3Cimg%20id=%22img%22%20src=%22http://jy.shangxueba.com/GetImges/753001-756000/753508/ROOT-2.jpg?_=5949248%22%20style=%22border:none;max-width:848px%22%3E%3Cscript%3Ewindow.onload%20=%20function%20()%20%7Bvar%20img%20=%20document.getElementById('img');%20window.parent.postMessage(%7BiframeId:'iframe_0.5558239947614803',width:img.width,height:img.height%7D,%20'http://www.cnblogs.com');%7D%3C/script%3E" frameborder="0" scrolling="no"></iframe>

我们来仔细分析一下这个结构,每条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.

<iframe id="iframe_0.6764983566213066" style="border: medium; border-image: none; width: 574px; height: 372px;" src="data:text/html;charset=utf8,%3Cimg%20id=%22img%22%20src=%22http://jy.shangxueba.com/GetImges/753001-756000/753508/META.jpg?_=5949248%22%20style=%22border:none;max-width:848px%22%3E%3Cscript%3Ewindow.onload%20=%20function%20()%20%7Bvar%20img%20=%20document.getElementById('img');%20window.parent.postMessage(%7BiframeId:'iframe_0.6764983566213066',width:img.width,height:img.height%7D,%20'http://www.cnblogs.com');%7D%3C/script%3E" frameborder="0" scrolling="no"></iframe>

现在假设我们要从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-

<iframe id="iframe_0.8172739591912391" style="border: medium; border-image: none; width: 571px; height: 169px;" src="data:text/html;charset=utf8,%3Cimg%20id=%22img%22%20src=%22http://jy.shangxueba.com/GetImges/753001-756000/753508/ROOT.jpg?_=5949248%22%20style=%22border:none;max-width:848px%22%3E%3Cscript%3Ewindow.onload%20=%20function%20()%20%7Bvar%20img%20=%20document.getElementById('img');%20window.parent.postMessage(%7BiframeId:'iframe_0.8172739591912391',width:img.width,height:img.height%7D,%20'http://www.cnblogs.com');%7D%3C/script%3E" frameborder="0" scrolling="no"></iframe>

<iframe id="iframe_0.5846802344164925" style="border: medium; border-image: none; width: 399px; height: 228px;" src="data:text/html;charset=utf8,%3Cimg%20id=%22img%22%20src=%22http://jy.shangxueba.com/GetImges/753001-756000/753508/image0030.jpg?_=5949248%22%20style=%22border:none;max-width:848px%22%3E%3Cscript%3Ewindow.onload%20=%20function%20()%20%7Bvar%20img%20=%20document.getElementById('img');%20window.parent.postMessage(%7BiframeId:'iframe_0.5846802344164925',width:img.width,height:img.height%7D,%20'http://www.cnblogs.com');%7D%3C/script%3E" frameborder="0" scrolling="no"></iframe>

  

<iframe id="iframe_0.02427535458580965" style="border: medium; border-image: none; width: 500px; height: 380px;" src="data:text/html;charset=utf8,%3Cimg%20id=%22img%22%20src=%22http://jy.shangxueba.com/GetImges/753001-756000/753508/134285165.jpg?_=5949248%22%20style=%22border:none;max-width:848px%22%3E%3Cscript%3Ewindow.onload%20=%20function%20()%20%7Bvar%20img%20=%20document.getElementById('img');%20window.parent.postMessage(%7BiframeId:'iframe_0.02427535458580965',width:img.width,height:img.height%7D,%20'http://www.cnblogs.com');%7D%3C/script%3E" frameborder="0" scrolling="no"></iframe>

  

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

/hbase/root-region-server

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

现在让我们从头来过,我们要查询Table2中RowKey是RK10000的数据。整个路由过程的主要代码在   

[java]  view plain  copy
 
  1. org.apache.hadoop.hbase.client.HConnectionManager.TableServers中:  
  2. private HRegionLocation locateRegion(final byte [] tableName,  
  3.      
  4. final byte [] row, boolean useCache)  
  5. throws IOException{  
  6.      
  7. if (tableName == null || tableName.length == 0) {  
  8.      
  9. throw new IllegalArgumentException(  
  10.      
  11. “table name cannot be null or zero length”);  
  12.      
  13. }  
  14.      
  15. if (Bytes.equals(tableName, ROOT_TABLE_NAME)) {  
  16.      
  17. synchronized (rootRegionLock) {  
  18.      
  19. // This block guards against two threads trying to find the root  
  20.      
  21. // region at the same time. One will go do the find while the  
  22.      
  23. // second waits. The second thread will not do find.  
  24.      
  25. if (!useCache || rootRegionLocation == null) {  
  26.      
  27. this.rootRegionLocation = locateRootRegion();  
  28.      
  29. }  
  30.      
  31. return this.rootRegionLocation;  
  32.      
  33. }  
  34.      
  35. else if (Bytes.equals(tableName, META_TABLE_NAME)) {  
  36.      
  37. return locateRegionInMeta(ROOT_TABLE_NAME, tableName, row, useCache,  
  38. metaRegionLock);  
  39.      
  40. else {  
  41.      
  42. // Region not in the cache – have to go to the meta. RS  
  43.      
  44. return locateRegionInMeta(META_TABLE_NAME, tableName, row, useCache, userRegionLock);  
  45.      
  46. }  
  47.      
  48. }  


  

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

获取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的方法。对于这个方法大家可以仔细揣摩一下,并不是很难理解。

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

在整个路由过程中并没有涉及到MasterServer,也就是说HBase日常的数据操作并不需要MasterServer,不会造成MasterServer的负担。

Client端并不会每次数据操作都做这整个路由过程,很多数据都会被Cache起来。至于如何Cache,则不在本文的讨论范围之内。

如何简化从hbase中查询数据

为了兼容以前从关系型数据库中查询数据的接口, 让Hbase可以通过sql语句来查询其中的数据.

Hive有这样的功能, 他支持通过类似sql语句的语法来操作hbase中的数据, 但是速度太慢了, 因为hive本身就不是用来查询数据的, hive是数据仓库, 做数据分析的, 不适合我们的应用场景.

hbase本身提供的api中, 只有scan是用来查询数据的, 因此我们需要将sql语句转成scan 参考<<利用hbase的coprocessor机制来在hbase上增加sql解析引擎–(一)原因&架构>>发现是可行的

因此总体架构

sql语句--sql解析器--> sql语法节点(对象)-> scan -> hbase ->ResultScanner->List<DynaBean>

例如一个简单的sql语句

select a, b from table1 where a =1and b =2

我们通过sql解析器可以得到sql语句的各个部分, 再调用hbase api中相应的语句来达到相同的效果

// 要查询的表HTable table =newHTable(conf,"table1");// 要查询的字段Scan scan =newScan();
scan.addColumn(Bytes.toBytes("cf"),Bytes.toBytes("a"));
scan.addColumn(Bytes.toBytes("cf"),Bytes.toBytes("b"));// where条件// a = 1SingleColumnValueFilter a =newSingleColumnValueFilter(Bytes.toBytes("cf"),
        Bytes.toBytes("a"),CompareOp.EQUAL,newBinaryComparator(Bytes.toBytes(1)));
filterList.addFilter(filter);// b = 2SingleColumnValueFilter b =newSingleColumnValueFilter(Bytes.toBytes("cf"),
        Bytes.toBytes("b"),CompareOp.EQUAL,newBinaryComparator(Bytes.toBytes(2)));// andFilterList filterList =newFilterList(Operator.MUST_PASS_ALL, a, b);
scan.setFilter(filterList);

目前支持的功能

具体细节请参考单元测试

1. 从Oracle数据库中导入表数据到hbase

OracleDataLoader.loadTable("TABLE_NAME",newString[]{"PK_COLUMN_NAME"});

2. 通过SQL语句来查询hbase中的表数据

List<DynaBean> rows =HbaseQuery.select("SQL");

目前支持的SQL语句


SELECT * FROM report1                       /* 查询所有数据 */
SELECT A, B FROM report1                    /* 只查询某些列 */
SELECT * FROM report1 WHERE A =1and B =2/* 过滤条件只能是AND逻辑, 而且是等于关系 */
SELECT * FROM report1 limit 3 offset 2      /* 分页 */

如何使用

1. 在Download中下载最新版的hbase-sql.jar, 将其放在lib中.

注意项目lib的依赖
  • commons-beanutils-core-1.8.0.jar
  • commons-configuration-1.6.jar
  • commons-dbutils-1.5.jar
  • commons-lang-2.5.jar
  • commons-logging-1.1.1.jar
  • hadoop-core-1.0.4.jar
  • hbase-0.94.3.jar
  • jsqlparser-0.7.0.jar
  • log4j-1.2.16.jar
  • ojdbc14-10.2.0.5.jar
  • protobuf-java-2.4.0a.jar
  • slf4j-api-1.4.3.jar
  • slf4j-log4j12-1.4.3.jar
  • zookeeper-3.4.3.jar

2. 在项目的src中配置好hbase-site.xml, 否则无法连接到hbase来体验hbase-sql的功能

3. 测试

List<DynaBean> rows =newHbaseQueryImpl().select("select * from report1");System.out.println(rows.size());

TODO

支持更复杂的SQL查询语句

你的快速是指什么? 是根据亿级的记录中快速查询,还是说以实时的方式查询数据。

A:如果快速查询(从磁盘读数据),hbase是根据rowkey查询的,只要能快速的定位rowkey,  就能实现快速的查询,主要是以下因素:
     1、hbase是可划分成多个region,你可以简单的理解为关系型数据库的多个分区。
      2、键是排好序了的
      3、按列存储的

首先,能快速找到行所在的region(分区),假设表有10亿条记录,占空间1TB,   分列成了500个region,  1个region占2个G. 最多读取2G的记录,就能找到对应记录; 

其次,是按列存储的,其实是列族,假设分为3个列族,每个列族就是666M, 如果要查询的东西在其中1个列族上,1个列族包含1个或者多个HStoreFile,假设一个HStoreFile是128M, 该列族包含5个HStoreFile在磁盘上. 剩下的在内存中。

再次,是排好序了的,你要的记录有可能在最前面,也有可能在最后面,假设在中间,我们只需遍历2.5个HStoreFile共300M

最后,每个HStoreFile(HFile的封装),是以键值对(key-value)方式存储,只要遍历一个个数据块中的key的位置,并判断符合条件可以了。 一般key是有限的长度,假设跟value是1:19(忽略HFile上其它块),最终只需要15M就可获取的对应的记录,按照磁盘的访问100M/S,只需0.15秒。 加上块缓存机制(LRU原则),会取得更高的效率。

猜你喜欢

转载自weitao1026.iteye.com/blog/2342531