org.apache.hadoop.hbase.mob.MobFileCache Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hbase-server Show documentation
Show all versions of hbase-server Show documentation
Server functionality for HBase
/*
* 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.mob;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.io.hfile.CacheConfig;
import org.apache.hadoop.hbase.util.IdLock;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hbase.thirdparty.com.google.common.hash.Hashing;
import org.apache.hbase.thirdparty.com.google.common.util.concurrent.ThreadFactoryBuilder;
/**
* The cache for mob files. This cache doesn't cache the mob file blocks. It only caches the
* references of mob files. We are doing this to avoid opening and closing mob files all the time.
* We just keep references open.
*/
@InterfaceAudience.Private
public class MobFileCache {
private static final Logger LOG = LoggerFactory.getLogger(MobFileCache.class);
/*
* Eviction and statistics thread. Periodically run to print the statistics and evict the lru
* cached mob files when the count of the cached files is larger than the threshold.
*/
static class EvictionThread extends Thread {
MobFileCache lru;
public EvictionThread(MobFileCache lru) {
super("MobFileCache.EvictionThread");
setDaemon(true);
this.lru = lru;
}
@Override
public void run() {
lru.evict();
}
}
// a ConcurrentHashMap, accesses to this map are synchronized.
private Map map = null;
// caches access count
private final AtomicLong count = new AtomicLong(0);
private long lastAccess = 0;
private final LongAdder miss = new LongAdder();
private long lastMiss = 0;
private final LongAdder evictedFileCount = new LongAdder();
private long lastEvictedFileCount = 0;
// a lock to sync the evict to guarantee the eviction occurs in sequence.
// the method evictFile is not sync by this lock, the ConcurrentHashMap does the sync there.
private final ReentrantLock evictionLock = new ReentrantLock(true);
// stripes lock on each mob file based on its hash. Sync the openFile/closeFile operations.
private final IdLock keyLock = new IdLock();
private final ScheduledExecutorService scheduleThreadPool = Executors.newScheduledThreadPool(1,
new ThreadFactoryBuilder().setNameFormat("MobFileCache #%d").setDaemon(true).build());
private final Configuration conf;
// the count of the cached references to mob files
private final int mobFileMaxCacheSize;
private final boolean isCacheEnabled;
private float evictRemainRatio;
public MobFileCache(Configuration conf) {
this.conf = conf;
this.mobFileMaxCacheSize =
conf.getInt(MobConstants.MOB_FILE_CACHE_SIZE_KEY, MobConstants.DEFAULT_MOB_FILE_CACHE_SIZE);
isCacheEnabled = (mobFileMaxCacheSize > 0);
map = new ConcurrentHashMap<>(mobFileMaxCacheSize);
if (isCacheEnabled) {
long period = conf.getLong(MobConstants.MOB_CACHE_EVICT_PERIOD,
MobConstants.DEFAULT_MOB_CACHE_EVICT_PERIOD); // in seconds
evictRemainRatio = conf.getFloat(MobConstants.MOB_CACHE_EVICT_REMAIN_RATIO,
MobConstants.DEFAULT_EVICT_REMAIN_RATIO);
if (evictRemainRatio < 0.0) {
evictRemainRatio = 0.0f;
LOG.warn(MobConstants.MOB_CACHE_EVICT_REMAIN_RATIO + " is less than 0.0, 0.0 is used.");
} else if (evictRemainRatio > 1.0) {
evictRemainRatio = 1.0f;
LOG.warn(MobConstants.MOB_CACHE_EVICT_REMAIN_RATIO + " is larger than 1.0, 1.0 is used.");
}
this.scheduleThreadPool.scheduleAtFixedRate(new EvictionThread(this), period, period,
TimeUnit.SECONDS);
if (LOG.isDebugEnabled()) {
LOG.debug("MobFileCache enabled with cacheSize=" + mobFileMaxCacheSize + ", evictPeriods="
+ period + "sec, evictRemainRatio=" + evictRemainRatio);
}
} else {
LOG.info("MobFileCache disabled");
}
}
/**
* Evicts the lru cached mob files when the count of the cached files is larger than the
* threshold.
*/
public void evict() {
if (isCacheEnabled) {
// Ensure only one eviction at a time
if (!evictionLock.tryLock()) {
return;
}
printStatistics();
List evictedFiles = new ArrayList<>();
try {
if (map.size() <= mobFileMaxCacheSize) {
return;
}
List files = new ArrayList<>(map.values());
Collections.sort(files);
int start = (int) (mobFileMaxCacheSize * evictRemainRatio);
if (start >= 0) {
for (int i = start; i < files.size(); i++) {
String name = files.get(i).getFileName();
CachedMobFile evictedFile = map.remove(name);
if (evictedFile != null) {
evictedFiles.add(evictedFile);
}
}
}
} finally {
evictionLock.unlock();
}
// EvictionLock is released. Close the evicted files one by one.
// The closes are sync in the closeFile method.
for (CachedMobFile evictedFile : evictedFiles) {
closeFile(evictedFile);
}
evictedFileCount.add(evictedFiles.size());
}
}
/**
* Evicts the cached file by the name.
* @param fileName The name of a cached file.
*/
public void evictFile(String fileName) {
if (isCacheEnabled) {
IdLock.Entry lockEntry = null;
try {
// obtains the lock to close the cached file.
lockEntry = keyLock.getLockEntry(hashFileName(fileName));
CachedMobFile evictedFile = map.remove(fileName);
if (evictedFile != null) {
evictedFile.close();
evictedFileCount.increment();
}
} catch (IOException e) {
LOG.error("Failed to evict the file " + fileName, e);
} finally {
if (lockEntry != null) {
keyLock.releaseLockEntry(lockEntry);
}
}
}
}
/**
* Opens a mob file.
* @param fs The current file system.
* @param path The file path.
* @param cacheConf The current MobCacheConfig
* @return A opened mob file.
*/
public MobFile openFile(FileSystem fs, Path path, CacheConfig cacheConf) throws IOException {
if (!isCacheEnabled) {
MobFile mobFile = MobFile.create(fs, path, conf, cacheConf);
mobFile.open();
return mobFile;
} else {
String fileName = path.getName();
CachedMobFile cached = map.get(fileName);
IdLock.Entry lockEntry = keyLock.getLockEntry(hashFileName(fileName));
try {
if (cached == null) {
cached = map.get(fileName);
if (cached == null) {
if (map.size() > mobFileMaxCacheSize) {
evict();
}
cached = CachedMobFile.create(fs, path, conf, cacheConf);
cached.open();
map.put(fileName, cached);
miss.increment();
}
}
cached.open();
cached.access(count.incrementAndGet());
} finally {
keyLock.releaseLockEntry(lockEntry);
}
return cached;
}
}
/**
* Closes a mob file.
* @param file The mob file that needs to be closed.
*/
public void closeFile(MobFile file) {
IdLock.Entry lockEntry = null;
try {
if (!isCacheEnabled) {
file.close();
} else {
lockEntry = keyLock.getLockEntry(hashFileName(file.getFileName()));
file.close();
}
} catch (IOException e) {
LOG.error("MobFileCache, Exception happen during close " + file.getFileName(), e);
} finally {
if (lockEntry != null) {
keyLock.releaseLockEntry(lockEntry);
}
}
}
public void shutdown() {
this.scheduleThreadPool.shutdown();
for (int i = 0; i < 100; i++) {
if (!this.scheduleThreadPool.isShutdown()) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
LOG.warn("Interrupted while sleeping");
Thread.currentThread().interrupt();
break;
}
}
}
if (!this.scheduleThreadPool.isShutdown()) {
List runnables = this.scheduleThreadPool.shutdownNow();
LOG.debug("Still running " + runnables);
}
}
/**
* Gets the count of cached mob files.
* @return The count of the cached mob files.
*/
public int getCacheSize() {
return map == null ? 0 : map.size();
}
/**
* Gets the count of accesses to the mob file cache.
* @return The count of accesses to the mob file cache.
*/
public long getAccessCount() {
return count.get();
}
/**
* Gets the count of misses to the mob file cache.
* @return The count of misses to the mob file cache.
*/
public long getMissCount() {
return miss.sum();
}
/**
* Gets the number of items evicted from the mob file cache.
* @return The number of items evicted from the mob file cache.
*/
public long getEvictedFileCount() {
return evictedFileCount.sum();
}
/**
* Gets the hit ratio to the mob file cache.
* @return The hit ratio to the mob file cache.
*/
public double getHitRatio() {
return count.get() == 0 ? 0 : ((float) (count.get() - miss.sum())) / (float) count.get();
}
/**
* Prints the statistics.
*/
public void printStatistics() {
long access = count.get() - lastAccess;
long missed = miss.sum() - lastMiss;
long evicted = evictedFileCount.sum() - lastEvictedFileCount;
int hitRatio = access == 0 ? 0 : (int) (((float) (access - missed)) / (float) access * 100);
LOG.info("MobFileCache Statistics, access: " + access + ", miss: " + missed + ", hit: "
+ (access - missed) + ", hit ratio: " + hitRatio + "%, evicted files: " + evicted);
lastAccess += access;
lastMiss += missed;
lastEvictedFileCount += evicted;
}
/**
* Use murmurhash to reduce the conflicts of hashed file names. We should notice that the hash
* conflicts may bring deadlocks, when opening mob files with evicting some other files, as
* described in HBASE-28047.
*/
private long hashFileName(String fileName) {
return Hashing.murmur3_128().hashString(fileName, java.nio.charset.StandardCharsets.UTF_8)
.asLong();
}
}