Java tutorial
/* * Copyright 2013 Rackspace * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.rackspacecloud.blueflood.cache; import com.netflix.astyanax.model.ColumnFamily; import com.rackspacecloud.blueflood.internal.Account; import com.rackspacecloud.blueflood.internal.ClusterException; import com.rackspacecloud.blueflood.internal.InternalAPI; import com.rackspacecloud.blueflood.io.AstyanaxIO; import com.rackspacecloud.blueflood.rollup.Granularity; import com.rackspacecloud.blueflood.types.Locator; import com.rackspacecloud.blueflood.utils.TimeValue; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheStats; import com.yammer.metrics.Metrics; import com.yammer.metrics.core.Meter; import org.apache.http.client.HttpResponseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.management.MBeanServer; import javax.management.ObjectName; import java.lang.management.ManagementFactory; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; // todo: need to go into safety mode if http fetches are taking too long. public class TtlCache extends AbstractJmxCache implements TtlCacheMBean { private static final Logger log = LoggerFactory.getLogger(TtlCache.class); // these values get used in the absence of a ttl (internal API failure, etc.). static final Map<ColumnFamily<Locator, Long>, TimeValue> SAFETY_TTLS = new HashMap<ColumnFamily<Locator, Long>, TimeValue>() { { /// FIX: TTLs should be specified at CF level for (Granularity gran : Granularity.granularities()) put(AstyanaxIO.getColumnFamilyMapper().get(gran.name()), new TimeValue(gran.getTTL().getValue() * 5, gran.getTTL().getUnit())); } }; private final com.google.common.cache.LoadingCache<String, Map<ColumnFamily<Locator, Long>, TimeValue>> cache; private final Meter generalErrorMeter; private final Meter httpErrorMeter; // allowable errors per minute. private double safetyThreshold = 10d; private volatile long lastFetchError = 0; public TtlCache(String label, TimeValue expiration, int cacheConcurrency, final InternalAPI internalAPI) { try { final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); final String name = String.format( TtlCache.class.getPackage().getName() + ":type=%s,scope=%s,name=Stats", TtlCache.class.getSimpleName(), label); final ObjectName nameObj = new ObjectName(name); mbs.registerMBean(this, nameObj); instantiateYammerMetrics(TtlCache.class, label, nameObj); } catch (Exception ex) { log.error("Unable to register mbean for " + getClass().getName()); } generalErrorMeter = Metrics.newMeter(TtlCache.class, "Load Errors", label, "Rollups", TimeUnit.MINUTES); httpErrorMeter = Metrics.newMeter(TtlCache.class, "Http Errors", label, "Rollups", TimeUnit.MINUTES); CacheLoader<String, Map<ColumnFamily<Locator, Long>, TimeValue>> loader = new CacheLoader<String, Map<ColumnFamily<Locator, Long>, TimeValue>>() { // values from the default account are used to build a ttl map for tenants that do not exist in the // internal API. These values are put into the cache, meaning subsequent cache requests do not // incur a miss and hit the internal API. private final Account DEFAULT_ACCOUNT = new Account() { @Override public TimeValue getMetricTtl(String resolution) { return SAFETY_TTLS .get(AstyanaxIO.getColumnFamilyMapper().get(Granularity.fromString(resolution).name())); } }; @Override public Map<ColumnFamily<Locator, Long>, TimeValue> load(final String key) throws Exception { // load account, build ttl map. try { Account acct = internalAPI.fetchAccount(key); return buildTtlMap(acct); } catch (HttpResponseException ex) { // cache the default value on a 404. this means that we will not be hammering the API for values // that are constantly not there. The other option was to let the Http error bubble out, use a // and value from SAFETY_TTLS. But the same thing (an HTTP round trip) would happen the very next // time a TTL is requested. if (ex.getStatusCode() == 404) { httpErrorMeter.mark(); log.warn(ex.getMessage()); return buildTtlMap(DEFAULT_ACCOUNT); } else throw ex; } } }; cache = CacheBuilder.newBuilder().expireAfterWrite(expiration.getValue(), expiration.getUnit()) .concurrencyLevel(cacheConcurrency).recordStats().build(loader); } // override this if you're not interested in caching the entire ttl map. protected Map<ColumnFamily<Locator, Long>, TimeValue> buildTtlMap(Account acct) { Map<ColumnFamily<Locator, Long>, TimeValue> map = new HashMap<ColumnFamily<Locator, Long>, TimeValue>(); for (Granularity gran : Granularity.granularities()) map.put(AstyanaxIO.getColumnFamilyMapper().get(gran.name()), acct.getMetricTtl(gran.shortName())); return map; } // may return null (e.g.: if the granularity isn't in the build-map. public TimeValue getTtl(String acctId, ColumnFamily<Locator, Long> CF) { // if error rate exceeds a threshold return SAFETY_TTL. Otherwise, we spend a non-trivial amount of time stuck // in blocking calls into whatever obtains the account. if (isSafetyMode()) return SAFETY_TTLS.get(CF); try { return cache.get(acctId).get(CF); } catch (ExecutionException ex) { if (ex.getCause() instanceof HttpResponseException) { httpErrorMeter.mark(); log.debug(ex.getCause().getMessage()); } else { // log this every 10s, then use a sane default value for the ttl. generalErrorMeter.mark(); long now = System.currentTimeMillis(); if (now - lastFetchError > 2000) { log.error("Problem fetching accounts from internal API"); if (ex.getCause() instanceof ClusterException) { for (Throwable subCause : ((ClusterException) ex.getCause()).getExceptions()) log.error("Subcause: " + subCause.getMessage()); } else log.error(ex.getCause().getMessage()); lastFetchError = now; // I don't care about races here. } } return SAFETY_TTLS.get(CF); } } // // Jmx methods // @Override public CacheStats getStats() { return cache.stats(); } public synchronized boolean isSafetyMode() { return generalErrorMeter.oneMinuteRate() > safetyThreshold; } public synchronized void setSafetyThreshold(double d) { safetyThreshold = d; } public synchronized double getSafetyThreshold() { return safetyThreshold; } }