001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase; 019 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collections; 024import java.util.List; 025import java.util.Map; 026import java.util.NavigableMap; 027import java.util.Optional; 028import java.util.SortedMap; 029import java.util.concurrent.CompletableFuture; 030import java.util.regex.Matcher; 031import java.util.regex.Pattern; 032import java.util.stream.Collectors; 033import org.apache.hadoop.hbase.MetaTableAccessor.CollectingVisitor; 034import org.apache.hadoop.hbase.MetaTableAccessor.QueryType; 035import org.apache.hadoop.hbase.MetaTableAccessor.Visitor; 036import org.apache.hadoop.hbase.client.AdvancedScanResultConsumer; 037import org.apache.hadoop.hbase.client.AsyncTable; 038import org.apache.hadoop.hbase.client.Consistency; 039import org.apache.hadoop.hbase.client.Get; 040import org.apache.hadoop.hbase.client.RegionInfo; 041import org.apache.hadoop.hbase.client.RegionReplicaUtil; 042import org.apache.hadoop.hbase.client.Result; 043import org.apache.hadoop.hbase.client.Scan; 044import org.apache.hadoop.hbase.client.Scan.ReadType; 045import org.apache.hadoop.hbase.client.TableState; 046import org.apache.hadoop.hbase.exceptions.DeserializationException; 047import org.apache.hadoop.hbase.util.Bytes; 048import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 049import org.apache.hadoop.hbase.util.Pair; 050import org.apache.yetus.audience.InterfaceAudience; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054/** 055 * The asynchronous meta table accessor. Used to read/write region and assignment information store 056 * in <code>hbase:meta</code>. 057 * @since 2.0.0 058 */ 059@InterfaceAudience.Private 060public class AsyncMetaTableAccessor { 061 062 private static final Logger LOG = LoggerFactory.getLogger(AsyncMetaTableAccessor.class); 063 064 065 /** The delimiter for meta columns for replicaIds > 0 */ 066 private static final char META_REPLICA_ID_DELIMITER = '_'; 067 068 /** A regex for parsing server columns from meta. See above javadoc for meta layout */ 069 private static final Pattern SERVER_COLUMN_PATTERN = Pattern 070 .compile("^server(_[0-9a-fA-F]{4})?$"); 071 072 public static CompletableFuture<Boolean> tableExists(AsyncTable<?> metaTable, 073 TableName tableName) { 074 return getTableState(metaTable, tableName).thenApply(Optional::isPresent); 075 } 076 077 public static CompletableFuture<Optional<TableState>> getTableState(AsyncTable<?> metaTable, 078 TableName tableName) { 079 CompletableFuture<Optional<TableState>> future = new CompletableFuture<>(); 080 Get get = new Get(tableName.getName()).addColumn(getTableFamily(), getStateColumn()); 081 long time = EnvironmentEdgeManager.currentTime(); 082 try { 083 get.setTimeRange(0, time); 084 metaTable.get(get).whenComplete((result, error) -> { 085 if (error != null) { 086 future.completeExceptionally(error); 087 return; 088 } 089 try { 090 future.complete(getTableState(result)); 091 } catch (IOException e) { 092 future.completeExceptionally(e); 093 } 094 }); 095 } catch (IOException ioe) { 096 future.completeExceptionally(ioe); 097 } 098 return future; 099 } 100 101 /** 102 * Returns the HRegionLocation from meta for the given region 103 * @param metaTable 104 * @param regionName region we're looking for 105 * @return HRegionLocation for the given region 106 */ 107 public static CompletableFuture<Optional<HRegionLocation>> getRegionLocation( 108 AsyncTable<?> metaTable, byte[] regionName) { 109 CompletableFuture<Optional<HRegionLocation>> future = new CompletableFuture<>(); 110 try { 111 RegionInfo parsedRegionInfo = MetaTableAccessor.parseRegionInfoFromRegionName(regionName); 112 metaTable.get( 113 new Get(MetaTableAccessor.getMetaKeyForRegion(parsedRegionInfo)) 114 .addFamily(HConstants.CATALOG_FAMILY)).whenComplete( 115 (r, err) -> { 116 if (err != null) { 117 future.completeExceptionally(err); 118 return; 119 } 120 future.complete(getRegionLocations(r).map( 121 locations -> locations.getRegionLocation(parsedRegionInfo.getReplicaId()))); 122 }); 123 } catch (IOException parseEx) { 124 LOG.warn("Failed to parse the passed region name: " + Bytes.toStringBinary(regionName)); 125 future.completeExceptionally(parseEx); 126 } 127 return future; 128 } 129 130 /** 131 * Returns the HRegionLocation from meta for the given encoded region name 132 * @param metaTable 133 * @param encodedRegionName region we're looking for 134 * @return HRegionLocation for the given region 135 */ 136 public static CompletableFuture<Optional<HRegionLocation>> getRegionLocationWithEncodedName( 137 AsyncTable<?> metaTable, byte[] encodedRegionName) { 138 CompletableFuture<Optional<HRegionLocation>> future = new CompletableFuture<>(); 139 metaTable.scanAll(new Scan().setReadType(ReadType.PREAD).addFamily(HConstants.CATALOG_FAMILY)) 140 .whenComplete( 141 (results, err) -> { 142 if (err != null) { 143 future.completeExceptionally(err); 144 return; 145 } 146 String encodedRegionNameStr = Bytes.toString(encodedRegionName); 147 results 148 .stream() 149 .filter(result -> !result.isEmpty()) 150 .filter(result -> MetaTableAccessor.getRegionInfo(result) != null) 151 .forEach( 152 result -> { 153 getRegionLocations(result).ifPresent( 154 locations -> { 155 for (HRegionLocation location : locations.getRegionLocations()) { 156 if (location != null 157 && encodedRegionNameStr.equals(location.getRegion() 158 .getEncodedName())) { 159 future.complete(Optional.of(location)); 160 return; 161 } 162 } 163 }); 164 }); 165 future.complete(Optional.empty()); 166 }); 167 return future; 168 } 169 170 private static Optional<TableState> getTableState(Result r) throws IOException { 171 Cell cell = r.getColumnLatestCell(getTableFamily(), getStateColumn()); 172 if (cell == null) return Optional.empty(); 173 try { 174 return Optional.of(TableState.parseFrom( 175 TableName.valueOf(r.getRow()), 176 Arrays.copyOfRange(cell.getValueArray(), cell.getValueOffset(), cell.getValueOffset() 177 + cell.getValueLength()))); 178 } catch (DeserializationException e) { 179 throw new IOException("Failed to parse table state from result: " + r, e); 180 } 181 } 182 183 /** 184 * Used to get all region locations for the specific table. 185 * @param metaTable 186 * @param tableName table we're looking for, can be null for getting all regions 187 * @return the list of region locations. The return value will be wrapped by a 188 * {@link CompletableFuture}. 189 */ 190 public static CompletableFuture<List<HRegionLocation>> getTableHRegionLocations( 191 AsyncTable<AdvancedScanResultConsumer> metaTable, Optional<TableName> tableName) { 192 CompletableFuture<List<HRegionLocation>> future = new CompletableFuture<>(); 193 getTableRegionsAndLocations(metaTable, tableName, true).whenComplete( 194 (locations, err) -> { 195 if (err != null) { 196 future.completeExceptionally(err); 197 } else if (locations == null || locations.isEmpty()) { 198 future.complete(Collections.emptyList()); 199 } else { 200 List<HRegionLocation> regionLocations = locations.stream() 201 .map(loc -> new HRegionLocation(loc.getFirst(), loc.getSecond())) 202 .collect(Collectors.toList()); 203 future.complete(regionLocations); 204 } 205 }); 206 return future; 207 } 208 209 /** 210 * Used to get table regions' info and server. 211 * @param metaTable 212 * @param tableName table we're looking for, can be null for getting all regions 213 * @param excludeOfflinedSplitParents don't return split parents 214 * @return the list of regioninfos and server. The return value will be wrapped by a 215 * {@link CompletableFuture}. 216 */ 217 private static CompletableFuture<List<Pair<RegionInfo, ServerName>>> getTableRegionsAndLocations( 218 AsyncTable<AdvancedScanResultConsumer> metaTable, final Optional<TableName> tableName, 219 final boolean excludeOfflinedSplitParents) { 220 CompletableFuture<List<Pair<RegionInfo, ServerName>>> future = new CompletableFuture<>(); 221 if (tableName.filter((t) -> t.equals(TableName.META_TABLE_NAME)).isPresent()) { 222 future.completeExceptionally(new IOException( 223 "This method can't be used to locate meta regions;" + " use MetaTableLocator instead")); 224 } 225 226 // Make a version of CollectingVisitor that collects RegionInfo and ServerAddress 227 CollectingVisitor<Pair<RegionInfo, ServerName>> visitor = new CollectingVisitor<Pair<RegionInfo, ServerName>>() { 228 private Optional<RegionLocations> current = null; 229 230 @Override 231 public boolean visit(Result r) throws IOException { 232 current = getRegionLocations(r); 233 if (!current.isPresent() || current.get().getRegionLocation().getRegion() == null) { 234 LOG.warn("No serialized RegionInfo in " + r); 235 return true; 236 } 237 RegionInfo hri = current.get().getRegionLocation().getRegion(); 238 if (excludeOfflinedSplitParents && hri.isSplitParent()) return true; 239 // Else call super and add this Result to the collection. 240 return super.visit(r); 241 } 242 243 @Override 244 void add(Result r) { 245 if (!current.isPresent()) { 246 return; 247 } 248 for (HRegionLocation loc : current.get().getRegionLocations()) { 249 if (loc != null) { 250 this.results.add(new Pair<RegionInfo, ServerName>(loc.getRegion(), loc 251 .getServerName())); 252 } 253 } 254 } 255 }; 256 257 scanMeta(metaTable, tableName, QueryType.REGION, visitor).whenComplete((v, error) -> { 258 if (error != null) { 259 future.completeExceptionally(error); 260 return; 261 } 262 future.complete(visitor.getResults()); 263 }); 264 return future; 265 } 266 267 /** 268 * Performs a scan of META table for given table. 269 * @param metaTable 270 * @param tableName table withing we scan 271 * @param type scanned part of meta 272 * @param visitor Visitor invoked against each row 273 */ 274 private static CompletableFuture<Void> scanMeta(AsyncTable<AdvancedScanResultConsumer> metaTable, 275 Optional<TableName> tableName, QueryType type, final Visitor visitor) { 276 return scanMeta(metaTable, getTableStartRowForMeta(tableName, type), 277 getTableStopRowForMeta(tableName, type), type, Integer.MAX_VALUE, visitor); 278 } 279 280 /** 281 * Performs a scan of META table for given table. 282 * @param metaTable 283 * @param startRow Where to start the scan 284 * @param stopRow Where to stop the scan 285 * @param type scanned part of meta 286 * @param maxRows maximum rows to return 287 * @param visitor Visitor invoked against each row 288 */ 289 private static CompletableFuture<Void> scanMeta(AsyncTable<AdvancedScanResultConsumer> metaTable, 290 Optional<byte[]> startRow, Optional<byte[]> stopRow, QueryType type, int maxRows, 291 final Visitor visitor) { 292 int rowUpperLimit = maxRows > 0 ? maxRows : Integer.MAX_VALUE; 293 Scan scan = getMetaScan(metaTable, rowUpperLimit); 294 for (byte[] family : type.getFamilies()) { 295 scan.addFamily(family); 296 } 297 startRow.ifPresent(scan::withStartRow); 298 stopRow.ifPresent(scan::withStopRow); 299 300 if (LOG.isDebugEnabled()) { 301 LOG.debug("Scanning META" + " starting at row=" + Bytes.toStringBinary(scan.getStartRow()) 302 + " stopping at row=" + Bytes.toStringBinary(scan.getStopRow()) + " for max=" 303 + rowUpperLimit + " with caching=" + scan.getCaching()); 304 } 305 306 CompletableFuture<Void> future = new CompletableFuture<Void>(); 307 metaTable.scan(scan, new MetaTableScanResultConsumer(rowUpperLimit, visitor, future)); 308 return future; 309 } 310 311 private static final class MetaTableScanResultConsumer implements AdvancedScanResultConsumer { 312 313 private int currentRowCount; 314 315 private final int rowUpperLimit; 316 317 private final Visitor visitor; 318 319 private final CompletableFuture<Void> future; 320 321 MetaTableScanResultConsumer(int rowUpperLimit, Visitor visitor, 322 CompletableFuture<Void> future) { 323 this.rowUpperLimit = rowUpperLimit; 324 this.visitor = visitor; 325 this.future = future; 326 this.currentRowCount = 0; 327 } 328 329 @Override 330 public void onError(Throwable error) { 331 future.completeExceptionally(error); 332 } 333 334 @Override 335 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "NP_NONNULL_PARAM_VIOLATION", 336 justification = "https://github.com/findbugsproject/findbugs/issues/79") 337 public void onComplete() { 338 future.complete(null); 339 } 340 341 @Override 342 public void onNext(Result[] results, ScanController controller) { 343 boolean terminateScan = false; 344 for (Result result : results) { 345 try { 346 if (!visitor.visit(result)) { 347 terminateScan = true; 348 break; 349 } 350 } catch (Exception e) { 351 future.completeExceptionally(e); 352 terminateScan = true; 353 break; 354 } 355 if (++currentRowCount >= rowUpperLimit) { 356 terminateScan = true; 357 break; 358 } 359 } 360 if (terminateScan) { 361 controller.terminate(); 362 } 363 } 364 } 365 366 private static Scan getMetaScan(AsyncTable<?> metaTable, int rowUpperLimit) { 367 Scan scan = new Scan(); 368 int scannerCaching = metaTable.getConfiguration().getInt(HConstants.HBASE_META_SCANNER_CACHING, 369 HConstants.DEFAULT_HBASE_META_SCANNER_CACHING); 370 if (metaTable.getConfiguration().getBoolean(HConstants.USE_META_REPLICAS, 371 HConstants.DEFAULT_USE_META_REPLICAS)) { 372 scan.setConsistency(Consistency.TIMELINE); 373 } 374 if (rowUpperLimit <= scannerCaching) { 375 scan.setLimit(rowUpperLimit); 376 } 377 int rows = Math.min(rowUpperLimit, scannerCaching); 378 scan.setCaching(rows); 379 return scan; 380 } 381 382 /** 383 * Returns an HRegionLocationList extracted from the result. 384 * @return an HRegionLocationList containing all locations for the region range or null if we 385 * can't deserialize the result. 386 */ 387 private static Optional<RegionLocations> getRegionLocations(final Result r) { 388 if (r == null) return Optional.empty(); 389 Optional<RegionInfo> regionInfo = getHRegionInfo(r, getRegionInfoColumn()); 390 if (!regionInfo.isPresent()) return Optional.empty(); 391 392 List<HRegionLocation> locations = new ArrayList<HRegionLocation>(1); 393 NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyMap = r.getNoVersionMap(); 394 395 locations.add(getRegionLocation(r, regionInfo.get(), 0)); 396 397 NavigableMap<byte[], byte[]> infoMap = familyMap.get(getCatalogFamily()); 398 if (infoMap == null) return Optional.of(new RegionLocations(locations)); 399 400 // iterate until all serverName columns are seen 401 int replicaId = 0; 402 byte[] serverColumn = getServerColumn(replicaId); 403 SortedMap<byte[], byte[]> serverMap = null; 404 serverMap = infoMap.tailMap(serverColumn, false); 405 406 if (serverMap.isEmpty()) return Optional.of(new RegionLocations(locations)); 407 408 for (Map.Entry<byte[], byte[]> entry : serverMap.entrySet()) { 409 replicaId = parseReplicaIdFromServerColumn(entry.getKey()); 410 if (replicaId < 0) { 411 break; 412 } 413 HRegionLocation location = getRegionLocation(r, regionInfo.get(), replicaId); 414 // In case the region replica is newly created, it's location might be null. We usually do not 415 // have HRL's in RegionLocations object with null ServerName. They are handled as null HRLs. 416 if (location == null || location.getServerName() == null) { 417 locations.add(null); 418 } else { 419 locations.add(location); 420 } 421 } 422 423 return Optional.of(new RegionLocations(locations)); 424 } 425 426 /** 427 * Returns the HRegionLocation parsed from the given meta row Result 428 * for the given regionInfo and replicaId. The regionInfo can be the default region info 429 * for the replica. 430 * @param r the meta row result 431 * @param regionInfo RegionInfo for default replica 432 * @param replicaId the replicaId for the HRegionLocation 433 * @return HRegionLocation parsed from the given meta row Result for the given replicaId 434 */ 435 private static HRegionLocation getRegionLocation(final Result r, final RegionInfo regionInfo, 436 final int replicaId) { 437 Optional<ServerName> serverName = getServerName(r, replicaId); 438 long seqNum = getSeqNumDuringOpen(r, replicaId); 439 RegionInfo replicaInfo = RegionReplicaUtil.getRegionInfoForReplica(regionInfo, replicaId); 440 return new HRegionLocation(replicaInfo, serverName.orElse(null), seqNum); 441 } 442 443 /** 444 * Returns a {@link ServerName} from catalog table {@link Result}. 445 * @param r Result to pull from 446 * @return A ServerName instance. 447 */ 448 private static Optional<ServerName> getServerName(final Result r, final int replicaId) { 449 byte[] serverColumn = getServerColumn(replicaId); 450 Cell cell = r.getColumnLatestCell(getCatalogFamily(), serverColumn); 451 if (cell == null || cell.getValueLength() == 0) return Optional.empty(); 452 String hostAndPort = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), 453 cell.getValueLength()); 454 byte[] startcodeColumn = getStartCodeColumn(replicaId); 455 cell = r.getColumnLatestCell(getCatalogFamily(), startcodeColumn); 456 if (cell == null || cell.getValueLength() == 0) return Optional.empty(); 457 try { 458 return Optional.of(ServerName.valueOf(hostAndPort, 459 Bytes.toLong(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()))); 460 } catch (IllegalArgumentException e) { 461 LOG.error("Ignoring invalid region for server " + hostAndPort + "; cell=" + cell, e); 462 return Optional.empty(); 463 } 464 } 465 466 /** 467 * The latest seqnum that the server writing to meta observed when opening the region. 468 * E.g. the seqNum when the result of {@link #getServerName(Result, int)} was written. 469 * @param r Result to pull the seqNum from 470 * @return SeqNum, or HConstants.NO_SEQNUM if there's no value written. 471 */ 472 private static long getSeqNumDuringOpen(final Result r, final int replicaId) { 473 Cell cell = r.getColumnLatestCell(getCatalogFamily(), getSeqNumColumn(replicaId)); 474 if (cell == null || cell.getValueLength() == 0) return HConstants.NO_SEQNUM; 475 return Bytes.toLong(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()); 476 } 477 478 /** 479 * @param tableName table we're working with 480 * @return start row for scanning META according to query type 481 */ 482 private static Optional<byte[]> getTableStartRowForMeta(Optional<TableName> tableName, 483 QueryType type) { 484 return tableName.map((table) -> { 485 switch (type) { 486 case REGION: 487 case REPLICATION: 488 byte[] startRow = new byte[table.getName().length + 2]; 489 System.arraycopy(table.getName(), 0, startRow, 0, table.getName().length); 490 startRow[startRow.length - 2] = HConstants.DELIMITER; 491 startRow[startRow.length - 1] = HConstants.DELIMITER; 492 return startRow; 493 case ALL: 494 case TABLE: 495 default: 496 return table.getName(); 497 } 498 }); 499 } 500 501 /** 502 * @param tableName table we're working with 503 * @return stop row for scanning META according to query type 504 */ 505 private static Optional<byte[]> getTableStopRowForMeta(Optional<TableName> tableName, 506 QueryType type) { 507 return tableName.map((table) -> { 508 final byte[] stopRow; 509 switch (type) { 510 case REGION: 511 case REPLICATION: 512 stopRow = new byte[table.getName().length + 3]; 513 System.arraycopy(table.getName(), 0, stopRow, 0, table.getName().length); 514 stopRow[stopRow.length - 3] = ' '; 515 stopRow[stopRow.length - 2] = HConstants.DELIMITER; 516 stopRow[stopRow.length - 1] = HConstants.DELIMITER; 517 break; 518 case ALL: 519 case TABLE: 520 default: 521 stopRow = new byte[table.getName().length + 1]; 522 System.arraycopy(table.getName(), 0, stopRow, 0, table.getName().length); 523 stopRow[stopRow.length - 1] = ' '; 524 break; 525 } 526 return stopRow; 527 }); 528 } 529 530 /** 531 * Returns the RegionInfo object from the column {@link HConstants#CATALOG_FAMILY} and 532 * <code>qualifier</code> of the catalog table result. 533 * @param r a Result object from the catalog table scan 534 * @param qualifier Column family qualifier 535 * @return An RegionInfo instance. 536 */ 537 private static Optional<RegionInfo> getHRegionInfo(final Result r, byte[] qualifier) { 538 Cell cell = r.getColumnLatestCell(getCatalogFamily(), qualifier); 539 if (cell == null) return Optional.empty(); 540 return Optional.ofNullable(RegionInfo.parseFromOrNull(cell.getValueArray(), 541 cell.getValueOffset(), cell.getValueLength())); 542 } 543 544 /** 545 * Returns the column family used for meta columns. 546 * @return HConstants.CATALOG_FAMILY. 547 */ 548 private static byte[] getCatalogFamily() { 549 return HConstants.CATALOG_FAMILY; 550 } 551 552 /** 553 * Returns the column family used for table columns. 554 * @return HConstants.TABLE_FAMILY. 555 */ 556 private static byte[] getTableFamily() { 557 return HConstants.TABLE_FAMILY; 558 } 559 560 /** 561 * Returns the column qualifier for serialized region info 562 * @return HConstants.REGIONINFO_QUALIFIER 563 */ 564 private static byte[] getRegionInfoColumn() { 565 return HConstants.REGIONINFO_QUALIFIER; 566 } 567 568 /** 569 * Returns the column qualifier for serialized table state 570 * @return HConstants.TABLE_STATE_QUALIFIER 571 */ 572 private static byte[] getStateColumn() { 573 return HConstants.TABLE_STATE_QUALIFIER; 574 } 575 576 /** 577 * Returns the column qualifier for server column for replicaId 578 * @param replicaId the replicaId of the region 579 * @return a byte[] for server column qualifier 580 */ 581 private static byte[] getServerColumn(int replicaId) { 582 return replicaId == 0 583 ? HConstants.SERVER_QUALIFIER 584 : Bytes.toBytes(HConstants.SERVER_QUALIFIER_STR + META_REPLICA_ID_DELIMITER 585 + String.format(RegionInfo.REPLICA_ID_FORMAT, replicaId)); 586 } 587 588 /** 589 * Returns the column qualifier for server start code column for replicaId 590 * @param replicaId the replicaId of the region 591 * @return a byte[] for server start code column qualifier 592 */ 593 private static byte[] getStartCodeColumn(int replicaId) { 594 return replicaId == 0 595 ? HConstants.STARTCODE_QUALIFIER 596 : Bytes.toBytes(HConstants.STARTCODE_QUALIFIER_STR + META_REPLICA_ID_DELIMITER 597 + String.format(RegionInfo.REPLICA_ID_FORMAT, replicaId)); 598 } 599 600 /** 601 * Returns the column qualifier for seqNum column for replicaId 602 * @param replicaId the replicaId of the region 603 * @return a byte[] for seqNum column qualifier 604 */ 605 private static byte[] getSeqNumColumn(int replicaId) { 606 return replicaId == 0 607 ? HConstants.SEQNUM_QUALIFIER 608 : Bytes.toBytes(HConstants.SEQNUM_QUALIFIER_STR + META_REPLICA_ID_DELIMITER 609 + String.format(RegionInfo.REPLICA_ID_FORMAT, replicaId)); 610 } 611 612 /** 613 * Parses the replicaId from the server column qualifier. See top of the class javadoc 614 * for the actual meta layout 615 * @param serverColumn the column qualifier 616 * @return an int for the replicaId 617 */ 618 private static int parseReplicaIdFromServerColumn(byte[] serverColumn) { 619 String serverStr = Bytes.toString(serverColumn); 620 621 Matcher matcher = SERVER_COLUMN_PATTERN.matcher(serverStr); 622 if (matcher.matches() && matcher.groupCount() > 0) { 623 String group = matcher.group(1); 624 if (group != null && group.length() > 0) { 625 return Integer.parseInt(group.substring(1), 16); 626 } else { 627 return 0; 628 } 629 } 630 return -1; 631 } 632}