All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.hadoop.hbase.wal.AbstractFSWALProvider Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta-1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.hadoop.hbase.wal;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.Abortable;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.regionserver.wal.AbstractFSWAL;
import org.apache.hadoop.hbase.regionserver.wal.WALActionsListener;
import org.apache.hadoop.hbase.util.Addressing;
import org.apache.hadoop.hbase.util.CancelableProgressable;
import org.apache.hadoop.hbase.util.CommonFSUtils;
import org.apache.hadoop.hbase.util.RecoverLeaseFSUtils;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.yetus.audience.InterfaceStability;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.hbase.thirdparty.com.google.common.collect.Lists;

/**
 * Base class of a WAL Provider that returns a single thread safe WAL that writes to Hadoop FS. By
 * default, this implementation picks a directory in Hadoop FS based on a combination of
 * 
    *
  • the HBase root directory *
  • HConstants.HREGION_LOGDIR_NAME *
  • the given factory's factoryId (usually identifying the regionserver by host:port) *
* It also uses the providerId to differentiate among files. */ @InterfaceAudience.Private @InterfaceStability.Evolving public abstract class AbstractFSWALProvider> implements WALProvider { private static final Logger LOG = LoggerFactory.getLogger(AbstractFSWALProvider.class); /** Separate old log into different dir by regionserver name **/ public static final String SEPARATE_OLDLOGDIR = "hbase.separate.oldlogdir.by.regionserver"; public static final boolean DEFAULT_SEPARATE_OLDLOGDIR = false; public interface Initializer { /** * A method to initialize a WAL reader. * @param startPosition the start position you want to read from, -1 means start reading from * the first WAL entry. Notice that, the first entry is not started at * position as we have several headers, so typically you should not pass 0 * here. */ void init(FileSystem fs, Path path, Configuration c, long startPosition) throws IOException; } protected volatile T wal; protected WALFactory factory; protected Configuration conf; protected List listeners = new ArrayList<>(); protected String providerId; protected AtomicBoolean initialized = new AtomicBoolean(false); // for default wal provider, logPrefix won't change protected String logPrefix; protected Abortable abortable; /** * We use walCreateLock to prevent wal recreation in different threads, and also prevent getWALs * missing the newly created WAL, see HBASE-21503 for more details. */ private final ReadWriteLock walCreateLock = new ReentrantReadWriteLock(); /** * @param factory factory that made us, identity used for FS layout. may not be null * @param conf may not be null * @param providerId differentiate between providers from one factory, used for FS layout. may be * null */ @Override public void init(WALFactory factory, Configuration conf, String providerId, Abortable abortable) throws IOException { if (!initialized.compareAndSet(false, true)) { throw new IllegalStateException("WALProvider.init should only be called once."); } this.factory = factory; this.conf = conf; this.providerId = providerId; // get log prefix StringBuilder sb = new StringBuilder().append(factory.factoryId); if (providerId != null) { if (providerId.startsWith(WAL_FILE_NAME_DELIMITER)) { sb.append(providerId); } else { sb.append(WAL_FILE_NAME_DELIMITER).append(providerId); } } logPrefix = sb.toString(); this.abortable = abortable; doInit(conf); } @Override public List getWALs() { if (wal != null) { return Lists.newArrayList(wal); } walCreateLock.readLock().lock(); try { if (wal == null) { return Collections.emptyList(); } else { return Lists.newArrayList(wal); } } finally { walCreateLock.readLock().unlock(); } } @Override public T getWAL(RegionInfo region) throws IOException { T walCopy = wal; if (walCopy != null) { return walCopy; } walCreateLock.writeLock().lock(); try { walCopy = wal; if (walCopy != null) { return walCopy; } walCopy = createWAL(); boolean succ = false; try { walCopy.init(); succ = true; } finally { if (!succ) { walCopy.close(); } } wal = walCopy; return walCopy; } finally { walCreateLock.writeLock().unlock(); } } protected abstract T createWAL() throws IOException; protected abstract void doInit(Configuration conf) throws IOException; @Override public void shutdown() throws IOException { T log = this.wal; if (log != null) { log.shutdown(); } } @Override public void close() throws IOException { T log = this.wal; if (log != null) { log.close(); } } /** * iff the given WALFactory is using the DefaultWALProvider for meta and/or non-meta, count the * number of files (rolled and active). if either of them aren't, count 0 for that provider. */ @Override public long getNumLogFiles() { T log = this.wal; return log == null ? 0 : log.getNumLogFiles(); } /** * iff the given WALFactory is using the DefaultWALProvider for meta and/or non-meta, count the * size of files (only rolled). if either of them aren't, count 0 for that provider. */ @Override public long getLogFileSize() { T log = this.wal; return log == null ? 0 : log.getLogFileSize(); } /** * returns the number of rolled WAL files. */ public static int getNumRolledLogFiles(WAL wal) { return ((AbstractFSWAL) wal).getNumRolledLogFiles(); } /** * returns the size of rolled WAL files. */ public static long getLogFileSize(WAL wal) { return ((AbstractFSWAL) wal).getLogFileSize(); } /** * return the current filename from the current wal. */ public static Path getCurrentFileName(final WAL wal) { return ((AbstractFSWAL) wal).getCurrentFileName(); } /** * request a log roll, but don't actually do it. */ static void requestLogRoll(final WAL wal) { ((AbstractFSWAL) wal).requestLogRoll(); } // should be package private; more visible for use in AbstractFSWAL public static final String WAL_FILE_NAME_DELIMITER = "."; /** The hbase:meta region's WAL filename extension */ public static final String META_WAL_PROVIDER_ID = ".meta"; static final String DEFAULT_PROVIDER_ID = "default"; // Implementation details that currently leak in tests or elsewhere follow /** File Extension used while splitting an WAL into regions (HBASE-2312) */ public static final String SPLITTING_EXT = "-splitting"; /** * Pattern used to validate a WAL file name see {@link #validateWALFilename(String)} for * description. */ private static final Pattern WAL_FILE_NAME_PATTERN = Pattern.compile("(.+)\\.(\\d+)(\\.[0-9A-Za-z]+)?"); /** * Define for when no timestamp found. */ private static final long NO_TIMESTAMP = -1L; /** * It returns the file create timestamp (the 'FileNum') from the file name. For name format see * {@link #validateWALFilename(String)} public until remaining tests move to o.a.h.h.wal * @param wal must not be null * @return the file number that is part of the WAL file name */ public static long extractFileNumFromWAL(final WAL wal) { final Path walPath = ((AbstractFSWAL) wal).getCurrentFileName(); if (walPath == null) { throw new IllegalArgumentException("The WAL path couldn't be null"); } String name = walPath.getName(); long timestamp = getTimestamp(name); if (timestamp == NO_TIMESTAMP) { throw new IllegalArgumentException(name + " is not a valid wal file name"); } return timestamp; } /** * A WAL file name is of the format: <wal-name>{@link #WAL_FILE_NAME_DELIMITER} * <file-creation-timestamp>[.<suffix>]. provider-name is usually made up of a * server-name and a provider-id * @param filename name of the file to validate * @return true if the filename matches an WAL, false otherwise */ public static boolean validateWALFilename(String filename) { return WAL_FILE_NAME_PATTERN.matcher(filename).matches(); } /** * Split a WAL filename to get a start time. WALs usually have the time we start writing to them * with as part of their name, usually the suffix. Sometimes there will be an extra suffix as when * it is a WAL for the meta table. For example, WALs might look like this * 10.20.20.171%3A60020.1277499063250 where 1277499063250 is the * timestamp. Could also be a meta WAL which adds a '.meta' suffix or a synchronous replication * WAL which adds a '.syncrep' suffix. Check for these. File also may have no timestamp on it. For * example the recovered.edits files are WALs but are named in ascending order. Here is an * example: 0000000000000016310. Allow for this. * @param name Name of the WAL file. * @return Timestamp or {@link #NO_TIMESTAMP}. */ public static long getTimestamp(String name) { Matcher matcher = WAL_FILE_NAME_PATTERN.matcher(name); return matcher.matches() ? Long.parseLong(matcher.group(2)) : NO_TIMESTAMP; } public static final Comparator TIMESTAMP_COMPARATOR = Comparator. comparing(p -> AbstractFSWALProvider.getTimestamp(p.getName())) .thenComparing(Path::getName); /** * Construct the directory name for all WALs on a given server. Dir names currently look like this * for WALs: hbase//WALs/kalashnikov.att.net,61634,1486865297088. * @param serverName Server name formatted as described in {@link ServerName} * @return the relative WAL directory name, e.g. .logs/1.example.org,60030,12345 if * serverName passed is 1.example.org,60030,12345 */ public static String getWALDirectoryName(final String serverName) { StringBuilder dirName = new StringBuilder(HConstants.HREGION_LOGDIR_NAME); dirName.append("/"); dirName.append(serverName); return dirName.toString(); } /** * Construct the directory name for all old WALs on a given server. The default old WALs dir looks * like: hbase/oldWALs. If you config hbase.separate.oldlogdir.by.regionserver to * true, it looks like hbase//oldWALs/kalashnikov.att.net,61634,1486865297088. * @param serverName Server name formatted as described in {@link ServerName} * @return the relative WAL directory name */ public static String getWALArchiveDirectoryName(Configuration conf, final String serverName) { StringBuilder dirName = new StringBuilder(HConstants.HREGION_OLDLOGDIR_NAME); if (conf.getBoolean(SEPARATE_OLDLOGDIR, DEFAULT_SEPARATE_OLDLOGDIR)) { dirName.append(Path.SEPARATOR); dirName.append(serverName); } return dirName.toString(); } /** * List all the old wal files for a dead region server. *

* Initially added for supporting replication, where we need to get the wal files to replicate for * a dead region server. */ public static List getArchivedWALFiles(Configuration conf, ServerName serverName, String logPrefix) throws IOException { Path walRootDir = CommonFSUtils.getWALRootDir(conf); FileSystem fs = walRootDir.getFileSystem(conf); List archivedWalFiles = new ArrayList<>(); // list both the root old wal dir and the separate old wal dir, so we will not miss any files if // the SEPARATE_OLDLOGDIR config is changed Path oldWalDir = new Path(walRootDir, HConstants.HREGION_OLDLOGDIR_NAME); try { for (FileStatus status : fs.listStatus(oldWalDir, p -> p.getName().startsWith(logPrefix))) { if (status.isFile()) { archivedWalFiles.add(status.getPath()); } } } catch (FileNotFoundException e) { LOG.info("Old WAL dir {} not exists", oldWalDir); return Collections.emptyList(); } Path separatedOldWalDir = new Path(oldWalDir, serverName.toString()); try { for (FileStatus status : fs.listStatus(separatedOldWalDir, p -> p.getName().startsWith(logPrefix))) { if (status.isFile()) { archivedWalFiles.add(status.getPath()); } } } catch (FileNotFoundException e) { LOG.info("Seprated old WAL dir {} not exists", separatedOldWalDir); } return archivedWalFiles; } /** * List all the wal files for a logPrefix. */ public static List getWALFiles(Configuration c, ServerName serverName) throws IOException { Path walRoot = new Path(CommonFSUtils.getWALRootDir(c), HConstants.HREGION_LOGDIR_NAME); FileSystem fs = walRoot.getFileSystem(c); List walFiles = new ArrayList<>(); Path walDir = new Path(walRoot, serverName.toString()); try { for (FileStatus status : fs.listStatus(walDir)) { if (status.isFile()) { walFiles.add(status.getPath()); } } } catch (FileNotFoundException e) { LOG.info("WAL dir {} not exists", walDir); } return walFiles; } /** * Pulls a ServerName out of a Path generated according to our layout rules. In the below layouts, * this method ignores the format of the logfile component. Current format: [base directory for * hbase]/hbase/.logs/ServerName/logfile or [base directory for * hbase]/hbase/.logs/ServerName-splitting/logfile Expected to work for individual log files and * server-specific directories. * @return null if it's not a log file. Returns the ServerName of the region server that created * this log file otherwise. */ public static ServerName getServerNameFromWALDirectoryName(Configuration conf, String path) throws IOException { if (path == null || path.length() <= HConstants.HREGION_LOGDIR_NAME.length()) { return null; } if (conf == null) { throw new IllegalArgumentException("parameter conf must be set"); } final String rootDir = conf.get(HConstants.HBASE_DIR); if (rootDir == null || rootDir.isEmpty()) { throw new IllegalArgumentException(HConstants.HBASE_DIR + " key not found in conf."); } final StringBuilder startPathSB = new StringBuilder(rootDir); if (!rootDir.endsWith("/")) { startPathSB.append('/'); } startPathSB.append(HConstants.HREGION_LOGDIR_NAME); if (!HConstants.HREGION_LOGDIR_NAME.endsWith("/")) { startPathSB.append('/'); } final String startPath = startPathSB.toString(); String fullPath; try { fullPath = FileSystem.get(conf).makeQualified(new Path(path)).toString(); } catch (IllegalArgumentException e) { LOG.info("Call to makeQualified failed on " + path + " " + e.getMessage()); return null; } if (!fullPath.startsWith(startPath)) { return null; } final String serverNameAndFile = fullPath.substring(startPath.length()); if (serverNameAndFile.indexOf('/') < "a,0,0".length()) { // Either it's a file (not a directory) or it's not a ServerName format return null; } Path p = new Path(path); return getServerNameFromWALDirectoryName(p); } /** * This function returns region server name from a log file name which is in one of the following * formats: *

    *
  • hdfs://<name node>/hbase/.logs/<server name>-splitting/...
  • *
  • hdfs://<name node>/hbase/.logs/<server name>/...
  • *
* @return null if the passed in logFile isn't a valid WAL file path */ public static ServerName getServerNameFromWALDirectoryName(Path logFile) { String logDirName = logFile.getParent().getName(); // We were passed the directory and not a file in it. if (logDirName.equals(HConstants.HREGION_LOGDIR_NAME)) { logDirName = logFile.getName(); } ServerName serverName = null; if (logDirName.endsWith(SPLITTING_EXT)) { logDirName = logDirName.substring(0, logDirName.length() - SPLITTING_EXT.length()); } try { serverName = ServerName.parseServerName(logDirName); } catch (IllegalArgumentException | IllegalStateException ex) { serverName = null; LOG.warn("Cannot parse a server name from path={}", logFile, ex); } if (serverName != null && serverName.getStartCode() < 0) { LOG.warn("Invalid log file path={}, start code {} is less than 0", logFile, serverName.getStartCode()); serverName = null; } return serverName; } public static boolean isMetaFile(Path p) { return isMetaFile(p.getName()); } /** Returns True if String ends in {@link #META_WAL_PROVIDER_ID} */ public static boolean isMetaFile(String p) { return p != null && p.endsWith(META_WAL_PROVIDER_ID); } /** * Comparator used to compare WAL files together based on their start time. Just compares start * times and nothing else. */ public static class WALStartTimeComparator implements Comparator { @Override public int compare(Path o1, Path o2) { return Long.compare(getTS(o1), getTS(o2)); } /** * Split a path to get the start time For example: 10.20.20.171%3A60020.1277499063250 Could also * be a meta WAL which adds a '.meta' suffix or a synchronous replication WAL which adds a * '.syncrep' suffix. Check. * @param p path to split * @return start time */ public static long getTS(Path p) { return getTimestamp(p.getName()); } } public static boolean isArchivedLogFile(Path p) { String oldLog = Path.SEPARATOR + HConstants.HREGION_OLDLOGDIR_NAME + Path.SEPARATOR; return p.toString().contains(oldLog); } /** * Find the archived WAL file path if it is not able to locate in WALs dir. * @param path - active WAL file path * @param conf - configuration * @return archived path if exists, null - otherwise * @throws IOException exception */ public static Path findArchivedLog(Path path, Configuration conf) throws IOException { // If the path contains oldWALs keyword then exit early. if (path.toString().contains(HConstants.HREGION_OLDLOGDIR_NAME)) { return null; } Path walRootDir = CommonFSUtils.getWALRootDir(conf); FileSystem fs = path.getFileSystem(conf); // Try finding the log in old dir Path oldLogDir = new Path(walRootDir, HConstants.HREGION_OLDLOGDIR_NAME); Path archivedLogLocation = new Path(oldLogDir, path.getName()); if (fs.exists(archivedLogLocation)) { LOG.info("Log " + path + " was moved to " + archivedLogLocation); return archivedLogLocation; } ServerName serverName = getServerNameFromWALDirectoryName(path); if (serverName == null) { LOG.warn("Can not extract server name from path {}, " + "give up searching the separated old log dir", path); return null; } // Try finding the log in separate old log dir oldLogDir = new Path(walRootDir, new StringBuilder(HConstants.HREGION_OLDLOGDIR_NAME) .append(Path.SEPARATOR).append(serverName.getServerName()).toString()); archivedLogLocation = new Path(oldLogDir, path.getName()); if (fs.exists(archivedLogLocation)) { LOG.info("Log " + path + " was moved to " + archivedLogLocation); return archivedLogLocation; } LOG.error("Couldn't locate log: " + path); return null; } // For HBASE-15019 public static void recoverLease(Configuration conf, Path path) { try { final FileSystem dfs = CommonFSUtils.getCurrentFileSystem(conf); RecoverLeaseFSUtils.recoverFileLease(dfs, path, conf, new CancelableProgressable() { @Override public boolean progress() { LOG.debug("Still trying to recover WAL lease: " + path); return true; } }); } catch (IOException e) { LOG.warn("unable to recover lease for WAL: " + path, e); } } @Override public void addWALActionsListener(WALActionsListener listener) { listeners.add(listener); } private static String getWALNameGroupFromWALName(String name, int group) { Matcher matcher = WAL_FILE_NAME_PATTERN.matcher(name); if (matcher.matches()) { return matcher.group(group); } else { throw new IllegalArgumentException(name + " is not a valid wal file name"); } } /** * Get prefix of the log from its name, assuming WAL name in format of * log_prefix.filenumber.log_suffix * @param name Name of the WAL to parse * @return prefix of the log * @throws IllegalArgumentException if the name passed in is not a valid wal file name * @see AbstractFSWAL#getCurrentFileName() */ public static String getWALPrefixFromWALName(String name) { return getWALNameGroupFromWALName(name, 1); } private static final Pattern SERVER_NAME_PATTERN = Pattern.compile("^[^" + ServerName.SERVERNAME_SEPARATOR + "]+" + ServerName.SERVERNAME_SEPARATOR + Addressing.VALID_PORT_REGEX + ServerName.SERVERNAME_SEPARATOR + Addressing.VALID_PORT_REGEX); /** * Parse the server name from wal prefix. A wal's name is always started with a server name in non * test code. * @throws IllegalArgumentException if the name passed in is not started with a server name * @return the server name */ public static ServerName parseServerNameFromWALName(String name) { String decoded; try { decoded = URLDecoder.decode(name, StandardCharsets.UTF_8.name()); } catch (UnsupportedEncodingException e) { throw new AssertionError("should never happen", e); } Matcher matcher = SERVER_NAME_PATTERN.matcher(decoded); if (matcher.find()) { return ServerName.valueOf(matcher.group()); } else { throw new IllegalArgumentException(name + " is not started with a server name"); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy