2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 package org
.apache
.hadoop
.hbase
.client
;
21 import static org
.apache
.hadoop
.hbase
.util
.ConcurrentMapUtils
.computeIfAbsent
;
24 import java
.util
.Map
.Entry
;
26 import java
.util
.concurrent
.ConcurrentMap
;
27 import java
.util
.concurrent
.ConcurrentNavigableMap
;
28 import java
.util
.concurrent
.CopyOnWriteArraySet
;
30 import org
.apache
.hadoop
.hbase
.HConstants
;
31 import org
.apache
.hadoop
.hbase
.HRegionLocation
;
32 import org
.apache
.hadoop
.hbase
.RegionLocations
;
33 import org
.apache
.hadoop
.hbase
.ServerName
;
34 import org
.apache
.hadoop
.hbase
.TableName
;
35 import org
.apache
.hadoop
.hbase
.types
.CopyOnWriteArrayMap
;
36 import org
.apache
.hadoop
.hbase
.util
.Bytes
;
37 import org
.apache
.yetus
.audience
.InterfaceAudience
;
38 import org
.slf4j
.Logger
;
39 import org
.slf4j
.LoggerFactory
;
42 * A cache implementation for region locations from meta.
44 @InterfaceAudience.Private
45 public class MetaCache
{
47 private static final Logger LOG
= LoggerFactory
.getLogger(MetaCache
.class);
50 * Map of table to table {@link HRegionLocation}s.
52 private final ConcurrentMap
<TableName
, ConcurrentNavigableMap
<byte[], RegionLocations
>>
53 cachedRegionLocations
= new CopyOnWriteArrayMap
<>();
55 // The presence of a server in the map implies it's likely that there is an
56 // entry in cachedRegionLocations that map to this server; but the absence
57 // of a server in this map guarantees that there is no entry in cache that
58 // maps to the absent server.
59 // The access to this attribute must be protected by a lock on cachedRegionLocations
60 private final Set
<ServerName
> cachedServers
= new CopyOnWriteArraySet
<>();
62 private final MetricsConnection metrics
;
64 public MetaCache(MetricsConnection metrics
) {
65 this.metrics
= metrics
;
69 * Search the cache for a location that fits our table and row key.
70 * Return null if no suitable region is located.
72 * @return Null or region location found in cache.
74 public RegionLocations
getCachedLocation(final TableName tableName
, final byte [] row
) {
75 ConcurrentNavigableMap
<byte[], RegionLocations
> tableLocations
=
76 getTableLocations(tableName
);
78 Entry
<byte[], RegionLocations
> e
= tableLocations
.floorEntry(row
);
80 if (metrics
!= null) metrics
.incrMetaCacheMiss();
83 RegionLocations possibleRegion
= e
.getValue();
85 // make sure that the end key is greater than the row we're looking
86 // for, otherwise the row actually belongs in the next region, not
87 // this one. the exception case is when the endkey is
88 // HConstants.EMPTY_END_ROW, signifying that the region we're
89 // checking is actually the last region in the table.
90 byte[] endKey
= possibleRegion
.getRegionLocation().getRegion().getEndKey();
91 // Here we do direct Bytes.compareTo and not doing CellComparator/MetaCellComparator path.
92 // MetaCellComparator is for comparing against data in META table which need special handling.
93 // Not doing that is ok for this case because
94 // 1. We are getting the Region location for the given row in non META tables only. The compare
95 // checks the given row is within the end key of the found region. So META regions are not
97 // 2. Even if META region comes in, its end key will be empty byte[] and so Bytes.equals(endKey,
98 // HConstants.EMPTY_END_ROW) check itself will pass.
99 if (Bytes
.equals(endKey
, HConstants
.EMPTY_END_ROW
) ||
100 Bytes
.compareTo(endKey
, 0, endKey
.length
, row
, 0, row
.length
) > 0) {
101 if (metrics
!= null) metrics
.incrMetaCacheHit();
102 return possibleRegion
;
105 // Passed all the way through, so we got nothing - complete cache miss
106 if (metrics
!= null) metrics
.incrMetaCacheMiss();
111 * Put a newly discovered HRegionLocation into the cache.
112 * @param tableName The table name.
113 * @param source the source of the new location
114 * @param location the new location
116 public void cacheLocation(final TableName tableName
, final ServerName source
,
117 final HRegionLocation location
) {
118 assert source
!= null;
119 byte [] startKey
= location
.getRegion().getStartKey();
120 ConcurrentMap
<byte[], RegionLocations
> tableLocations
= getTableLocations(tableName
);
121 RegionLocations locations
= new RegionLocations(new HRegionLocation
[] {location
}) ;
122 RegionLocations oldLocations
= tableLocations
.putIfAbsent(startKey
, locations
);
123 boolean isNewCacheEntry
= (oldLocations
== null);
124 if (isNewCacheEntry
) {
125 if (LOG
.isTraceEnabled()) {
126 LOG
.trace("Cached location: " + location
);
128 addToCachedServers(locations
);
132 // If the server in cache sends us a redirect, assume it's always valid.
133 HRegionLocation oldLocation
= oldLocations
.getRegionLocation(
134 location
.getRegion().getReplicaId());
135 boolean force
= oldLocation
!= null && oldLocation
.getServerName() != null
136 && oldLocation
.getServerName().equals(source
);
138 // For redirect if the number is equal to previous
139 // record, the most common case is that first the region was closed with seqNum, and then
140 // opened with the same seqNum; hence we will ignore the redirect.
141 // There are so many corner cases with various combinations of opens and closes that
142 // an additional counter on top of seqNum would be necessary to handle them all.
143 RegionLocations updatedLocations
= oldLocations
.updateLocation(location
, false, force
);
144 if (oldLocations
!= updatedLocations
) {
145 boolean replaced
= tableLocations
.replace(startKey
, oldLocations
, updatedLocations
);
146 if (replaced
&& LOG
.isTraceEnabled()) {
147 LOG
.trace("Changed cached location to: " + location
);
149 addToCachedServers(updatedLocations
);
154 * Put a newly discovered HRegionLocation into the cache.
155 * @param tableName The table name.
156 * @param locations the new locations
158 public void cacheLocation(final TableName tableName
, final RegionLocations locations
) {
159 byte [] startKey
= locations
.getRegionLocation().getRegion().getStartKey();
160 ConcurrentMap
<byte[], RegionLocations
> tableLocations
= getTableLocations(tableName
);
161 RegionLocations oldLocation
= tableLocations
.putIfAbsent(startKey
, locations
);
162 boolean isNewCacheEntry
= (oldLocation
== null);
163 if (isNewCacheEntry
) {
164 if (LOG
.isTraceEnabled()) {
165 LOG
.trace("Cached location: " + locations
);
167 addToCachedServers(locations
);
171 // merge old and new locations and add it to the cache
172 // Meta record might be stale - some (probably the same) server has closed the region
173 // with later seqNum and told us about the new location.
174 RegionLocations mergedLocation
= oldLocation
.mergeLocations(locations
);
175 boolean replaced
= tableLocations
.replace(startKey
, oldLocation
, mergedLocation
);
176 if (replaced
&& LOG
.isTraceEnabled()) {
177 LOG
.trace("Merged cached locations: " + mergedLocation
);
179 addToCachedServers(locations
);
182 private void addToCachedServers(RegionLocations locations
) {
183 for (HRegionLocation loc
: locations
.getRegionLocations()) {
185 cachedServers
.add(loc
.getServerName());
192 * @return Map of cached locations for passed <code>tableName</code>
194 private ConcurrentNavigableMap
<byte[], RegionLocations
> getTableLocations(
195 final TableName tableName
) {
196 // find the map of cached locations for this table
197 return computeIfAbsent(cachedRegionLocations
, tableName
,
198 () -> new CopyOnWriteArrayMap
<>(Bytes
.BYTES_COMPARATOR
));
202 * Check the region cache to see whether a region is cached yet or not.
203 * @param tableName tableName
205 * @return Region cached or not.
207 public boolean isRegionCached(TableName tableName
, final byte[] row
) {
208 RegionLocations location
= getCachedLocation(tableName
, row
);
209 return location
!= null;
213 * Return the number of cached region for a table. It will only be called
216 public int getNumberOfCachedRegionLocations(final TableName tableName
) {
217 Map
<byte[], RegionLocations
> tableLocs
= this.cachedRegionLocations
.get(tableName
);
218 if (tableLocs
== null) {
222 for (RegionLocations tableLoc
: tableLocs
.values()) {
223 numRegions
+= tableLoc
.numNonNullElements();
229 * Delete all cached entries.
231 public void clearCache() {
232 this.cachedRegionLocations
.clear();
233 this.cachedServers
.clear();
237 * Delete all cached entries of a server.
239 public void clearCache(final ServerName serverName
) {
240 if (!this.cachedServers
.contains(serverName
)) {
244 boolean deletedSomething
= false;
245 synchronized (this.cachedServers
) {
246 // We block here, because if there is an error on a server, it's likely that multiple
247 // threads will get the error simultaneously. If there are hundreds of thousand of
248 // region location to check, it's better to do this only once. A better pattern would
249 // be to check if the server is dead when we get the region location.
250 if (!this.cachedServers
.contains(serverName
)) {
253 for (ConcurrentMap
<byte[], RegionLocations
> tableLocations
: cachedRegionLocations
.values()){
254 for (Entry
<byte[], RegionLocations
> e
: tableLocations
.entrySet()) {
255 RegionLocations regionLocations
= e
.getValue();
256 if (regionLocations
!= null) {
257 RegionLocations updatedLocations
= regionLocations
.removeByServer(serverName
);
258 if (updatedLocations
!= regionLocations
) {
259 if (updatedLocations
.isEmpty()) {
260 deletedSomething
|= tableLocations
.remove(e
.getKey(), regionLocations
);
262 deletedSomething
|= tableLocations
.replace(e
.getKey(), regionLocations
,
269 this.cachedServers
.remove(serverName
);
271 if (deletedSomething
) {
272 if (metrics
!= null) {
273 metrics
.incrMetaCacheNumClearServer();
275 if (LOG
.isTraceEnabled()) {
276 LOG
.trace("Removed all cached region locations that map to " + serverName
);
282 * Delete all cached entries of a table.
284 public void clearCache(final TableName tableName
) {
285 if (LOG
.isTraceEnabled()) {
286 LOG
.trace("Removed all cached region locations for table " + tableName
);
288 this.cachedRegionLocations
.remove(tableName
);
292 * Delete a cached location, no matter what it is. Called when we were told to not use cache.
293 * @param tableName tableName
296 public void clearCache(final TableName tableName
, final byte [] row
) {
297 ConcurrentMap
<byte[], RegionLocations
> tableLocations
= getTableLocations(tableName
);
299 RegionLocations regionLocations
= getCachedLocation(tableName
, row
);
300 if (regionLocations
!= null) {
301 byte[] startKey
= regionLocations
.getRegionLocation().getRegion().getStartKey();
302 boolean removed
= tableLocations
.remove(startKey
, regionLocations
);
304 if (metrics
!= null) {
305 metrics
.incrMetaCacheNumClearRegion();
307 if (LOG
.isTraceEnabled()) {
308 LOG
.trace("Removed " + regionLocations
+ " from cache");
315 * Delete a cached location with specific replicaId.
316 * @param tableName tableName
318 * @param replicaId region replica id
320 public void clearCache(final TableName tableName
, final byte [] row
, int replicaId
) {
321 ConcurrentMap
<byte[], RegionLocations
> tableLocations
= getTableLocations(tableName
);
323 RegionLocations regionLocations
= getCachedLocation(tableName
, row
);
324 if (regionLocations
!= null) {
325 HRegionLocation toBeRemoved
= regionLocations
.getRegionLocation(replicaId
);
326 if (toBeRemoved
!= null) {
327 RegionLocations updatedLocations
= regionLocations
.remove(replicaId
);
328 byte[] startKey
= regionLocations
.getRegionLocation().getRegion().getStartKey();
330 if (updatedLocations
.isEmpty()) {
331 removed
= tableLocations
.remove(startKey
, regionLocations
);
333 removed
= tableLocations
.replace(startKey
, regionLocations
, updatedLocations
);
337 if (metrics
!= null) {
338 metrics
.incrMetaCacheNumClearRegion();
340 if (LOG
.isTraceEnabled()) {
341 LOG
.trace("Removed " + toBeRemoved
+ " from cache");
349 * Delete a cached location for a table, row and server
351 public void clearCache(final TableName tableName
, final byte [] row
, ServerName serverName
) {
352 ConcurrentMap
<byte[], RegionLocations
> tableLocations
= getTableLocations(tableName
);
354 RegionLocations regionLocations
= getCachedLocation(tableName
, row
);
355 if (regionLocations
!= null) {
356 RegionLocations updatedLocations
= regionLocations
.removeByServer(serverName
);
357 if (updatedLocations
!= regionLocations
) {
358 byte[] startKey
= regionLocations
.getRegionLocation().getRegion().getStartKey();
359 boolean removed
= false;
360 if (updatedLocations
.isEmpty()) {
361 removed
= tableLocations
.remove(startKey
, regionLocations
);
363 removed
= tableLocations
.replace(startKey
, regionLocations
, updatedLocations
);
366 if (metrics
!= null) {
367 metrics
.incrMetaCacheNumClearRegion();
369 if (LOG
.isTraceEnabled()) {
370 LOG
.trace("Removed locations of table: " + tableName
+ " ,row: " + Bytes
.toString(row
)
371 + " mapping to server: " + serverName
+ " from cache");
379 * Deletes the cached location of the region if necessary, based on some error from source.
380 * @param hri The region in question.
382 public void clearCache(RegionInfo hri
) {
383 ConcurrentMap
<byte[], RegionLocations
> tableLocations
= getTableLocations(hri
.getTable());
384 RegionLocations regionLocations
= tableLocations
.get(hri
.getStartKey());
385 if (regionLocations
!= null) {
386 HRegionLocation oldLocation
= regionLocations
.getRegionLocation(hri
.getReplicaId());
387 if (oldLocation
== null) return;
388 RegionLocations updatedLocations
= regionLocations
.remove(oldLocation
);
390 if (updatedLocations
!= regionLocations
) {
391 if (updatedLocations
.isEmpty()) {
392 removed
= tableLocations
.remove(hri
.getStartKey(), regionLocations
);
394 removed
= tableLocations
.replace(hri
.getStartKey(), regionLocations
, updatedLocations
);
397 if (metrics
!= null) {
398 metrics
.incrMetaCacheNumClearRegion();
400 if (LOG
.isTraceEnabled()) {
401 LOG
.trace("Removed " + oldLocation
+ " from cache");
408 public void clearCache(final HRegionLocation location
) {
409 if (location
== null) {
412 TableName tableName
= location
.getRegion().getTable();
413 ConcurrentMap
<byte[], RegionLocations
> tableLocations
= getTableLocations(tableName
);
414 RegionLocations regionLocations
= tableLocations
.get(location
.getRegion().getStartKey());
415 if (regionLocations
!= null) {
416 RegionLocations updatedLocations
= regionLocations
.remove(location
);
418 if (updatedLocations
!= regionLocations
) {
419 if (updatedLocations
.isEmpty()) {
420 removed
= tableLocations
.remove(location
.getRegion().getStartKey(), regionLocations
);
422 removed
= tableLocations
.replace(location
.getRegion().getStartKey(), regionLocations
,
426 if (metrics
!= null) {
427 metrics
.incrMetaCacheNumClearRegion();
429 if (LOG
.isTraceEnabled()) {
430 LOG
.trace("Removed " + location
+ " from cache");