1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
45
46
47
48
49 @InterfaceAudience.Private
50 public class MobFileCache {
51
52 private static final Log LOG = LogFactory.getLog(MobFileCache.class);
53
54
55
56
57
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
75 private Map<String, CachedMobFile> map = null;
76
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
85
86 private final ReentrantLock evictionLock = new ReentrantLock(true);
87
88
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
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);
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
126
127
128 public void evict() {
129 if (isCacheEnabled) {
130
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
156
157 for (CachedMobFile evictedFile : evictedFiles) {
158 closeFile(evictedFile);
159 }
160 evictedFileCount.addAndGet(evictedFiles.size());
161 }
162 }
163
164
165
166
167
168 public void evictFile(String fileName) {
169 if (isCacheEnabled) {
170 IdLock.Entry lockEntry = null;
171 try {
172
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
191
192
193
194
195
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
230
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
272
273
274 public int getCacheSize() {
275 return map == null ? 0 : map.size();
276 }
277
278
279
280
281
282 public long getAccessCount() {
283 return count.get();
284 }
285
286
287
288
289
290 public long getMissCount() {
291 return miss.get();
292 }
293
294
295
296
297
298 public long getEvictedFileCount() {
299 return evictedFileCount.get();
300 }
301
302
303
304
305
306 public double getHitRatio() {
307 return count.get() == 0 ? 0 : ((float) (count.get() - miss.get())) / (float) count.get();
308 }
309
310
311
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 }