Package org.apache.hadoop.hdfs

Source Code of org.apache.hadoop.hdfs.LookasideCache$CacheEntry

/**
* 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.hdfs;

import java.io.IOException;
import java.util.Collection;
import java.util.Arrays;
import java.util.Comparator;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.*;
import org.apache.commons.logging.LogFactory;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.metrics.LookasideMetrics.LocalMetrics;

/**
* The cache is a typical implementation of a LRU cache.
*/

public class LookasideCache {

  public static final Log LOG = LogFactory.getLog(LookasideCache.class);

  // The size of the cache and its default value.
  public static String CACHESIZE = "fs.lookasidecache.size";
  public static long CACHESIZE_DEFAULT = 10 * 1024 * 1024;

  // The cache eviction percentage and its default value
  public static String CACHEEVICT_PERCENT = "fs.lookasidecache.evict.percent";
  public static long CACHEEVICT_PERCENT_DEFAULT = 10;

  // A static object to record metrics for all instances of this cache
  public static LocalMetrics localMetrics = new LocalMetrics();

  /*
   * Metrics are locally gathers in the static structure called 'stats'.
   * These are returned here to the metrics module, and the local values
   * in 'stats' are zeroed out.
   */
  public static LocalMetrics copyZeroLocalMetrics() {
    return new LocalMetrics(localMetrics);
  }

  /**
   * Returns a copy of the local Metrics
   */
  static LocalMetrics getLocalMetrics() {
    return localMetrics;
  }

  // The configuration
  private Configuration conf;

  // The global stamp is a monotonically increasing number, simulates
  // the passage of time.
  private static AtomicLong globalStamp = new AtomicLong(0);

  // a hashMap that keeps a  record of all entries in the cache. The
  // key to this hashMap is the full path name of the hdfs file. The
  // value is a record that describes the local file and its age.
  private ConcurrentHashMap<Path, CacheEntry> cacheMap;

  // the total size occupied by the cache. This is a virtual size,
  // the application can specify a size with each entry to be cached.
  private AtomicLong cacheSize = new AtomicLong(0);

  // is eviction in progress?
  private boolean evictionInProgress = false;

  // The maximum size of the cache. If it exceeds this number,
  // then eviction will start. However, current threads may
  // temporarily exceed this specified threshold.
  private long cacheSizeMax;

  // The percentage of the cache that will be evicted
  // if (and when) it becomes full.
  private long cacheEvictPercent;

  // call back into the application when an entry needs to be evicted
  private final Eviction evictionIface;

  // The details of each cache entry. It maps a hdfs path to
  // a local path.
  static class CacheEntry {
    Path hdfsPath;
    Path localPath;
    long genStamp;  // the timestamp of last access
    long entrySize; // the size of this entry

    CacheEntry(Path hdfsPath, Path localPath, long entrySize) {
      this.hdfsPath = hdfsPath;
      this.localPath = localPath;
      this.genStamp = globalStamp.incrementAndGet();
      this.entrySize = entrySize;
    }

    // stamp the latest timestamp in this entry
    synchronized void setGenstamp(long newValue) {
      assert this.genStamp < newValue;
      this.genStamp = newValue;
    }
  }

  // a comparator to sort CacheEntry in ascending order of genStamp
  static class CacheEntryComparator implements Comparator<CacheEntry> {
    public int compare(CacheEntry t1, CacheEntry t2) {
      if (t1.genStamp < t2.genStamp) {
        return -1;
      } else if (t1.genStamp > t2.genStamp) {
        return 1;
      } else {
        return 0;
      }
    }
  }
  static CacheEntryComparator LRU_COMPARATOR = new CacheEntryComparator();

  /**
   * The application that uses this cachemodule have to define this
   * interface so that the cache module can call back into the applcation
   * when an entry needs to be evicted from the cache.
   */
  interface Eviction {
    void evictCache(Path hdfsPath, Path localPath, long size)
      throws IOException;
  }

  /**
   * Create an instance of this Cache. Eviction notices are not
   * served to the application.
   */
  LookasideCache(Configuration conf) throws IOException {
    this(conf, null);
  }

  /**
   * Create an instance of this Cache. It has a maximum size. If
   * it fills up, we evict oldest entries from the cache until it has
   * a configured percentage of free space.
   */
  LookasideCache(Configuration conf, Eviction evictIface) throws IOException {
    this.conf = conf;
    this.cacheMap = new ConcurrentHashMap<Path, CacheEntry>();

    this.cacheSizeMax = conf.getLong(CACHESIZE, CACHESIZE_DEFAULT);
    this.cacheEvictPercent = conf.getLong(CACHEEVICT_PERCENT,
                                          CACHEEVICT_PERCENT_DEFAULT);
    this.evictionIface = evictIface;
    if (LOG.isDebugEnabled()) {
      LOG.debug("Cache size " + this.cacheSizeMax +
                " Cache evict percentage " + this.cacheEvictPercent);
    }
  }

  /**
   * Returns the max size of the cache
   */
  long getCacheMaxSize() {
    return cacheSizeMax;
  }

  /**
   * Returns the eviction percentage of the cache
   */
  long getCacheEvictPercent() {
    return cacheEvictPercent;
  }

  /**
   * Returns the current size of the cache
   */
  long getCacheSize() {
    return cacheSize.get();
  }

