[Google Guava] Cache

example

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(10, TimeUnit.MINUTES)
        .removalListener(MY_LISTENER)
        .build(
            new CacheLoader<Key, Graph>() {
                public Graph load(Key key) throws AnyException {
                    return createExpensiveGraph(key);
                }
        });

applicability

Caching is quite useful in many scenarios. For example, caching should be considered when computing or retrieving a value is expensive and requires more than one fetch for the same input.

Guava Cache is ConcurrentMapvery similar, but not exactly the same. The most basic difference is ConcurrentMapthat all added elements are kept until explicitly removed. In contrast, in order to limit memory usage, Guava Cache is usually set to automatically recycle elements. In some scenarios LoadingCache, it is useful even though the element is not recycled, because it automatically loads the cache.

Generally speaking, Guava Cache is suitable for:

  • You are willing to consume some memory space for speed.
  • You expect some keys to be queried more than once.
  • The total amount of data stored in the cache will not exceed the memory capacity. (Guava Cache is a local cache for a single application runtime. It does not store data to files or external servers. If this does not meet your needs, try a tool like Memcached)

If your scenario fits each of the above, Guava Cache is for you.

As shown in the sample code, Cache instances are obtained through the CacheBuildergenerator pattern, but customizing your cache is the most interesting part.

Note: If you don't need the features in the Cache, it's more memory efficient to use ConcurrentHashMap - but most features of the Cache are hard to replicate over the old ConcurrentMap, or even impossible.

load

Before using a cache, first ask yourself a question: is there a sensible default way to load or compute the value associated with a key? If there is, you should use it CacheLoader. If not, or if you want to override the default load operation while preserving the atomic semantics of "get-cache-if-absent-compute" [get-if-absent-compute], you should getpass in an Callableinstance when calling. Cached elements can also be Cache.putinserted directly via methods, but autoloading is preferred because it makes it easier to infer the consistency of all cached content.

CacheLoader

LoadingCacheis CacheLoadera built-in cache implementation. Creating your own CacheLoaderusually involves simply implementing the V load(K key) throws Exceptionmethod. For example, you can build with the following code LoadingCache:

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
        .maximumSize(1000)
        .build(
            new CacheLoader<Key, Graph>() {
                public Graph load(Key key) throws AnyException {
                    return createExpensiveGraph(key);
                }
            });

...
try {
    return graphs.get(key);
} catch (ExecutionException e) {
    throw new OtherException(e.getCause());
}

LoadingCacheThe normal way to query from is to use a method get(K). This method either returns the already cached value, or CacheLoaderatomically loads the new value into the cache using. Also declared to throw exceptions due to the CacheLoaderpossibility of throwing exceptions. If your definition does not declare any checked exception, you can pass the lookup cache; but it must be noted that once a checked exception is declared, it cannot be called .LoadingCache.get(K)ExecutionExceptionCacheLoadergetUnchecked(K)CacheLoadergetUnchecked(K)

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
        .expireAfterAccess(10, TimeUnit.MINUTES)
        .build(
            new CacheLoader<Key, Graph>() {
                public Graph load(Key key) { // no checked exception
                    return createExpensiveGraph(key);
                }
            });

...
return graphs.getUnchecked(key);

getAll(Iterable<? extends K>)method is used to perform batch queries. By default, for each key that is not in the cache, the getAllmethod is called separately CacheLoader.loadto load the cache item. If batch loads are more efficient than multiple individual loads, you can overload CacheLoader.loadAllto take advantage of this. getAll(Iterable)performance will be improved accordingly.

Note: Implementations of CacheLoader.loadAll can load cached values ​​for keys that are not explicitly requested. For example, when calculating a value for any key in a group, all key values ​​in the group can be obtained, and the loadAll method can be implemented to obtain other key values ​​in the group at the same time. Note: getAll(Iterable<? extends K>)The method will call loadAll, but it will filter the results and only return the requested key-value pair.

I don't quite understand the above sentence

Callable

All types of Guava Cache, with or without autoloading, support the get(K, Callable) method. This method returns the corresponding value in the cache, or operates on the given Callable and adds the result to the cache. The observable state associated with the cache item does not change until the entire load method completes. This method simply implements the pattern "return if cached; otherwise compute, cache, then return".

