This is the 24th day of my participation in the August Text Challenge.More challenges in August

Since bloggers need to use local caches to store frequently used but unimportant data these days,Google’s Guava is a good fit to keep track of their queries.

Making website:

Github.com/google/guav…

英 文 名 称 :

Ifeve.com/google-guav…

References:

www.jianshu.com/p/afe7b2dcc…

My.oschina.net/chkui/blog/…

www.bbsmax.com/A/MyJxP0jXJ…

1 overview of cache

Guava is an open source Java-based library developed by Google that contains many of the Google core libraries that contribute to best coding practices and help reduce coding errors. Concurrency Libraries For collections, cache caching, Primitives support], concurrency libraries, Common Annotations, and string handling Processing, I/O, and so on provide utility methods.

Cache is the caching part of Google’s Guava.

It has many excellent characteristics:

  • Data writing to the cache is atomic.
  • When the cached data reaches its maximum size, the least Recently used (LRU) algorithm is used to clear the cached data.
  • Each piece of data can also be recycled based on time. When the unused time exceeds a certain period, the data will be recycled.
  • You can send a notification when the cache is cleared
  • Provides access statistics function

2 Official Documents

Official example:

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

Guava Cache is similar to ConcurrentMap, but not exactly the same. The basic difference is that ConcurrentMap keeps all added elements until they are explicitly removed. In contrast, the Guava Cache is typically set to auto-recycle elements to limit memory footprint. Although LoadingCache does not reclaim elements, it is useful in some scenarios because it automatically loads the cache.

Scope of use:

  • Memory consumption improves access speed
  • The accessed data will be used multiple times
  • The amount of cached data is small because it is stored in memory and only supports individual applications.

1 load

CacheLoader

LoadingCache is a cache implementation built with the cache reader. Creating your own CacheLoader usually requires simply implementing the V Load (K Key) throws Exception method.

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .maximumSize(1000)
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) throws AnyException {
               returncreateExpensiveGraph(key); }}); .try {
  return graphs.get(key);
} catch (ExecutionException e) {
  throw new OtherException(e.getCause());
}
Copy the code

The normal way to query from LoadingCache is to use the get(K) method. This method either returns the already cached value or uses CacheLoader to atomically load a new value into the cache

Callable

All types of Guava Cache support the get(K, Callable) method, with or without auto-loading. This method returns the corresponding value in the cache, or it takes the given Callable operation and adds the result to the cache. The observable state associated with the cache item does not change until the entire load method is complete. This method simply implements the pattern “return if there is a cache; Otherwise calculate, cache, and return.

Cache<Key, Value> 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<Value>() {
    @Override
    public Value call(a) throws AnyException {
      returndoThingsTheHardWay(key); }}); }catch (ExecutionException e) {
  throw new OtherException(e.getCause());
}

Copy the code

Explicit insert

Use the cache.put(key, value) method to insert values directly into the cache, overwriting the values mapped before a given key.

2 Cache Reclaiming

Guava Cache provides three basic Cache reclamation methods: volume-based reclamation, timed reclamation, and reference-based reclamation.

1 Capacity based eviction

If you want to limit the number of cache items to a fixed value, simply use cacheBuilder.maximumsize (long), and the cache will attempt to reclaim cache items that have not been used recently or are rarely used in general.

The cache may reclaim before the number of cached items reaches the limit — typically, this happens when the number of cached items approaches the limit.

Cache weights:

