View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.mob;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.concurrent.ConcurrentHashMap;
27  import java.util.concurrent.Executors;
28  import java.util.concurrent.ScheduledExecutorService;
29  import java.util.concurrent.TimeUnit;
30  import java.util.concurrent.atomic.AtomicLong;
31  import java.util.concurrent.locks.ReentrantLock;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.apache.hadoop.hbase.classification.InterfaceAudience;
36  import org.apache.hadoop.conf.Configuration;
37  import org.apache.hadoop.fs.FileSystem;
38  import org.apache.hadoop.fs.Path;
39  import org.apache.hadoop.hbase.util.IdLock;
40  
41  import com.google.common.util.concurrent.ThreadFactoryBuilder;
42  
43  /**
44   * The cache for mob files.
45   * This cache doesn't cache the mob file blocks. It only caches the references of mob files.
46   * We are doing this to avoid opening and closing mob files all the time. We just keep
47   * references open.
48   */
49  @InterfaceAudience.Private
50  public class MobFileCache {
51  
52    private static final Log LOG = LogFactory.getLog(MobFileCache.class);
53  
54    /*
55     * Eviction and statistics thread. Periodically run to print the statistics and
56     * evict the lru cached mob files when the count of the cached files is larger
57     * than the threshold.
58     */
59    static class EvictionThread extends Thread {
60      MobFileCache lru;
61  
62      public EvictionThread(MobFileCache lru) {
63        super("MobFileCache.EvictionThread");
64        setDaemon(true);
65        this.lru = lru;
66      }
67  
68      @Override
69      public void run() {
70        lru.evict();
71      }
72    }
73  
74    // a ConcurrentHashMap, accesses to this map are synchronized.
75    private Map<String, CachedMobFile> map = null;
76    // caches access count
77    private final AtomicLong count = new AtomicLong(0);
78    private long lastAccess = 0;
79    private final AtomicLong miss = new AtomicLong(0);
80    private long lastMiss = 0;
81    private final AtomicLong evictedFileCount = new AtomicLong(0);
82    private long lastEvictedFileCount = 0;
83  
84    // a lock to sync the evict to guarantee the eviction occurs in sequence.
85    // the method evictFile is not sync by this lock, the ConcurrentHashMap does the sync there.
86    private final ReentrantLock evictionLock = new ReentrantLock(true);
87  
88    //stripes lock on each mob file based on its hash. Sync the openFile/closeFile operations.
89    private final IdLock keyLock = new IdLock();
90  
91    private final ScheduledExecutorService scheduleThreadPool = Executors.newScheduledThreadPool(1,
92        new ThreadFactoryBuilder().setNameFormat("MobFileCache #%d").setDaemon(true).build());
93    private final Configuration conf;
94  
95    // the count of the cached references to mob files
96    private final int mobFileMaxCacheSize;
97    private final boolean isCacheEnabled;
98    private float evictRemainRatio;
99  
100   public MobFileCache(Configuration conf) {
101     this.conf = conf;
102     this.mobFileMaxCacheSize = conf.getInt(MobConstants.MOB_FILE_CACHE_SIZE_KEY,
103         MobConstants.DEFAULT_MOB_FILE_CACHE_SIZE);
104     isCacheEnabled = (mobFileMaxCacheSize > 0);
105     map = new ConcurrentHashMap<String, CachedMobFile>(mobFileMaxCacheSize);
106     if (isCacheEnabled) {
107       long period = conf.getLong(MobConstants.MOB_CACHE_EVICT_PERIOD,
108           MobConstants.DEFAULT_MOB_CACHE_EVICT_PERIOD); // in seconds
109       evictRemainRatio = conf.getFloat(MobConstants.MOB_CACHE_EVICT_REMAIN_RATIO,
110           MobConstants.DEFAULT_EVICT_REMAIN_RATIO);
111       if (evictRemainRatio < 0.0) {
112         evictRemainRatio = 0.0f;
113         LOG.warn(MobConstants.MOB_CACHE_EVICT_REMAIN_RATIO + " is less than 0.0, 0.0 is used.");
114       } else if (evictRemainRatio > 1.0) {
115         evictRemainRatio = 1.0f;
116         LOG.warn(MobConstants.MOB_CACHE_EVICT_REMAIN_RATIO + " is larger than 1.0, 1.0 is used.");
117       }
118       this.scheduleThreadPool.scheduleAtFixedRate(new EvictionThread(this), period, period,
119           TimeUnit.SECONDS);
120     }
121     LOG.info("MobFileCache is initialized, and the cache size is " + mobFileMaxCacheSize);
122   }
123 
124   /**
125    * Evicts the lru cached mob files when the count of the cached files is larger
126    * than the threshold.
127    */
128   public void evict() {
129     if (isCacheEnabled) {
130       // Ensure only one eviction at a time
131       if (!evictionLock.tryLock()) {
132         return;
133       }
134       printStatistics();
135       List<CachedMobFile> evictedFiles = new ArrayList<CachedMobFile>();
136       try {
137         if (map.size() <= mobFileMaxCacheSize) {
138           return;
139         }
140         List<CachedMobFile> files = new ArrayList<CachedMobFile>(map.values());
141         Collections.sort(files);
142         int start = (int) (mobFileMaxCacheSize * evictRemainRatio);
143         if (start >= 0) {
144           for (int i = start; i < files.size(); i++) {
145             String name = files.get(i).getFileName();
146             CachedMobFile evictedFile = map.remove(name);
147             if (evictedFile != null) {
148               evictedFiles.add(evictedFile);
149             }
150           }
151         }
152       } finally {
153         evictionLock.unlock();
154       }
155       // EvictionLock is released. Close the evicted files one by one.
156       // The closes are sync in the closeFile method.
157       for (CachedMobFile evictedFile : evictedFiles) {
158         closeFile(evictedFile);
159       }
160       evictedFileCount.addAndGet(evictedFiles.size());
161     }
162   }
163 
164   /**
165    * Evicts the cached file by the name.
166    * @param fileName The name of a cached file.
167    */
168   public void evictFile(String fileName) {
169     if (isCacheEnabled) {
170       IdLock.Entry lockEntry = null;
171       try {
172         // obtains the lock to close the cached file.
173         lockEntry = keyLock.getLockEntry(fileName.hashCode());
174         CachedMobFile evictedFile = map.remove(fileName);
175         if (evictedFile != null) {
176           evictedFile.close();
177           evictedFileCount.incrementAndGet();
178         }
179       } catch (IOException e) {
180         LOG.error("Failed to evict the file " + fileName, e);
181       } finally {
182         if (lockEntry != null) {
183           keyLock.releaseLockEntry(lockEntry);
184         }
185       }
186     }
187   }
188 
189   /**
190    * Opens a mob file.
191    * @param fs The current file system.
192    * @param path The file path.
193    * @param cacheConf The current MobCacheConfig
194    * @return A opened mob file.
195    * @throws IOException
196    */
197   public MobFile openFile(FileSystem fs, Path path, MobCacheConfig cacheConf) throws IOException {
198     if (!isCacheEnabled) {
199       MobFile mobFile = MobFile.create(fs, path, conf, cacheConf);
200       mobFile.open();
201       return mobFile;
202     } else {
203       String fileName = path.getName();
204       CachedMobFile cached = map.get(fileName);
205       IdLock.Entry lockEntry = keyLock.getLockEntry(fileName.hashCode());
206       try {
207         if (cached == null) {
208           cached = map.get(fileName);
209           if (cached == null) {
210             if (map.size() > mobFileMaxCacheSize) {
211               evict();
212             }
213             cached = CachedMobFile.create(fs, path, conf, cacheConf);
214             cached.open();
215             map.put(fileName, cached);
216             miss.incrementAndGet();
217           }
218         }
219         cached.open();
220         cached.access(count.incrementAndGet());
221       } finally {
222         keyLock.releaseLockEntry(lockEntry);
223       }
224       return cached;
225     }
226   }
227 
228   /**
229    * Closes a mob file.
230    * @param file The mob file that needs to be closed.
231    */
232   public void closeFile(MobFile file) {
233     IdLock.Entry lockEntry = null;
234     try {
235       if (!isCacheEnabled) {
236         file.close();
237       } else {
238         lockEntry = keyLock.getLockEntry(file.getFileName().hashCode());
239         file.close();
240       }
241     } catch (IOException e) {
242       LOG.error("MobFileCache, Exception happen during close " + file.getFileName(), e);
243     } finally {
244       if (lockEntry != null) {
245         keyLock.releaseLockEntry(lockEntry);
246       }
247     }
248   }
249 
250   public void shutdown() {
251     this.scheduleThreadPool.shutdown();
252     for (int i = 0; i < 100; i++) {
253       if (!this.scheduleThreadPool.isShutdown()) {
254         try {
255           Thread.sleep(10);
256         } catch (InterruptedException e) {
257           LOG.warn("Interrupted while sleeping");
258           Thread.currentThread().interrupt();
259           break;
260         }
261       }
262     }
263 
264     if (!this.scheduleThreadPool.isShutdown()) {
265       List<Runnable> runnables = this.scheduleThreadPool.shutdownNow();
266       LOG.debug("Still running " + runnables);
267     }
268   }
269 
270   /**
271    * Gets the count of cached mob files.
272    * @return The count of the cached mob files.
273    */
274   public int getCacheSize() {
275     return map == null ? 0 : map.size();
276   }
277 
278   /**
279    * Gets the count of accesses to the mob file cache.
280    * @return The count of accesses to the mob file cache.
281    */
282   public long getAccessCount() {
283     return count.get();
284   }
285 
286   /**
287    * Gets the count of misses to the mob file cache.
288    * @return The count of misses to the mob file cache.
289    */
290   public long getMissCount() {
291     return miss.get();
292   }
293 
294   /**
295    * Gets the number of items evicted from the mob file cache.
296    * @return The number of items evicted from the mob file cache.
297    */
298   public long getEvictedFileCount() {
299     return evictedFileCount.get();
300   }
301 
302   /**
303    * Gets the hit ratio to the mob file cache.
304    * @return The hit ratio to the mob file cache.
305    */
306   public double getHitRatio() {
307     return count.get() == 0 ? 0 : ((float) (count.get() - miss.get())) / (float) count.get();
308   }
309 
310   /**
311    * Prints the statistics.
312    */
313   public void printStatistics() {
314     long access = count.get() - lastAccess;
315     long missed = miss.get() - lastMiss;
316     long evicted = evictedFileCount.get() - lastEvictedFileCount;
317     int hitRatio = access == 0 ? 0 : (int) (((float) (access - missed)) / (float) access * 100);
318     LOG.info("MobFileCache Statistics, access: " + access + ", miss: " + missed + ", hit: "
319         + (access - missed) + ", hit ratio: " + hitRatio + "%, evicted files: " + evicted);
320     lastAccess += access;
321     lastMiss += missed;
322     lastEvictedFileCount += evicted;
323   }
324 
325 }