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.io.hfile.slab;
20  
21  import java.nio.ByteBuffer;
22  import java.util.Iterator;
23  import java.util.concurrent.ConcurrentMap;
24  import java.util.concurrent.atomic.AtomicLong;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.classification.InterfaceAudience;
29  import org.apache.hadoop.hbase.io.HeapSize;
30  import org.apache.hadoop.hbase.io.hfile.BlockCache;
31  import org.apache.hadoop.hbase.io.hfile.BlockCacheKey;
32  import org.apache.hadoop.hbase.io.hfile.CacheStats;
33  import org.apache.hadoop.hbase.io.hfile.Cacheable;
34  import org.apache.hadoop.hbase.io.hfile.CacheableDeserializer;
35  import org.apache.hadoop.hbase.io.hfile.CachedBlock;
36  import org.apache.hadoop.hbase.util.Bytes;
37  import org.apache.hadoop.hbase.util.ClassSize;
38  import org.apache.hadoop.util.StringUtils;
39  
40  import com.google.common.cache.CacheBuilder;
41  import com.google.common.cache.RemovalListener;
42  import com.google.common.cache.RemovalNotification;
43  
44  /**
45   * SingleSizeCache is a slab allocated cache that caches elements up to a single
46   * size. It uses a slab allocator (Slab.java) to divide a direct bytebuffer,
47   * into evenly sized blocks. Any cached data will take up exactly 1 block. An
48   * exception will be thrown if the cached data cannot fit into the blockSize of
49   * this SingleSizeCache.
50   *
51   * Eviction and LRUness is taken care of by Guava's MapMaker, which creates a
52   * ConcurrentLinkedHashMap.
53   *
54   * @deprecated As of 1.0, replaced by {@link org.apache.hadoop.hbase.io.hfile.bucket.BucketCache}.
55   **/
56  @InterfaceAudience.Private
57  @Deprecated
58  public class SingleSizeCache implements BlockCache, HeapSize {
59    private final Slab backingStore;
60    private final ConcurrentMap<BlockCacheKey, CacheablePair> backingMap;
61    private final int numBlocks;
62    private final int blockSize;
63    private final CacheStats stats;
64    private final SlabItemActionWatcher actionWatcher;
65    private final AtomicLong size;
66    private final AtomicLong timeSinceLastAccess;
67    public final static long CACHE_FIXED_OVERHEAD = ClassSize
68        .align((2 * Bytes.SIZEOF_INT) + (5 * ClassSize.REFERENCE)
69            + +ClassSize.OBJECT);
70  
71    static final Log LOG = LogFactory.getLog(SingleSizeCache.class);
72  
73    /**
74     * Default constructor. Specify the size of the blocks, number of blocks, and
75     * the SlabCache this cache will be assigned to.
76     *
77     *
78     * @param blockSize the size of each block, in bytes
79     *
80     * @param numBlocks the number of blocks of blockSize this cache will hold.
81     *
82     * @param master the SlabCache this SingleSlabCache is assigned to.
83     */
84    public SingleSizeCache(int blockSize, int numBlocks,
85        SlabItemActionWatcher master) {
86      this.blockSize = blockSize;
87      this.numBlocks = numBlocks;
88      backingStore = new Slab(blockSize, numBlocks);
89      this.stats = new CacheStats();
90      this.actionWatcher = master;
91      this.size = new AtomicLong(CACHE_FIXED_OVERHEAD + backingStore.heapSize());
92      this.timeSinceLastAccess = new AtomicLong();
93  
94      // This evictionListener is called whenever the cache automatically
95      // evicts something.
96      RemovalListener<BlockCacheKey, CacheablePair> listener =
97        new RemovalListener<BlockCacheKey, CacheablePair>() {
98          @Override
99          public void onRemoval(
100             RemovalNotification<BlockCacheKey, CacheablePair> notification) {
101           if (!notification.wasEvicted()) {
102             // Only process removals by eviction, not by replacement or
103             // explicit removal
104             return;
105           }
106           CacheablePair value = notification.getValue();
107           timeSinceLastAccess.set(System.nanoTime()
108               - value.recentlyAccessed.get());
109           stats.evict();
110           doEviction(notification.getKey(), value);
111         }
112       };
113 
114     backingMap = CacheBuilder.newBuilder()
115         .maximumSize(numBlocks - 1)
116         .removalListener(listener)
117         .<BlockCacheKey, CacheablePair>build()
118         .asMap();
119   }
120 
121   @Override
122   public void cacheBlock(BlockCacheKey blockName, Cacheable toBeCached) {
123     ByteBuffer storedBlock;
124 
125     try {
126       storedBlock = backingStore.alloc(toBeCached.getSerializedLength());
127     } catch (InterruptedException e) {
128       LOG.warn("SlabAllocator was interrupted while waiting for block to become available");
129       LOG.warn(e);
130       return;
131     }
132 
133     CacheablePair newEntry = new CacheablePair(toBeCached.getDeserializer(),
134         storedBlock);
135     toBeCached.serialize(storedBlock);
136 
137     synchronized (this) {
138       CacheablePair alreadyCached = backingMap.putIfAbsent(blockName, newEntry);
139 
140       if (alreadyCached != null) {
141         backingStore.free(storedBlock);
142         throw new RuntimeException("already cached " + blockName);
143       }
144       if (actionWatcher != null) {
145         actionWatcher.onInsertion(blockName, this);
146       }
147     }
148     newEntry.recentlyAccessed.set(System.nanoTime());
149     this.size.addAndGet(newEntry.heapSize());
150   }
151 
152   @Override
153   public Cacheable getBlock(BlockCacheKey key, boolean caching, boolean repeat,
154       boolean updateCacheMetrics) {
155     CacheablePair contentBlock = backingMap.get(key);
156     if (contentBlock == null) {
157       if (!repeat && updateCacheMetrics) stats.miss(caching);
158       return null;
159     }
160 
161     if (updateCacheMetrics) stats.hit(caching);
162     // If lock cannot be obtained, that means we're undergoing eviction.
163     try {
164       contentBlock.recentlyAccessed.set(System.nanoTime());
165       synchronized (contentBlock) {
166         if (contentBlock.serializedData == null) {
167           // concurrently evicted
168           LOG.warn("Concurrent eviction of " + key);
169           return null;
170         }
171         return contentBlock.deserializer
172             .deserialize(contentBlock.serializedData.asReadOnlyBuffer());
173       }
174     } catch (Throwable t) {
175       LOG.error("Deserializer threw an exception. This may indicate a bug.", t);
176       return null;
177     }
178   }
179 
180   /**
181    * Evicts the block
182    *
183    * @param key the key of the entry we are going to evict
184    * @return the evicted ByteBuffer
185    */
186   public boolean evictBlock(BlockCacheKey key) {
187     stats.evict();
188     CacheablePair evictedBlock = backingMap.remove(key);
189 
190     if (evictedBlock != null) {
191       doEviction(key, evictedBlock);
192     }
193     return evictedBlock != null;
194   }
195 
196   private void doEviction(BlockCacheKey key, CacheablePair evictedBlock) {
197     long evictedHeap = 0;
198     synchronized (evictedBlock) {
199       if (evictedBlock.serializedData == null) {
200         // someone else already freed
201         return;
202       }
203       evictedHeap = evictedBlock.heapSize();
204       ByteBuffer bb = evictedBlock.serializedData;
205       evictedBlock.serializedData = null;
206       backingStore.free(bb);
207 
208       // We have to do this callback inside the synchronization here.
209       // Otherwise we can have the following interleaving:
210       // Thread A calls getBlock():
211       // SlabCache directs call to this SingleSizeCache
212       // It gets the CacheablePair object
213       // Thread B runs eviction
214       // doEviction() is called and sets serializedData = null, here.
215       // Thread A sees the null serializedData, and returns null
216       // Thread A calls cacheBlock on the same block, and gets
217       // "already cached" since the block is still in backingStore
218 
219       if (actionWatcher != null) {
220         actionWatcher.onEviction(key, this);
221       }
222     }
223     stats.evicted();
224     size.addAndGet(-1 * evictedHeap);
225   }
226 
227   public void logStats() {
228 
229     long milliseconds = this.timeSinceLastAccess.get() / 1000000;
230 
231     LOG.info("For Slab of size " + this.blockSize + ": "
232         + this.getOccupiedSize() / this.blockSize
233         + " occupied, out of a capacity of " + this.numBlocks
234         + " blocks. HeapSize is "
235         + StringUtils.humanReadableInt(this.heapSize()) + " bytes." + ", "
236         + "churnTime=" + StringUtils.formatTime(milliseconds));
237 
238     LOG.info("Slab Stats: " + "accesses="
239         + stats.getRequestCount()
240         + ", "
241         + "hits="
242         + stats.getHitCount()
243         + ", "
244         + "hitRatio="
245         + (stats.getHitCount() == 0 ? "0" : (StringUtils.formatPercent(
246             stats.getHitRatio(), 2) + "%, "))
247         + "cachingAccesses="
248         + stats.getRequestCachingCount()
249         + ", "
250         + "cachingHits="
251         + stats.getHitCachingCount()
252         + ", "
253         + "cachingHitsRatio="
254         + (stats.getHitCachingCount() == 0 ? "0" : (StringUtils.formatPercent(
255             stats.getHitCachingRatio(), 2) + "%, ")) + "evictions="
256         + stats.getEvictionCount() + ", " + "evicted="
257         + stats.getEvictedCount() + ", " + "evictedPerRun="
258         + stats.evictedPerEviction());
259 
260   }
261 
262   public void shutdown() {
263     backingStore.shutdown();
264   }
265 
266   public long heapSize() {
267     return this.size.get() + backingStore.heapSize();
268   }
269 
270   public long size() {
271     return (long) this.blockSize * (long) this.numBlocks;
272   }
273 
274   public long getFreeSize() {
275     return (long) backingStore.getBlocksRemaining() * (long) blockSize;
276   }
277 
278   public long getOccupiedSize() {
279     return (long) (numBlocks - backingStore.getBlocksRemaining()) * (long) blockSize;
280   }
281 
282   public long getEvictedCount() {
283     return stats.getEvictedCount();
284   }
285 
286   public CacheStats getStats() {
287     return this.stats;
288   }
289 
290   @Override
291   public long getBlockCount() {
292     return numBlocks - backingStore.getBlocksRemaining();
293   }
294 
295   /* Since its offheap, it doesn't matter if its in memory or not */
296   @Override
297   public void cacheBlock(BlockCacheKey cacheKey, Cacheable buf, boolean inMemory,
298       final boolean cacheDataInL1) {
299     this.cacheBlock(cacheKey, buf);
300   }
301 
302   /*
303    * This is never called, as evictions are handled in the SlabCache layer,
304    * implemented in the event we want to use this as a standalone cache.
305    */
306   @Override
307   public int evictBlocksByHfileName(String hfileName) {
308     int evictedCount = 0;
309     for (BlockCacheKey e : backingMap.keySet()) {
310       if (e.getHfileName().equals(hfileName)) {
311         this.evictBlock(e);
312       }
313     }
314     return evictedCount;
315   }
316 
317   @Override
318   public long getCurrentSize() {
319     return 0;
320   }
321 
322   /* Just a pair class, holds a reference to the parent cacheable */
323   private static class CacheablePair implements HeapSize {
324     final CacheableDeserializer<Cacheable> deserializer;
325     ByteBuffer serializedData;
326     AtomicLong recentlyAccessed;
327 
328     private CacheablePair(CacheableDeserializer<Cacheable> deserializer,
329         ByteBuffer serializedData) {
330       this.recentlyAccessed = new AtomicLong();
331       this.deserializer = deserializer;
332       this.serializedData = serializedData;
333     }
334 
335     /*
336      * Heapsize overhead of this is the default object overhead, the heapsize of
337      * the serialized object, and the cost of a reference to the bytebuffer,
338      * which is already accounted for in SingleSizeCache
339      */
340     @Override
341     public long heapSize() {
342       return ClassSize.align(ClassSize.OBJECT + ClassSize.REFERENCE * 3
343           + ClassSize.ATOMIC_LONG);
344     }
345   }
346 
347   @Override
348   public Iterator<CachedBlock> iterator() {
349     return null;
350   }
351 
352   @Override
353   public BlockCache[] getBlockCaches() {
354     return null;
355   }
356 }