View Javadoc

1   /**
2    * Copyright The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements. See the NOTICE file distributed with this
6    * work for additional information regarding copyright ownership. The ASF
7    * licenses this file to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance with the License.
9    * 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, WITHOUT
15   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16   * License for the specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.hadoop.hbase.io.hfile;
21  
22  import net.spy.memcached.CachedData;
23  import net.spy.memcached.ConnectionFactoryBuilder;
24  import net.spy.memcached.FailureMode;
25  import net.spy.memcached.MemcachedClient;
26  import net.spy.memcached.transcoders.Transcoder;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.hbase.HConstants;
31  import org.apache.hadoop.hbase.classification.InterfaceAudience;
32  import org.apache.hadoop.hbase.util.Addressing;
33  import org.apache.htrace.Trace;
34  import org.apache.htrace.TraceScope;
35  
36  import java.io.IOException;
37  import java.net.InetSocketAddress;
38  import java.nio.ByteBuffer;
39  import java.util.ArrayList;
40  import java.util.Iterator;
41  import java.util.List;
42  import java.util.NoSuchElementException;
43  import java.util.concurrent.ExecutionException;
44  
45  /**
46   * Class to store blocks into memcached.
47   * This should only be used on a cluster of Memcached daemons that are tuned well and have a
48   * good network connection to the HBase regionservers. Any other use will likely slow down HBase
49   * greatly.
50   */
51  @InterfaceAudience.Private
52  public class MemcachedBlockCache implements BlockCache {
53    private static final Log LOG = LogFactory.getLog(MemcachedBlockCache.class.getName());
54  
55    // Some memcache versions won't take more than 1024 * 1024. So set the limit below
56    // that just in case this client is used with those versions.
57    public static final int MAX_SIZE = 1020 * 1024;
58  
59    // Config key for what memcached servers to use.
60    // They should be specified in a comma sperated list with ports.
61    // like:
62    //
63    // host1:11211,host3:8080,host4:11211
64    public static final String MEMCACHED_CONFIG_KEY = "hbase.cache.memcached.servers";
65    public static final String MEMCACHED_TIMEOUT_KEY = "hbase.cache.memcached.timeout";
66    public static final String MEMCACHED_OPTIMEOUT_KEY = "hbase.cache.memcached.optimeout";
67    public static final String MEMCACHED_OPTIMIZE_KEY = "hbase.cache.memcached.spy.optimze";
68    public static final long MEMCACHED_DEFAULT_TIMEOUT = 500;
69    public static final boolean MEMCACHED_OPTIMIZE_DEFAULT = false;
70  
71    private final MemcachedClient client;
72    private final HFileBlockTranscoder tc = new HFileBlockTranscoder();
73    private final CacheStats cacheStats = new CacheStats("MemcachedBlockCache");
74  
75    public MemcachedBlockCache(Configuration c) throws IOException {
76      LOG.info("Creating MemcachedBlockCache");
77  
78      long opTimeout = c.getLong(MEMCACHED_OPTIMEOUT_KEY, MEMCACHED_DEFAULT_TIMEOUT);
79      long queueTimeout = c.getLong(MEMCACHED_TIMEOUT_KEY, opTimeout + MEMCACHED_DEFAULT_TIMEOUT);
80      boolean optimize = c.getBoolean(MEMCACHED_OPTIMIZE_KEY, MEMCACHED_OPTIMIZE_DEFAULT);
81  
82      ConnectionFactoryBuilder builder = new ConnectionFactoryBuilder()
83          .setOpTimeout(opTimeout)
84          .setOpQueueMaxBlockTime(queueTimeout) // Cap the max time before anything times out
85          .setFailureMode(FailureMode.Redistribute)
86          .setShouldOptimize(optimize)
87          .setDaemon(true)                      // Don't keep threads around past the end of days.
88          .setUseNagleAlgorithm(false)          // Ain't nobody got time for that
89          .setReadBufferSize(HConstants.DEFAULT_BLOCKSIZE * 4 * 1024); // Much larger just in case
90  
91      // Assume only the localhost is serving memecached.
92      // A la mcrouter or co-locating memcached with split regionservers.
93      //
94      // If this config is a pool of memecached servers they will all be used according to the
95      // default hashing scheme defined by the memcache client. Spy Memecache client in this
96      // case.
97      String serverListString = c.get(MEMCACHED_CONFIG_KEY,"localhost:11211");
98      String[] servers = serverListString.split(",");
99      // MemcachedClient requires InetSocketAddresses, we have to create them now. Implies any
100     // resolved identities cannot have their address mappings changed while the MemcachedClient
101     // instance is alive. We won't get a chance to trigger re-resolution.
102     List<InetSocketAddress> serverAddresses = new ArrayList<InetSocketAddress>(servers.length);
103     for (String s:servers) {
104       serverAddresses.add(Addressing.createInetSocketAddressFromHostAndPortStr(s));
105     }
106 
107     client = new MemcachedClient(builder.build(), serverAddresses);
108   }
109 
110   @Override
111   public void cacheBlock(BlockCacheKey cacheKey,
112                          Cacheable buf,
113                          boolean inMemory,
114                          boolean cacheDataInL1) {
115     cacheBlock(cacheKey, buf);
116   }
117 
118   @Override
119   public void cacheBlock(BlockCacheKey cacheKey, Cacheable buf) {
120     if (buf instanceof HFileBlock) {
121       client.add(cacheKey.toString(), MAX_SIZE, (HFileBlock) buf, tc);
122     } else {
123       if (LOG.isDebugEnabled()) {
124         LOG.debug("MemcachedBlockCache can not cache Cacheable's of type "
125             + buf.getClass().toString());
126       }
127     }
128   }
129 
130   @Override
131   public Cacheable getBlock(BlockCacheKey cacheKey, boolean caching,
132                             boolean repeat, boolean updateCacheMetrics) {
133     // Assume that nothing is the block cache
134     HFileBlock result = null;
135 
136     try (TraceScope traceScope = Trace.startSpan("MemcachedBlockCache.getBlock")) {
137       result = client.get(cacheKey.toString(), tc);
138     } catch (Exception e) {
139       // Catch a pretty broad set of exceptions to limit any changes in the memecache client
140       // and how it handles failures from leaking into the read path.
141       if (LOG.isDebugEnabled()) {
142         LOG.debug("Exception pulling from memcached [ "
143             + cacheKey.toString()
144             + " ]. Treating as a miss.", e);
145       }
146       result = null;
147     } finally {
148       // Update stats if this request doesn't have it turned off 100% of the time
149       if (updateCacheMetrics) {
150         if (result == null) {
151           cacheStats.miss(caching, cacheKey.isPrimary(), cacheKey.getBlockType());
152         } else {
153           cacheStats.hit(caching, cacheKey.isPrimary(), cacheKey.getBlockType());
154         }
155       }
156     }
157 
158     return result;
159   }
160 
161   @Override
162   public boolean evictBlock(BlockCacheKey cacheKey) {
163     try {
164       cacheStats.evict();
165       return client.delete(cacheKey.toString()).get();
166     } catch (InterruptedException e) {
167       LOG.warn("Error deleting " + cacheKey.toString(), e);
168       Thread.currentThread().interrupt();
169     } catch (ExecutionException e) {
170       if (LOG.isDebugEnabled()) {
171         LOG.debug("Error deleting " + cacheKey.toString(), e);
172       }
173     }
174     return false;
175   }
176 
177   /**
178    * This method does nothing so that memcached can handle all evictions.
179    */
180   @Override
181   public int evictBlocksByHfileName(String hfileName) {
182     return 0;
183   }
184 
185   @Override
186   public CacheStats getStats() {
187     return cacheStats;
188   }
189 
190   @Override
191   public void shutdown() {
192     client.shutdown();
193   }
194 
195   @Override
196   public long size() {
197     return 0;
198   }
199 
200   @Override
201   public long getMaxSize() {
202     return 0;
203   }
204 
205   @Override
206   public long getFreeSize() {
207     return 0;
208   }
209 
210   @Override
211   public long getCurrentSize() {
212     return 0;
213   }
214 
215   @Override
216   public long getCurrentDataSize() {
217     return 0;
218   }
219 
220   @Override
221   public long getBlockCount() {
222     return 0;
223   }
224 
225   @Override
226   public long getDataBlockCount() {
227     return 0;
228   }
229 
230   @Override
231   public Iterator<CachedBlock> iterator() {
232     return new Iterator<CachedBlock>() {
233       @Override
234       public boolean hasNext() {
235         return false;
236       }
237 
238       @Override
239       public CachedBlock next() {
240         throw new NoSuchElementException("MemcachedBlockCache can't iterate over blocks.");
241       }
242 
243       @Override
244       public void remove() {
245 
246       }
247     };
248   }
249 
250   @Override
251   public BlockCache[] getBlockCaches() {
252     return null;
253   }
254 
255   /**
256    * Class to encode and decode an HFileBlock to and from memecached's resulting byte arrays.
257    */
258   private static class HFileBlockTranscoder implements Transcoder<HFileBlock> {
259 
260     @Override
261     public boolean asyncDecode(CachedData d) {
262       return false;
263     }
264 
265     @Override
266     public CachedData encode(HFileBlock block) {
267       ByteBuffer bb = ByteBuffer.allocate(block.getSerializedLength());
268       block.serialize(bb, true);
269       return new CachedData(0, bb.array(), CachedData.MAX_SIZE);
270     }
271 
272     @Override
273     public HFileBlock decode(CachedData d) {
274       try {
275         ByteBuffer buf = ByteBuffer.wrap(d.getData());
276         return (HFileBlock) HFileBlock.BLOCK_DESERIALIZER.deserialize(buf, true);
277       } catch (IOException e) {
278         LOG.warn("Error deserializing data from memcached",e);
279       }
280       return null;
281     }
282 
283     @Override
284     public int getMaxSize() {
285       return MAX_SIZE;
286     }
287   }
288 
289 }