Cache<Key, Graph> cache = CacheBuilder.newBuilder()
        .maximumSize(1000)
        .build(); // look Ma, no CacheLoader
...
try {
    // If the key wasn't in the "easy to compute" group, we need to
    // do things the hard way.
    cache.get(key, new Callable<Key, Graph>() {
        @Override
        public Value call() throws AnyException {
            return doThingsTheHardWay(key);
        }
    });
} catch (ExecutionException e) {
    throw new OtherException(e.getCause());
}

explicit insertion

Use cache.put(key, value)methods to insert values ​​directly into the cache, which directly overwrites the previously mapped value for the given key. Cache.asMap()The cache can also be modified using any of the methods provided by the view. Note, however, that asMapnone of the view's methods guarantee that the cached item will be loaded into the cache atomically. Furthermore, asMapatomic operations on views are outside the scope of atomic loading in Guava Cache, so should always be used in preference to .Cache.asMap().putIfAbsent(K,
V),Cache.get(K, Callable<V>)

cache reclamation

A harsh reality is that we almost certainly don't have enough memory to cache all the data. You have to decide: when is a cache item not worth keeping? Guava Cache provides three basic cache recycling methods: capacity-based recycling, timing recycling, and reference-based recycling.

  • size-based eviction

    Only use if you want to specify that the number of cached items does not exceed a fixed value CacheBuilder.maximumSize(long). The cache will attempt to reclaim cached items that have not been used recently or are rarely used overall. - WARNING: The cache may be reclaimed before the number of cached items reaches the limit - usually, this happens when the number of cached items approaches the limit.

    In addition, different cache items have different "weights" - for example, if your cached values ​​occupy completely different memory spaces, you can use CacheBuilder.weigher(Weigher)a weight function and CacheBuilder.maximumWeight(long)specify a maximum total weight. In the weight-limited scenario, in addition to paying attention to the fact that the recycling is also carried out when the weight approaches the limit value, it is also necessary to know that the weight is calculated when the cache is created, so the complexity of the weight calculation must be considered.

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
        .maximumWeight(100000)
        .weigher(new Weigher<Key, Graph>() {
            public int weigh(Key k, Graph g) {
                return g.vertices().size();
            }
        })
        .build(
            new CacheLoader<Key, Graph>() {
                public Graph load(Key key) { // no checked exception
                    return createExpensiveGraph(key);
                }
            });
  • Timed Eviction

    CacheBuilderThere are two methods of timing recycling:

    expireAfterAccess(long, TimeUnit): The cache item is reclaimed if it has not been read/write accessed within the given time. Note that the order of evictions for this cache is the same as for size-based evictions.
    expireAfterWrite(long, TimeUnit): The cache item is not write-accessed (created or overwritten) within the given time, then reclaimed. This recycling method is desirable if it is considered that cached data will always become stale and unavailable after a fixed amount of time.
    As discussed below, timed reclamation is performed periodically on write operations and occasionally on read operations.

    Test timing recovery

    When testing timed recycling, you don't necessarily have to spend two seconds to test for a two-second expiration. Instead of having to use the system clock, you can use Tickerinterfaces and CacheBuilder.ticker(Ticker)methods to customize a time source in the cache.

  • Reference-based Eviction

    By using weakly referenced keys, or weakly referenced values, or soft referenced values, Guava Cache can set up the cache to allow garbage collection:

    CacheBuilder.weakKeys(): Store keys using weak references. Cache items can be garbage collected when the key has no other (strong or soft) references. Because garbage collection relies only on identities (==), caches that use weakly referenced keys compare keys with == instead of equals.
    CacheBuilder.weakValues(): Use weak references to store values. Cache items can be garbage collected when the value has no other (strong or soft) references. Because garbage collection relies only on identities (==), caches that use weakly referenced values ​​compare values ​​with == instead of equals.
    CacheBuilder.softValues(): Use soft references to store values. Soft references are reclaimed in global least recently used order only when needed in response to memory. Given the performance impact of using soft references, we generally recommend using a more performance-predictive cache size limit (see above, based on capacity reclamation). Caches that use soft reference values ​​also use == instead of equals to compare values.

