001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.io.hfile;
019
020import static org.apache.hadoop.hbase.HConstants.BUCKET_CACHE_IOENGINE_KEY;
021import static org.apache.hadoop.hbase.HConstants.BUCKET_CACHE_SIZE_KEY;
022
023import java.io.IOException;
024
025import org.apache.hadoop.conf.Configuration;
026import org.apache.hadoop.hbase.HConstants;
027import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache;
028import org.apache.hadoop.hbase.io.util.MemorySizeUtil;
029import org.apache.hadoop.hbase.util.ReflectionUtils;
030import org.apache.hadoop.util.StringUtils;
031import org.apache.yetus.audience.InterfaceAudience;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035@InterfaceAudience.Private
036public final class BlockCacheFactory {
037
038  private static final Logger LOG = LoggerFactory.getLogger(BlockCacheFactory.class.getName());
039
040  /**
041   * Configuration keys for Bucket cache
042   */
043
044  /**
045   * If the chosen ioengine can persist its state across restarts, the path to the file to persist
046   * to. This file is NOT the data file. It is a file into which we will serialize the map of
047   * what is in the data file. For example, if you pass the following argument as
048   * BUCKET_CACHE_IOENGINE_KEY ("hbase.bucketcache.ioengine"),
049   * <code>file:/tmp/bucketcache.data </code>, then we will write the bucketcache data to the file
050   * <code>/tmp/bucketcache.data</code> but the metadata on where the data is in the supplied file
051   * is an in-memory map that needs to be persisted across restarts. Where to store this
052   * in-memory state is what you supply here: e.g. <code>/tmp/bucketcache.map</code>.
053   */
054  public static final String BUCKET_CACHE_PERSISTENT_PATH_KEY = "hbase.bucketcache.persistent.path";
055
056  public static final String BUCKET_CACHE_WRITER_THREADS_KEY = "hbase.bucketcache.writer.threads";
057
058  public static final String BUCKET_CACHE_WRITER_QUEUE_KEY = "hbase.bucketcache.writer.queuelength";
059
060  /**
061   * A comma-delimited array of values for use as bucket sizes.
062   */
063  public static final String BUCKET_CACHE_BUCKETS_KEY = "hbase.bucketcache.bucket.sizes";
064
065  /**
066   * Defaults for Bucket cache
067   */
068  public static final int DEFAULT_BUCKET_CACHE_WRITER_THREADS = 3;
069  public static final int DEFAULT_BUCKET_CACHE_WRITER_QUEUE = 64;
070
071  /**
072   * The target block size used by blockcache instances. Defaults to
073   * {@link HConstants#DEFAULT_BLOCKSIZE}.
074   */
075  public static final String BLOCKCACHE_BLOCKSIZE_KEY = "hbase.blockcache.minblocksize";
076
077  private static final String EXTERNAL_BLOCKCACHE_KEY = "hbase.blockcache.use.external";
078  private static final boolean EXTERNAL_BLOCKCACHE_DEFAULT = false;
079
080  private static final String EXTERNAL_BLOCKCACHE_CLASS_KEY = "hbase.blockcache.external.class";
081
082  /**
083   * @deprecated use {@link BlockCacheFactory#BLOCKCACHE_BLOCKSIZE_KEY} instead.
084   */
085  @Deprecated
086  static final String DEPRECATED_BLOCKCACHE_BLOCKSIZE_KEY = "hbase.offheapcache.minblocksize";
087
088  /**
089   * The config point hbase.offheapcache.minblocksize is completely wrong, which is replaced by
090   * {@link BlockCacheFactory#BLOCKCACHE_BLOCKSIZE_KEY}. Keep the old config key here for backward
091   * compatibility.
092   */
093  static {
094    Configuration.addDeprecation(DEPRECATED_BLOCKCACHE_BLOCKSIZE_KEY, BLOCKCACHE_BLOCKSIZE_KEY);
095  }
096
097  private BlockCacheFactory() {
098  }
099
100  public static BlockCache createBlockCache(Configuration conf) {
101    if (conf.get(DEPRECATED_BLOCKCACHE_BLOCKSIZE_KEY) != null) {
102      LOG.warn("The config key {} is deprecated now, instead please use {}. In future release "
103          + "we will remove the deprecated config.", DEPRECATED_BLOCKCACHE_BLOCKSIZE_KEY,
104        BLOCKCACHE_BLOCKSIZE_KEY);
105    }
106    LruBlockCache onHeapCache = createOnHeapCache(conf);
107    if (onHeapCache == null) {
108      return null;
109    }
110    boolean useExternal = conf.getBoolean(EXTERNAL_BLOCKCACHE_KEY, EXTERNAL_BLOCKCACHE_DEFAULT);
111    if (useExternal) {
112      BlockCache l2CacheInstance = createExternalBlockcache(conf);
113      return l2CacheInstance == null ?
114          onHeapCache :
115          new InclusiveCombinedBlockCache(onHeapCache, l2CacheInstance);
116    } else {
117      // otherwise use the bucket cache.
118      BucketCache bucketCache = createBucketCache(conf);
119      if (!conf.getBoolean("hbase.bucketcache.combinedcache.enabled", true)) {
120        // Non combined mode is off from 2.0
121        LOG.warn(
122            "From HBase 2.0 onwards only combined mode of LRU cache and bucket cache is available");
123      }
124      return bucketCache == null ? onHeapCache : new CombinedBlockCache(onHeapCache, bucketCache);
125    }
126  }
127
128  private static LruBlockCache createOnHeapCache(final Configuration c) {
129    final long cacheSize = MemorySizeUtil.getOnHeapCacheSize(c);
130    if (cacheSize < 0) {
131      return null;
132    }
133    int blockSize = c.getInt(BLOCKCACHE_BLOCKSIZE_KEY, HConstants.DEFAULT_BLOCKSIZE);
134    LOG.info(
135        "Allocating onheap LruBlockCache size=" + StringUtils.byteDesc(cacheSize) + ", blockSize="
136            + StringUtils.byteDesc(blockSize));
137    return new LruBlockCache(cacheSize, blockSize, true, c);
138  }
139
140  /**
141   * Enum of all built in external block caches.
142   * This is used for config.
143   */
144  private static enum ExternalBlockCaches {
145    memcached("org.apache.hadoop.hbase.io.hfile.MemcachedBlockCache");
146    // TODO(eclark): Consider more. Redis, etc.
147    Class<? extends BlockCache> clazz;
148    ExternalBlockCaches(String clazzName) {
149      try {
150        clazz = (Class<? extends BlockCache>) Class.forName(clazzName);
151      } catch (ClassNotFoundException cnef) {
152        clazz = null;
153      }
154    }
155    ExternalBlockCaches(Class<? extends BlockCache> clazz) {
156      this.clazz = clazz;
157    }
158  }
159
160  private static BlockCache createExternalBlockcache(Configuration c) {
161    if (LOG.isDebugEnabled()) {
162      LOG.debug("Trying to use External l2 cache");
163    }
164    Class klass = null;
165
166    // Get the class, from the config. s
167    try {
168      klass = ExternalBlockCaches
169          .valueOf(c.get(EXTERNAL_BLOCKCACHE_CLASS_KEY, "memcache")).clazz;
170    } catch (IllegalArgumentException exception) {
171      try {
172        klass = c.getClass(EXTERNAL_BLOCKCACHE_CLASS_KEY, Class.forName(
173            "org.apache.hadoop.hbase.io.hfile.MemcachedBlockCache"));
174      } catch (ClassNotFoundException e) {
175        return null;
176      }
177    }
178
179    // Now try and create an instance of the block cache.
180    try {
181      LOG.info("Creating external block cache of type: " + klass);
182      return (BlockCache) ReflectionUtils.newInstance(klass, c);
183    } catch (Exception e) {
184      LOG.warn("Error creating external block cache", e);
185    }
186    return null;
187
188  }
189
190  private static BucketCache createBucketCache(Configuration c) {
191    // Check for L2.  ioengine name must be non-null.
192    String bucketCacheIOEngineName = c.get(BUCKET_CACHE_IOENGINE_KEY, null);
193    if (bucketCacheIOEngineName == null || bucketCacheIOEngineName.length() <= 0) {
194      return null;
195    }
196
197    int blockSize = c.getInt(BLOCKCACHE_BLOCKSIZE_KEY, HConstants.DEFAULT_BLOCKSIZE);
198    final long bucketCacheSize = MemorySizeUtil.getBucketCacheSize(c);
199    if (bucketCacheSize <= 0) {
200      throw new IllegalStateException("bucketCacheSize <= 0; Check " +
201          BUCKET_CACHE_SIZE_KEY + " setting and/or server java heap size");
202    }
203    if (c.get("hbase.bucketcache.percentage.in.combinedcache") != null) {
204      LOG.warn("Configuration 'hbase.bucketcache.percentage.in.combinedcache' is no longer "
205          + "respected. See comments in http://hbase.apache.org/book.html#_changes_of_note");
206    }
207    int writerThreads = c.getInt(BUCKET_CACHE_WRITER_THREADS_KEY,
208        DEFAULT_BUCKET_CACHE_WRITER_THREADS);
209    int writerQueueLen = c.getInt(BUCKET_CACHE_WRITER_QUEUE_KEY,
210        DEFAULT_BUCKET_CACHE_WRITER_QUEUE);
211    String persistentPath = c.get(BUCKET_CACHE_PERSISTENT_PATH_KEY);
212    String[] configuredBucketSizes = c.getStrings(BUCKET_CACHE_BUCKETS_KEY);
213    int [] bucketSizes = null;
214    if (configuredBucketSizes != null) {
215      bucketSizes = new int[configuredBucketSizes.length];
216      for (int i = 0; i < configuredBucketSizes.length; i++) {
217        int bucketSize = Integer.parseInt(configuredBucketSizes[i].trim());
218        if (bucketSize % 256 != 0) {
219          // We need all the bucket sizes to be multiples of 256. Having all the configured bucket
220          // sizes to be multiples of 256 will ensure that the block offsets within buckets,
221          // that are calculated, will also be multiples of 256.
222          // See BucketEntry where offset to each block is represented using 5 bytes (instead of 8
223          // bytes long). We would like to save heap overhead as less as possible.
224          throw new IllegalArgumentException("Illegal value: " + bucketSize + " configured for '"
225              + BUCKET_CACHE_BUCKETS_KEY + "'. All bucket sizes to be multiples of 256");
226        }
227        bucketSizes[i] = bucketSize;
228      }
229    }
230    BucketCache bucketCache = null;
231    try {
232      int ioErrorsTolerationDuration = c.getInt(
233          "hbase.bucketcache.ioengine.errors.tolerated.duration",
234          BucketCache.DEFAULT_ERROR_TOLERATION_DURATION);
235      // Bucket cache logs its stats on creation internal to the constructor.
236      bucketCache = new BucketCache(bucketCacheIOEngineName,
237          bucketCacheSize, blockSize, bucketSizes, writerThreads, writerQueueLen, persistentPath,
238          ioErrorsTolerationDuration, c);
239    } catch (IOException ioex) {
240      LOG.error("Can't instantiate bucket cache", ioex); throw new RuntimeException(ioex);
241    }
242    return bucketCache;
243  }
244}