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.master;
019
020import static org.apache.hadoop.hbase.util.ConcurrentMapUtils.computeIfAbsent;
021
022import java.io.IOException;
023import java.net.InetAddress;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.HashSet;
027import java.util.Iterator;
028import java.util.List;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.Objects;
032import java.util.Set;
033import java.util.concurrent.ConcurrentNavigableMap;
034import java.util.concurrent.ConcurrentSkipListMap;
035import java.util.concurrent.CopyOnWriteArrayList;
036import java.util.concurrent.atomic.AtomicBoolean;
037import java.util.function.Predicate;
038import org.apache.hadoop.conf.Configuration;
039import org.apache.hadoop.fs.FSDataInputStream;
040import org.apache.hadoop.fs.FSDataOutputStream;
041import org.apache.hadoop.fs.FileSystem;
042import org.apache.hadoop.fs.Path;
043import org.apache.hadoop.hbase.ClockOutOfSyncException;
044import org.apache.hadoop.hbase.HConstants;
045import org.apache.hadoop.hbase.NotServingRegionException;
046import org.apache.hadoop.hbase.RegionMetrics;
047import org.apache.hadoop.hbase.ScheduledChore;
048import org.apache.hadoop.hbase.ServerMetrics;
049import org.apache.hadoop.hbase.ServerMetricsBuilder;
050import org.apache.hadoop.hbase.ServerName;
051import org.apache.hadoop.hbase.YouAreDeadException;
052import org.apache.hadoop.hbase.client.AsyncClusterConnection;
053import org.apache.hadoop.hbase.client.AsyncRegionServerAdmin;
054import org.apache.hadoop.hbase.client.RegionInfo;
055import org.apache.hadoop.hbase.conf.ConfigurationObserver;
056import org.apache.hadoop.hbase.ipc.DecommissionedHostRejectedException;
057import org.apache.hadoop.hbase.ipc.RemoteWithExtrasException;
058import org.apache.hadoop.hbase.master.assignment.RegionStates;
059import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure;
060import org.apache.hadoop.hbase.monitoring.MonitoredTask;
061import org.apache.hadoop.hbase.procedure2.Procedure;
062import org.apache.hadoop.hbase.util.Bytes;
063import org.apache.hadoop.hbase.util.CommonFSUtils;
064import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
065import org.apache.hadoop.hbase.util.FutureUtils;
066import org.apache.hadoop.hbase.zookeeper.ZKUtil;
067import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
068import org.apache.hadoop.hbase.zookeeper.ZNodePaths;
069import org.apache.yetus.audience.InterfaceAudience;
070import org.apache.zookeeper.KeeperException;
071import org.slf4j.Logger;
072import org.slf4j.LoggerFactory;
073
074import org.apache.hbase.thirdparty.com.google.protobuf.ByteString;
075import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
076
077import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
078import org.apache.hadoop.hbase.shaded.protobuf.RequestConverter;
079import org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos.RegionStoreSequenceIds;
080import org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos.StoreSequenceId;
081import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.FlushedRegionSequenceId;
082import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.FlushedSequenceId;
083import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.FlushedStoreSequenceId;
084import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.RegionServerStartupRequest;
085
086/**
087 * The ServerManager class manages info about region servers.
088 * <p>
089 * Maintains lists of online and dead servers. Processes the startups, shutdowns, and deaths of
090 * region servers.
091 * <p>
092 * Servers are distinguished in two different ways. A given server has a location, specified by
093 * hostname and port, and of which there can only be one online at any given time. A server instance
094 * is specified by the location (hostname and port) as well as the startcode (timestamp from when
095 * the server was started). This is used to differentiate a restarted instance of a given server
096 * from the original instance.
097 * <p>
098 * If a sever is known not to be running any more, it is called dead. The dead server needs to be
099 * handled by a ServerShutdownHandler. If the handler is not enabled yet, the server can't be
100 * handled right away so it is queued up. After the handler is enabled, the server will be submitted
101 * to a handler to handle. However, the handler may be just partially enabled. If so, the server
102 * cannot be fully processed, and be queued up for further processing. A server is fully processed
103 * only after the handler is fully enabled and has completed the handling.
104 */
105@InterfaceAudience.Private
106public class ServerManager implements ConfigurationObserver {
107  public static final String WAIT_ON_REGIONSERVERS_MAXTOSTART =
108    "hbase.master.wait.on.regionservers.maxtostart";
109
110  public static final String WAIT_ON_REGIONSERVERS_MINTOSTART =
111    "hbase.master.wait.on.regionservers.mintostart";
112
113  public static final String WAIT_ON_REGIONSERVERS_TIMEOUT =
114    "hbase.master.wait.on.regionservers.timeout";
115
116  public static final String WAIT_ON_REGIONSERVERS_INTERVAL =
117    "hbase.master.wait.on.regionservers.interval";
118
119  /**
120   * see HBASE-20727 if set to true, flushedSequenceIdByRegion and storeFlushedSequenceIdsByRegion
121   * will be persisted to HDFS and loaded when master restart to speed up log split
122   */
123  public static final String PERSIST_FLUSHEDSEQUENCEID =
124    "hbase.master.persist.flushedsequenceid.enabled";
125
126  public static final boolean PERSIST_FLUSHEDSEQUENCEID_DEFAULT = true;
127
128  public static final String FLUSHEDSEQUENCEID_FLUSHER_INTERVAL =
129    "hbase.master.flushedsequenceid.flusher.interval";
130
131  public static final int FLUSHEDSEQUENCEID_FLUSHER_INTERVAL_DEFAULT = 3 * 60 * 60 * 1000; // 3
132                                                                                           // hours
133
134  public static final String MAX_CLOCK_SKEW_MS = "hbase.master.maxclockskew";
135
136  private static final Logger LOG = LoggerFactory.getLogger(ServerManager.class);
137
138  // Set if we are to shutdown the cluster.
139  private AtomicBoolean clusterShutdown = new AtomicBoolean(false);
140
141  /**
142   * The last flushed sequence id for a region.
143   */
144  private final ConcurrentNavigableMap<byte[], Long> flushedSequenceIdByRegion =
145    new ConcurrentSkipListMap<>(Bytes.BYTES_COMPARATOR);
146
147  private boolean persistFlushedSequenceId = true;
148  private volatile boolean isFlushSeqIdPersistInProgress = false;
149  /** File on hdfs to store last flushed sequence id of regions */
150  private static final String LAST_FLUSHED_SEQ_ID_FILE = ".lastflushedseqids";
151  private FlushedSequenceIdFlusher flushedSeqIdFlusher;
152
153  /**
154   * The last flushed sequence id for a store in a region.
155   */
156  private final ConcurrentNavigableMap<byte[],
157    ConcurrentNavigableMap<byte[], Long>> storeFlushedSequenceIdsByRegion =
158      new ConcurrentSkipListMap<>(Bytes.BYTES_COMPARATOR);
159
160  /** Map of registered servers to their current load */
161  private final ConcurrentNavigableMap<ServerName, ServerMetrics> onlineServers =
162    new ConcurrentSkipListMap<>();
163
164  /** List of region servers that should not get any more new regions. */
165  private final ArrayList<ServerName> drainingServers = new ArrayList<>();
166
167  private final MasterServices master;
168  private final RegionServerList storage;
169
170  private final DeadServer deadservers = new DeadServer();
171
172  private final long maxSkew;
173  private final long warningSkew;
174
175  /** Listeners that are called on server events. */
176  private List<ServerListener> listeners = new CopyOnWriteArrayList<>();
177
178  /** Configured value of HConstants.REJECT_DECOMMISSIONED_HOSTS_KEY */
179  private volatile boolean rejectDecommissionedHostsConfig;
180
181  /**
182   * Constructor.
183   */
184  public ServerManager(final MasterServices master, RegionServerList storage) {
185    this.master = master;
186    this.storage = storage;
187    Configuration c = master.getConfiguration();
188    maxSkew = c.getLong(MAX_CLOCK_SKEW_MS, 30000);
189    warningSkew = c.getLong("hbase.master.warningclockskew", 10000);
190    persistFlushedSequenceId =
191      c.getBoolean(PERSIST_FLUSHEDSEQUENCEID, PERSIST_FLUSHEDSEQUENCEID_DEFAULT);
192    rejectDecommissionedHostsConfig = getRejectDecommissionedHostsConfig(c);
193  }
194
195  /**
196   * Implementation of the ConfigurationObserver interface. We are interested in live-loading the
197   * configuration value of HConstants.REJECT_DECOMMISSIONED_HOSTS_KEY
198   * @param conf Server configuration instance
199   */
200  @Override
201  public void onConfigurationChange(Configuration conf) {
202    final boolean newValue = getRejectDecommissionedHostsConfig(conf);
203    if (rejectDecommissionedHostsConfig == newValue) {
204      // no-op
205      return;
206    }
207
208    LOG.info("Config Reload for RejectDecommissionedHosts. previous value: {}, new value: {}",
209      rejectDecommissionedHostsConfig, newValue);
210
211    rejectDecommissionedHostsConfig = newValue;
212  }
213
214  /**
215   * Reads the value of HConstants.REJECT_DECOMMISSIONED_HOSTS_KEY from the config and returns it
216   * @param conf Configuration instance of the Master
217   */
218  public boolean getRejectDecommissionedHostsConfig(Configuration conf) {
219    return conf.getBoolean(HConstants.REJECT_DECOMMISSIONED_HOSTS_KEY,
220      HConstants.REJECT_DECOMMISSIONED_HOSTS_DEFAULT);
221  }
222
223  /**
224   * Add the listener to the notification list.
225   * @param listener The ServerListener to register
226   */
227  public void registerListener(final ServerListener listener) {
228    this.listeners.add(listener);
229  }
230
231  /**
232   * Remove the listener from the notification list.
233   * @param listener The ServerListener to unregister
234   */
235  public boolean unregisterListener(final ServerListener listener) {
236    return this.listeners.remove(listener);
237  }
238
239  /**
240   * Removes all of the ServerListeners of this collection that satisfy the given predicate.
241   * @param filter a predicate which returns true for ServerListener to be removed
242   */
243  public boolean unregisterListenerIf(final Predicate<ServerListener> filter) {
244    return this.listeners.removeIf(filter);
245  }
246
247  /**
248   * Let the server manager know a new regionserver has come online
249   * @param request       the startup request
250   * @param versionNumber the version number of the new regionserver
251   * @param version       the version of the new regionserver, could contain strings like "SNAPSHOT"
252   * @param ia            the InetAddress from which request is received
253   * @return The ServerName we know this server as.
254   */
255  ServerName regionServerStartup(RegionServerStartupRequest request, int versionNumber,
256    String version, InetAddress ia) throws IOException {
257    // Test for case where we get a region startup message from a regionserver
258    // that has been quickly restarted but whose znode expiration handler has
259    // not yet run, or from a server whose fail we are currently processing.
260    // Test its host+port combo is present in serverAddressToServerInfo. If it
261    // is, reject the server and trigger its expiration. The next time it comes
262    // in, it should have been removed from serverAddressToServerInfo and queued
263    // for processing by ProcessServerShutdown.
264
265    // if use-ip is enabled, we will use ip to expose Master/RS service for client,
266    // see HBASE-27304 for details.
267    boolean useIp = master.getConfiguration().getBoolean(HConstants.HBASE_SERVER_USEIP_ENABLED_KEY,
268      HConstants.HBASE_SERVER_USEIP_ENABLED_DEFAULT);
269    String isaHostName = useIp ? ia.getHostAddress() : ia.getHostName();
270    final String hostname =
271      request.hasUseThisHostnameInstead() ? request.getUseThisHostnameInstead() : isaHostName;
272    ServerName sn = ServerName.valueOf(hostname, request.getPort(), request.getServerStartCode());
273
274    // Check if the host should be rejected based on it's decommissioned status
275    checkRejectableDecommissionedStatus(sn);
276
277    checkClockSkew(sn, request.getServerCurrentTime());
278    checkIsDead(sn, "STARTUP");
279    if (!checkAndRecordNewServer(sn, ServerMetricsBuilder.of(sn, versionNumber, version))) {
280      LOG.warn("THIS SHOULD NOT HAPPEN, RegionServerStartup could not record the server: {}", sn);
281    }
282    storage.started(sn);
283    return sn;
284  }
285
286  /**
287   * Updates last flushed sequence Ids for the regions on server sn
288   */
289  private void updateLastFlushedSequenceIds(ServerName sn, ServerMetrics hsl) {
290    for (Entry<byte[], RegionMetrics> entry : hsl.getRegionMetrics().entrySet()) {
291      byte[] encodedRegionName = Bytes.toBytes(RegionInfo.encodeRegionName(entry.getKey()));
292      final long completedSeqId = entry.getValue().getCompletedSequenceId();
293      // Atomic read-modify-write so a concurrent reportRegionOpen seed (which uses
294      // merge(Math::max)) cannot be clobbered by a stale in-flight heartbeat carrying a
295      // lower completedSequenceId. Don't let smaller sequence ids override greater ones.
296      flushedSequenceIdByRegion.compute(encodedRegionName, (k, existingValue) -> {
297        if (LOG.isTraceEnabled()) {
298          LOG.trace(Bytes.toString(k) + ", existingValue=" + existingValue + ", completeSequenceId="
299            + completedSeqId);
300        }
301        if (existingValue == null) {
302          return completedSeqId;
303        }
304        if (completedSeqId != HConstants.NO_SEQNUM && completedSeqId > existingValue) {
305          return completedSeqId;
306        }
307        if (completedSeqId != HConstants.NO_SEQNUM && completedSeqId < existingValue) {
308          LOG.warn("RegionServer " + sn + " indicates a last flushed sequence id (" + completedSeqId
309            + ") that is less than the previous last flushed sequence id (" + existingValue
310            + ") for region " + Bytes.toString(entry.getKey()) + " Ignoring.");
311        }
312        return existingValue;
313      });
314      ConcurrentNavigableMap<byte[], Long> storeFlushedSequenceId =
315        computeIfAbsent(storeFlushedSequenceIdsByRegion, encodedRegionName,
316          () -> new ConcurrentSkipListMap<>(Bytes.BYTES_COMPARATOR));
317      for (Entry<byte[], Long> storeSeqId : entry.getValue().getStoreSequenceId().entrySet()) {
318        byte[] family = storeSeqId.getKey();
319        final long storeCompletedSeqId = storeSeqId.getValue();
320        storeFlushedSequenceId.compute(family, (k, existingValue) -> {
321          if (LOG.isTraceEnabled()) {
322            LOG.trace(Bytes.toString(encodedRegionName) + ", family=" + Bytes.toString(k)
323              + ", existingValue=" + existingValue + ", completeSequenceId=" + storeCompletedSeqId);
324          }
325          if (existingValue == null) {
326            return storeCompletedSeqId;
327          }
328          if (
329            storeCompletedSeqId != HConstants.NO_SEQNUM
330              && storeCompletedSeqId > existingValue.longValue()
331          ) {
332            return storeCompletedSeqId;
333          }
334          return existingValue;
335        });
336      }
337    }
338  }
339
340  public void regionServerReport(ServerName sn, ServerMetrics sl) throws YouAreDeadException {
341    checkIsDead(sn, "REPORT");
342    if (null == this.onlineServers.replace(sn, sl)) {
343      // Already have this host+port combo and its just different start code?
344      // Just let the server in. Presume master joining a running cluster.
345      // recordNewServer is what happens at the end of reportServerStartup.
346      // The only thing we are skipping is passing back to the regionserver
347      // the ServerName to use. Here we presume a master has already done
348      // that so we'll press on with whatever it gave us for ServerName.
349      if (!checkAndRecordNewServer(sn, sl)) {
350        // Master already registered server with same (host + port) and higher startcode.
351        // This can happen if regionserver report comes late from old server
352        // (possible race condition), by that time master has already processed SCP for that
353        // server and started accepting regionserver report from new server i.e. server with
354        // same (host + port) and higher startcode.
355        // The exception thrown here is not meant to tell the region server it is dead because if
356        // there is a new server on the same host port, the old server should have already been
357        // dead in ideal situation.
358        // The exception thrown here is to skip the later steps of the whole regionServerReport
359        // request processing. Usually, after recording it in ServerManager, we will call the
360        // related methods in AssignmentManager to record region states. If the region server
361        // is already dead, we should not do these steps anymore, so here we throw an exception
362        // to let the upper layer know that they should not continue processing anymore.
363        final String errorMsg = "RegionServerReport received from " + sn
364          + ", but another server with the same name and higher startcode is already registered,"
365          + " ignoring";
366        LOG.warn(errorMsg);
367        throw new YouAreDeadException(errorMsg);
368      }
369    }
370    updateLastFlushedSequenceIds(sn, sl);
371  }
372
373  /**
374   * Checks if the Master is configured to reject decommissioned hosts or not. When it's configured
375   * to do so, any RegionServer trying to join the cluster will have it's host checked against the
376   * list of hosts of currently decommissioned servers and potentially get prevented from reporting
377   * for duty; otherwise, we do nothing and we let them pass to the next check. See HBASE-28342 for
378   * details.
379   * @param sn The ServerName to check for
380   * @throws DecommissionedHostRejectedException if the Master is configured to reject
381   *                                             decommissioned hosts and this host exists in the
382   *                                             list of the decommissioned servers
383   */
384  private void checkRejectableDecommissionedStatus(ServerName sn)
385    throws DecommissionedHostRejectedException {
386    LOG.info("Checking decommissioned status of RegionServer {}", sn.getServerName());
387
388    // If the Master is not configured to reject decommissioned hosts, return early.
389    if (!rejectDecommissionedHostsConfig) {
390      return;
391    }
392
393    // Look for a match for the hostname in the list of decommissioned servers
394    for (ServerName server : getDrainingServersList()) {
395      if (Objects.equals(server.getHostname(), sn.getHostname())) {
396        // Found a match and master is configured to reject decommissioned hosts, throw exception!
397        LOG.warn(
398          "Rejecting RegionServer {} from reporting for duty because Master is configured "
399            + "to reject decommissioned hosts and this host was marked as such in the past.",
400          sn.getServerName());
401        throw new DecommissionedHostRejectedException(String.format(
402          "Host %s exists in the list of decommissioned servers and Master is configured to "
403            + "reject decommissioned hosts",
404          sn.getHostname()));
405      }
406    }
407  }
408
409  /**
410   * Check is a server of same host and port already exists, if not, or the existed one got a
411   * smaller start code, record it.
412   * @param serverName the server to check and record
413   * @param sl         the server load on the server
414   * @return true if the server is recorded, otherwise, false
415   */
416  boolean checkAndRecordNewServer(final ServerName serverName, final ServerMetrics sl) {
417    ServerName existingServer = null;
418    synchronized (this.onlineServers) {
419      existingServer = findServerWithSameHostnamePortWithLock(serverName);
420      if (existingServer != null && (existingServer.getStartcode() > serverName.getStartcode())) {
421        LOG.info("Server serverName=" + serverName + " rejected; we already have "
422          + existingServer.toString() + " registered with same hostname and port");
423        return false;
424      }
425      recordNewServerWithLock(serverName, sl);
426    }
427
428    // Tell our listeners that a server was added
429    if (!this.listeners.isEmpty()) {
430      for (ServerListener listener : this.listeners) {
431        listener.serverAdded(serverName);
432      }
433    }
434
435    // Note that we assume that same ts means same server, and don't expire in that case.
436    // TODO: ts can theoretically collide due to clock shifts, so this is a bit hacky.
437    if (existingServer != null && (existingServer.getStartcode() < serverName.getStartcode())) {
438      LOG.info("Triggering server recovery; existingServer " + existingServer
439        + " looks stale, new server:" + serverName);
440      expireServer(existingServer);
441    }
442    return true;
443  }
444
445  /**
446   * Find out the region servers crashed between the crash of the previous master instance and the
447   * current master instance and schedule SCP for them.
448   * <p/>
449   * Since the {@code RegionServerTracker} has already helped us to construct the online servers set
450   * by scanning zookeeper, now we can compare the online servers with {@code liveServersFromWALDir}
451   * to find out whether there are servers which are already dead.
452   * <p/>
453   * Must be called inside the initialization method of {@code RegionServerTracker} to avoid
454   * concurrency issue.
455   * @param deadServersWithDeathTimeFromPE the region servers which already have an SCP associated,
456   *                                       have time of death as value.
457   * @param liveServersFromWALDir          the live region servers from wal directory.
458   */
459  void findDeadServersAndProcess(Map<ServerName, Long> deadServersWithDeathTimeFromPE,
460    Set<ServerName> liveServersFromWALDir) {
461    deadServersWithDeathTimeFromPE.forEach(deadservers::putIfAbsent);
462    liveServersFromWALDir.stream().filter(sn -> !onlineServers.containsKey(sn))
463      .forEach(this::expireServer);
464  }
465
466  /**
467   * Checks if the clock skew between the server and the master. If the clock skew exceeds the
468   * configured max, it will throw an exception; if it exceeds the configured warning threshold, it
469   * will log a warning but start normally.
470   * @param serverName Incoming servers's name
471   * @throws ClockOutOfSyncException if the skew exceeds the configured max value
472   */
473  private void checkClockSkew(final ServerName serverName, final long serverCurrentTime)
474    throws ClockOutOfSyncException {
475    long skew = Math.abs(EnvironmentEdgeManager.currentTime() - serverCurrentTime);
476    if (skew > maxSkew) {
477      String message = "Server " + serverName + " has been "
478        + "rejected; Reported time is too far out of sync with master.  " + "Time difference of "
479        + skew + "ms > max allowed of " + maxSkew + "ms";
480      LOG.warn(message);
481      throw new ClockOutOfSyncException(message);
482    } else if (skew > warningSkew) {
483      String message = "Reported time for server " + serverName + " is out of sync with master "
484        + "by " + skew + "ms. (Warning threshold is " + warningSkew + "ms; " + "error threshold is "
485        + maxSkew + "ms)";
486      LOG.warn(message);
487    }
488  }
489
490  /**
491   * Called when RegionServer first reports in for duty and thereafter each time it heartbeats to
492   * make sure it is has not been figured for dead. If this server is on the dead list, reject it
493   * with a YouAreDeadException. If it was dead but came back with a new start code, remove the old
494   * entry from the dead list.
495   * @param what START or REPORT
496   */
497  private void checkIsDead(final ServerName serverName, final String what)
498    throws YouAreDeadException {
499    if (this.deadservers.isDeadServer(serverName)) {
500      // Exact match: host name, port and start code all match with existing one of the
501      // dead servers. So, this server must be dead. Tell it to kill itself.
502      String message =
503        "Server " + what + " rejected; currently processing " + serverName + " as dead server";
504      LOG.debug(message);
505      throw new YouAreDeadException(message);
506    }
507    // Remove dead server with same hostname and port of newly checking in rs after master
508    // initialization. See HBASE-5916 for more information.
509    if (
510      (this.master == null || this.master.isInitialized())
511        && this.deadservers.cleanPreviousInstance(serverName)
512    ) {
513      // This server has now become alive after we marked it as dead.
514      // We removed it's previous entry from the dead list to reflect it.
515      LOG.debug("{} {} came back up, removed it from the dead servers list", what, serverName);
516    }
517  }
518
519  /**
520   * Assumes onlineServers is locked.
521   * @return ServerName with matching hostname and port.
522   */
523  public ServerName findServerWithSameHostnamePortWithLock(final ServerName serverName) {
524    ServerName end =
525      ServerName.valueOf(serverName.getHostname(), serverName.getPort(), Long.MAX_VALUE);
526
527    ServerName r = onlineServers.lowerKey(end);
528    if (r != null) {
529      if (ServerName.isSameAddress(r, serverName)) {
530        return r;
531      }
532    }
533    return null;
534  }
535
536  /**
537   * Adds the onlineServers list. onlineServers should be locked.
538   * @param serverName The remote servers name.
539   */
540  void recordNewServerWithLock(final ServerName serverName, final ServerMetrics sl) {
541    LOG.info("Registering regionserver=" + serverName);
542    this.onlineServers.put(serverName, sl);
543    master.getAssignmentManager().getRegionStates().createServer(serverName);
544  }
545
546  public ConcurrentNavigableMap<byte[], Long> getFlushedSequenceIdByRegion() {
547    return flushedSequenceIdByRegion;
548  }
549
550  public RegionStoreSequenceIds getLastFlushedSequenceId(byte[] encodedRegionName) {
551    RegionStoreSequenceIds.Builder builder = RegionStoreSequenceIds.newBuilder();
552    Long seqId = flushedSequenceIdByRegion.get(encodedRegionName);
553    builder.setLastFlushedSequenceId(seqId != null ? seqId.longValue() : HConstants.NO_SEQNUM);
554    Map<byte[], Long> storeFlushedSequenceId =
555      storeFlushedSequenceIdsByRegion.get(encodedRegionName);
556    if (storeFlushedSequenceId != null) {
557      for (Map.Entry<byte[], Long> entry : storeFlushedSequenceId.entrySet()) {
558        builder.addStoreSequenceId(StoreSequenceId.newBuilder()
559          .setFamilyName(UnsafeByteOperations.unsafeWrap(entry.getKey()))
560          .setSequenceId(entry.getValue().longValue()).build());
561      }
562    }
563    return builder.build();
564  }
565
566  /** Returns ServerMetrics if serverName is known else null */
567  public ServerMetrics getLoad(final ServerName serverName) {
568    return this.onlineServers.get(serverName);
569  }
570
571  /**
572   * Compute the average load across all region servers. Currently, this uses a very naive
573   * computation - just uses the number of regions being served, ignoring stats about number of
574   * requests.
575   * @return the average load
576   */
577  public double getAverageLoad() {
578    int totalLoad = 0;
579    int numServers = 0;
580    for (ServerMetrics sl : this.onlineServers.values()) {
581      numServers++;
582      totalLoad += sl.getRegionMetrics().size();
583    }
584    return numServers == 0 ? 0 : (double) totalLoad / (double) numServers;
585  }
586
587  /** Returns the count of active regionservers */
588  public int countOfRegionServers() {
589    // Presumes onlineServers is a concurrent map
590    return this.onlineServers.size();
591  }
592
593  /** Returns Read-only map of servers to serverinfo */
594  public Map<ServerName, ServerMetrics> getOnlineServers() {
595    // Presumption is that iterating the returned Map is OK.
596    synchronized (this.onlineServers) {
597      return Collections.unmodifiableMap(this.onlineServers);
598    }
599  }
600
601  public DeadServer getDeadServers() {
602    return this.deadservers;
603  }
604
605  /**
606   * Checks if any dead servers are currently in progress.
607   * @return true if any RS are being processed as dead, false if not
608   */
609  public boolean areDeadServersInProgress() throws IOException {
610    return master.getProcedures().stream()
611      .anyMatch(p -> !p.isFinished() && p instanceof ServerCrashProcedure);
612  }
613
614  void letRegionServersShutdown() {
615    long previousLogTime = 0;
616    ServerName sn = master.getServerName();
617    ZKWatcher zkw = master.getZooKeeper();
618    int onlineServersCt;
619    while ((onlineServersCt = onlineServers.size()) > 0) {
620      if (EnvironmentEdgeManager.currentTime() > (previousLogTime + 1000)) {
621        Set<ServerName> remainingServers = onlineServers.keySet();
622        synchronized (onlineServers) {
623          if (remainingServers.size() == 1 && remainingServers.contains(sn)) {
624            // Master will delete itself later.
625            return;
626          }
627        }
628        StringBuilder sb = new StringBuilder();
629        // It's ok here to not sync on onlineServers - merely logging
630        for (ServerName key : remainingServers) {
631          if (sb.length() > 0) {
632            sb.append(", ");
633          }
634          sb.append(key);
635        }
636        LOG.info("Waiting on regionserver(s) " + sb.toString());
637        previousLogTime = EnvironmentEdgeManager.currentTime();
638      }
639
640      try {
641        List<String> servers = getRegionServersInZK(zkw);
642        if (
643          servers == null || servers.isEmpty()
644            || (servers.size() == 1 && servers.contains(sn.toString()))
645        ) {
646          LOG.info("ZK shows there is only the master self online, exiting now");
647          // Master could have lost some ZK events, no need to wait more.
648          break;
649        }
650      } catch (KeeperException ke) {
651        LOG.warn("Failed to list regionservers", ke);
652        // ZK is malfunctioning, don't hang here
653        break;
654      }
655      synchronized (onlineServers) {
656        try {
657          if (onlineServersCt == onlineServers.size()) onlineServers.wait(100);
658        } catch (InterruptedException ignored) {
659          // continue
660        }
661      }
662    }
663  }
664
665  private List<String> getRegionServersInZK(final ZKWatcher zkw) throws KeeperException {
666    return ZKUtil.listChildrenNoWatch(zkw, zkw.getZNodePaths().rsZNode);
667  }
668
669  /**
670   * Expire the passed server. Add it to list of dead servers and queue a shutdown processing.
671   * @return pid if we queued a ServerCrashProcedure else {@link Procedure#NO_PROC_ID} if we did not
672   *         (could happen for many reasons including the fact that its this server that is going
673   *         down or we already have queued an SCP for this server or SCP processing is currently
674   *         disabled because we are in startup phase).
675   */
676  // Redo test so we can make this protected.
677  public synchronized long expireServer(final ServerName serverName) {
678    return expireServer(serverName, false);
679
680  }
681
682  synchronized long expireServer(final ServerName serverName, boolean force) {
683    // THIS server is going down... can't handle our own expiration.
684    if (serverName.equals(master.getServerName())) {
685      if (!(master.isAborted() || master.isStopped())) {
686        master.stop("We lost our znode?");
687      }
688      return Procedure.NO_PROC_ID;
689    }
690    if (this.deadservers.isDeadServer(serverName)) {
691      LOG.warn("Expiration called on {} but already in DeadServer", serverName);
692      return Procedure.NO_PROC_ID;
693    }
694    moveFromOnlineToDeadServers(serverName);
695
696    // If server is in draining mode, remove corresponding znode
697    // In some tests, the mocked HM may not have ZK Instance, hence null check
698    if (master.getZooKeeper() != null) {
699      String drainingZnode = ZNodePaths
700        .joinZNode(master.getZooKeeper().getZNodePaths().drainingZNode, serverName.getServerName());
701      try {
702        ZKUtil.deleteNodeFailSilent(master.getZooKeeper(), drainingZnode);
703      } catch (KeeperException e) {
704        LOG.warn(
705          "Error deleting the draining znode for stopping server " + serverName.getServerName(), e);
706      }
707    }
708
709    // If cluster is going down, yes, servers are going to be expiring; don't
710    // process as a dead server
711    if (isClusterShutdown()) {
712      LOG.info("Cluster shutdown set; " + serverName + " expired; onlineServers="
713        + this.onlineServers.size());
714      if (this.onlineServers.isEmpty()) {
715        master.stop("Cluster shutdown set; onlineServer=0");
716      }
717      return Procedure.NO_PROC_ID;
718    }
719    LOG.info("Processing expiration of " + serverName + " on " + this.master.getServerName());
720    long pid = master.getAssignmentManager().submitServerCrash(serverName, true, force);
721    if (pid == Procedure.NO_PROC_ID) {
722      // skip later processing as we failed to submit SCP
723      return Procedure.NO_PROC_ID;
724    }
725    storage.expired(serverName);
726    // Tell our listeners that a server was removed
727    if (!this.listeners.isEmpty()) {
728      this.listeners.stream().forEach(l -> l.serverRemoved(serverName));
729    }
730    // trigger a persist of flushedSeqId
731    if (flushedSeqIdFlusher != null) {
732      flushedSeqIdFlusher.triggerNow();
733    }
734    return pid;
735  }
736
737  /**
738   * Called when server has expired.
739   */
740  // Locking in this class needs cleanup.
741  public synchronized void moveFromOnlineToDeadServers(final ServerName sn) {
742    synchronized (this.onlineServers) {
743      boolean online = this.onlineServers.containsKey(sn);
744      if (online) {
745        // Remove the server from the known servers lists and update load info BUT
746        // add to deadservers first; do this so it'll show in dead servers list if
747        // not in online servers list.
748        this.deadservers.putIfAbsent(sn);
749        this.onlineServers.remove(sn);
750        onlineServers.notifyAll();
751      } else {
752        // If not online, that is odd but may happen if 'Unknown Servers' -- where meta
753        // has references to servers not online nor in dead servers list. If
754        // 'Unknown Server', don't add to DeadServers else will be there for ever.
755        LOG.trace("Expiration of {} but server not online", sn);
756      }
757    }
758  }
759
760  /*
761   * Remove the server from the drain list.
762   */
763  public synchronized boolean removeServerFromDrainList(final ServerName sn) {
764    LOG.info("Removing server {} from the draining list.", sn);
765
766    // Remove the server from the draining servers lists.
767    return this.drainingServers.remove(sn);
768  }
769
770  /**
771   * Add the server to the drain list.
772   * @return True if the server is added or the server is already on the drain list.
773   */
774  public synchronized boolean addServerToDrainList(final ServerName sn) {
775    // If master is not rejecting decommissioned hosts, warn if the server (sn) is not online.
776    // However, we want to add servers even if they're not online if the master is configured
777    // to reject decommissioned hosts
778    if (!rejectDecommissionedHostsConfig && !this.isServerOnline(sn)) {
779      LOG.warn("Server {} is not currently online. Ignoring request to add it to draining list.",
780        sn);
781      return false;
782    }
783
784    // Add the server to the draining servers lists, if it's not already in it.
785    if (this.drainingServers.contains(sn)) {
786      LOG.warn(
787        "Server {} is already in the draining server list. Ignoring request to add it again.", sn);
788      return true;
789    }
790
791    LOG.info("Server {} added to draining server list.", sn);
792    return this.drainingServers.add(sn);
793  }
794
795  /**
796   * Contacts a region server and waits up to timeout ms to close the region. This bypasses the
797   * active hmaster. Pass -1 as timeout if you do not want to wait on result.
798   */
799  public static void closeRegionSilentlyAndWait(AsyncClusterConnection connection,
800    ServerName server, RegionInfo region, long timeout) throws IOException, InterruptedException {
801    AsyncRegionServerAdmin admin = connection.getRegionServerAdmin(server);
802    try {
803      FutureUtils.get(
804        admin.closeRegion(ProtobufUtil.buildCloseRegionRequest(server, region.getRegionName())));
805    } catch (IOException e) {
806      LOG.warn("Exception when closing region: " + region.getRegionNameAsString(), e);
807    }
808    if (timeout < 0) {
809      return;
810    }
811    long expiration = timeout + EnvironmentEdgeManager.currentTime();
812    while (EnvironmentEdgeManager.currentTime() < expiration) {
813      try {
814        RegionInfo rsRegion = ProtobufUtil.toRegionInfo(FutureUtils
815          .get(
816            admin.getRegionInfo(RequestConverter.buildGetRegionInfoRequest(region.getRegionName())))
817          .getRegionInfo());
818        if (rsRegion == null) {
819          return;
820        }
821      } catch (IOException ioe) {
822        if (
823          ioe instanceof NotServingRegionException
824            || (ioe instanceof RemoteWithExtrasException && ((RemoteWithExtrasException) ioe)
825              .unwrapRemoteException() instanceof NotServingRegionException)
826        ) {
827          // no need to retry again
828          return;
829        }
830        LOG.warn("Exception when retrieving regioninfo from: " + region.getRegionNameAsString(),
831          ioe);
832      }
833      Thread.sleep(1000);
834    }
835    throw new IOException("Region " + region + " failed to close within" + " timeout " + timeout);
836  }
837
838  /**
839   * Calculate min necessary to start. This is not an absolute. It is just a friction that will
840   * cause us hang around a bit longer waiting on RegionServers to check-in.
841   */
842  private int getMinToStart() {
843    if (master.isInMaintenanceMode()) {
844      // If in maintenance mode, then in process region server hosting meta will be the only server
845      // available
846      return 1;
847    }
848
849    int minimumRequired = 1;
850    int minToStart = this.master.getConfiguration().getInt(WAIT_ON_REGIONSERVERS_MINTOSTART, -1);
851    // Ensure we are never less than minimumRequired else stuff won't work.
852    return Math.max(minToStart, minimumRequired);
853  }
854
855  /**
856   * Wait for the region servers to report in. We will wait until one of this condition is met: -
857   * the master is stopped - the 'hbase.master.wait.on.regionservers.maxtostart' number of region
858   * servers is reached - the 'hbase.master.wait.on.regionservers.mintostart' is reached AND there
859   * have been no new region server in for 'hbase.master.wait.on.regionservers.interval' time AND
860   * the 'hbase.master.wait.on.regionservers.timeout' is reached
861   */
862  public void waitForRegionServers(MonitoredTask status) throws InterruptedException {
863    final long interval =
864      this.master.getConfiguration().getLong(WAIT_ON_REGIONSERVERS_INTERVAL, 1500);
865    final long timeout =
866      this.master.getConfiguration().getLong(WAIT_ON_REGIONSERVERS_TIMEOUT, 4500);
867    // Min is not an absolute; just a friction making us wait longer on server checkin.
868    int minToStart = getMinToStart();
869    int maxToStart =
870      this.master.getConfiguration().getInt(WAIT_ON_REGIONSERVERS_MAXTOSTART, Integer.MAX_VALUE);
871    if (maxToStart < minToStart) {
872      LOG.warn(String.format("The value of '%s' (%d) is set less than '%s' (%d), ignoring.",
873        WAIT_ON_REGIONSERVERS_MAXTOSTART, maxToStart, WAIT_ON_REGIONSERVERS_MINTOSTART,
874        minToStart));
875      maxToStart = Integer.MAX_VALUE;
876    }
877
878    long now = EnvironmentEdgeManager.currentTime();
879    final long startTime = now;
880    long slept = 0;
881    long lastLogTime = 0;
882    long lastCountChange = startTime;
883    int count = countOfRegionServers();
884    int oldCount = 0;
885    // This while test is a little hard to read. We try to comment it in below but in essence:
886    // Wait if Master is not stopped and the number of regionservers that have checked-in is
887    // less than the maxToStart. Both of these conditions will be true near universally.
888    // Next, we will keep cycling if ANY of the following three conditions are true:
889    // 1. The time since a regionserver registered is < interval (means servers are actively
890    // checking in).
891    // 2. We are under the total timeout.
892    // 3. The count of servers is < minimum.
893    for (ServerListener listener : this.listeners) {
894      listener.waiting();
895    }
896    while (
897      !this.master.isStopped() && !isClusterShutdown() && count < maxToStart
898        && ((lastCountChange + interval) > now || timeout > slept || count < minToStart)
899    ) {
900      // Log some info at every interval time or if there is a change
901      if (oldCount != count || lastLogTime + interval < now) {
902        lastLogTime = now;
903        String msg =
904          "Waiting on regionserver count=" + count + "; waited=" + slept + "ms, expecting min="
905            + minToStart + " server(s), max=" + getStrForMax(maxToStart) + " server(s), "
906            + "timeout=" + timeout + "ms, lastChange=" + (now - lastCountChange) + "ms";
907        LOG.info(msg);
908        status.setStatus(msg);
909      }
910
911      // We sleep for some time
912      final long sleepTime = 50;
913      Thread.sleep(sleepTime);
914      now = EnvironmentEdgeManager.currentTime();
915      slept = now - startTime;
916
917      oldCount = count;
918      count = countOfRegionServers();
919      if (count != oldCount) {
920        lastCountChange = now;
921      }
922    }
923    // Did we exit the loop because cluster is going down?
924    if (isClusterShutdown()) {
925      this.master.stop("Cluster shutdown");
926    }
927    LOG.info("Finished waiting on RegionServer count=" + count + "; waited=" + slept + "ms,"
928      + " expected min=" + minToStart + " server(s), max=" + getStrForMax(maxToStart)
929      + " server(s)," + " master is " + (this.master.isStopped() ? "stopped." : "running"));
930  }
931
932  private String getStrForMax(final int max) {
933    return max == Integer.MAX_VALUE ? "NO_LIMIT" : Integer.toString(max);
934  }
935
936  /** Returns A copy of the internal list of online servers. */
937  public List<ServerName> getOnlineServersList() {
938    // TODO: optimize the load balancer call so we don't need to make a new list
939    // TODO: FIX. THIS IS POPULAR CALL.
940    return new ArrayList<>(this.onlineServers.keySet());
941  }
942
943  /**
944   * @param keys                 The target server name
945   * @param idleServerPredicator Evaluates the server on the given load
946   * @return A copy of the internal list of online servers matched by the predicator
947   */
948  public List<ServerName> getOnlineServersListWithPredicator(List<ServerName> keys,
949    Predicate<ServerMetrics> idleServerPredicator) {
950    List<ServerName> names = new ArrayList<>();
951    if (keys != null && idleServerPredicator != null) {
952      keys.forEach(name -> {
953        ServerMetrics load = onlineServers.get(name);
954        if (load != null) {
955          if (idleServerPredicator.test(load)) {
956            names.add(name);
957          }
958        }
959      });
960    }
961    return names;
962  }
963
964  /** Returns A copy of the internal list of draining servers. */
965  public List<ServerName> getDrainingServersList() {
966    return new ArrayList<>(this.drainingServers);
967  }
968
969  public boolean isServerOnline(ServerName serverName) {
970    return serverName != null && onlineServers.containsKey(serverName);
971  }
972
973  public enum ServerLiveState {
974    LIVE,
975    DEAD,
976    UNKNOWN
977  }
978
979  /** Returns whether the server is online, dead, or unknown. */
980  public synchronized ServerLiveState isServerKnownAndOnline(ServerName serverName) {
981    return onlineServers.containsKey(serverName)
982      ? ServerLiveState.LIVE
983      : (deadservers.isDeadServer(serverName) ? ServerLiveState.DEAD : ServerLiveState.UNKNOWN);
984  }
985
986  /**
987   * Check if a server is known to be dead. A server can be online, or known to be dead, or unknown
988   * to this manager (i.e, not online, not known to be dead either; it is simply not tracked by the
989   * master any more, for example, a very old previous instance).
990   */
991  public synchronized boolean isServerDead(ServerName serverName) {
992    return serverName == null || deadservers.isDeadServer(serverName);
993  }
994
995  /**
996   * Check if a server is unknown. A server can be online, or known to be dead, or unknown to this
997   * manager (i.e, not online, not known to be dead either; it is simply not tracked by the master
998   * anymore, for example, a very old previous instance). A serverName with value null should not be
999   * considered unknown. We set value of regionLocation to null just before finding the assign
1000   * candidate (in-between region transition) or while marking it OFFLINE/FAILED_OPEN. Refer
1001   * HBASE-30142.
1002   */
1003  public boolean isServerUnknown(ServerName serverName) {
1004    return serverName != null
1005      && (!onlineServers.containsKey(serverName) && !deadservers.isDeadServer(serverName));
1006  }
1007
1008  public void shutdownCluster() {
1009    String statusStr = "Cluster shutdown requested of master=" + this.master.getServerName();
1010    LOG.info(statusStr);
1011    this.clusterShutdown.set(true);
1012    if (onlineServers.isEmpty()) {
1013      // we do not synchronize here so this may cause a double stop, but not a big deal
1014      master.stop("OnlineServer=0 right after cluster shutdown set");
1015    }
1016  }
1017
1018  public boolean isClusterShutdown() {
1019    return this.clusterShutdown.get();
1020  }
1021
1022  /**
1023   * start chore in ServerManager
1024   */
1025  public void startChore() {
1026    Configuration c = master.getConfiguration();
1027    if (persistFlushedSequenceId) {
1028      new Thread(() -> {
1029        // after AM#loadMeta, RegionStates should be loaded, and some regions are
1030        // deleted by drop/split/merge during removeDeletedRegionFromLoadedFlushedSequenceIds,
1031        // but these deleted regions are not added back to RegionStates,
1032        // so we can safely remove deleted regions.
1033        removeDeletedRegionFromLoadedFlushedSequenceIds();
1034      }, "RemoveDeletedRegionSyncThread").start();
1035      int flushPeriod =
1036        c.getInt(FLUSHEDSEQUENCEID_FLUSHER_INTERVAL, FLUSHEDSEQUENCEID_FLUSHER_INTERVAL_DEFAULT);
1037      flushedSeqIdFlusher = new FlushedSequenceIdFlusher("FlushedSequenceIdFlusher", flushPeriod);
1038      master.getChoreService().scheduleChore(flushedSeqIdFlusher);
1039    }
1040  }
1041
1042  /**
1043   * Stop the ServerManager.
1044   */
1045  public void stop() {
1046    if (flushedSeqIdFlusher != null) {
1047      flushedSeqIdFlusher.shutdown();
1048    }
1049    if (persistFlushedSequenceId) {
1050      try {
1051        persistRegionLastFlushedSequenceIds();
1052      } catch (IOException e) {
1053        LOG.warn("Failed to persist last flushed sequence id of regions" + " to file system", e);
1054      }
1055    }
1056  }
1057
1058  /**
1059   * Creates a list of possible destinations for a region. It contains the online servers, but not
1060   * the draining or dying servers.
1061   * @param serversToExclude can be null if there is no server to exclude
1062   */
1063  public List<ServerName> createDestinationServersList(final List<ServerName> serversToExclude) {
1064    Set<ServerName> destServers = new HashSet<>();
1065    onlineServers.forEach((sn, sm) -> {
1066      if (sm.getLastReportTimestamp() > 0) {
1067        // This means we have already called regionServerReport at leaset once, then let's include
1068        // this server for region assignment. This is an optimization to avoid assigning regions to
1069        // an uninitialized server. See HBASE-25032 for more details.
1070        destServers.add(sn);
1071      }
1072    });
1073
1074    if (serversToExclude != null) {
1075      destServers.removeAll(serversToExclude);
1076    }
1077
1078    // Loop through the draining server list and remove them from the server list
1079    final List<ServerName> drainingServersCopy = getDrainingServersList();
1080    destServers.removeAll(drainingServersCopy);
1081
1082    return new ArrayList<>(destServers);
1083  }
1084
1085  /**
1086   * Calls {@link #createDestinationServersList} without server to exclude.
1087   */
1088  public List<ServerName> createDestinationServersList() {
1089    return createDestinationServersList(null);
1090  }
1091
1092  /**
1093   * To clear any dead server with same host name and port of any online server
1094   */
1095  void clearDeadServersWithSameHostNameAndPortOfOnlineServer() {
1096    for (ServerName serverName : getOnlineServersList()) {
1097      deadservers.cleanAllPreviousInstances(serverName);
1098    }
1099  }
1100
1101  /**
1102   * Called by delete table and similar to notify the ServerManager that a region was removed.
1103   */
1104  public void removeRegion(final RegionInfo regionInfo) {
1105    final byte[] encodedName = regionInfo.getEncodedNameAsBytes();
1106    storeFlushedSequenceIdsByRegion.remove(encodedName);
1107    flushedSequenceIdByRegion.remove(encodedName);
1108  }
1109
1110  /**
1111   * Called on region OPEN to seed {@link #flushedSequenceIdByRegion} with the region's
1112   * {@code openSeqNum}. Without this, the entry stays absent until the hosting server's next
1113   * heartbeat, so {@link #getLastFlushedSequenceId} returns {@link HConstants#NO_SEQNUM} and
1114   * WALSplitter conservatively treats already-durable edits as unflushed - producing orphaned
1115   * recovered.edits when the source server crashes soon after a drain-move. Uses {@code merge} with
1116   * {@link Math#max} so a heartbeat-supplied value (which may reflect flushes after open) is never
1117   * regressed - and, unlike {@code putIfAbsent}, a stale-low prior value is lifted to
1118   * {@code openSeqNum}. Safe because at OPEN a region cannot have flushed past its own
1119   * {@code openSeqNum}. See HBASE-30335.
1120   */
1121  public void reportRegionOpen(final RegionInfo regionInfo, final long openSeqNum) {
1122    if (openSeqNum < 0) { // NO_SEQNUM == -1
1123      return;
1124    }
1125    flushedSequenceIdByRegion.merge(regionInfo.getEncodedNameAsBytes(), openSeqNum, Math::max);
1126  }
1127
1128  public boolean isRegionInServerManagerStates(final RegionInfo hri) {
1129    final byte[] encodedName = hri.getEncodedNameAsBytes();
1130    return (storeFlushedSequenceIdsByRegion.containsKey(encodedName)
1131      || flushedSequenceIdByRegion.containsKey(encodedName));
1132  }
1133
1134  /**
1135   * Called by delete table and similar to notify the ServerManager that a region was removed.
1136   */
1137  public void removeRegions(final List<RegionInfo> regions) {
1138    for (RegionInfo hri : regions) {
1139      removeRegion(hri);
1140    }
1141  }
1142
1143  /**
1144   * May return 0 when server is not online.
1145   */
1146  public int getVersionNumber(ServerName serverName) {
1147    ServerMetrics serverMetrics = onlineServers.get(serverName);
1148    return serverMetrics != null ? serverMetrics.getVersionNumber() : 0;
1149  }
1150
1151  /**
1152   * May return "0.0.0" when server is not online
1153   */
1154  public String getVersion(ServerName serverName) {
1155    ServerMetrics serverMetrics = onlineServers.get(serverName);
1156    return serverMetrics != null ? serverMetrics.getVersion() : "0.0.0";
1157  }
1158
1159  public int getInfoPort(ServerName serverName) {
1160    ServerMetrics serverMetrics = onlineServers.get(serverName);
1161    return serverMetrics != null ? serverMetrics.getInfoServerPort() : 0;
1162  }
1163
1164  /**
1165   * Persist last flushed sequence id of each region to HDFS
1166   * @throws IOException if persit to HDFS fails
1167   */
1168  private void persistRegionLastFlushedSequenceIds() throws IOException {
1169    if (isFlushSeqIdPersistInProgress) {
1170      return;
1171    }
1172    isFlushSeqIdPersistInProgress = true;
1173    try {
1174      Configuration conf = master.getConfiguration();
1175      Path rootDir = CommonFSUtils.getRootDir(conf);
1176      Path lastFlushedSeqIdPath = new Path(rootDir, LAST_FLUSHED_SEQ_ID_FILE);
1177      FileSystem fs = FileSystem.get(conf);
1178      if (fs.exists(lastFlushedSeqIdPath)) {
1179        LOG.info("Rewriting .lastflushedseqids file at: " + lastFlushedSeqIdPath);
1180        if (!fs.delete(lastFlushedSeqIdPath, false)) {
1181          throw new IOException("Unable to remove existing " + lastFlushedSeqIdPath);
1182        }
1183      } else {
1184        LOG.info("Writing .lastflushedseqids file at: " + lastFlushedSeqIdPath);
1185      }
1186      FSDataOutputStream out = fs.create(lastFlushedSeqIdPath);
1187      FlushedSequenceId.Builder flushedSequenceIdBuilder = FlushedSequenceId.newBuilder();
1188      try {
1189        for (Entry<byte[], Long> entry : flushedSequenceIdByRegion.entrySet()) {
1190          FlushedRegionSequenceId.Builder flushedRegionSequenceIdBuilder =
1191            FlushedRegionSequenceId.newBuilder();
1192          flushedRegionSequenceIdBuilder.setRegionEncodedName(ByteString.copyFrom(entry.getKey()));
1193          flushedRegionSequenceIdBuilder.setSeqId(entry.getValue());
1194          ConcurrentNavigableMap<byte[], Long> storeSeqIds =
1195            storeFlushedSequenceIdsByRegion.get(entry.getKey());
1196          if (storeSeqIds != null) {
1197            for (Entry<byte[], Long> store : storeSeqIds.entrySet()) {
1198              FlushedStoreSequenceId.Builder flushedStoreSequenceIdBuilder =
1199                FlushedStoreSequenceId.newBuilder();
1200              flushedStoreSequenceIdBuilder.setFamily(ByteString.copyFrom(store.getKey()));
1201              flushedStoreSequenceIdBuilder.setSeqId(store.getValue());
1202              flushedRegionSequenceIdBuilder.addStores(flushedStoreSequenceIdBuilder);
1203            }
1204          }
1205          flushedSequenceIdBuilder.addRegionSequenceId(flushedRegionSequenceIdBuilder);
1206        }
1207        flushedSequenceIdBuilder.build().writeDelimitedTo(out);
1208      } finally {
1209        if (out != null) {
1210          out.close();
1211        }
1212      }
1213    } finally {
1214      isFlushSeqIdPersistInProgress = false;
1215    }
1216  }
1217
1218  /**
1219   * Load last flushed sequence id of each region from HDFS, if persisted
1220   */
1221  public void loadLastFlushedSequenceIds() throws IOException {
1222    if (!persistFlushedSequenceId) {
1223      return;
1224    }
1225    Configuration conf = master.getConfiguration();
1226    Path rootDir = CommonFSUtils.getRootDir(conf);
1227    Path lastFlushedSeqIdPath = new Path(rootDir, LAST_FLUSHED_SEQ_ID_FILE);
1228    FileSystem fs = FileSystem.get(conf);
1229    if (!fs.exists(lastFlushedSeqIdPath)) {
1230      LOG.info("No .lastflushedseqids found at " + lastFlushedSeqIdPath
1231        + " will record last flushed sequence id"
1232        + " for regions by regionserver report all over again");
1233      return;
1234    } else {
1235      LOG.info("begin to load .lastflushedseqids at " + lastFlushedSeqIdPath);
1236    }
1237    FSDataInputStream in = fs.open(lastFlushedSeqIdPath);
1238    try {
1239      FlushedSequenceId flushedSequenceId = FlushedSequenceId.parseDelimitedFrom(in);
1240      if (flushedSequenceId == null) {
1241        LOG.info(".lastflushedseqids found at {} is empty", lastFlushedSeqIdPath);
1242        return;
1243      }
1244      for (FlushedRegionSequenceId flushedRegionSequenceId : flushedSequenceId
1245        .getRegionSequenceIdList()) {
1246        byte[] encodedRegionName = flushedRegionSequenceId.getRegionEncodedName().toByteArray();
1247        flushedSequenceIdByRegion.putIfAbsent(encodedRegionName,
1248          flushedRegionSequenceId.getSeqId());
1249        if (
1250          flushedRegionSequenceId.getStoresList() != null
1251            && flushedRegionSequenceId.getStoresList().size() != 0
1252        ) {
1253          ConcurrentNavigableMap<byte[], Long> storeFlushedSequenceId =
1254            computeIfAbsent(storeFlushedSequenceIdsByRegion, encodedRegionName,
1255              () -> new ConcurrentSkipListMap<>(Bytes.BYTES_COMPARATOR));
1256          for (FlushedStoreSequenceId flushedStoreSequenceId : flushedRegionSequenceId
1257            .getStoresList()) {
1258            storeFlushedSequenceId.put(flushedStoreSequenceId.getFamily().toByteArray(),
1259              flushedStoreSequenceId.getSeqId());
1260          }
1261        }
1262      }
1263    } finally {
1264      in.close();
1265    }
1266  }
1267
1268  /**
1269   * Regions may have been removed between latest persist of FlushedSequenceIds and master abort. So
1270   * after loading FlushedSequenceIds from file, and after meta loaded, we need to remove the
1271   * deleted region according to RegionStates.
1272   */
1273  public void removeDeletedRegionFromLoadedFlushedSequenceIds() {
1274    RegionStates regionStates = master.getAssignmentManager().getRegionStates();
1275    Iterator<byte[]> it = flushedSequenceIdByRegion.keySet().iterator();
1276    while (it.hasNext()) {
1277      byte[] regionEncodedName = it.next();
1278      if (regionStates.getRegionState(Bytes.toStringBinary(regionEncodedName)) == null) {
1279        it.remove();
1280        storeFlushedSequenceIdsByRegion.remove(regionEncodedName);
1281      }
1282    }
1283  }
1284
1285  private class FlushedSequenceIdFlusher extends ScheduledChore {
1286
1287    public FlushedSequenceIdFlusher(String name, int p) {
1288      super(name, master, p, 60 * 1000); // delay one minute before first execute
1289    }
1290
1291    @Override
1292    protected void chore() {
1293      try {
1294        persistRegionLastFlushedSequenceIds();
1295      } catch (IOException e) {
1296        LOG.debug("Failed to persist last flushed sequence id of regions" + " to file system", e);
1297      }
1298    }
1299  }
1300}