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.regionserver.snapshot; 019 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Iterator; 024import java.util.List; 025import java.util.concurrent.Callable; 026import java.util.concurrent.ExecutionException; 027import java.util.concurrent.ExecutorCompletionService; 028import java.util.concurrent.Future; 029import java.util.concurrent.ThreadPoolExecutor; 030import java.util.concurrent.TimeUnit; 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.hbase.Abortable; 033import org.apache.hadoop.hbase.DroppedSnapshotException; 034import org.apache.hadoop.hbase.HBaseInterfaceAudience; 035import org.apache.hadoop.hbase.TableName; 036import org.apache.hadoop.hbase.client.RegionReplicaUtil; 037import org.apache.hadoop.hbase.errorhandling.ForeignException; 038import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher; 039import org.apache.hadoop.hbase.master.snapshot.MasterSnapshotVerifier; 040import org.apache.hadoop.hbase.master.snapshot.SnapshotManager; 041import org.apache.hadoop.hbase.procedure.ProcedureMember; 042import org.apache.hadoop.hbase.procedure.ProcedureMemberRpcs; 043import org.apache.hadoop.hbase.procedure.RegionServerProcedureManager; 044import org.apache.hadoop.hbase.procedure.Subprocedure; 045import org.apache.hadoop.hbase.procedure.SubprocedureFactory; 046import org.apache.hadoop.hbase.procedure.ZKProcedureMemberRpcs; 047import org.apache.hadoop.hbase.regionserver.HRegion; 048import org.apache.hadoop.hbase.regionserver.HRegionServer; 049import org.apache.hadoop.hbase.regionserver.RegionServerServices; 050import org.apache.hadoop.hbase.util.Threads; 051import org.apache.hadoop.hbase.zookeeper.ZKWatcher; 052import org.apache.yetus.audience.InterfaceAudience; 053import org.apache.yetus.audience.InterfaceStability; 054import org.apache.zookeeper.KeeperException; 055import org.slf4j.Logger; 056import org.slf4j.LoggerFactory; 057 058import org.apache.hbase.thirdparty.com.google.common.util.concurrent.ThreadFactoryBuilder; 059 060import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription; 061 062/** 063 * This manager class handles the work dealing with snapshots for a {@link HRegionServer}. 064 * <p> 065 * This provides the mechanism necessary to kick off a online snapshot specific {@link Subprocedure} 066 * that is responsible for the regions being served by this region server. If any failures occur 067 * with the subprocedure, the RegionSeverSnapshotManager's subprocedure handler, 068 * {@link ProcedureMember}, notifies the master's ProcedureCoordinator to abort all others. 069 * <p> 070 * On startup, requires {@link #start()} to be called. 071 * <p> 072 * On shutdown, requires {@link #stop(boolean)} to be called 073 */ 074@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG) 075@InterfaceStability.Unstable 076public class RegionServerSnapshotManager extends RegionServerProcedureManager { 077 private static final Logger LOG = LoggerFactory.getLogger(RegionServerSnapshotManager.class); 078 079 /** Maximum number of snapshot region tasks that can run concurrently */ 080 private static final String CONCURENT_SNAPSHOT_TASKS_KEY = 081 "hbase.snapshot.region.concurrentTasks"; 082 private static final int DEFAULT_CONCURRENT_SNAPSHOT_TASKS = 3; 083 084 /** Conf key for number of request threads to start snapshots on regionservers */ 085 public static final String SNAPSHOT_REQUEST_THREADS_KEY = "hbase.snapshot.region.pool.threads"; 086 /** # of threads for snapshotting regions on the rs. */ 087 public static final int SNAPSHOT_REQUEST_THREADS_DEFAULT = 10; 088 089 /** 090 * Conf key for max time to keep threads in snapshot request pool waiting. The configured value 091 * must be greater than 0. 092 */ 093 public static final String SNAPSHOT_TIMEOUT_MILLIS_KEY = "hbase.snapshot.region.timeout"; 094 /** Keep threads alive in request pool for max of 300 seconds */ 095 public static final long SNAPSHOT_TIMEOUT_MILLIS_DEFAULT = 5 * 60000; 096 097 /** Conf key for millis between checks to see if snapshot completed or if there are errors */ 098 public static final String SNAPSHOT_REQUEST_WAKE_MILLIS_KEY = 099 "hbase.snapshot.region.wakefrequency"; 100 /** Default amount of time to check for errors while regions finish snapshotting */ 101 private static final long SNAPSHOT_REQUEST_WAKE_MILLIS_DEFAULT = 500; 102 103 private RegionServerServices rss; 104 private ProcedureMemberRpcs memberRpcs; 105 private ProcedureMember member; 106 107 static long getSnapshotTimeoutMillis(Configuration conf) { 108 long timeoutMillis = conf.getLong(SNAPSHOT_TIMEOUT_MILLIS_KEY, SNAPSHOT_TIMEOUT_MILLIS_DEFAULT); 109 if (timeoutMillis > 0) { 110 return timeoutMillis; 111 } 112 LOG.warn("{} must be greater than 0, but is {}. Using default value {}", 113 SNAPSHOT_TIMEOUT_MILLIS_KEY, timeoutMillis, SNAPSHOT_TIMEOUT_MILLIS_DEFAULT); 114 return SNAPSHOT_TIMEOUT_MILLIS_DEFAULT; 115 } 116 117 /** 118 * Exposed for testing. 119 * @param conf HBase configuration. 120 * @param parent parent running the snapshot handler 121 * @param memberRpc use specified memberRpc instance 122 * @param procMember use specified ProcedureMember 123 */ 124 RegionServerSnapshotManager(Configuration conf, HRegionServer parent, 125 ProcedureMemberRpcs memberRpc, ProcedureMember procMember) { 126 this.rss = parent; 127 this.memberRpcs = memberRpc; 128 this.member = procMember; 129 } 130 131 public RegionServerSnapshotManager() { 132 } 133 134 /** 135 * Start accepting snapshot requests. 136 */ 137 @Override 138 public void start() { 139 LOG.debug("Start Snapshot Manager " + rss.getServerName().toString()); 140 this.memberRpcs.start(rss.getServerName().toString(), member); 141 } 142 143 /** 144 * Close <tt>this</tt> and all running snapshot tasks 145 * @param force forcefully stop all running tasks 146 */ 147 @Override 148 public void stop(boolean force) throws IOException { 149 String mode = force ? "abruptly" : "gracefully"; 150 LOG.info("Stopping RegionServerSnapshotManager " + mode + "."); 151 152 try { 153 this.member.close(); 154 } finally { 155 this.memberRpcs.close(); 156 } 157 } 158 159 /** 160 * If in a running state, creates the specified subprocedure for handling an online snapshot. 161 * Because this gets the local list of regions to snapshot and not the set the master had, there 162 * is a possibility of a race where regions may be missed. This detected by the master in the 163 * snapshot verification step. 164 * @return Subprocedure to submit to the ProcedureMember. 165 */ 166 public Subprocedure buildSubprocedure(SnapshotDescription snapshot) { 167 168 // don't run a snapshot if the parent is stop(ping) 169 if (rss.isStopping() || rss.isStopped()) { 170 throw new IllegalStateException( 171 "Can't start snapshot on RS: " + rss.getServerName() + ", because stopping/stopped!"); 172 } 173 174 // check to see if this server is hosting any regions for the snapshots 175 // check to see if we have regions for the snapshot 176 List<HRegion> involvedRegions; 177 try { 178 involvedRegions = getRegionsToSnapshot(snapshot); 179 } catch (IOException e1) { 180 throw new IllegalStateException("Failed to figure out if we should handle a snapshot - " 181 + "something has gone awry with the online regions.", e1); 182 } 183 184 // We need to run the subprocedure even if we have no relevant regions. The coordinator 185 // expects participation in the procedure and without sending message the snapshot attempt 186 // will hang and fail. 187 188 LOG.debug("Launching subprocedure for snapshot " + snapshot.getName() + " from table " 189 + snapshot.getTable() + " type " + snapshot.getType()); 190 ForeignExceptionDispatcher exnDispatcher = new ForeignExceptionDispatcher(snapshot.getName()); 191 Configuration conf = rss.getConfiguration(); 192 long timeoutMillis = getSnapshotTimeoutMillis(conf); 193 long wakeMillis = 194 conf.getLong(SNAPSHOT_REQUEST_WAKE_MILLIS_KEY, SNAPSHOT_REQUEST_WAKE_MILLIS_DEFAULT); 195 196 switch (snapshot.getType()) { 197 case FLUSH: 198 SnapshotSubprocedurePool taskManager = 199 new SnapshotSubprocedurePool(rss.getServerName().toString(), conf, rss); 200 return new FlushSnapshotSubprocedure(member, exnDispatcher, wakeMillis, timeoutMillis, 201 involvedRegions, snapshot, taskManager); 202 case SKIPFLUSH: 203 /* 204 * This is to take an online-snapshot without force a coordinated flush to prevent pause The 205 * snapshot type is defined inside the snapshot description. FlushSnapshotSubprocedure 206 * should be renamed to distributedSnapshotSubprocedure, and the flush() behavior can be 207 * turned on/off based on the flush type. To minimized the code change, class name is not 208 * changed. 209 */ 210 SnapshotSubprocedurePool taskManager2 = 211 new SnapshotSubprocedurePool(rss.getServerName().toString(), conf, rss); 212 return new FlushSnapshotSubprocedure(member, exnDispatcher, wakeMillis, timeoutMillis, 213 involvedRegions, snapshot, taskManager2); 214 215 default: 216 throw new UnsupportedOperationException("Unrecognized snapshot type:" + snapshot.getType()); 217 } 218 } 219 220 /** 221 * Determine if the snapshot should be handled on this server NOTE: This is racy -- the master 222 * expects a list of regionservers. This means if a region moves somewhere between the calls we'll 223 * miss some regions. For example, a region move during a snapshot could result in a region to be 224 * skipped or done twice. This is manageable because the {@link MasterSnapshotVerifier} will 225 * double check the region lists after the online portion of the snapshot completes and will 226 * explicitly fail the snapshot. 227 * @return the list of online regions. Empty list is returned if no regions are responsible for 228 * the given snapshot. 229 */ 230 private List<HRegion> getRegionsToSnapshot(SnapshotDescription snapshot) throws IOException { 231 List<HRegion> onlineRegions = 232 (List<HRegion>) rss.getRegions(TableName.valueOf(snapshot.getTable())); 233 Iterator<HRegion> iterator = onlineRegions.iterator(); 234 // remove the non-default regions 235 while (iterator.hasNext()) { 236 HRegion r = iterator.next(); 237 if (!RegionReplicaUtil.isDefaultReplica(r.getRegionInfo())) { 238 iterator.remove(); 239 } 240 } 241 return onlineRegions; 242 } 243 244 /** 245 * Build the actual snapshot runner that will do all the 'hard' work 246 */ 247 public class SnapshotSubprocedureBuilder implements SubprocedureFactory { 248 249 @Override 250 public Subprocedure buildSubprocedure(String name, byte[] data) { 251 try { 252 // unwrap the snapshot information 253 SnapshotDescription snapshot = SnapshotDescription.parseFrom(data); 254 return RegionServerSnapshotManager.this.buildSubprocedure(snapshot); 255 } catch (IOException e) { 256 throw new IllegalArgumentException("Could not read snapshot information from request."); 257 } 258 } 259 260 } 261 262 /** 263 * We use the SnapshotSubprocedurePool, a class specific thread pool instead of 264 * {@link org.apache.hadoop.hbase.executor.ExecutorService}. It uses a 265 * {@link java.util.concurrent.ExecutorCompletionService} which provides queuing of completed 266 * tasks which lets us efficiently cancel pending tasks upon the earliest operation failures. 267 * HBase's ExecutorService (different from {@link java.util.concurrent.ExecutorService}) isn't 268 * really built for coordinated tasks where multiple threads as part of one larger task. In RS's 269 * the HBase Executor services are only used for open and close and not other threadpooled 270 * operations such as compactions and replication sinks. 271 */ 272 static class SnapshotSubprocedurePool { 273 private final Abortable abortable; 274 private final ExecutorCompletionService<Void> taskPool; 275 private final ThreadPoolExecutor executor; 276 private volatile boolean stopped; 277 private final List<Future<Void>> futures = new ArrayList<>(); 278 private final String name; 279 280 SnapshotSubprocedurePool(String name, Configuration conf, Abortable abortable) { 281 this.abortable = abortable; 282 // configure the executor service 283 long keepAlive = getSnapshotTimeoutMillis(conf); 284 int threads = conf.getInt(CONCURENT_SNAPSHOT_TASKS_KEY, DEFAULT_CONCURRENT_SNAPSHOT_TASKS); 285 this.name = name; 286 executor = Threads.getBoundedCachedThreadPool(threads, keepAlive, TimeUnit.MILLISECONDS, 287 new ThreadFactoryBuilder().setNameFormat("rs(" + name + ")-snapshot-pool-%d") 288 .setDaemon(true).setUncaughtExceptionHandler(Threads.LOGGING_EXCEPTION_HANDLER).build()); 289 taskPool = new ExecutorCompletionService<>(executor); 290 } 291 292 boolean hasTasks() { 293 return futures.size() != 0; 294 } 295 296 /** 297 * Submit a task to the pool. NOTE: all must be submitted before you can safely 298 * {@link #waitForOutstandingTasks()}. This version does not support issuing tasks from multiple 299 * concurrent table snapshots requests. 300 */ 301 void submitTask(final Callable<Void> task) { 302 Future<Void> f = this.taskPool.submit(task); 303 futures.add(f); 304 } 305 306 /** 307 * Wait for all of the currently outstanding tasks submitted via {@link #submitTask(Callable)}. 308 * This *must* be called after all tasks are submitted via submitTask. 309 * @return <tt>true</tt> on success, <tt>false</tt> otherwise 310 * @throws SnapshotCreationException if the snapshot failed while we were waiting 311 */ 312 boolean waitForOutstandingTasks() throws ForeignException, InterruptedException { 313 LOG.debug("Waiting for local region snapshots to finish."); 314 315 int sz = futures.size(); 316 try { 317 // Using the completion service to process the futures that finish first first. 318 for (int i = 0; i < sz; i++) { 319 Future<Void> f = taskPool.take(); 320 f.get(); 321 if (!futures.remove(f)) { 322 LOG.warn("unexpected future" + f); 323 } 324 LOG.debug("Completed " + (i + 1) + "/" + sz + " local region snapshots."); 325 } 326 LOG.debug("Completed " + sz + " local region snapshots."); 327 return true; 328 } catch (InterruptedException e) { 329 LOG.warn("Got InterruptedException in SnapshotSubprocedurePool", e); 330 if (!stopped) { 331 Thread.currentThread().interrupt(); 332 throw new ForeignException("SnapshotSubprocedurePool", e); 333 } 334 // we are stopped so we can just exit. 335 } catch (ExecutionException e) { 336 Throwable cause = e.getCause(); 337 if (cause instanceof ForeignException) { 338 LOG.warn("Rethrowing ForeignException from SnapshotSubprocedurePool", e); 339 throw (ForeignException) e.getCause(); 340 } else if (cause instanceof DroppedSnapshotException) { 341 // we have to abort the region server according to contract of flush 342 abortable.abort("Received DroppedSnapshotException, aborting", cause); 343 } 344 LOG.warn("Got Exception in SnapshotSubprocedurePool", e); 345 throw new ForeignException(name, e.getCause()); 346 } finally { 347 cancelTasks(); 348 } 349 return false; 350 } 351 352 /** 353 * This attempts to cancel out all pending and in progress tasks (interruptions issues) 354 */ 355 void cancelTasks() throws InterruptedException { 356 Collection<Future<Void>> tasks = futures; 357 LOG.debug("cancelling " + tasks.size() + " tasks for snapshot " + name); 358 for (Future<Void> f : tasks) { 359 // TODO Ideally we'd interrupt hbase threads when we cancel. However it seems that there 360 // are places in the HBase code where row/region locks are taken and not released in a 361 // finally block. Thus we cancel without interrupting. Cancellations will be slower to 362 // complete but we won't suffer from unreleased locks due to poor code discipline. 363 f.cancel(false); 364 } 365 366 // evict remaining tasks and futures from taskPool. 367 futures.clear(); 368 while (taskPool.poll() != null) { 369 } 370 stop(); 371 } 372 373 /** 374 * Abruptly shutdown the thread pool. Call when exiting a region server. 375 */ 376 void stop() { 377 if (this.stopped) return; 378 379 this.stopped = true; 380 this.executor.shutdown(); 381 } 382 } 383 384 /** 385 * Create a default snapshot handler - uses a zookeeper based member controller. 386 * @param rss region server running the handler 387 * @throws KeeperException if the zookeeper cluster cannot be reached 388 */ 389 @Override 390 public void initialize(RegionServerServices rss) throws KeeperException { 391 this.rss = rss; 392 ZKWatcher zkw = rss.getZooKeeper(); 393 this.memberRpcs = 394 new ZKProcedureMemberRpcs(zkw, SnapshotManager.ONLINE_SNAPSHOT_CONTROLLER_DESCRIPTION); 395 396 // read in the snapshot request configuration properties 397 Configuration conf = rss.getConfiguration(); 398 long keepAlive = getSnapshotTimeoutMillis(conf); 399 int opThreads = conf.getInt(SNAPSHOT_REQUEST_THREADS_KEY, SNAPSHOT_REQUEST_THREADS_DEFAULT); 400 401 // create the actual snapshot procedure member 402 ThreadPoolExecutor pool = 403 ProcedureMember.defaultPool(rss.getServerName().toString(), opThreads, keepAlive); 404 this.member = new ProcedureMember(memberRpcs, pool, new SnapshotSubprocedureBuilder()); 405 } 406 407 @Override 408 public String getProcedureSignature() { 409 return SnapshotManager.ONLINE_SNAPSHOT_CONTROLLER_DESCRIPTION; 410 } 411 412}