From bfad75b4a7e128a9d3af96d261818e9a094177b7 Mon Sep 17 00:00:00 2001 From: Duo Zhang Date: Tue, 22 Jan 2019 16:56:47 +0800 Subject: [PATCH] HBASE-21753 Support getting the locations for all the replicas of a region --- .../hadoop/hbase/client/HRegionLocator.java | 84 ++------- .../apache/hadoop/hbase/client/HTable.java | 2 +- .../hadoop/hbase/client/RegionLocator.java | 93 ++++++++-- .../hbase/client/TestFromClientSide.java | 15 +- .../hbase/client/TestRegionLocator.java | 166 ++++++++++++++++++ 5 files changed, 279 insertions(+), 81 deletions(-) create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestRegionLocator.java diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HRegionLocator.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HRegionLocator.java index 255911471e..3827d31f63 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HRegionLocator.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HRegionLocator.java @@ -20,36 +20,29 @@ package org.apache.hadoop.hbase.client; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; - -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.HRegionLocation; import org.apache.hadoop.hbase.MetaTableAccessor; import org.apache.hadoop.hbase.RegionLocations; import org.apache.hadoop.hbase.TableName; -import org.apache.hadoop.hbase.util.Pair; import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; - -import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; /** * An implementation of {@link RegionLocator}. Used to view region location information for a single * HBase table. Lightweight. Get as needed and just close when done. Instances of this class SHOULD * NOT be constructed directly. Obtain an instance via {@link Connection}. See * {@link ConnectionFactory} class comment for an example of how. - * - *

This class is thread safe + *

+ * This class is thread safe */ @InterfaceAudience.Private -@InterfaceStability.Stable public class HRegionLocator implements RegionLocator { private final TableName tableName; - private final ClusterConnection connection; + private final ConnectionImplementation connection; - public HRegionLocator(TableName tableName, ClusterConnection connection) { + public HRegionLocator(TableName tableName, ConnectionImplementation connection) { this.connection = connection; this.tableName = tableName; } @@ -63,22 +56,18 @@ public class HRegionLocator implements RegionLocator { // persistent state, so there is no need to do anything here. } - /** - * {@inheritDoc} - */ @Override - public HRegionLocation getRegionLocation(final byte [] row) - throws IOException { - return connection.getRegionLocation(tableName, row, false); + public HRegionLocation getRegionLocation(byte[] row, int replicaId, boolean reload) + throws IOException { + return connection.locateRegion(tableName, row, !reload, true, replicaId) + .getRegionLocation(replicaId); } - /** - * {@inheritDoc} - */ @Override - public HRegionLocation getRegionLocation(final byte [] row, boolean reload) - throws IOException { - return connection.getRegionLocation(tableName, row, reload); + public List getRegionLocations(byte[] row, boolean reload) throws IOException { + RegionLocations locs = + connection.locateRegion(tableName, row, !reload, true, RegionInfo.DEFAULT_REPLICA_ID); + return Arrays.asList(locs.getRegionLocations()); } @Override @@ -94,42 +83,9 @@ public class HRegionLocator implements RegionLocator { return regions; } - /** - * {@inheritDoc} - */ @Override - public byte[][] getStartKeys() throws IOException { - return getStartEndKeys().getFirst(); - } - - /** - * {@inheritDoc} - */ - @Override - public byte[][] getEndKeys() throws IOException { - return getStartEndKeys().getSecond(); - } - - /** - * {@inheritDoc} - */ - @Override - public Pair getStartEndKeys() throws IOException { - return getStartEndKeys(listRegionLocations()); - } - - @VisibleForTesting - Pair getStartEndKeys(List regions) { - final byte[][] startKeyList = new byte[regions.size()][]; - final byte[][] endKeyList = new byte[regions.size()][]; - - for (int i = 0; i < regions.size(); i++) { - HRegionInfo region = regions.get(i).getRegionLocation().getRegionInfo(); - startKeyList[i] = region.getStartKey(); - endKeyList[i] = region.getEndKey(); - } - - return new Pair<>(startKeyList, endKeyList); + public void clearRegionLocationCache() { + connection.clearRegionCache(tableName); } @Override @@ -137,14 +93,15 @@ public class HRegionLocator implements RegionLocator { return this.tableName; } - @VisibleForTesting - List listRegionLocations() throws IOException { + private List listRegionLocations() throws IOException { final List regions = new ArrayList<>(); MetaTableAccessor.Visitor visitor = new MetaTableAccessor.TableVisitorBase(tableName) { @Override public boolean visitInternal(Result result) throws IOException { RegionLocations locations = MetaTableAccessor.getRegionLocations(result); - if (locations == null) return true; + if (locations == null) { + return true; + } regions.add(locations); return true; } @@ -153,7 +110,4 @@ public class HRegionLocator implements RegionLocator { return regions; } - public Configuration getConfiguration() { - return connection.getConfiguration(); - } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java index fb69a2530b..33a854b969 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java @@ -158,7 +158,7 @@ public class HTable implements Table { * @param pool ExecutorService to be used. */ @InterfaceAudience.Private - protected HTable(final ClusterConnection connection, + protected HTable(final ConnectionImplementation connection, final TableBuilderBase builder, final RpcRetryingCallerFactory rpcCallerFactory, final RpcControllerFactory rpcControllerFactory, diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionLocator.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionLocator.java index a44f739b83..fbea0f5711 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionLocator.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionLocator.java @@ -21,16 +21,15 @@ package org.apache.hadoop.hbase.client; import java.io.Closeable; import java.io.IOException; import java.util.List; - +import java.util.stream.Collectors; import org.apache.hadoop.hbase.HRegionLocation; import org.apache.hadoop.hbase.TableName; import org.apache.yetus.audience.InterfaceAudience; import org.apache.hadoop.hbase.util.Pair; /** - * Used to view region location information for a single HBase table. - * Obtain an instance from an {@link Connection}. - * + * Used to view region location information for a single HBase table. Obtain an instance from an + * {@link Connection}. * @see ConnectionFactory * @see Connection * @see Table @@ -44,7 +43,9 @@ public interface RegionLocator extends Closeable { * @return Location of the row. * @throws IOException if a remote or network exception occurs */ - public HRegionLocation getRegionLocation(final byte [] row) throws IOException; + default HRegionLocation getRegionLocation(byte[] row) throws IOException { + return getRegionLocation(row, false); + } /** * Finds the region on which the given row is being served. @@ -53,16 +54,65 @@ public interface RegionLocator extends Closeable { * @return Location of the row. * @throws IOException if a remote or network exception occurs */ - public HRegionLocation getRegionLocation(final byte [] row, boolean reload) - throws IOException; + default HRegionLocation getRegionLocation(byte[] row, boolean reload) throws IOException { + return getRegionLocation(row, RegionInfo.DEFAULT_REPLICA_ID, reload); + } + + /** + * Finds the region with the given replica id on which the given row is being served. + * @param row Row to find. + * @param replicaId the replica id + * @return Location of the row. + * @throws IOException if a remote or network exception occurs + */ + default HRegionLocation getRegionLocation(byte[] row, int replicaId) throws IOException { + return getRegionLocation(row, replicaId, false); + } + + /** + * Finds the region with the given replica id on which the given row is being served. + * @param row Row to find. + * @param replicaId the replica id + * @param reload true to reload information or false to use cached information + * @return Location of the row. + * @throws IOException if a remote or network exception occurs + */ + HRegionLocation getRegionLocation(byte[] row, int replicaId, boolean reload) throws IOException; + + /** + * Find all the replicas for the region on which the given row is being served. + * @param row Row to find. + * @return Locations for all the replicas of the row. + * @throws IOException if a remote or network exception occurs + */ + default List getRegionLocations(byte[] row) throws IOException { + return getRegionLocations(row, false); + } + + /** + * Find all the replicas for the region on which the given row is being served. + * @param row Row to find. + * @param reload true to reload information or false to use cached information + * @return Locations for all the replicas of the row. + * @throws IOException if a remote or network exception occurs + */ + List getRegionLocations(byte[] row, boolean reload) throws IOException; + + /** + * Clear all the entries in the region location cache. + *

+ * This may cause performance issue so use it with caution. + */ + void clearRegionLocationCache(); /** * Retrieves all of the regions associated with this table. + *

+ * Notice that the location for region replicas other than the default replica are also returned. * @return a {@link List} of all regions associated with this table. * @throws IOException if a remote or network exception occurs */ - public List getAllRegionLocations() - throws IOException; + List getAllRegionLocations() throws IOException; /** * Gets the starting row key for every region in the currently open table. @@ -71,7 +121,9 @@ public interface RegionLocator extends Closeable { * @return Array of region starting row keys * @throws IOException if a remote or network exception occurs */ - public byte [][] getStartKeys() throws IOException; + default byte[][] getStartKeys() throws IOException { + return getStartEndKeys().getFirst(); + } /** * Gets the ending row key for every region in the currently open table. @@ -80,17 +132,30 @@ public interface RegionLocator extends Closeable { * @return Array of region ending row keys * @throws IOException if a remote or network exception occurs */ - public byte[][] getEndKeys() throws IOException; + default byte[][] getEndKeys() throws IOException { + return getStartEndKeys().getSecond(); + } /** - * Gets the starting and ending row keys for every region in the currently - * open table. + * Gets the starting and ending row keys for every region in the currently open table. *

* This is mainly useful for the MapReduce integration. * @return Pair of arrays of region starting and ending row keys * @throws IOException if a remote or network exception occurs */ - public Pair getStartEndKeys() throws IOException; + default Pair getStartEndKeys() throws IOException { + List regions = getAllRegionLocations().stream() + .filter(loc -> RegionReplicaUtil.isDefaultReplica(loc.getRegion())) + .collect(Collectors.toList()); + byte[][] startKeys = new byte[regions.size()][]; + byte[][] endKeys = new byte[regions.size()][]; + for (int i = 0, n = regions.size(); i < n; i++) { + RegionInfo region = regions.get(i).getRegion(); + startKeys[i] = region.getStartKey(); + endKeys[i] = region.getEndKey(); + } + return Pair.newPair(startKeys, endKeys); + } /** * Gets the fully qualified table name instance of this table. diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java index 21e44379c0..d9432e4f3a 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java @@ -6352,6 +6352,19 @@ public class TestFromClientSide { assertEquals(4, count); // 003 004 005 006 } + private static Pair getStartEndKeys(List regions) { + final byte[][] startKeyList = new byte[regions.size()][]; + final byte[][] endKeyList = new byte[regions.size()][]; + + for (int i = 0; i < regions.size(); i++) { + RegionInfo region = regions.get(i).getRegionLocation().getRegion(); + startKeyList[i] = region.getStartKey(); + endKeyList[i] = region.getEndKey(); + } + + return new Pair<>(startKeyList, endKeyList); + } + @Test public void testGetStartEndKeysWithRegionReplicas() throws IOException { HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(name.getMethodName())); @@ -6376,7 +6389,7 @@ public class TestFromClientSide { regionLocations.add(new RegionLocations(arr)); } - Pair startEndKeys = locator.getStartEndKeys(regionLocations); + Pair startEndKeys = getStartEndKeys(regionLocations); assertEquals(KEYS.length + 1, startEndKeys.getFirst().length); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestRegionLocator.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestRegionLocator.java new file mode 100644 index 0000000000..e0634ae6d1 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestRegionLocator.java @@ -0,0 +1,166 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.client; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import org.apache.hadoop.hbase.HBaseClassTestRule; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.HRegionLocation; +import org.apache.hadoop.hbase.ServerName; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.regionserver.Region; +import org.apache.hadoop.hbase.testclassification.ClientTests; +import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.Pair; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category({ MediumTests.class, ClientTests.class }) +public class TestRegionLocator { + + @ClassRule + public static final HBaseClassTestRule CLASS_RULE = + HBaseClassTestRule.forClass(TestRegionLocator.class); + + private static final HBaseTestingUtility UTIL = new HBaseTestingUtility(); + + private static TableName TABLE_NAME = TableName.valueOf("Locator"); + + private static byte[] FAMILY = Bytes.toBytes("family"); + + private static int REGION_REPLICATION = 3; + + private static byte[][] SPLIT_KEYS; + + @BeforeClass + public static void setUp() throws Exception { + UTIL.startMiniCluster(3); + TableDescriptor td = + TableDescriptorBuilder.newBuilder(TABLE_NAME).setRegionReplication(REGION_REPLICATION) + .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY)).build(); + SPLIT_KEYS = new byte[9][]; + for (int i = 0; i < 9; i++) { + SPLIT_KEYS[i] = Bytes.toBytes(Integer.toString(i + 1)); + } + UTIL.getAdmin().createTable(td, SPLIT_KEYS); + UTIL.waitTableAvailable(TABLE_NAME); + UTIL.getAdmin().balancerSwitch(false, true); + } + + @AfterClass + public static void tearDown() throws Exception { + UTIL.shutdownMiniCluster(); + } + + @After + public void tearDownAfterTest() throws IOException { + try (RegionLocator locator = UTIL.getConnection().getRegionLocator(TABLE_NAME)) { + locator.clearRegionLocationCache(); + } + } + + private byte[] getStartKey(int index) { + return index == 0 ? HConstants.EMPTY_START_ROW : SPLIT_KEYS[index - 1]; + } + + private byte[] getEndKey(int index) { + return index == SPLIT_KEYS.length ? HConstants.EMPTY_END_ROW : SPLIT_KEYS[index]; + } + + private void assertStartKeys(byte[][] startKeys) { + assertEquals(SPLIT_KEYS.length + 1, startKeys.length); + for (int i = 0; i < startKeys.length; i++) { + assertArrayEquals(getStartKey(i), startKeys[i]); + } + } + + private void assertEndKeys(byte[][] endKeys) { + assertEquals(SPLIT_KEYS.length + 1, endKeys.length); + for (int i = 0; i < endKeys.length; i++) { + assertArrayEquals(getEndKey(i), endKeys[i]); + } + } + + @Test + public void testStartEndKeys() throws IOException { + try (RegionLocator locator = UTIL.getConnection().getRegionLocator(TABLE_NAME)) { + assertStartKeys(locator.getStartKeys()); + assertEndKeys(locator.getEndKeys()); + Pair startEndKeys = locator.getStartEndKeys(); + assertStartKeys(startEndKeys.getFirst()); + assertEndKeys(startEndKeys.getSecond()); + } + } + + private void assertRegionLocation(HRegionLocation loc, int index, int replicaId) { + RegionInfo region = loc.getRegion(); + byte[] startKey = getStartKey(index); + assertArrayEquals(startKey, region.getStartKey()); + assertArrayEquals(getEndKey(index), region.getEndKey()); + assertEquals(replicaId, region.getReplicaId()); + ServerName expected = + UTIL.getMiniHBaseCluster().getRegionServerThreads().stream().map(t -> t.getRegionServer()) + .filter(rs -> rs.getRegions(TABLE_NAME).stream().map(Region::getRegionInfo) + .anyMatch(r -> r.containsRow(startKey) && r.getReplicaId() == replicaId)) + .findFirst().get().getServerName(); + assertEquals(expected, loc.getServerName()); + } + + @Test + public void testGetRegionLocation() throws IOException { + try (RegionLocator locator = UTIL.getConnection().getRegionLocator(TABLE_NAME)) { + for (int i = 0; i <= SPLIT_KEYS.length; i++) { + for (int replicaId = 0; replicaId < REGION_REPLICATION; replicaId++) { + assertRegionLocation(locator.getRegionLocation(getStartKey(i), replicaId), i, replicaId); + } + } + } + } + + @Test + public void testGetAllRegionLocations() throws IOException { + try (RegionLocator locator = UTIL.getConnection().getRegionLocator(TABLE_NAME)) { + List locs = locator.getAllRegionLocations(); + assertEquals(REGION_REPLICATION * (SPLIT_KEYS.length + 1), locs.size()); + Collections.sort(locs, (l1, l2) -> { + int c = Bytes.compareTo(l1.getRegion().getStartKey(), l2.getRegion().getStartKey()); + if (c != 0) { + return c; + } + return Integer.compare(l1.getRegion().getReplicaId(), l2.getRegion().getReplicaId()); + }); + for (int i = 0; i <= SPLIT_KEYS.length; i++) { + for (int replicaId = 0; replicaId < REGION_REPLICATION; replicaId++) { + assertRegionLocation(locs.get(i * REGION_REPLICATION + replicaId), i, replicaId); + } + } + } + } +} -- 2.17.1