一、调用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:图片请右键在新页面打开