com.rackspacecloud.blueflood.cache.TtlCache.java Source code

Java tutorial

Introduction

Here is the source code for com.rackspacecloud.blueflood.cache.TtlCache.java

Source

/*
 * 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;
    }
}