explicit clear

At any time, you can explicitly clear the cache item instead of waiting for it to be reclaimed:

Individual Clear: Cache.invalidate(key)
Bulk Clear: Cache.invalidateAll(keys)
Clear All Cached Items:Cache.invalidateAll()

remove listener

With CacheBuilder.removalListener(RemovalListener)that, you can declare a listener to do some extra action when a cached item is removed. When a cached item is removed, RemovalListenera removal notification [ RemovalNotification] is obtained with the reason for removal [ RemovalCause], key, and value.

Note that RemovalListenerany exception thrown will be discarded [swallowed] after being logged.

CacheLoader<Key, DatabaseConnection> loader = new CacheLoader<Key, DatabaseConnection> () {
    public DatabaseConnection load(Key key) throws Exception {
        return openConnection(key);
    }
};

RemovalListener<Key, DatabaseConnection> removalListener = new RemovalListener<Key, DatabaseConnection>() {
    public void onRemoval(RemovalNotification<Key, DatabaseConnection> removal) {
        DatabaseConnection conn = removal.getValue();
        conn.close(); // tear down properly
    }
};

return CacheBuilder.newBuilder()
    .expireAfterWrite(2, TimeUnit.MINUTES)
    .removalListener(removalListener)
    .build(loader);

Warning: By default, the listener method is called synchronously when the cache is removed. Because cache maintenance and request response are usually done concurrently, expensive listener methods in synchronous mode can slow down normal cache requests. In this case, you can RemovalListeners.asynchronous(RemovalListener, Executor)decorate the listener with an asynchronous operation.

When does the cleanup happen?

Using CacheBuilderthe built cache does not "automatically" perform cleanup and recycling, nor does it clean up immediately after a cache item expires, and there is no such cleanup mechanism. Instead, it will do a small amount of maintenance on-the-fly with writes, or occasionally with reads —if there are too few writes.

The reason for this is that if the cache is to be cleaned continuously and automatically, there must be a thread that competes with user operations for a shared lock. Additionally, thread creation may be limited in some environments and thus CacheBuildernot available.

Instead, we put the choice in your hands. If your cache is high throughput, you don't need to worry about cache maintenance and cleanup. If your cache only has occasional writes, and you don't want cleanup to block reads, you can create your own maintenance thread that is called at regular intervals Cache.cleanUp(). ScheduledExecutorServiceIt can help you implement such timing scheduling well.

refresh

Refreshing is not the same as recycling. As LoadingCache.refresh(K)stated, a refresh means loading a new value for a key, and this process can be asynchronous. While the flush operation is in progress, the cache can still return the old value to other threads, unlike a recycle operation where the thread reading the cache has to wait for the new value to be loaded.

If the flushing process throws an exception, the cache will keep the old value, and the exception will be discarded after logging [swallowed].

Overloading CacheLoader.reload(K, V)can extend the behavior on refresh, this method allows the developer to use the old value when calculating the new value.

//有些键不需要刷新,并且我们希望刷新是异步完成的
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
        .maximumSize(1000)
        .refreshAfterWrite(1, TimeUnit.MINUTES)
        .build(
            new CacheLoader<Key, Graph>() {
                public Graph load(Key key) { // no checked exception
                    return getGraphFromDatabase(key);
                }

                public ListenableFuture<Key, Graph> reload(final Key key, Graph prevGraph) {
                    if (neverNeedsRefresh(key)) {
                        return Futures.immediateFuture(prevGraph);
                    }else{
                        // asynchronous!
                        ListenableFutureTask<Key, Graph> task=ListenableFutureTask.create(new Callable<Key, Graph>() {
                            public Graph call() {
                                return getGraphFromDatabase(key);
                            }
                        });
                        executor.execute(task);
                        return task;
                    }
                }
            });

