HBase分析之Get、Scan(一)

来源:互联网 发布:mac如何下载eclipse 编辑:程序博客网 时间:2024/05/18 15:06

Get

Get操作调用的是RSRpcServices的get方法,调用过程首先找到包含数据的Region,然后从这个Region中获取需要的数据。

public GetResponse get(final RpcController controller,    final GetRequest request) throws ServiceException {  ...  try {    ...    Region region = getRegion(request.getRegion());    ...    if (get.hasClosestRowBefore() && get.getClosestRowBefore()) {      ...      r = region.getClosestRowBefore(row, family);    } else {      ...        r = region.get(clientGet);      ...    }    ...    return builder.build();    ...}

region.getClosestRowBefore实际也是调用的region.get方法,所以直接看get方法

public Result get(final Get get) throws IOException {  checkRow(get.getRow(), "Get");  // Verify families are all valid  if (get.hasFamilies()) {    for (byte [] family: get.familySet()) {      checkFamily(family);    }  } else { // Adding all families to scanner    for (byte[] family: this.htableDescriptor.getFamiliesKeys()) {      get.addFamily(family);    }  }  List<Cell> results = get(get, true);  boolean stale = this.getRegionInfo().getReplicaId() != 0;  return Result.create(results, get.isCheckExistenceOnly() ?                        !results.isEmpty() : null, stale);}

这个方法特别有意思,如果这个请求里包含了family,就检查下这些family是否存在;如果这个请求里不包含family,就把这个表里所有的family都加到这个请求里。所以不设置family时,就会遍历所有的family来找到需要的数据。然后看List<Cell> results = get(get, true);,这个方法创建了一个Scan,调用了Scan,所以要了解Get,还看Scan!

public List<Cell> get(Get get, boolean withCoprocessor) throws IOException {  ...  Scan scan = new Scan(get);  RegionScanner scanner = null;  try {    scanner = getScanner(scan);    scanner.next(results);  }  ...  return results;}

Scan

又是一段400多行的代码,完整代码就不贴了,有兴趣可以阅读下源码,在RSRpcServices#scan方法中。

整理一下代码的逻辑,共处理了Scan过程中3个阶段的操作:

  1. 获得scanner id,签订租约Leases
  2. 扫描获取数据,续约Leases
  3. 再次请求,确认数据扫描已经完成

第一步,获得scanner id,签订租约Leases。

代码中依靠scanner id来判断是否已经完成了第一阶段,如果没有完成,就查找到数据所在的Region,创建一个scanner,并把这个scanner添加到缓存中。(查找Region的过程就是从map里取一个Region,这个在Put一节中已经讲过了)

if (request.hasScannerId()) {  ...} else {  region = getRegion(request.getRegion());  ...  if (!scan.hasFamilies()) {    // Adding all families to scanner    for (byte[] family: region.getTableDesc().getFamiliesKeys()) {      scan.addFamily(family);    }  }  ...  if (scanner == null) {    scanner = region.getScanner(scan);  }  ...  scannerId = addScanner(scanner, region);  scannerName = String.valueOf(scannerId);  ttl = this.scannerLeaseTimeoutPeriod;}

scannerId = addScanner(scanner, region);方法中,scan与Region Server签订了租约,表示scanner会其缓存多长时间。通过hbase.client.scanner.timeout.perio参数设置,默认情况下为60000ms,即一分钟。租约是一个异步线程,通过线程sleep,等待到租约到期,然后清除缓存。

public void createLease(String leaseName, int leaseTimeoutPeriod, final LeaseListener listener)    throws LeaseStillHeldException {  addLease(new Lease(leaseName, leaseTimeoutPeriod, listener));}

然后设置上ttl、scanner id、moreResults(这里的moreResults为初始值true),就返回了

if (ttl > 0) {  builder.setTtl(ttl);}builder.setScannerId(scannerId);builder.setMoreResults(moreResults);return builder.build();

第二步,扫描获取数据,签订租约Leases

如果超出了租约时间继续请求,那就会抛出错误;如果在租约时间内继续请求了,那么就到了第二阶段,扫描获取数据。

首先从request中获得scanner id。

long scannerId = -1;if (request.hasScannerId()) {  scannerId = request.getScannerId();  scannerName = String.valueOf(scannerId);}

然后从缓存中获取缓存的scanner,这里就是第一步的if中省略的代码

if (request.hasScannerId()) {  rsh = scanners.get(scannerName);  scanner = rsh.s;  HRegionInfo hri = scanner.getRegionInfo();  region = regionServer.getRegion(hri.getRegionName());}

然后将租约移除,因为租约是异步的,所以很可能在执行过程中过期了,还是先移除掉。

lease = regionServer.leases.removeLease(scannerName);

然后获取数据,循环调用scanner.nextRaw方法获取数据,获取到的数据先存入values,转换完成后放入results中。如果results中的数量达到了上限或者没有更多数据了,就不再获取了,break出来。scanner.nextRaw方法比较复杂,这里涉及到多个Put版本、Delete的数据扫描,需要再开一篇来讲。

List<Result> results = new ArrayList<Result>();...while (i < rows) {  ...  moreRows = scanner.nextRaw(values, scannerContext);  if (!values.isEmpty()) {    final boolean partial = scannerContext.partialResultFormed();    Result r = Result.create(values, null, stale, partial);    lastBlock = addSize(context, r, lastBlock);    results.add(r);    i++;  }  if (limitReached || !moreRows) {    break;  }}

经过一轮扫描,如果没有更多数据、或者达到了一次请求的上限,就把已经取到的数据results放进builder返回。然后续签一个新的租约,租约时长还是1分钟。

if (scanner.isFilterDone() && results.isEmpty()) {  // 如果扫描完成,这就是第三步的事情了} else {  // 没完成,就把数据放进builder返回  addResults(builder, results, controller, RegionReplicaUtil.isDefaultReplica(region.getRegionInfo()));}// 续约Leases,addLease方法中会重新刷新租期if (scanners.containsKey(scannerName)) {  if (lease != null) regionServer.leases.addLease(lease);  ttl = this.scannerLeaseTimeoutPeriod;}

第三步,再次请求,确认数据扫描已经完成

和第二步干的事情一样,只不过这次发现扫描完成了

if (scanner.isFilterDone() && results.isEmpty()) {  moreResults = false;  results = null;}

当moreResults为false时,就会关闭缓存的scanner,closeScanner方法中会将租约移除,这样占用的资源就释放完了,就可以返回没有results的builder,确认扫描完成了。

if (!moreResults || closeScanner) {  ttl = 0;  moreResults = false;  closeScanner(region, scanner, scannerName);}

好了,Get、Scan的流程看完了,下篇看scanner.nextRaw方法

-END-

原创粉丝点击