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