CacheBuilder.refreshAfterWrite(long, TimeUnit)Automatic timing refresh function can be added to the cache. expireAfterWriteConversely, cached refreshAfterWriteitems can be kept available through scheduled refreshes, but be warned: cached items are only actually refreshed when they are retrieved (if CacheLoader.refreshimplemented asynchronously, retrievals will not be slowed down by refreshes). Therefore, if you declare expireAfterWriteand at the same time on the cache refreshAfterWrite, the cache will not be blindly reset periodically due to flushing. If the cache item is not retrieved, the flush will not actually happen, and the cache item will also become recyclable after the expiration time. .

Other features

  • statistics

    CacheBuilder.recordStats()Used to enable the statistics function of Guava Cache. When statistics is turned on, the Cache.stats()method returns CacheStatsan object to provide the following statistics:

    hitRate(): Cache hit ratio;
    averageLoadPenalty(): Average time to load a new value, in nanoseconds;
    evictionCount(): Total number of cached items being reclaimed, excluding explicit clears.
    In addition, there are many other statistics. These statistics are critical for tuning cache settings, and we recommend paying close attention to these statistics in applications with high performance requirements.

  • asMapview

    asMapViews provide a ConcurrentMapform of caching, but asMapthe interaction of views with caches requires attention:

    cache.asMap()Contains all items currently loaded into the cache. Accordingly, cache.asMap().keySet()all currently loaded keys are included;
    asMap().get(key)essentially equivalent to cache.getIfPresent(key), and not causing the cache entry to be loaded. This Mapis consistent with the semantic convention of .
    All read and write operations reset the access time of the associated cache item, including Cache.asMap().get(Object)methods and Cache.asMap().put(K, V)methods, but not Cache.asMap().containsKey(Object)methods, nor Cache.asMap()operations on collection views. For example, traversal Cache.asMap().entrySet()does not reset read times for cached items.

  • Interrupting
    cache loading methods such as Cache.get will not throw InterruptedException. We can also make these methods support InterruptedException, but such support is bound to be incomplete and adds cost to all users, while only a few users actually benefit. Read on for details.

    Cache.getWhen requesting an uncached value, there are two situations: the current thread loads the value; or it waits for another thread that is loading the value. The interrupts in these two cases are not the same. The simpler case is waiting for another thread that is loading a value: interrupt support is achieved using interruptible waits; but the case where the current thread loads a value is more complicated: since the value CacheLoaderis loaded by the user, if it is Interruptible, then we can also support interrupts, otherwise we can't do anything.

If user-supplied CacheLoaderis interruptible, why not let Cache.getinterrupts also be supported? In a sense, it is actually supported: if CacheLoaderthrown InterruptedException, Cache.getit will return immediately (just like other exceptions); in addition, in the thread that loads the cached value, the interrupt will be resumed after Cache.getcatching , while in other threads is packaged .InterruptedExceptionInterruptedExceptionExecutionException

In principle, we could unpack and change ExecutionExceptionto InterruptedException, but that would cause all LoadingCacheconsumers to handle interrupt exceptions, even if the ones they provide are CacheLoadernot interruptible. This may be worthwhile if you consider that all non-loading threads' waits can still be interrupted. But many caches are only used in a single thread, and their users still have to catch exceptions that cannot be thrown InterruptedException. Even users who share the cache across threads can only interrupt their getcalls sometimes, depending on which thread made the request first.

Our guideline for this decision is to have the cache always behave as if it were loading the value on the current thread. This principle makes it easy to switch between using a cache or calculating a value every time. If the old code (the code that loads the value) is uninterruptible, then the new code (the code that uses the cache to load the value) should probably also be uninterruptible.

As mentioned above, Guava Cache supports interrupts in a sense. In another sense, Guava Cache does not support interrupts, which makes it LoadingCachea leaky abstraction: when the loading process is interrupted, it is treated like any other exception, which is fine in most cases; but if more If two threads are waiting to load the same cache item, even if the loading thread is interrupted, it should not let all other threads fail (caught in the wrapper ExecutionException) InterruptedException, the correct behavior is to let one of the remaining threads retry the load. For this, we have logged a bug. However, rather than risk fixing this bug, we might put more effort into implementing another proposal AsyncLoadingCachethat returns a Future object with the correct breaking behavior.

Original article, please indicate: Reprinted from Concurrent Programming Network – ifeve.com Link to this article: [Google Guava] 3-Cache

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326008581&siteId=291194637