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.wal; 019 020import java.io.FileNotFoundException; 021import java.io.IOException; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.List; 025import java.util.Objects; 026import java.util.concurrent.atomic.AtomicBoolean; 027import java.util.regex.Pattern; 028 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.fs.FSDataInputStream; 031import org.apache.hadoop.fs.FileSystem; 032import org.apache.hadoop.fs.Path; 033import org.apache.hadoop.hbase.HConstants; 034import org.apache.hadoop.hbase.ServerName; 035import org.apache.hadoop.hbase.client.RegionInfo; 036import org.apache.yetus.audience.InterfaceAudience; 037import org.apache.yetus.audience.InterfaceStability; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040import org.apache.hadoop.hbase.regionserver.wal.AbstractFSWAL; 041import org.apache.hadoop.hbase.regionserver.wal.WALActionsListener; 042import org.apache.hadoop.hbase.util.CancelableProgressable; 043import org.apache.hadoop.hbase.util.FSUtils; 044import org.apache.hadoop.hbase.util.LeaseNotRecoveredException; 045import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; 046 047/** 048 * Base class of a WAL Provider that returns a single thread safe WAL that writes to Hadoop FS. By 049 * default, this implementation picks a directory in Hadoop FS based on a combination of 050 * <ul> 051 * <li>the HBase root directory 052 * <li>HConstants.HREGION_LOGDIR_NAME 053 * <li>the given factory's factoryId (usually identifying the regionserver by host:port) 054 * </ul> 055 * It also uses the providerId to differentiate among files. 056 */ 057@InterfaceAudience.Private 058@InterfaceStability.Evolving 059public abstract class AbstractFSWALProvider<T extends AbstractFSWAL<?>> implements WALProvider { 060 061 private static final Logger LOG = LoggerFactory.getLogger(AbstractFSWALProvider.class); 062 063 /** Separate old log into different dir by regionserver name **/ 064 public static final String SEPARATE_OLDLOGDIR = "hbase.separate.oldlogdir.by.regionserver"; 065 public static final boolean DEFAULT_SEPARATE_OLDLOGDIR = false; 066 067 // Only public so classes back in regionserver.wal can access 068 public interface Reader extends WAL.Reader { 069 /** 070 * @param fs File system. 071 * @param path Path. 072 * @param c Configuration. 073 * @param s Input stream that may have been pre-opened by the caller; may be null. 074 */ 075 void init(FileSystem fs, Path path, Configuration c, FSDataInputStream s) throws IOException; 076 } 077 078 protected volatile T wal; 079 protected WALFactory factory; 080 protected Configuration conf; 081 protected List<WALActionsListener> listeners = new ArrayList<>(); 082 protected String providerId; 083 protected AtomicBoolean initialized = new AtomicBoolean(false); 084 // for default wal provider, logPrefix won't change 085 protected String logPrefix; 086 087 /** 088 * we synchronized on walCreateLock to prevent wal recreation in different threads 089 */ 090 private final Object walCreateLock = new Object(); 091 092 /** 093 * @param factory factory that made us, identity used for FS layout. may not be null 094 * @param conf may not be null 095 * @param providerId differentiate between providers from one factory, used for FS layout. may be 096 * null 097 */ 098 @Override 099 public void init(WALFactory factory, Configuration conf, String providerId) throws IOException { 100 if (!initialized.compareAndSet(false, true)) { 101 throw new IllegalStateException("WALProvider.init should only be called once."); 102 } 103 this.factory = factory; 104 this.conf = conf; 105 this.providerId = providerId; 106 // get log prefix 107 StringBuilder sb = new StringBuilder().append(factory.factoryId); 108 if (providerId != null) { 109 if (providerId.startsWith(WAL_FILE_NAME_DELIMITER)) { 110 sb.append(providerId); 111 } else { 112 sb.append(WAL_FILE_NAME_DELIMITER).append(providerId); 113 } 114 } 115 logPrefix = sb.toString(); 116 doInit(conf); 117 } 118 119 @Override 120 public List<WAL> getWALs() { 121 if (wal == null) { 122 return Collections.emptyList(); 123 } 124 List<WAL> wals = new ArrayList<>(1); 125 wals.add(wal); 126 return wals; 127 } 128 129 @Override 130 public T getWAL(RegionInfo region) throws IOException { 131 T walCopy = wal; 132 if (walCopy == null) { 133 // only lock when need to create wal, and need to lock since 134 // creating hlog on fs is time consuming 135 synchronized (walCreateLock) { 136 walCopy = wal; 137 if (walCopy == null) { 138 walCopy = createWAL(); 139 wal = walCopy; 140 } 141 } 142 } 143 return walCopy; 144 } 145 146 protected abstract T createWAL() throws IOException; 147 148 protected abstract void doInit(Configuration conf) throws IOException; 149 150 @Override 151 public void shutdown() throws IOException { 152 T log = this.wal; 153 if (log != null) { 154 log.shutdown(); 155 } 156 } 157 158 @Override 159 public void close() throws IOException { 160 T log = this.wal; 161 if (log != null) { 162 log.close(); 163 } 164 } 165 166 /** 167 * iff the given WALFactory is using the DefaultWALProvider for meta and/or non-meta, count the 168 * number of files (rolled and active). if either of them aren't, count 0 for that provider. 169 */ 170 @Override 171 public long getNumLogFiles() { 172 T log = this.wal; 173 return log == null ? 0 : log.getNumLogFiles(); 174 } 175 176 /** 177 * iff the given WALFactory is using the DefaultWALProvider for meta and/or non-meta, count the 178 * size of files (only rolled). if either of them aren't, count 0 for that provider. 179 */ 180 @Override 181 public long getLogFileSize() { 182 T log = this.wal; 183 return log == null ? 0 : log.getLogFileSize(); 184 } 185 186 /** 187 * returns the number of rolled WAL files. 188 */ 189 @VisibleForTesting 190 public static int getNumRolledLogFiles(WAL wal) { 191 return ((AbstractFSWAL<?>) wal).getNumRolledLogFiles(); 192 } 193 194 /** 195 * returns the size of rolled WAL files. 196 */ 197 @VisibleForTesting 198 public static long getLogFileSize(WAL wal) { 199 return ((AbstractFSWAL<?>) wal).getLogFileSize(); 200 } 201 202 /** 203 * return the current filename from the current wal. 204 */ 205 @VisibleForTesting 206 public static Path getCurrentFileName(final WAL wal) { 207 return ((AbstractFSWAL<?>) wal).getCurrentFileName(); 208 } 209 210 /** 211 * request a log roll, but don't actually do it. 212 */ 213 @VisibleForTesting 214 static void requestLogRoll(final WAL wal) { 215 ((AbstractFSWAL<?>) wal).requestLogRoll(); 216 } 217 218 // should be package private; more visible for use in AbstractFSWAL 219 public static final String WAL_FILE_NAME_DELIMITER = "."; 220 /** The hbase:meta region's WAL filename extension */ 221 @VisibleForTesting 222 public static final String META_WAL_PROVIDER_ID = ".meta"; 223 static final String DEFAULT_PROVIDER_ID = "default"; 224 225 // Implementation details that currently leak in tests or elsewhere follow 226 /** File Extension used while splitting an WAL into regions (HBASE-2312) */ 227 public static final String SPLITTING_EXT = "-splitting"; 228 229 /** 230 * It returns the file create timestamp from the file name. For name format see 231 * {@link #validateWALFilename(String)} public until remaining tests move to o.a.h.h.wal 232 * @param wal must not be null 233 * @return the file number that is part of the WAL file name 234 */ 235 @VisibleForTesting 236 public static long extractFileNumFromWAL(final WAL wal) { 237 final Path walName = ((AbstractFSWAL<?>) wal).getCurrentFileName(); 238 if (walName == null) { 239 throw new IllegalArgumentException("The WAL path couldn't be null"); 240 } 241 final String[] walPathStrs = walName.toString().split("\\" + WAL_FILE_NAME_DELIMITER); 242 return Long.parseLong(walPathStrs[walPathStrs.length - (isMetaFile(walName) ? 2 : 1)]); 243 } 244 245 /** 246 * Pattern used to validate a WAL file name see {@link #validateWALFilename(String)} for 247 * description. 248 */ 249 private static final Pattern pattern = 250 Pattern.compile(".*\\.\\d*(" + META_WAL_PROVIDER_ID + ")*"); 251 252 /** 253 * A WAL file name is of the format: <wal-name>{@link #WAL_FILE_NAME_DELIMITER} 254 * <file-creation-timestamp>[.meta]. provider-name is usually made up of a server-name and a 255 * provider-id 256 * @param filename name of the file to validate 257 * @return <tt>true</tt> if the filename matches an WAL, <tt>false</tt> otherwise 258 */ 259 public static boolean validateWALFilename(String filename) { 260 return pattern.matcher(filename).matches(); 261 } 262 263 /** 264 * Construct the directory name for all WALs on a given server. Dir names currently look like this 265 * for WALs: <code>hbase//WALs/kalashnikov.att.net,61634,1486865297088</code>. 266 * @param serverName Server name formatted as described in {@link ServerName} 267 * @return the relative WAL directory name, e.g. <code>.logs/1.example.org,60030,12345</code> if 268 * <code>serverName</code> passed is <code>1.example.org,60030,12345</code> 269 */ 270 public static String getWALDirectoryName(final String serverName) { 271 StringBuilder dirName = new StringBuilder(HConstants.HREGION_LOGDIR_NAME); 272 dirName.append("/"); 273 dirName.append(serverName); 274 return dirName.toString(); 275 } 276 277 /** 278 * Construct the directory name for all old WALs on a given server. The default old WALs dir looks 279 * like: <code>hbase/oldWALs</code>. If you config hbase.separate.oldlogdir.by.regionserver to 280 * true, it looks like <code>hbase//oldWALs/kalashnikov.att.net,61634,1486865297088</code>. 281 * @param conf 282 * @param serverName Server name formatted as described in {@link ServerName} 283 * @return the relative WAL directory name 284 */ 285 public static String getWALArchiveDirectoryName(Configuration conf, final String serverName) { 286 StringBuilder dirName = new StringBuilder(HConstants.HREGION_OLDLOGDIR_NAME); 287 if (conf.getBoolean(SEPARATE_OLDLOGDIR, DEFAULT_SEPARATE_OLDLOGDIR)) { 288 dirName.append(Path.SEPARATOR); 289 dirName.append(serverName); 290 } 291 return dirName.toString(); 292 } 293 294 /** 295 * Pulls a ServerName out of a Path generated according to our layout rules. In the below layouts, 296 * this method ignores the format of the logfile component. Current format: [base directory for 297 * hbase]/hbase/.logs/ServerName/logfile or [base directory for 298 * hbase]/hbase/.logs/ServerName-splitting/logfile Expected to work for individual log files and 299 * server-specific directories. 300 * @return null if it's not a log file. Returns the ServerName of the region server that created 301 * this log file otherwise. 302 */ 303 public static ServerName getServerNameFromWALDirectoryName(Configuration conf, String path) 304 throws IOException { 305 if (path == null || path.length() <= HConstants.HREGION_LOGDIR_NAME.length()) { 306 return null; 307 } 308 309 if (conf == null) { 310 throw new IllegalArgumentException("parameter conf must be set"); 311 } 312 313 final String rootDir = conf.get(HConstants.HBASE_DIR); 314 if (rootDir == null || rootDir.isEmpty()) { 315 throw new IllegalArgumentException(HConstants.HBASE_DIR + " key not found in conf."); 316 } 317 318 final StringBuilder startPathSB = new StringBuilder(rootDir); 319 if (!rootDir.endsWith("/")) { 320 startPathSB.append('/'); 321 } 322 startPathSB.append(HConstants.HREGION_LOGDIR_NAME); 323 if (!HConstants.HREGION_LOGDIR_NAME.endsWith("/")) { 324 startPathSB.append('/'); 325 } 326 final String startPath = startPathSB.toString(); 327 328 String fullPath; 329 try { 330 fullPath = FileSystem.get(conf).makeQualified(new Path(path)).toString(); 331 } catch (IllegalArgumentException e) { 332 LOG.info("Call to makeQualified failed on " + path + " " + e.getMessage()); 333 return null; 334 } 335 336 if (!fullPath.startsWith(startPath)) { 337 return null; 338 } 339 340 final String serverNameAndFile = fullPath.substring(startPath.length()); 341 342 if (serverNameAndFile.indexOf('/') < "a,0,0".length()) { 343 // Either it's a file (not a directory) or it's not a ServerName format 344 return null; 345 } 346 347 Path p = new Path(path); 348 return getServerNameFromWALDirectoryName(p); 349 } 350 351 /** 352 * This function returns region server name from a log file name which is in one of the following 353 * formats: 354 * <ul> 355 * <li>hdfs://<name node>/hbase/.logs/<server name>-splitting/...</li> 356 * <li>hdfs://<name node>/hbase/.logs/<server name>/...</li> 357 * </ul> 358 * @return null if the passed in logFile isn't a valid WAL file path 359 */ 360 public static ServerName getServerNameFromWALDirectoryName(Path logFile) { 361 String logDirName = logFile.getParent().getName(); 362 // We were passed the directory and not a file in it. 363 if (logDirName.equals(HConstants.HREGION_LOGDIR_NAME)) { 364 logDirName = logFile.getName(); 365 } 366 ServerName serverName = null; 367 if (logDirName.endsWith(SPLITTING_EXT)) { 368 logDirName = logDirName.substring(0, logDirName.length() - SPLITTING_EXT.length()); 369 } 370 try { 371 serverName = ServerName.parseServerName(logDirName); 372 } catch (IllegalArgumentException | IllegalStateException ex) { 373 serverName = null; 374 LOG.warn("Cannot parse a server name from path=" + logFile + "; " + ex.getMessage()); 375 } 376 if (serverName != null && serverName.getStartcode() < 0) { 377 LOG.warn("Invalid log file path=" + logFile); 378 serverName = null; 379 } 380 return serverName; 381 } 382 383 public static boolean isMetaFile(Path p) { 384 return isMetaFile(p.getName()); 385 } 386 387 public static boolean isMetaFile(String p) { 388 if (p != null && p.endsWith(META_WAL_PROVIDER_ID)) { 389 return true; 390 } 391 return false; 392 } 393 394 public static boolean isArchivedLogFile(Path p) { 395 String oldLog = Path.SEPARATOR + HConstants.HREGION_OLDLOGDIR_NAME + Path.SEPARATOR; 396 return p.toString().contains(oldLog); 397 } 398 399 /** 400 * Get the archived WAL file path 401 * @param path - active WAL file path 402 * @param conf - configuration 403 * @return archived path if exists, path - otherwise 404 * @throws IOException exception 405 */ 406 public static Path getArchivedLogPath(Path path, Configuration conf) throws IOException { 407 Path rootDir = FSUtils.getRootDir(conf); 408 Path oldLogDir = new Path(rootDir, HConstants.HREGION_OLDLOGDIR_NAME); 409 if (conf.getBoolean(SEPARATE_OLDLOGDIR, DEFAULT_SEPARATE_OLDLOGDIR)) { 410 ServerName serverName = getServerNameFromWALDirectoryName(path); 411 if (serverName == null) { 412 LOG.error("Couldn't locate log: " + path); 413 return path; 414 } 415 oldLogDir = new Path(oldLogDir, serverName.getServerName()); 416 } 417 Path archivedLogLocation = new Path(oldLogDir, path.getName()); 418 final FileSystem fs = FSUtils.getCurrentFileSystem(conf); 419 420 if (fs.exists(archivedLogLocation)) { 421 LOG.info("Log " + path + " was moved to " + archivedLogLocation); 422 return archivedLogLocation; 423 } else { 424 LOG.error("Couldn't locate log: " + path); 425 return path; 426 } 427 } 428 429 /** 430 * Opens WAL reader with retries and additional exception handling 431 * @param path path to WAL file 432 * @param conf configuration 433 * @return WAL Reader instance 434 * @throws IOException 435 */ 436 public static org.apache.hadoop.hbase.wal.WAL.Reader openReader(Path path, Configuration conf) 437 throws IOException 438 439 { 440 long retryInterval = 2000; // 2 sec 441 int maxAttempts = 30; 442 int attempt = 0; 443 Exception ee = null; 444 org.apache.hadoop.hbase.wal.WAL.Reader reader = null; 445 while (reader == null && attempt++ < maxAttempts) { 446 try { 447 // Detect if this is a new file, if so get a new reader else 448 // reset the current reader so that we see the new data 449 reader = WALFactory.createReader(path.getFileSystem(conf), path, conf); 450 return reader; 451 } catch (FileNotFoundException fnfe) { 452 // If the log was archived, continue reading from there 453 Path archivedLog = AbstractFSWALProvider.getArchivedLogPath(path, conf); 454 if (!Objects.equals(path, archivedLog)) { 455 return openReader(archivedLog, conf); 456 } else { 457 throw fnfe; 458 } 459 } catch (LeaseNotRecoveredException lnre) { 460 // HBASE-15019 the WAL was not closed due to some hiccup. 461 LOG.warn("Try to recover the WAL lease " + path, lnre); 462 recoverLease(conf, path); 463 reader = null; 464 ee = lnre; 465 } catch (NullPointerException npe) { 466 // Workaround for race condition in HDFS-4380 467 // which throws a NPE if we open a file before any data node has the most recent block 468 // Just sleep and retry. Will require re-reading compressed WALs for compressionContext. 469 LOG.warn("Got NPE opening reader, will retry."); 470 reader = null; 471 ee = npe; 472 } 473 if (reader == null) { 474 // sleep before next attempt 475 try { 476 Thread.sleep(retryInterval); 477 } catch (InterruptedException e) { 478 } 479 } 480 } 481 throw new IOException("Could not open reader", ee); 482 } 483 484 // For HBASE-15019 485 private static void recoverLease(final Configuration conf, final Path path) { 486 try { 487 final FileSystem dfs = FSUtils.getCurrentFileSystem(conf); 488 FSUtils fsUtils = FSUtils.getInstance(dfs, conf); 489 fsUtils.recoverFileLease(dfs, path, conf, new CancelableProgressable() { 490 @Override 491 public boolean progress() { 492 LOG.debug("Still trying to recover WAL lease: " + path); 493 return true; 494 } 495 }); 496 } catch (IOException e) { 497 LOG.warn("unable to recover lease for WAL: " + path, e); 498 } 499 } 500 501 @Override 502 public void addWALActionsListener(WALActionsListener listener) { 503 listeners.add(listener); 504 } 505 506 /** 507 * Get prefix of the log from its name, assuming WAL name in format of 508 * log_prefix.filenumber.log_suffix 509 * @param name Name of the WAL to parse 510 * @return prefix of the log 511 * @see AbstractFSWAL#getCurrentFileName() 512 */ 513 public static String getWALPrefixFromWALName(String name) { 514 int endIndex = name.replaceAll(META_WAL_PROVIDER_ID, "").lastIndexOf("."); 515 return name.substring(0, endIndex); 516 } 517}