  /**
   * Adds an entry into the cache.
   * The size is the virtual size of this entry.
   */
  void addCache(Path hdfsPath, Path localPath, long size) throws IOException {
    localMetrics.numAdd++;
    CacheEntry c = new CacheEntry(hdfsPath, localPath, size);
    CacheEntry found = cacheMap.putIfAbsent(hdfsPath, c);
    if (found != null) {
      // If entry was already in the cache, update its timestamp
      assert size == found.entrySize;
      assert localPath.equals(found.localPath);
      found.setGenstamp(globalStamp.incrementAndGet());
      localMetrics.numAddExisting++;
      if (LOG.isDebugEnabled()) {
        LOG.debug("LookasideCache updating path " + hdfsPath);
      }
    } else {
      // We just inserted an entry in the cache. Increment the
      // recorded size of the cache.
      cacheSize.addAndGet(size);
      localMetrics.numAddNew++;
      if (LOG.isDebugEnabled()) {
        LOG.debug("LookasideCache add new path:" + hdfsPath +
                  " cachedPath:" + localPath +
                  " size " + size);
      }
    }
    // check if we need to evict because cache is full
    if (cacheSize.get() > cacheSizeMax) {
      checkEvict();
    }
  }

  /**
   * Change the localPath in the cache. The size remains the
   * same. The accesstime is updated.
   */
  void renameCache(Path oldhdfsPath, Path newhdfsPath,
    Path localPath) throws IOException {
    CacheEntry found = cacheMap.remove(oldhdfsPath);
    if (found == null) {
      String msg = "LookasideCache error renaming path: " + oldhdfsPath +
                   " to: " + newhdfsPath +
                   " Path " + newhdfsPath +
                   " because it does not exists in the cache.";
      LOG.warn(msg);
      return;
    }
    // Update its timestamp and localPath
    found.hdfsPath = newhdfsPath;
    found.setGenstamp(globalStamp.incrementAndGet());
    found.localPath = localPath;

    // add it back to the cache
    CacheEntry empty = cacheMap.putIfAbsent(newhdfsPath, found);
    if (empty != null) {
      String msg = "LookasideCache error renaming path: " + oldhdfsPath +
                   " to: " + newhdfsPath +
                   " Path " + newhdfsPath +
                   " already exists in the cache.";
      LOG.warn(msg);
      throw new IOException(msg);
    }
    localMetrics.numRename++;
    if (LOG.isDebugEnabled()) {
      LOG.debug("LookasideCache renamed path:" + oldhdfsPath +
                " to:" + newhdfsPath +
                " cachedPath: " + localPath);
    }
  }

  /**
   * Delete an entry from the cache.
   */
  void removeCache(Path hdfsPath) {
    CacheEntry c = cacheMap.remove(hdfsPath);
    if (c != null) {
      cacheSize.addAndGet(-c.entrySize);
      localMetrics.numRemove++;
      if (LOG.isDebugEnabled()) {
        LOG.debug("LookasideCache removed path:" + hdfsPath +
                  " freed up size: " + c.entrySize);
      }
    }
  }

  /**
   * Evicts an entry from the cache. This calls back into
   * the application to indicate that a cache entry has been
   * reclaimed.
   */
  void evictCache(Path hdfsPath) throws IOException {
    CacheEntry c = cacheMap.remove(hdfsPath);
    if (c != null) {
      cacheSize.addAndGet(-c.entrySize);
      if (evictionIface != null) {
        evictionIface.evictCache(c.hdfsPath, c.localPath, c.entrySize);
      }
      localMetrics.numEvict++;
      if (LOG.isDebugEnabled()) {
        LOG.debug("LookasideCache removed path:" + hdfsPath +
                  " freed up size: " + c.entrySize);
      }
    }
  }

  /**
   * Maps the hdfs pathname to a local pathname. Returns null
   * if this is not found in the cache.
   */
  Path getCache(Path hdfsPath) {
    CacheEntry c = cacheMap.get(hdfsPath);
    localMetrics.numGetAttempts++;
    if (c != null) {
      // update the accessTime before returning to caller
      c.setGenstamp(globalStamp.incrementAndGet());
      localMetrics.numGetHits++;
      return c.localPath;
    }
    return null; // not in cache
  }

  /**
   * Eviction occurs if the cache is full, we free up a specified
   * percentage of the cache on every run. This method is synchronized
   * so that only one thread is doing the eviction.
   */
  synchronized void checkEvict() throws IOException {
    if (cacheSize.get() < cacheSizeMax) {
      return;        // nothing to do, plenty of free space
    }

    // Only one thread should be doing the eviction. Do not block
    // current thread, it is ok to oversubscribe the cache size
    // temporarily.
    if (evictionInProgress) {
      return;
    }

    // record the fact that eviction has started.
    evictionInProgress = true;

    try {
      // if the cache has reached a threshold size, then free old entries.
      long curSize = cacheSize.get();

      // how much to evict in one iteration
      long targetSize = cacheSizeMax -
                        (cacheSizeMax * cacheEvictPercent)/100;

      if (LOG.isDebugEnabled()) {
        LOG.debug("Cache size " + curSize + " has exceeded the " +
                  " maximum configured cacpacity " + cacheSizeMax +
                  ". Eviction has to reduce cache size to " +
                  targetSize);
      }

      // sort all entries based on their accessTimes
      Collection<CacheEntry> values = cacheMap.values();
      CacheEntry[] records = values.toArray(new CacheEntry[values.size()]);
      Arrays.sort(records, LRU_COMPARATOR);

      for (int i = 0; i < records.length; i++) {
        if (cacheSize.get() <= targetSize) {
          break;           // we reclaimed everything we wanted to
        }
        CacheEntry c = records[i];
        evictCache(c.hdfsPath);
      }
    } finally {
      evictionInProgress = false; // eviction done.
    }
    if (LOG.isDebugEnabled()) {
      LOG.debug("Cache eviction complete. Current cache size is " +
                cacheSize.get());
    }
  }
}
TOP

Related Classes of org.apache.hadoop.hdfs.LookasideCache$CacheEntry

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.