Different cache entries have different weights (weights). If your cache values occupy completely different memory space, you can specify a weight function using ‘ ‘CacheBuilder. Weigher (weigher). And use CacheBuilder. MaximumWeight (long) ` specifies the maximum total weight.

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
               returncreateExpensiveGraph(key); }});Copy the code

Timed Eviction

Two periodic reclamation policies are provided:

  • expireAfterAccess(long, TimeUnit)The cache item is reclaimed if it is not read/write accessed within a given time.
  • expireAfterWrite(long, TimeUnit)The cache item is reclaimed if it has not been write accessed (created or overwritten) within a given time.

3 Reference-based Eviction

Guava Cache can set the Cache to allow garbage collection by using a weak reference key, or a weak reference value, or a soft reference value:

  • CacheBuilder.weakKeys()Use weak references to store keys. Cached items can be garbage collected when there are no other (strong or soft) references to the key (because garbage collection only relies on the identity (= =), using the cache of weak reference keys with == instead of equals.
  • CacheBuilder.weakValues() Use weak references to store values. Cached items can be garbage collected when there are no other (strong or soft) references to the value (because garbage collection only relies on the identity (= =), using the cache of weak reference keys with == instead of equals.
  • CacheBuilder.softValues()Use soft references to store values. Soft references are recycled in the order of least recently used globally only in response to memory needs.(Caches that use soft reference values also compare == instead of equals values.)

4 Explicit reclamation

You can explicitly clear cached entries manually, that is, through code.

Clearing method:

  • A single clearCache.invalidate(key)
  • The batch to clearCache.invalidateAll(keys)
  • Clear all cachesCache.invalidateAll()

5 the listener

Through CacheBuilder. RemovalListener (removalListener), you can declare a listener, to cache entries have been removed when doing some additional operations. When a cache item is removed, the RemovalListener gets the RemovalNotification, which contains the RemovalCause, key, and value.

CacheLoader<Key, DatabaseConnection> loader = new CacheLoader<Key, DatabaseConnection> () {
  public DatabaseConnection load(Key key) throws Exception {
    returnopenConnection(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);

Copy the code

By default, the listener method is called synchronously when the cache is removed. Because the maintenance of the cache and the request response usually occur simultaneously, an expensive listener approach can slow down normal cache requests in synchronous mode. Can use RemovalListeners. Asynchronous (RemovalListener, Executor) the decoration of the listeners for asynchronous operations.

6 When will it be cleaned up?

Caches built using CacheBuilder do not clean up and reclaim “automatically”, nor do they clean up immediately after a cache entry expires, nor do they have such mechanisms. Instead, it does a little maintenance work in between writes, or occasionally reads, if there are too few writes.

If the cache is to be continuously cleared automatically, there must be a thread that competes with the user operation for the shared lock. In addition, thread creation may be limited in some environments, making the CacheBuilder unavailable.

If your cache is high throughput, you don’t need to worry about maintaining and cleaning the cache. If your Cache only has occasional writes and you don’t want to block reads with cleanUp, creating your own maintenance thread that calls cache.cleanup () and ScheduledExecutorService at regular intervals can help you do this

7 refresh

Refreshing is not quite the same as recycling. As loadingCache.refresh (K) declares, refresh means loading new values for the key, and this process can be asynchronous. The cache can still return old values to other threads while a refresh operation is in progress, unlike a reclaim operation, where the thread reading the cache must wait for the new value to complete.

// Some keys don't need refreshing, and we want refreshes to be done asynchronously.
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<Graph> reload(final Key key, Graph prevGraph) {
               if (neverNeedsRefresh(key)) {
                 return Futures.immediateFuture(prevGraph);
               } else {
                 // asynchronous!
                 ListenableFutureTask<Graph> task = ListenableFutureTask.create(new Callable<Graph>() {
                   public Graph call(a) {
                     returngetGraphFromDatabase(key); }}); executor.execute(task);returntask; }}});Copy the code

CacheBuilder. RefreshAfterWrite (long, TimeUnit) can add automatic timing to cache refresh function. In contrast to expireAfterWrite, refreshAfterWrite keeps cached items available by periodically refreshing them.

8 Other Features

1 statistics

Cachebuilder.recordstats () is used to enable Guava Cache statistics.

The cache.stats () method returns a CacheStats object to provide the following statistics:

  • hitRate() Cache hit ratio
  • averageLoadPenalty()The average time to load a new value, in nanoseconds
  • evictionCount()Total number of cache entries reclaimed, excluding explicit cleanup

2 asMap view

The asMap view provides the ConcurrentMap form of the cache.

  • Cache.asmap () contains all items currently loaded into the cache.
  • AsMap ().get(key) is essentially the same as cache.getifPresent (key) and does not cause the cache item to be loaded.
  • Cache.asmap ().get(Object) and cache.asmap ().put(K, V), but not cache.asmap ().containsKey(Object). Operations on the collection view of cache.asmap () are also not included.

3 the interrupt

Cache loading methods such as cache.get do not throw InterruptedException.

Cache.get requests for an uncached value encounter two situations: the current thread loads the value; Or wait for another thread that is loading a value. The interruption is different in these two cases. Waiting for another thread to load a value is a simpler case: interrupt support is achieved using interruptible wait; The current thread loading the value is more complicated: since the CacheLoader that loads the value is provided by the user, we can implement interruptible support if it is interruptible, but we can’t do anything else.

AsyncLoadingCache is recommended. This implementation will return a Future object with the correct interrupt behavior.

3 Usage

To use maven, you only need to import maven dependencies

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
Copy the code

Example 1:

Guava is used to build a local cache. In the interceptor, it stores the call information of the interface and verifies the repeated calls of the interface within a certain period of time.

@Component
@Slf4j
public class MyInterceptor implements HandlerInterceptor {

    // Cache scheme
    // Scenario 1 uses Redis to save the number of times the interface is called by this IP address within a certain period of time (for clusters)
// @Autowired
// private RedisTemplate redisTemplate;

    public static final int IntervalTime = 8;

    // Scenario 2: Google Guava, use local cache, set validity period 8 seconds (for singleton)
    private final Cache<String, Integer> cache = CacheBuilder.newBuilder()
            .expireAfterAccess(IntervalTime, TimeUnit.SECONDS).build();


    /** ** is called before the request is processed@param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("= = = = = = = = = = = = = = = = = = = = = = = = into interceptor 1 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");

        HandlerMethod handlerMethod = (HandlerMethod) handler;

        // See if there are any comments on the method that need to be intercepted
        MyAnnotation myAnnotation = handlerMethod.getMethodAnnotation(MyAnnotation.class);
        // Annotations exist
        if(myAnnotation ! =null) {
            int maxCount = myAnnotation.maxCount();
            int seconds = myAnnotation.seconds();

            // 1 Obtain IP Address Possible proxy IP address
            // The proxy server added it when requesting forwarding
            String ip = request.getHeader("x-forwarded-for");
            log.info("x-forwarded-for = {} ", ip);

            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
                log.info("Proxy-Client-IP = {} ", ip);
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
                log.info("WL-Proxy-Client-IP = {} ", ip);
            }
            // remote_addr is automatically added during HTTP transmission and is not controlled by request headers
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
                log.info("remote_addr = {} ", ip);
            }
            // Cache scheme 1 redis
// if (redisCache(response, maxCount, seconds, ip)) {return false; }

            // Cache solution 2: Google's Guava
            if (googleCache(response, maxCount, seconds, ip)) {return false;}
        }

        // Do not intercept, go to the next interceptor
        return true;
    }

    /** * Use Google guava *@param response
     * @param maxCount
     * @param seconds
     * @param ip
     * @return
     * @throws IOException
     */
    private boolean googleCache(HttpServletResponse response, int maxCount, int seconds, String ip) throws IOException, ExecutionException {
        // 2 Obtain the number of accesses to this IP address from the cache
        Integer count = cache.getIfPresent(ip);

        // 3 Determine whether the requirements for annotation Settings are met
        if (count == null) {
            cache.put(ip,1);
        } else if (count < maxCount) {
            3.2 The number of access times meets the requirements within the validity period
            cache.put(ip,++count);
        } else {
            // 3.3 The request was rejected when the maximum number of access times was exceeded
            log.info("Access times exceeded requirements, please access system later = {}", ip);
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            ResultResponse resultResponse = new ResultResponse();
            resultResponse.setResult("Operation too fast, please access system later.");
            Object obj = JSONObject.toJSON(resultResponse);
            response.getWriter().write(JSONObject.toJSONString(obj));
            return true;
        }
        return false;
    }


    /** * use redis as cache *@param response
     * @param maxCount
     * @param seconds
     * @param ip
     * @return
     * @throws IOException
     */
/* private boolean redisCache(HttpServletResponse response, int maxCount, int seconds, String IP) throws IOException {/ / 2 to get the IP number of visits from redis Integer count = (Integer) redisTemplate. OpsForValue () get (IP); / / 3 determine whether meet the requirements set the if (count = = null) {/ / 3.1 first visit, set number, valid redisTemplate. OpsForValue (). The set (IP, 1); redisTemplate.expire(ip, seconds, TimeUnit.SECONDS); } else if (count < maxCount) {/ / 3.2 in the period of validity, visits required redisTemplate. OpsForValue (). The set (IP + + count); } else {// 3.3 Reject the request log.info(" Access times exceed requirements, please access system later = {} ", IP); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); ResultResponse resultResponse = new ResultResponse(); Resultresponse.setresult (" Operation is too fast, please access the system later "); Object obj = JSONObject.toJSON(resultResponse); response.getWriter().write(JSONObject.toJSONString(obj)); return true; } return false; } * /
}

Copy the code

Example 2

Set a cache cache with a validity period of 5 seconds. If a key does not exist, query the database and cache the data. If the key exists, return directly. After the validity period expires, the key is automatically deleted.

public class GoogleGuavaCacheTest {

    private final LoadingCache<String, String> cache;

    public GoogleGuavaCacheTest(a) {
        /** * Set automatic expiration to 5 seconds */
        cache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.SECONDS).build(new CacheLoader<String, String>() {
            public String load(String id) throws Exception {
                System.out.println("method inovke");
                // Where to query the database, and other complex logic
                return "User:"+ id; }}); }public String getAndyName(String id) throws Exception {
        returncache.get(id); }}/ / test
class GuavaCacheTest {
    public static void main(String[] args) throws Exception {
        GoogleGuavaCacheTest us = new GoogleGuavaCacheTest();
        for (int i = 0; i < 20; i++) {
            System.out.println(us.getAndyName("6666"));
            TimeUnit.SECONDS.sleep(1); }}}Copy the code