`
wangzjie
  • 浏览: 72819 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

hbase读 源码分析(0.96)

阅读更多
一、调用RPC框架:Caller和Callable

主要对象用途

caller主要是进行rpc的重复尝试调用;

callable由caller来调用,负责寻找目标regionserver以及进行rpc调用。

流程图

说明

1、hbase客户端的rpc调用框架主要通过RpcRetryingCaller来调用,它的核心方法callwithRetires()会尝试一定次数直到成功或超时。

2、callWithRetires()中的核心是调用RegionServerCallable的prepare()和call()方法。

prepare:获得目标regionserver的HRegionLocation(在缓存中获取,缓存中找不到则从hbase:meta中定位寻找),以及进行rpc交互的客户端stub。

call:进行probuf的rpc调用。

二、hdfs上的文件结构

hbase:meta数据

通过hbase hfile -p -f /user/hbase/data/hbase/meta/1588230740/info/4308912b2ee74a37be992667d3f3ffbe  可以查看hfile中的值

K: device,,1386514827053.600cdf147050cefc646b3ba5b84c6513./info:/1386515047128/DeleteFamily/vlen=0/mvcc=0 V:
K: device,,1386514885394.fcd218f786702d8660a707e96d04ca94./info:regioninfo/1386514885956/Put/vlen=47/mvcc=0 V: PBUF\x08\x92\xD6\xE8\x95\xAD(\x12\x11\x0A\x07default\x12\x06device\x1A\x00"\x09551086096(\x000\x00
K: device,,1386514885394.fcd218f786702d8660a707e96d04ca94./info:seqnumDuringOpen/1386514886485/Put/vlen=8/mvcc=0 V: \x00\x00\x00\x00\x00\x00\x01t
K: device,,1386514885394.fcd218f786702d8660a707e96d04ca94./info:seqnumDuringOpen/1386514885956/Put/vlen=8/mvcc=0 V: \x00\x00\x00\x00\x00\x00\x00\x01
K: device,,1386514885394.fcd218f786702d8660a707e96d04ca94./info:server/1386514886485/Put/vlen=19/mvcc=0 V: 192.168.1.103:60020
K: device,,1386514885394.fcd218f786702d8660a707e96d04ca94./info:server/1386514885956/Put/vlen=19/mvcc=0 V: 192.168.1.103:60020
K: device,,1386514885394.fcd218f786702d8660a707e96d04ca94./info:serverstartcode/1386514886485/Put/vlen=8/mvcc=0 V: \x00\x00\x01B\xD2\xB7\xE1\x12
K: device,,1386514885394.fcd218f786702d8660a707e96d04ca94./info:serverstartcode/1386514885956/Put/vlen=8/mvcc=0 V: \x00\x00\x01B\xD2\xB7\xE1\x12
K: device,551086096,1386514885394.3d48139a0840cc6aa349988bc05b4416./info:regioninfo/1386514885956/Put/vlen=47/mvcc=0 V: PBUF\x08\x92\xD6\xE8\x95\xAD(\x12\x11\x0A\x07default\x12\x06device\x1A\x09551086096"\x00(\x000\x00
K: device,551086096,1386514885394.3d48139a0840cc6aa349988bc05b4416./info:seqnumDuringOpen/1386514886475/Put/vlen=8/mvcc=0 V: \x00\x00\x00\x00\x00\x00\x01u
K: device,551086096,1386514885394.3d48139a0840cc6aa349988bc05b4416./info:seqnumDuringOpen/1386514885956/Put/vlen=8/mvcc=0 V: \x00\x00\x00\x00\x00\x00\x00\x01
K: device,551086096,1386514885394.3d48139a0840cc6aa349988bc05b4416./info:server/1386514886475/Put/vlen=19/mvcc=0 V: 192.168.1.103:60020
K: device,551086096,1386514885394.3d48139a0840cc6aa349988bc05b4416./info:server/1386514885956/Put/vlen=19/mvcc=0 V: 192.168.1.103:60020
K: device,551086096,1386514885394.3d48139a0840cc6aa349988bc05b4416./info:serverstartcode/1386514886475/Put/vlen=8/mvcc=0 V: \x00\x00\x01B\xD2\xB7\xE1\x12
K: device,551086096,1386514885394.3d48139a0840cc6aa349988bc05b4416./info:serverstartcode/1386514885956/Put/vlen=8/mvcc=0 V: \x00\x00\x01B\xD2\xB7\xE1\x12
K: hbase:namespace,,1386514747061.b1fc08a6dffc6bf4d781fcdc7fddfd41./info:regioninfo/1386514747384/Put/vlen=39/mvcc=0 V: PBUF\x08\xB5\x9D\xE0\x95\xAD(\x12\x12\x0A\x05hbase\x12\x09namespace\x1A\x00"\x00(\x000\x00
K: hbase:namespace,,1386514747061.b1fc08a6dffc6bf4d781fcdc7fddfd41./info:seqnumDuringOpen/1386514747621/Put/vlen=8/mvcc=0 V: \x00\x00\x00\x00\x00\x00\x00\x01
K: hbase:namespace,,1386514747061.b1fc08a6dffc6bf4d781fcdc7fddfd41./info:server/1386514747621/Put/vlen=19/mvcc=0 V: 192.168.1.103:60020
K: hbase:namespace,,1386514747061.b1fc08a6dffc6bf4d781fcdc7fddfd41./info:serverstartcode/1386514747621/Put/vlen=8/mvcc=0 V: \x00\x00\x01B\xD2\xB7\xE1\x12

device有几个region就会在device下出现多条,device后的名称是device,startRow,建表时间戳.MD5(device,startRow,建表时间戳)中的MD5值
例:

device,,1386514885394.fcd218f786702d8660a707e96d04ca94
device,551086096,1386514885394.3d48139a0840cc6aa349988bc05b4416

各表在hdfs中的位置

hbase:meta : /user/hbase/data/hbase/meta/

其他表:/user/hbase/data/default下,以device表为例(/user/hbase/data/default/device/)看其hdfs文件路径:

/user/hbase/data/default/device下的信息
/user/hbase/data/default/device/.tabledesc  #表信息
/user/hbase/data/default/device/.tmp
#device表有两个region,则其目录下有两个文件夹,命名方式是MD5(device,startRow,建表时间戳)
/user/hbase/data/default/device/3d48139a0840cc6aa349988bc05b4416
/user/hbase/data/default/device/fcd218f786702d8660a707e96d04ca94
 
以3d48139a0840cc6aa349988bc05b4416为例看该region下的信息
/user/hbase/data/default/device/3d48139a0840cc6aa349988bc05b4416/.regioninfo
/user/hbase/data/default/device/3d48139a0840cc6aa349988bc05b4416/info  #info是ColumnFamily=info名称
/user/hbase/data/default/device/3d48139a0840cc6aa349988bc05b4416/action  #info是ColumnFamily=action名称
/user/hbase/data/default/device/3d48139a0840cc6aa349988bc05b4416/recovered.edits
 
/user/hbase/data/default/device/3d48139a0840cc6aa349988bc05b4416/info/25f785b2abb2470ca6b094f2c8f3bd9e就是hfile对应的hdfs文件了

三、get/scan client源码

主要对象说明

HTable: 负责表的get、scan操作。初始化时会创建HConnection对象以及缓存一些hbase:meta中的记录(默认10条)

HConnection/HConnectionManager.HConnectionImplementation:负责定位目标row对应的region(HRegionLocation对象)、缓存rpc stub、缓存相应表的region信息以及与zookeeper、与regionserver建立rpc交互。

HRegionLocation: region对象,包括了region信息(HRegionInfo)以及regionserver服务器信息(ServerName)。

ZookeeperRegistry:与zookeeper进行交互,主要获得hbase:meta的相关信息(meta表所在的regionserver)。

ResultScanner/ClientScanner: client端进行Scan操作的主要对象,其会创建Caller进行rpc调用(该调用中的callable是ScannerCallable)。

ScannerCallable:RegionServerCallable的子类,其call方法中有三个动作:打开scanner、获得数据、关闭scanner。

client get代码

HTable table = new HTable(conf, "device");
Get g = new Get(Bytes.toBytes("200006518"));
Result rs = table.get(g);
for(KeyValue kv : rs.raw()){
    System.out.println(kv);
}

源码流程

 

说明

1、创建HTable对象时会创建HConnectionImplementation对象,其中初始化了zookeeperRegitry专门用于与zookeeper进行交互。

2、HTable.finishSetup():判断目标表是否存在以及预先缓存hbase:meta表中的一些HRegionLocation记录。

3、HConnection.locateRegion()用于定位目标表的HRegionLocation记录。其中会调用:

     a、HConnetion.locateRegionInMeta()用于非meta表,即在hbase:meta表中定位目标表的HRegionLocation记录。

     b、HConnection.registry.getMetaRegionLoaction()用于定位hbase:meta的HReionLocation,从而创建ClientService与habse:meta所在的regionserver进行rpc交互。

4、prefetchRegionCache()预获取hbase:meta中的记录的操作中需要加锁,防止多个线程重复获取。预获取缓存过程是通过MetaScanner来实现的。

5、MetaScanner会先通过getRowOrBefore()尝试获得一条目标表的记录来判断目标表region记录是否存在,再进行scan操作获得相应记录(默认10条)。

6、scan操作主要通过ScannerCallable来实现。每次scan时都会与regionserver进行交互:

     a、打开scanner实始化scannerId。在openScanner()时服务器会保存存key=scannerId, value=RegionScannerHolder的Map,在查询时只要根据scannerId就可以得到对应的InternalScanner以及Region。

     b、rpc ScanRequest操作获得相应记录,在ClientScanner.next()时调用,next()方法第一次调用时获得数据并缓存,以后调用next时只从缓存中取数据。

     c、关闭该次scanner

7、ClientScanner的next()在缓存无数据时,如果ClientScanner未关闭(即currentRegion的endKey非空或者未达到stopRow),还会使用ScannerCallable进行下一轮scan。否则scan操作就结束。

8、HTable创建完成后,就开始调用HTable.get/scan方法(scan方法使用的是前面的ClientScanner逻辑)。get方法中就直接调用caller来进行rpc,其中调用callable的prepare()时,会从创建HTable时预先得到的region缓存中获得HRegionLocation,如果找不到的话就会重新去loateRegion。

HRegionLocation缓存

Map<TableName, SoftValueSortedMap<byte[], HRegionLocation>> cachedRegionLocations:  即<tableName,<startKey, HRegionLocation>>

四、读server端

HRegionServer上的get操作最终也会转化成Scan操作。而Scan的操作主要使用Hbase上的各种scanner(相当于迭代器)。hbase返回的结果会按KeyValue从小到大的顺序返回。

Scanner类图

scanner说明

scanner可分成四类:

1、InternalScanner: 可以认为是scanner的scanner,其每个元素都有子scanner。如RegionScanner中有StoreScanner,StoreScanner中有KeyValueScanner。

2、KeyValueScanner: 用于访问KeyValue值,主要是MemStoreScanner访问MemStore中的KeyValue和StoreFileScanner访问StoreFile中的KeyValue。

查询时由于目标数据还在memstore中未flush到storefile中 或已经多次flush到多个storefile,所以这些都是Scanner的目标。

3、KeyValueHeap: 保存了KeyValueScanner的优先队列。

4、HFileScanner: 访问HFile。

其中InternalScanner的next方法是next(List<Cell> result), 而KeyValueScanner的next方法没有参数,直接返回KeyValue。

next方法是获取数据并往后移,peek方法是获取数据但不往后移。

get/scan源码过程

说明

1、scanner初始化过程

     a、在Scan过程中先创建RegionScanner对象,其中会创建其familyColumn下的各个StoreScanner并存在RegionScanner的storeHeap中。

     b、StoreScanner在初始化时会分别从MemStore和StoreFile中获得Scanner并存到StoreScanner.heap中。其中的selectScannersFrom()方法会调用KeyValueScanner.shouldUseScanner()进行bloom filter(只针对storefile)、time range、memOnly、filesOnly等过滤。

2、scanner遍历过程

     a、起点:RegionScanner.next(),会调用其storeHeap.next方法即得到StoreScanner

     b、StoreScanner.next()会循环调用其heap.next()获得相应的KeyValueScanner,并调用MemStoreScanner/StoreFileScanner的next()方法。

优先级队列中scanner的大小比较规则

1、KeyValue的大小比较规则,优先级从大到小依次为RowKey cf+cq timestamp type, 除了ts是降序外,其他都是升序。比如说,在比较2个KeyValue时,先比较RowKey的大小('a' < 'b'),相同的情况下比较cf+cq的大小('cf1:q1'<'cf2:q1'<'cf2:q2'),如果还是相同的话就比较时间戳(3042211081<3042211080,注意 时间戳的long值越大,表示数据越新,在从小到大的队列中越靠前),如果上述仍然还相同则比较TYPE('DeleteFamily' < 'DeleteColumn' < 'Delete' < Put) 

2、KeyValueScanner的大小比较规则:其大小有peek()即第一个元素(即最小的)获取到KeyValue大小决定,即 KeyValueScanner1.peek() < KeyValueScanner2.peek() 则KeyValueScanner1 < KeyValueScanner2

优先队列KeyValueHeap.next(List<Cell> result):InternalScanner.next(List<Cell> result)

public boolean next(List<Cell> result, int limit) throws IOException {
    if (this.current == null) {
      return false;
    }
    InternalScanner currentAsInternal = (InternalScanner)this.current;//regionScanner中的storeHeap中的storeScanner走此路
    boolean mayContainMoreRows = currentAsInternal.next(result, limit);//这个是StoreScanner.next()
    KeyValue pee = this.current.peek();
    if (pee == null || !mayContainMoreRows) {
      this.current.close();
    else {
      this.heap.add(this.current);//还有数据则再加入到优先队列中
    }
    this.current = pollRealKV();
    return (this.current != null);
  }

优先队列KeyValueHeap.next():KeyValueScanner.next()

public KeyValue next()  throws IOException {
    if(this.current == null) {
      return null;
    }
    KeyValue kvReturn = this.current.next();//取出当前KeyValueScanner的要返回的KeyValue,并移除该元素
    KeyValue kvNext = this.current.peek();  //取出当前KeyValueScanner的下个元素
    if (kvNext == null) {
      this.current.close();
      this.current = pollRealKV();
    else {
      KeyValueScanner topScanner = this.heap.peek();//只取第一个数据不移除,即获得位于优先队列的头KeyValueScanner
      if (topScanner == null ||
          this.comparator.compare(kvNext, topScanner.peek()) >= 0) {//如果当前KeyValueScanner大于头Scanner,则下一个要取的数据位于头Scanner中。
        this.heap.add(this.current); //把当前的KeyValueScanner放到优先队列中排序
        this.current = pollRealKV();//取新的KeyValueScanner
      }
    }
    return kvReturn;
  }

KeyValueScanner.next说明

1、优先队列的初始化:

      a、在添加storefile前会根据timestamp,columns,bloomfilter过滤掉一部分。同时,storefile中最大的rowkey比当前查询的rowkey小的记录也会被过滤。

      b、storefile的内部有三维有序的,但是各个storefile之间并不是有序的。比如,storefile1中可能有rowkey为100到110的记录,而storefile2可能有rowkey为105到115的数据,所以storefile之间的rowkey的范围很有可能有交叉,所以查询数据的过程也不可能是对storefile的顺序查找。hbase采用的是优先队列方式来存储,其排序算法是根据上面的KeyValueScanner从小到大的顺序进行排序。

2、查询(以KeyValueScanner都是storefile为例,MemStoreScanner和其一样的规则):

      a、获取数据。通过poll取出队列的头storefile,会从storefile读取一条记录返回。

      b、决定下一条要scanner的KeyValue。当前storefile的下条记录并不一定是查询结果的下一条记录,因为队列的比较顺序是比较的每个storefile的第一条符合要求的KeyValue。所以,hbase会继续从队列中剩下的storefile取第一条记录,把该记录与当前storefile的第二条记录做比较,如果前者大,那么返回当前storefile的第二条记录;如果后者大,则会把当前storefile放回队列重新排序,在重新取队列的头storefile。然后重复上面的整个过程。

3、举例:表device,有两个storefile,storefile1中包括rowkey100,rowkey110;storefile2中包括rowkey104,rowkey108。 执行scan ‘device′扫描表device中的所有的记录。

根据前面提到的排序规则,队列中会有2个元素,按顺序分别为storefile1,storefile2。

       a、取出storefile1中的第一条记录rowkey100,并返回该结果

       b、取出storefile1中的下一条记录rowkey110,同时取出队列剩余storefile的第一条记录rowkey104,经过比较rowkey110大于rowkey104,则将storefile1放回队列中

       c、因为队列是有序的队列,会重新对storefile进行排序,因为此时storefile1的最小rowkey为110,而storefile2的最小rowkey为104,所以排序的结果为storefile2,storefile1

       d、重复上面的过程,直到查不到记录为止。

 最后查到的结果为:rowkey100,rowkey104,rowkey108,rowkey110。

BlockCache:从storefile中获得相应KeyValue时会先从BlockCache中查看是否有该数据,有则在BlockCache中获得数据,否则就得去查询HFile数据。

 

MemStore以及StoreFile以及HFile这几块还未深入研究,待更新...

 

note:图片请右键在新页面打开

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics