UserScan的处理流程分析

前置说明

Userscan是通过client或cp中发起的scanner操作。

在Scan中通过caching属性来返回能够返回多少条数据。每次进行next时。

通过batch属性来设置每次在rs端每次nextkv时,可读取多少个kv。(在同一行的情况下)

在生成Scan实例时。最好是把family与column都设置上,这样能保证查询的最高效.

client端通过生成Scan实例,通过HTable下的例如以下方法得到ClientScanner实例

publicResultScannergetScanner(finalScan
scan)

在生成的ClientScanner实例中的callable属性的值为生成的一个ScannerCallable实例。

并通过callable.prepare(tries!=
0);方法得到此scan的startkey所在的region的location.在meta表中。

把startkey相应的location中得到此location的HRegionInfo信息。

并设置ClientScanner.currentRegion的值为当前的region.也就是startkey所在的region.

通过ClientScanner.next向rs发起rpc调用操作。

调用HRegionServer.scan

publicScanResponse
scan(finalRpcControllercontroller,finalScanRequest
request)

ClientScanner.next时,首先是发起openScanner操作,得到一个ScannerId

通过ScannerCallable.call方法:

if(scannerId==
-1L) {

this.scannerId=
openScanner();

}
else{

openScanner方法:中发起一个scan操作,通过rpc调用rs.scan

ScanRequest
request=

RequestConverter.buildScanRequest(

getLocation().getRegionInfo().getRegionName(),

this.scan,0,
false);

try{

ScanResponse
response=
getStub().scan(null,request);

longid
=response.getScannerId();

if(logScannerActivity){

LOG.info("Openscanner="
+ id+
" for scan="+
scan.toString()

+
"on region " +getLocation().toString());

}

returnid;

HregionServer.scan中对openScanner的处理:

publicScanResponse
scan(finalRpcControllercontroller,finalScanRequest
request)

throwsServiceException {

Leases.Lease
lease=
null;

String
scannerName=
null;

........................................非常多代码没有显示

requestCount.increment();

intttl
= 0;

HRegion
region=
null;

RegionScannerscanner
=null;

RegionScannerHolder
rsh=
null;

booleanmoreResults=
true;

booleancloseScanner=
false;

ScanResponse.Builder
builder= ScanResponse.newBuilder();

if(request.hasCloseScanner()){

closeScanner=
request.getCloseScanner();

}

introws
= 1;

if(request.hasNumberOfRows()){

rows=
request.getNumberOfRows();

}

if(request.hasScannerId()){

.................................非常多代码没有显示

}
else{

得到请求的HRegion实例,也就是startkey所在的HRegion

region=
getRegion(request.getRegion());

ClientProtos.Scan
protoScan=
request.getScan();

booleanisLoadingCfsOnDemandSet=
protoScan.hasLoadColumnFamiliesOnDemand();

Scan
scan= ProtobufUtil.toScan(protoScan);

//if the request doesn't set this, get the default region setting.

if(!isLoadingCfsOnDemandSet){

scan.setLoadColumnFamiliesOnDemand(region.isLoadingCfsOnDemandDefault());

}

scan.getAttribute(Scan.SCAN_ATTRIBUTES_METRICS_ENABLE);

假设scan没有设置family,把region中全部的family当成scan的family

region.prepareScanner(scan);

if(region.getCoprocessorHost()!=
null){

scanner=
region.getCoprocessorHost().preScannerOpen(scan);

}

if(scanner
==null){

运行HRegion.getScanner方法。生成HRegion.RegionScannerImpl方法

scanner=
region.getScanner(scan);

}

if(region.getCoprocessorHost()!=
null){

scanner=
region.getCoprocessorHost().postScannerOpen(scan,scanner);

}

把生成的RegionScanner加入到scanners集合容器中。

并设置scannerid(一个随机的值),

scannername是scannerid的string版本号。加入过期监控处理,

通过hbase.client.scanner.timeout.period配置过期时间,默认值为60000ms

老版本号通过hbase.regionserver.lease.period配置。

过期检查线程通过Leases完毕。

对scanner的过期处理通过一个

HregionServer.ScannerListener.leaseExpired实例来完毕。

scannerId=
addScanner(scanner,region);

scannerName= String.valueOf(scannerId);

ttl=
this.scannerLeaseTimeoutPeriod;

}

............................................非常多代码没有显示

Hregion.getScanner方法生成RegionScanner实例流程

publicRegionScannergetScanner(Scanscan)throwsIOException
{

returngetScanner(scan,null);

}

层次的调用,此时传入的kvscannerlist为null

protectedRegionScannergetScanner(Scanscan,

List<KeyValueScanner>additionalScanners)throwsIOException
{

startRegionOperation(Operation.SCAN);

try{

//Verify families are all valid

prepareScanner(scan);

if(scan.hasFamilies()){

for(byte[]
family :scan.getFamilyMap().keySet()){

checkFamily(family);

}

}

returninstantiateRegionScanner(scan,additionalScanners);

}finally{

closeRegionOperation();

}

}

终于生成一个HRegion.RegionScannerImpl实例

protectedRegionScannerinstantiateRegionScanner(Scanscan,

List<KeyValueScanner>additionalScanners)throwsIOException
{

returnnewRegionScannerImpl(scan,additionalScanners,this);

}

RegionScanner实例的生成构造方法:

RegionScannerImpl(Scanscan,List<KeyValueScanner>additionalScanners,HRegion
region)

throwsIOException {

this.region=
region;

this.maxResultSize=
scan.getMaxResultSize();

if(scan.hasFilter()){

this.filter=
newFilterWrapper(scan.getFilter());

}
else{

this.filter=
null;

}

this.batch=
scan.getBatch();

if(Bytes.equals(scan.getStopRow(),HConstants.EMPTY_END_ROW)&&
!scan.isGetScan()){

this.stopRow=
null;

}
else{

this.stopRow=
scan.getStopRow();

}

//If we are doing a get, we want to be [startRow,endRow] normally

//it is [startRow,endRow) and if startRow=endRow we get nothing.

this.isScan=
scan.isGetScan()?

-1 : 0;

//synchronize on scannerReadPoints so that nobody calculates

//getSmallestReadPoint, before scannerReadPoints is updated.

IsolationLevelisolationLevel=
scan.getIsolationLevel();

synchronized(scannerReadPoints){

if(isolationLevel==
IsolationLevel.READ_UNCOMMITTED){

//This scan can read even uncommitted transactions

this.readPt=
Long.MAX_VALUE;

MultiVersionConsistencyControl.setThreadReadPoint(this.readPt);

}
else{

this.readPt=
MultiVersionConsistencyControl.resetThreadReadPoint(mvcc);

}

scannerReadPoints.put(this,this.readPt);

}

//Here we separate all scanners into two lists - scanner that providedata required

//by the filter to operate (scanners list) and all others(joinedScanners list).

List<KeyValueScanner>scanners
=newArrayList<KeyValueScanner>();

List<KeyValueScanner>joinedScanners=
newArrayList<KeyValueScanner>();

if(additionalScanners!=
null){

scanners.addAll(additionalScanners);

}

迭代每个要进行scan的store。生成详细的StoreScanner实例。

通常情况下joinedHead的值为null

for(Map.Entry<byte[],NavigableSet<byte[]>>entry
:

scan.getFamilyMap().entrySet()){

Storestore
=stores.get(entry.getKey());

生成StoreScanner实例。通过HStore.getScanner(scan,columns);

KeyValueScannerscanner
=store.getScanner(scan,entry.getValue());

if(this.filter==
null|| !scan.doLoadColumnFamiliesOnDemand()

||this.filter.isFamilyEssential(entry.getKey())){

scanners.add(scanner);

}
else{

joinedScanners.add(scanner);

}

}

生成KeyValueHeap实例。把全部的storescanner的開始位置移动到startkey的位置并得到top的StoreScanner,

this.storeHeap=
newKeyValueHeap(scanners,comparator);

if(!joinedScanners.isEmpty()){

this.joinedHeap=
newKeyValueHeap(joinedScanners,comparator);

}

}

得到StoreScanner实例的HStore.getScanner(scan,columns)方法

publicKeyValueScannergetScanner(Scanscan,

finalNavigableSet<byte[]>
targetCols)throwsIOException {

lock.readLock().lock();

try{

KeyValueScannerscanner
=null;

if(this.getCoprocessorHost()!=
null){

scanner=
this.getCoprocessorHost().preStoreScannerOpen(this,scan,targetCols);

}

if(scanner
==null){

scanner=
newStoreScanner(this,getScanInfo(),scan,targetCols);

}

returnscanner;

}finally{

lock.readLock().unlock();

}

}

生成StoreScanner的构造方法:

publicStoreScanner(Storestore,ScanInfo
scanInfo,Scan
scan,finalNavigableSet<byte[]>columns)

throwsIOException {

this(store,scan.getCacheBlocks(),scan,columns,scanInfo.getTtl(),

scanInfo.getMinVersions());

假设设置有scan的_raw_属性时。columns的值须要为null

if(columns
!=null&&
scan.isRaw()){

thrownewDoNotRetryIOException(

"Cannotspecify any column for a raw scan");

}

matcher=
newScanQueryMatcher(scan,scanInfo,columns,

ScanType.USER_SCAN,Long.MAX_VALUE,HConstants.LATEST_TIMESTAMP,

oldestUnexpiredTS);

得到StoreFileScanner,StoreFileScanner中引用的StoreFile.Reader中引用HFileReaderV2,

HFileReaderV2的实例在StoreFile.Reader中假设已经存在。不会又一次创建,这样会加快scanner的创建时间。

//Pass columns to try to filter out unnecessary StoreFiles.

List<KeyValueScanner>scanners
=getScannersNoCompaction();

//Seek all scanners to the start of the Row (or if the exact matchingrow

//key does not exist, then to the start of the next matching Row).

//Always check bloom filter to optimize the top row seek for delete

//family marker.

if(explicitColumnQuery&&
lazySeekEnabledGlobally){

for(KeyValueScannerscanner
:scanners) {

scanner.requestSeek(matcher.getStartKey(),false,true);

}

}else{

if(!isParallelSeekEnabled){

for(KeyValueScannerscanner
:scanners) {

scanner.seek(matcher.getStartKey());

}

}
else{

parallelSeek(scanners,matcher.getStartKey());

}

}

//set storeLimit

this.storeLimit=
scan.getMaxResultsPerColumnFamily();

//set rowOffset

this.storeOffset=
scan.getRowOffsetPerColumnFamily();

//Combine all
seekedscanners with a heap

heap=
newKeyValueHeap(scanners,store.getComparator());

注冊,假设有storefile更新时。把更新后的storefile加入到这个StoreScanner中来。

this.store.addChangedReaderObserver(this);

}

发起scan的rpc操作

client端发起openScanner操作后,得到一个scannerId.此时发起scan操作。

通过ScannerCallable.call中发起call的操作,在scannerId不等于-1时。

Result []
rrs= null;

ScanRequest
request=
null;

try{

incRPCcallsMetrics();

request= RequestConverter.buildScanRequest(scannerId,caching,false,nextCallSeq);

ScanResponse
response=
null;

PayloadCarryingRpcControllercontroller=
newPayloadCarryingRpcController();

try{

controller.setPriority(getTableName());

response=
getStub().scan(controller,request);

...................................此处省去一些代码

nextCallSeq++;

longtimestamp
=System.currentTimeMillis();

//Results are returned via controller

CellScannercellScanner=
controller.cellScanner();

rrs= ResponseConverter.getResults(cellScanner,response);

HregionServer.scan方法中对scan时的处理流程:

得到scan中的caching属性的值,此值主要用来响应client返回的条数。

假设一行数据包括多个kv,算一条

introws
= 1;

if(request.hasNumberOfRows()){

rows=
request.getNumberOfRows();

}

假设client传入的scannerId有值,也就是不等于-1时,表示不是openScanner操作,检查scannerid是否过期

if(request.hasScannerId()){

rsh=
scanners.get(scannerName);

if(rsh
==null){

LOG.info("Clienttried
to access missing scanner " +scannerName);

thrownewUnknownScannerException(

"Name:" +
scannerName+
", already closed?");

}

此处主要是检查region是否发生过split操作。假设是会出现NotServingRegionException操作。

scanner=
rsh.s;

HRegionInfo
hri=
scanner.getRegionInfo();

region=
getRegion(hri.getRegionName());

if(region
!=rsh.r){
// Yes, should be the same instance

thrownewNotServingRegionException("Regionwas
re-opened after the scanner"

+
scannerName+
" was created: "+
hri.getRegionNameAsString());

}

}
else{

...................................此处省去一些生成Regionscanner的代码

}

表示有设置caching,假设是运行scan,此时的默认值为1,当前scan中设置有caching后,使用scan中设置的值

if(rows
>0) {

//if nextCallSeq does not match throw Exception straight away. Thisneeds to be

//performed even before checking of Lease.

//See HBASE-5974

是否有配置nextCallSeq的值。第一次调用时,此值为0,每调用一次加一,client也一样,每调用一次加一。

if(request.hasNextCallSeq()){

if(rsh
==null){

rsh=
scanners.get(scannerName);

}

if(rsh
!=null){

if(request.getNextCallSeq()!=
rsh.nextCallSeq){

thrownewOutOfOrderScannerNextException("ExpectednextCallSeq:
" + rsh.nextCallSeq

+
"But the nextCallSeq got from client: "+
request.getNextCallSeq()+

";request=" +TextFormat.shortDebugString(request));

}

//Increment the nextCallSeq value which is the next expected fromclient.

rsh.nextCallSeq++;

}

}

try{

先从租约管理中移出此租约,防止查找时间大于过期时间而出现的超时

//Remove lease while its being processed in server; protects againstcase

//where processing of request takes > lease expiration time.

lease=
leases.removeLease(scannerName);

生成要返回的条数的一个列表。scan.caching

List<Result>results
=newArrayList<Result>(rows);

longcurrentScanResultSize=
0;

booleandone
=false;

调用cp的preScannernext,假设返回为true,表示不在运行scan操作。

//Call coprocessor. Get region info from scanner.

if(region
!=null&&
region.getCoprocessorHost()!=
null){

Boolean
bypass=
region.getCoprocessorHost().preScannerNext(

scanner,results,rows);

if(!results.isEmpty()){

for(Result
r :results) {

if(maxScannerResultSize<
Long.MAX_VALUE){

for(Cellkv
:r.rawCells()){

//TODO

currentScanResultSize+= KeyValueUtil.ensureKeyValue(kv).heapSize();

}

}

}

}

if(bypass
!=null&&
bypass.booleanValue()){

done=
true;

}

}

运行scan操作。

Cp的preScannerNext返回为false,或没有设置cp(主要是RegionObServer)

返回给client的最大size通过hbase.client.scanner.max.result.size配置。默觉得long.maxvalue

假设scan也设置有maxResultSize,使用scan设置的值

if(!done)
{

longmaxResultSize=
scanner.getMaxResultSize();

if(maxResultSize<=
0) {

maxResultSize=
maxScannerResultSize;

}

List<Cell>values
=newArrayList<Cell>();

MultiVersionConsistencyControl.setThreadReadPoint(scanner.getMvccReadPoint());

region.startRegionOperation(Operation.SCAN);

try{

inti
= 0;

synchronized(scanner){

此处開始迭代,開始调用regionScanner(HRegion.RegionScannerImpl.nextRaw(List))进行查找,

迭代的长度为scan设置的caching的大小,假设运行RegionScanner.nextRaw(List)返回为false,时也会停止迭代

for(;
i <rows

&&currentScanResultSize<
maxResultSize;i++) {

返回的true表示还有数据。能够接着查询,否则表示此region中已经没有符合条件的数据了。

//Collect values to be returned here

booleanmoreRows
=scanner.nextRaw(values);

if(!values.isEmpty()){

if(maxScannerResultSize<
Long.MAX_VALUE){

for(Cellkv
:values) {

currentScanResultSize+= KeyValueUtil.ensureKeyValue(kv).heapSize();

}

}

results.add(Result.create(values));

}

if(!moreRows){

break;

}

values.clear();

}

}

region.readRequestsCount.add(i);

}
finally{

region.closeRegionOperation();

}

//coprocessor postNext hook

if(region
!=null&&
region.getCoprocessorHost()!=
null){

region.getCoprocessorHost().postScannerNext(scanner,results,rows,true);

}

}

假设没有能够再查找的数据时。设置response的moreResults为false

//If the scanner's filter - if any - is done with the scan

//and wants to tell the client to stop the scan. This is done bypassing

//a null result, and setting moreResults to false.

if(scanner.isFilterDone()&&
results.isEmpty()){

moreResults=
false;

results=
null;

}
else{

加入结果到response中,假设hbase.client.rpc.codec配置有codec的值,

默认取hbase.client.default.rpc.codec配置的值。默觉得KeyValueCodec

假设上面说的codec配置不为null时,把results生成为一个iterator,并生成一个匿名的CallScanner实现类

设置到scan时传入的controller中。这样能提升查询数据的读取性能。

假设没有配置codec时。默认直接把results列表设置到response中,这样响应的数据可能会比較大。

addResults(builder,results,controller);

}

}
finally{

又一次把租约放入到租约检查管理器中,此租约主要来检查client多长时间没有发起过scan的操作。

//We're done. On way out re-add the above removed lease.

//Adding resets expiration time on lease.

if(scanners.containsKey(scannerName)){

if(lease
!=null)leases.addLease(lease);

ttl=
this.scannerLeaseTimeoutPeriod;

}

}

}

client端获取响应的数据:ScannerCallable.call方法中

rrs= ResponseConverter.getResults(cellScanner,response);

ResponseConverter.getResults方法的实现

publicstaticResult[]
getResults(CellScannercellScanner,ScanResponse
response)

throwsIOException {

if(response==
null)returnnull;

//If
cellscanner,then the number of Results to return is the count of elements in the

//cellsPerResult list. Otherwise, it is how many results are embeddedinside the response.

intnoOfResults=
cellScanner!=
null?

response.getCellsPerResultCount():response.getResultsCount();

Result[]
results=
newResult[noOfResults];

for(inti
= 0; i<
noOfResults;i++) {

cellScanner假设codec配置为有值时,在rs响应时会生成一个匿名的实现

if(cellScanner!=
null){

......................................

intnoOfCells
=response.getCellsPerResult(i);

List<Cell>cells
= newArrayList<Cell>(noOfCells);

for(intj
= 0; j<
noOfCells;j++) {

try{

if(cellScanner.advance()==
false){

.....................................

String
msg=
"Results sent from server="+
noOfResults+
". But only got "+
i

+
"results completely at client. Resetting the scanner to scan again.";

LOG.error(msg);

thrownewDoNotRetryIOException(msg);

}

}
catch(IOException
ioe){

...........................................

LOG.error("Exceptionwhile
reading cells from result."

+
"Resettingthe scanner to scan again.",
ioe);

thrownewDoNotRetryIOException("Resettingthe
scanner.", ioe);

}

cells.add(cellScanner.current());

}

results[i]=
Result.create(cells);

}
else{

否则,没有设置codec。直接从response中读取出来数据,

//Result is pure
pb.

results[i]=
ProtobufUtil.toResult(response.getResults(i));

}

}

returnresults;

}

,通过nextScanner又一次得到下一个region,并发起连接去scan数据。

Do{

.........................此处省去一些代码。

if(values
!=null&&
values.length> 0) {

for(Result
rs:
values) {

cache.add(rs);

for(Cellkv
:rs.rawCells()){

//TODOmake
method in Cell or CellUtil

remainingResultSize-= KeyValueUtil.ensureKeyValue(kv).heapSize();

}

countdown--;

this.lastResult=
rs;

}

}

}while(remainingResultSize>
0 && countdown> 0 &&
nextScanner(countdown,values ==null));

对于这样的类型的查询操作。能够使用得到一个ClientScanner后,不运行close操作。

在rs的timeout前每次定期去从rs中拿一定量的数据下来。

缓存到ClientScanner的cache中。

每次next时从cache中直接拿数据

Hregion.RegionScannerImpl.nextRaw(list)方法分析

RegionScannerImpl是对RegionScanner接口的实现。

Rs的scan在运行时通过regionScanner.nextRaw(list)来获取数据。

通过regionScanner.isFilterDone来检查此region的查找是否完毕。

调用nextRaw方法,此方法调用还有一个重载方法,batch是scan中设置的每次可查询最大的单行中的多少个kv的kv个数

publicbooleannextRaw(List<Cell>outResults)

throwsIOException {

returnnextRaw(outResults,batch);

}

publicbooleannextRaw(List<Cell>outResults,intlimit)throwsIOException
{

booleanreturnResult;

调用nextInternal方法。

if(outResults.isEmpty()){

//Usually outResults is empty. This is true when next is called

//to handle scan or get operation.

returnResult=
nextInternal(outResults,limit);

}
else{

List<Cell>tmpList
=newArrayList<Cell>();

returnResult=
nextInternal(tmpList,limit);

outResults.addAll(tmpList);

}

调用filter.reset方法,清空当前row的filter的相关信息。

ResetFilters();

假设filter.filterAllRemaining()的返回值为true,时表示当前region的查找条件已经结束,不能在运行查找操作。

没有能够接着查找的须要,也就是没有很多其它要查找的行了。

if(isFilterDone()){

returnfalse;

}

................................此处省去一些代码

returnreturnResult;

}

nextInternal方法处理流程:

privatebooleannextInternal(List<Cell>results,intlimit)

throwsIOException {

if(!results.isEmpty()){

thrownewIllegalArgumentException("Firstparameter
should be an empty list");

}

RpcCallContextrpcCall
=RpcServer.getCurrentCall();

//The loop here is used only when at some point during the next wedetermine

//that due to effects of filters or otherwise, we have an empty row inthe result.

//Then we loop and try again. Otherwise, we must get out on the firstiteration via return,

//"true" if there's more data to read, "false" ifthere isn't (storeHeap is at a stop row,

//and joinedHeap has no more data to read for the last row (if set,joinedContinuationRow).

while(true){

if(rpcCall
!=null){

//If a user specifies a too-restrictive or too-slow scanner, the

//client might time out and disconnect while the server side

//is still processing the request. We should abort aggressively

//in that case.

longafterTime
=rpcCall.disconnectSince();

if(afterTime>=
0) {

thrownewCallerDisconnectedException(

"Abortingon region " +getRegionNameAsString()+
", call "+

this+
" after "+
afterTime+
" ms, since "+

"callerdisconnected");

}

}

得到通过startkeyseek后当前最小的一个kv。

//Let's see what we have in the storeHeap.

KeyValue
current=
this.storeHeap.peek();

byte[]currentRow=
null;

intoffset
= 0;

shortlength
= 0;

if(current
!=null){

currentRow=
current.getBuffer();

offset=
current.getRowOffset();

length=
current.getRowLength();

}

检查是否到了stopkey,假设是。返回false,joinedContinuationRow是多个cf的关联查找。不用去管它

booleanstopRow
=isStopRow(currentRow,offset,length);

//Check if we were getting data from the joinedHeap and hit the limit.

//If not, then it's main path - getting results from storeHeap.

if(joinedContinuationRow==
null){

//First, check if we are at a stop row. If so, there are no moreresults.

if(stopRow)
{

假设是stopRow,同一时候filter.hasFilterRow返回为true时。

可通过filterRowCells来检查要返回的kvlist,也能够用来改动要返回的kvlist

if(filter
!=null&&
filter.hasFilterRow()){

filter.filterRowCells(results);

}

returnfalse;

}

通过filter.filterRowkey来过滤检查key是否须要排除,假设是排除返回true,否则返回false

//Check if rowkey filter wants to exclude this row. If so, loop tonext.

//Technically, if we hit limits before on this row, we don't need thiscall.

if(filterRowKey(currentRow,offset,length))
{

假设rowkey是须要排除的rowkey,检查是否有下一行数据。

假设没有下一行数据。返回flase,表示当前region查找结束

否则清空当前的results,又一次进行查找

booleanmoreRows
=nextRow(currentRow,offset,length);

if(!moreRows)returnfalse;

results.clear();

continue;

}

開始运行region下此scan须要的全部store的StoreScanner的next进行查找,把查找的结果放到results列表中。

假设一行中包括有多个kv,如今查找这些kv达到传入的limit的大小的时候,返回kv_limit的一个空的kv。

(查找的大小已经达到limit(batch)的一行最大scan的kv个数,返回kv_limit),

否则表示还没有查找到limit的kv个数,可是当前row相应的全部达到条件的kv都已经查找完毕,返回最后一个kv。

返回的kv假设不是kv_limit,那么有可能是null或者是下一行的第一个kv.

KeyValue
nextKv=
populateResult(results,this.storeHeap,limit,currentRow,offset,

length);

假设达到limit的限制时,filter.hasFilterRow的值一定得是false,

否则会throw
IncompatibleFilterException

假设达到limit的限制时。返回true,当前row的全部kv查找结束,返回true能够接着向下查找

提示:假设hbase一行数据中可能包括多个kv时,最好是在scan时设置batch的属性,否则会一直查找到全部的kv结束

//Ok, we are good, let's try to get some results from the main heap.

if(nextKv
==KV_LIMIT) {

if(this.filter!=
null&&
filter.hasFilterRow()){

thrownewIncompatibleFilterException(

"Filterwhose hasFilterRow() returns true is incompatible with scan withlimit!");

}

returntrue;//
We hit the limit.

}

是否到结束行,从这一行代码中能够看出,stoprow是不包括的。由于nextKv肯定是下一行row中第一个kv的值

stopRow=
nextKv ==null||

isStopRow(nextKv.getBuffer(),nextKv.getRowOffset(),nextKv.getRowLength());

//save that the row was empty before filters applied to it.

finalbooleanisEmptyRow=
results.isEmpty();

假设是stopRow,同一时候filter.hasFilterRow返回为true时,

可通过filterRowCells来检查要返回的kvlist,也能够用来改动要返回的kvlist

//We have the part of the row necessary for filtering (all of it,usually).

//First filter with the filterRow(List).

if(filter
!=null&&
filter.hasFilterRow()){

filter.filterRowCells(results);

}

假设当前row的查找没有找到合法的kv,也就是results的列表没有值,检查是否还有下一行,

假设有,又一次进行查找。否则表示当前region的查找最结尾处,不能再进行查找,返回fasle

if(isEmptyRow){

booleanmoreRows
=nextRow(currentRow,offset,length);

if(!moreRows)returnfalse;

results.clear();

//This row was totally filtered out, if this is NOT the last row,

//we should continue on. Otherwise, nothing else to do.

if(!stopRow)continue;

returnfalse;

}

//Ok, we are done with storeHeap for this row.

//Now we may need to fetch additional, non-essential data into row.

//These values are not needed for filter to work, so we postpone their

//fetch to (possibly) reduce amount of data loads from disk.

if(this.joinedHeap!=
null){

..................................进行关联查找的代码,不显示。也不分析

}

}
else{

多个store进行关联查询,不分析,通常情况不会有

//Populating from the joined heap was stopped by limits, populate somemore.

populateFromJoinedHeap(results,limit);

}

//We may have just called populateFromJoinedMap and hit the limits. Ifthat is

//the case, we need to call it again on the next next() invocation.

if(joinedContinuationRow!=
null){

returntrue;

}

假设这次的查找,results的结果为空。表示没有查找到结果。检查是否还有下一行数据,假设有又一次进行查找,

否则返回false表示此region的查找结束

//Finally, we are done with both joinedHeap and storeHeap.

//Double check to prevent empty rows from appearing in result. It couldbe

//the case when SingleColumnValueExcludeFilter is used.

if(results.isEmpty()){

booleanmoreRows
=nextRow(currentRow,offset,length);

if(!moreRows)returnfalse;

if(!stopRow)continue;

}

非stoprow时。表示还能够有下一行的数据,也就是能够接着进行next操作。否则表示此region的查找结束

//We are done. Return the result.

return!stopRow;

}

}

UserScan时的ScanQueryMatcher.match方法处理

userscan时的ScanQueryMatcher在newRegionScannerImpl(scan,additionalScanners,this);时生成。

在生成StoreScanner时通过例如以下代码生成matcher实例。

matcher=
newScanQueryMatcher(scan,scanInfo,columns,

ScanType.USER_SCAN,Long.MAX_VALUE,HConstants.LATEST_TIMESTAMP,

oldestUnexpiredTS);

matcher.isUserScan的值此时为true.

publicMatchCodematch(KeyValuekv)
throwsIOException {

检查当前region的查找是否结束。pageFilter就是通过控制此filter中的方法来检查是否须要

if(filter
!=null&&
filter.filterAllRemaining()){

returnMatchCode.DONE_SCAN;

}

byte[]
bytes =kv.getBuffer();

intoffset
=kv.getOffset();

intkeyLength
=Bytes.toInt(bytes,offset,Bytes.SIZEOF_INT);

offset+= KeyValue.ROW_OFFSET;

intinitialOffset=
offset;

shortrowLength
=Bytes.toShort(bytes,offset,Bytes.SIZEOF_SHORT);

offset+= Bytes.SIZEOF_SHORT;

检查传入的kv是否是当前行的kv。也就是rowkey是否同样,假设当前的rowkey小于传入的rowkey。

表示如今已经next到下一行了。返回DONE,表示当前行查找结束

intret
=this.rowComparator.compareRows(row,this.rowOffset,this.rowLength,

bytes,offset,rowLength);

if(ret
<=-1) {

returnMatchCode.DONE;

}elseif(ret
>=1) {

假设当前的rowkey大于传入的rowkey。表示当前next出来的kv比方今的kv要小,运行nextrow操作。

//could optimize this, if necessary?

//Could also be called SEEK_TO_CURRENT_ROW, but this

//should be rare/never happens.

returnMatchCode.SEEK_NEXT_ROW;

}

是否跳过当前行的其他kv比較。这是一个优化项。

//optimize case.

if(this.stickyNextRow)

returnMatchCode.SEEK_NEXT_ROW;

假设当前行的全部要查找的(scan)column都查找完毕了。其他的当前行中非要scan的kv。

直接不比較,运行nextrow操作。

if(this.columns.done()){

stickyNextRow=
true;

returnMatchCode.SEEK_NEXT_ROW;

}

//PassingrowLength

offset+=
rowLength;

//Skippingfamily

bytefamilyLength=
bytes[offset];

offset+=
familyLength+ 1;

intqualLength=
keyLength-

(offset-
initialOffset)- KeyValue.TIMESTAMP_TYPE_SIZE;

检查当前KV的TTL是否过期,假设过期,检查是否SCAN中还有下一个COLUMN,假设有返回SEEK_NEXT_COL

否则返回SEEK_NEXT_ROW

longtimestamp
=Bytes.toLong(bytes,initialOffset+
keyLength- KeyValue.TIMESTAMP_TYPE_SIZE);

//check for early out based on
timestampalone

if(columns.isDone(timestamp)){

returncolumns.getNextRowOrNextColumn(bytes,offset,qualLength);

}

/*

*The delete logic is pretty complicated now.

*This is corroborated by the following:

*1. The store might be instructed to keep deleted rows around.

*2. A scan can optionally see past a delete marker now.

*3. If deleted rows are kept, we have to find out when we can

* remove the delete markers.

*4. Family delete markers are always first (regardless of their TS)

*5. Delete markers should not be counted as version

*6. Delete markers affect puts of the *same* TS

*7. Delete marker need to be version counted together with puts

* they affect

*/

bytetype
=bytes[initialOffset+
keyLength– 1];

假设当前KV是删除的KV。

if(kv.isDelete()){

此处会进入。把删除的KV加入到DeleteTracker中,默认是ScanDeleteTracker

if(!keepDeletedCells){

//first ignore delete markers if the scanner can do so, and the

//range does not include the marker

//

//during flushes and
compactionsalso ignore delete markers newer

//than the
readpointof any open scanner, this prevents deleted

//rows that could still be seen by a scanner from being collected

booleanincludeDeleteMarker=
seePastDeleteMarkers?

tr.withinTimeRange(timestamp):

tr.withinOrAfterTimeRange(timestamp);

if(includeDeleteMarker

&&kv.getMvccVersion()<=
maxReadPointToTrackVersions){

this.deletes.add(bytes,offset,qualLength,timestamp,type);

}

//Can't early out now, because DelFam come before any other keys

}

此处的检查不会进入,userscan不保留删除的数据

if(retainDeletesInOutput

|| (!isUserScan&& (EnvironmentEdgeManager.currentTimeMillis()-
timestamp)<=
timeToPurgeDeletes)

||
kv.getMvccVersion()>
maxReadPointToTrackVersions){

//always include or it is not time yet to check whether it is OK

//to purge
deltesor not

if(!isUserScan){

//if this is not a user scan (compaction), we can filter thisdeletemarkerright
here

//otherwise (i.e. a "raw" scan) we fall through to normalversion and
timerangechecking

returnMatchCode.INCLUDE;

}

}
elseif(keepDeletedCells){

if(timestamp<
earliestPutTs){

//keeping delete rows, but there are no puts older than

//this delete in the store files.

returncolumns.getNextRowOrNextColumn(bytes,offset,qualLength);

}

//else: fall through and do version counting on the

//delete markers

}
else{

returnMatchCode.SKIP;

}

//note the following next else if...

//delete marker are not subject to other delete markers

}elseif(!this.deletes.isEmpty()){

假设deleteTracker中不为空时,也就是当前行中有删除的KV,检查当前KV是否是删除的KV

提示:删除的KV在compare时,比正常的KV要小,所以在运行next操作时,delete的KV会先被查找出来。

假设是删除的KV,依据KV的删除类型。假设是版本号被删除,返回SKIP

否则假设SCAN中还有下一个要SCAN的column时。返回SEEK_NEXT_COL

否则表示当前行没有须要在进行查找的KV,返回SEEK_NEXT_ROW

DeleteResultdeleteResult=
deletes.isDeleted(bytes,offset,qualLength,

timestamp);

switch(deleteResult){

caseFAMILY_DELETED:

caseCOLUMN_DELETED:

returncolumns.getNextRowOrNextColumn(bytes,offset,qualLength);

caseVERSION_DELETED:

caseFAMILY_VERSION_DELETED:

returnMatchCode.SKIP;

caseNOT_DELETED:

break;

default:

thrownewRuntimeException("UNEXPECTED");

}

}

检查KV的时间是否在SCAN要查找的时间范围内,

inttimestampComparison=
tr.compare(timestamp);

假设大于SCAN的最大时间。返回SKIP

if(timestampComparison>=
1) {

returnMatchCode.SKIP;

}elseif(timestampComparison<=
-1) {

假设小于SCAN的最小时间,假设SCAN中还有下一个要SCAN的column时。返回SEEK_NEXT_COL

否则表示当前行没有须要在进行查找的KV,返回SEEK_NEXT_ROW

returncolumns.getNextRowOrNextColumn(bytes,offset,qualLength);

}

检查当前KV的column是否是SCAN中指定的column列表中包括的值,假设是INCLUDE

否则假设SCAN中还有下一个要SCAN的column时,返回SEEK_NEXT_COL

否则表示当前行没有须要在进行查找的KV,返回SEEK_NEXT_ROW

//STEP 1: Check if the column is part of the requested columns

MatchCodecolChecker=
columns.checkColumn(bytes,offset,qualLength,type);

假设column是SCAN中要查找的column之中的一个

if(colChecker==
MatchCode.INCLUDE){

ReturnCodefilterResponse=
ReturnCode.SKIP;

//STEP 2: Yes, the column is part of the requested columns. Check iffilter is present

if(filter
!=null){

运行filter.filterKeyValue操作。并返回filter过滤的结果

//STEP 3: Filter the key value and return if it filters out

filterResponse=
filter.filterKeyValue(kv);

switch(filterResponse){

caseSKIP:

returnMatchCode.SKIP;

caseNEXT_COL:

假设SCAN中还有下一个要SCAN的column时。返回SEEK_NEXT_COL

否则表示当前行没有须要在进行查找的KV,返回SEEK_NEXT_ROW

returncolumns.getNextRowOrNextColumn(bytes,offset,qualLength);

caseNEXT_ROW:

stickyNextRow=
true;

returnMatchCode.SEEK_NEXT_ROW;

caseSEEK_NEXT_USING_HINT:

returnMatchCode.SEEK_NEXT_USING_HINT;

default:

//Itmeans it is either include or include and seek next

break;

}

}

/*

* STEP 4: Reaching this stepmeans the column is part of the requested columns and either

* the filter is null or thefilter has returned INCLUDE or INCLUDE_AND_NEXT_COL response.

* Now check the number ofversions needed. This method call returns SKIP, INCLUDE,

* INCLUDE_AND_SEEK_NEXT_ROW,INCLUDE_AND_SEEK_NEXT_COL.

*

* FilterResponse ColumnChecker Desired behavior

* INCLUDE SKIP row has already been included, SKIP.

* INCLUDE INCLUDE INCLUDE

* INCLUDE INCLUDE_AND_SEEK_NEXT_COL INCLUDE_AND_SEEK_NEXT_COL

* INCLUDE INCLUDE_AND_SEEK_NEXT_ROW INCLUDE_AND_SEEK_NEXT_ROW

* INCLUDE_AND_SEEK_NEXT_COL SKIP row has already been included, SKIP.

* INCLUDE_AND_SEEK_NEXT_COLINCLUDE INCLUDE_AND_SEEK_NEXT_COL

* INCLUDE_AND_SEEK_NEXT_COLINCLUDE_AND_SEEK_NEXT_COL INCLUDE_AND_SEEK_NEXT_COL

* INCLUDE_AND_SEEK_NEXT_COLINCLUDE_AND_SEEK_NEXT_ROW INCLUDE_AND_SEEK_NEXT_ROW

*

* In all the above scenarios, wereturn the column checker return value except for

* FilterResponse(INCLUDE_AND_SEEK_NEXT_COL) and ColumnChecker(INCLUDE)

*/

此处主要是检查KV的是否是SCAN的最大版本号内,到这个地方。除非是KV超过了要SCAN的最大版本号,或者KV的TTL过期。

否则肯定是会包括此KV的值。

colChecker=

columns.checkVersions(bytes,offset,qualLength,timestamp,type,

kv.getMvccVersion()>
maxReadPointToTrackVersions);

//Optimizewith stickyNextRow

stickyNextRow=
colChecker==
MatchCode.INCLUDE_AND_SEEK_NEXT_ROW?
true:
stickyNextRow;

return(filterResponse==
ReturnCode.INCLUDE_AND_NEXT_COL&&

colChecker==
MatchCode.INCLUDE)?
MatchCode.INCLUDE_AND_SEEK_NEXT_COL

:
colChecker;

}

stickyNextRow= (colChecker==
MatchCode.SEEK_NEXT_ROW)?

true

:
stickyNextRow;

returncolChecker;

}

UserScan的处理流程分析的更多相关文章

  1. 8、Struts2 运行流程分析

    1.流程分析: 请求发送给 StrutsPrepareAndExecuteFilter StrutsPrepareAndExecuteFilter 询问 ActionMapper: 该请求是否是一个 ...

  2. freeswitch呼叫流程分析

    今天翻文档时发现之前整理的关于freeswitch呼叫相关的内容,写成博文分享出来也方便我以后查阅. 整体结构图 FreeswitchCore 模块加载过程 freeswitch主程序初始化时会从mo ...

  3. u-boot 流程分析

    u-boot 介绍: 对于计算机来说 , 从一开始上机通电是无法直接启动操作系统的 , 这中间需要一个引导过程 , 嵌入式Linux系统同样离不开引导程序 ,  这个启动程序就叫启动加载程序(Boot ...

  4. thttpd和cgilua安装与运行流程分析

    安装 参考如下博文安装thttpd软件 http://blog.csdn.net/21aspnet/article/details/7045845 http://blog.csdn.net/drago ...

  5. 【转】Hostapd工作流程分析

    [转]Hostapd工作流程分析 转自:http://blog.chinaunix.net/uid-30081165-id-5290531.html Hostapd是一个运行在用户态的守护进程,可以通 ...

  6. u-boot中nandflash初始化流程分析(转)

    u-boot中nandflash初始化流程分析(转) 原文地址http://zhuairlunjj.blog.163.com/blog/static/80050945201092011249136/ ...

  7. Android7.0 Phone应用源码分析(二) phone来电流程分析

    接上篇博文:Android7.0 Phone应用源码分析(一) phone拨号流程分析 今天我们再来分析下Android7.0 的phone的来电流程 1.1TelephonyFramework 当有 ...

  8. runc start container流程分析

    1.runc/start.go Action: func(context *cli.Context) error 该函数首先调用container, err := getContainer(conte ...

  9. 从注册流程 分析如何安全退出多个Activity 多种方式(附DEMO)

      退出Activity注册Android遍历   目录(?)[+] 前言 知识结构 具体方案 方案1 方法采用FLAG_ACTIVITY_CLEAR_TOP退出整个程序多activity 方案2 方 ...

随机推荐

  1. cannot be cast to javassist.util.proxy.Proxy

    工程lib冲突 javassist-3.18.1-GA.jar javassist-3.11.0.GA.jar 删除一个

  2. 【背包DP】【OpenJudge4978】宠物小精灵之收服

    宠物小精灵之收服 总时间限制: 1000ms 内存限制: 65536kB [描述] 宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事. 一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生 ...

  3. 访问控制技术- 扩展IP访问列表

    1.设置pc IP 网关 192.168.1.1 192.168.1.254 192.168.1.2 192.169.1.254 192.168.3.1 192.168.3.254 192.168.3 ...

  4. 什么是IIS并发连接数

    http://blog.csdn.net/leftfist/article/details/38407223  https://wk.baidu.com/view/2962d073f242336c1e ...

  5. Linux内核分析 - 网络

    http://blog.csdn.net/column/details/network-kernel-yoyo.html

  6. linux的dd命令详解

    一.dd命令的解释 dd:用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换. 块大小可以使用的计量表 参数注释: 1. if=文件名:输入文件名,缺省为标准输入stdin.即指定源文件.< ...

  7. 通过JBoss反序列化(CVE-2017-12149)浅谈Java反序列化漏洞

    前段时间学校学习J2EE,用到了jboss,顺便看了下jboss的反序列化,再浅谈下反序列化漏洞. Java序列化,简而言之就是把java对象转化为字节序列的过程.而反序列话则是再把字节序列恢复为ja ...

  8. 在scala中:: , +:, :+, :::, +++的区别总结

    初学Scala的人都会被Seq的各种操作符所confuse.下面简单列举一下各个Seq操作符的区别. 4种操作符的区别和联系 :: 该方法被称为cons,意为构造,向队列的头部追加数据,创造新的列表. ...

  9. P6 EPPM 16 R1安装和配置文档

    白桃花心木P6企业项目组合管理文档库  描述 链接 下载 零件号  16 R1用户和集成文档 查看库 下载 E68199-01 16 R1安装和配置文档 查看库 下载 E68198-01 描述 链接 ...

  10. Easy way to change collation of all database objects in SQL Server

    This info is from: http://www.codeproject.com/Articles/302405/The-Easy-way-of-changing-Collation-of- ...