Guava experience: Guava Cache

Posted by jheitz on Sun, 07 Jun 2020 13:21:12 +0200

Caching is an indispensable method to solve performance problems in our daily development. In short, cache is a piece of memory space to improve system performance.

The main function of cache is to temporarily save the data processing results of business system in memory and wait for the next access. In many cases of daily development, due to the limited performance of hard disk IO or the data processing and acquisition of our own business system may be very time-consuming. When we find that our system has a large amount of data requests, frequent IO and frequent logic processing will lead to the bottleneck of hard disk and CPU resources. The function of cache is to save the data from the hard data in memory. When other threads or clients need to query the same data resources, they can directly return the data from the cache memory block, which can not only improve the response time of the system, but also save the resource consumption of the processing flow of these data. As a whole, the system performance will be greatly improved The promotion of.

Caching is widely used in many systems and architectures, such as:

  1. CPU cache
  2. Operating system cache
  3. Local cache
  4. Distributed cache
  5. HTTP cache
  6. Database cache

In the field of computer and network, cache is everywhere. It can be said that as long as there is hardware performance inequality, there will be cache in places involving network transmission.

Guava cache is a full memory local cache implementation, which provides a thread safe implementation mechanism. On the whole, Guava cache is the best choice for local cache, which is simple and easy to use with good performance.

There are two ways to create Guava Cache:

  1. cacheLoader
  2. callable callback

The cache created by these two methods is different from the way of using map to cache. The difference is that both methods implement a kind of logic: take the value of key X from the cache, if the value has been cached, return the value in the cache. If there is no cache, get the value through a certain method. But the difference is that the definition of cacheloader is relatively broad, which is defined for the whole cache. It can be considered as a unified method based on the key value load value. callable is more flexible, allowing you to specify when you get.

Implementation example of cacheLoader mode:

@Test
public void TestLoadingCache() throws Exception{
    LoadingCache<String,String> cahceBuilder=CacheBuilder
    .newBuilder()
    .build(new CacheLoader<String, String>(){
        @Override
        public String load(String key) throws Exception {        
            String strProValue="hello "+key+"!";                
            return strProValue;
        }
        
    });        
    
    System.out.println("jerry value:"+cahceBuilder.apply("jerry"));
    System.out.println("jerry value:"+cahceBuilder.get("jerry"));
    System.out.println("peida value:"+cahceBuilder.get("peida"));
    System.out.println("peida value:"+cahceBuilder.apply("peida"));
    System.out.println("lisa value:"+cahceBuilder.apply("lisa"));
    cahceBuilder.put("harry", "ssdded");
    System.out.println("harry value:"+cahceBuilder.get("harry"));
}

Output:

jerry value:hello jerry!
jerry value:hello jerry!
peida value:hello peida!
peida value:hello peida!
lisa value:hello lisa!
harry value:ssdded

Implementation of callable callback:

@Test
public void testcallableCache()throws Exception{
    Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(1000).build();  
    String resultVal = cache.get("jerry", new Callable<String>() {  
        public String call() {  
            String strProValue="hello "+"jerry"+"!";                
            return strProValue;
        }  
    });  
    System.out.println("jerry value : " + resultVal);
    
    resultVal = cache.get("peida", new Callable<String>() {  
        public String call() {  
            String strProValue="hello "+"peida"+"!";                
            return strProValue;
        }  
    });  
    System.out.println("peida value : " + resultVal);  
}

  //Output:
  jerry value : hello jerry!
  peida value : hello peida!

Parameter description of cache:

Recycled parameters:

  1. Size setting: CacheBuilder.maximumSize(long)  CacheBuilder.weigher(Weigher)  CacheBuilder.maxumumWeigher(long)
  2. Time: expireAfterAccess(long, TimeUnit) expireAfterWrite(long, TimeUnit)
  3. quote: CacheBuilder.weakKeys() CacheBuilder.weakValues()  CacheBuilder.softValues()
  4. Explicit deletion: invalidate(key) invalidateAll(keys) invalidateAll()
  5. To delete a listener: CacheBuilder.removalListener(RemovalListener)

refresh mechanism:

  1. LoadingCache.refresh(K) When a new value is generated, the old value will still be used.
  2. CacheLoader.reload(K, V) allow the old value to be used in the process of generating new value
  3. CacheBuilder.refreshAfterWrite(long, TimeUnit) refresh cache automatically

Generic based implementation:

 

/**
 * No need for delay processing (encapsulated in a generic way)
 * @return
 */
public  <K , V> LoadingCache<K , V> cached(CacheLoader<K , V> cacheLoader) {
      LoadingCache<K , V> cache = CacheBuilder
      .newBuilder()
      .maximumSize(2)
      .weakKeys()
      .softValues()
      .refreshAfterWrite(120, TimeUnit.SECONDS)
      .expireAfterWrite(10, TimeUnit.MINUTES)        
      .removalListener(new RemovalListener<K, V>(){
        @Override
        public void onRemoval(RemovalNotification<K, V> rn) {
            System.out.println(rn.getKey()+"Removed ");  
            
        }})
      .build(cacheLoader);
      return cache;
}

/**
 * Get value through key
 * Call mode commonCache.get(key) ; return String
 * @param key
 * @return
 * @throws Exception
 */
  
public  LoadingCache<String , String> commonCache(final String key) throws Exception{
    LoadingCache<String , String> commonCache= cached(new CacheLoader<String , String>(){
            @Override
            public String load(String key) throws Exception {
                return "hello "+key+"!";    
            }
      });
    return commonCache;
}

@Test
public void testCache() throws Exception{
    LoadingCache<String , String> commonCache=commonCache("peida");
    System.out.println("peida:"+commonCache.get("peida"));
    commonCache.apply("harry");
    System.out.println("harry:"+commonCache.get("harry"));
    commonCache.apply("lisa");
    System.out.println("lisa:"+commonCache.get("lisa"));
}

Output:

peida:hello peida!
harry:hello harry!
peida Removed 
lisa:hello lisa!

Implementation of Callable Cache based on generics:

private static Cache<String, String> cacheFormCallable = null; 


/**
 * This mechanism can be used for those that need delay processing; (encapsulated in a generic way)
 * @param <K>
 * @param <V>
 * @param key
 * @param callable
 * @return V
 * @throws Exception
 */
public static <K,V> Cache<K , V> callableCached() throws Exception {
      Cache<K, V> cache = CacheBuilder
      .newBuilder()
      .maximumSize(10000)
      .expireAfterWrite(10, TimeUnit.MINUTES)
      .build();
      return cache;
}


private String getCallableCache(final String userName) {
       try {
         //Callable will only be called if the cache value does not exist
         return cacheFormCallable.get(userName, new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println(userName+" from db");
                return "hello "+userName+"!";
           }
          });
       } catch (ExecutionException e) {
          e.printStackTrace();
          return null;
        } 
}

@Test
public void testCallableCache() throws Exception{
     final String u1name = "peida";
     final String u2name = "jerry"; 
     final String u3name = "lisa"; 
     cacheFormCallable=callableCached();
     System.out.println("peida:"+getCallableCache(u1name));
     System.out.println("jerry:"+getCallableCache(u2name));
     System.out.println("lisa:"+getCallableCache(u3name));
     System.out.println("peida:"+getCallableCache(u1name));
     
}

Output:

peida from db
peida:hello peida!
jerry from db
jerry:hello jerry!
lisa from db
lisa:hello lisa!
peida:hello peida!

Note: Callable can only be called when the cache value does not exist. For example, the second call to getCallableCache(u1name) directly returns the value in the cache

guava Cache data removal:

There are two ways to remove data when guava is doing cache: passive removal and active removal.

There are three ways to remove data passively, which are provided by guava by default:

  • 1. Size based removal: literally, it means to remove according to the size of the cache. If the specified size is about to be reached, the infrequently used key value pairs will be removed from the cache.

    The way of definition is generally CacheBuilder.maximumSize(long), there is also a way to calculate the weight. I don't think it's used in practice. There are several points for attention in this common view,

    • First, this size refers to the number of entries in the cache, not the memory size or others;
    • Second, it is not until the specified size system starts to remove the data that is not commonly used, but when it is close to this size, the system will start to remove;
    • Third, if a key value pair has been removed from the cache, and you request access again, if the cachebuild uses the cacheloader method, it will still take another value from the cacheloader. If it doesn't, an exception will be thrown
  • 2. Time based removal: guava provides two methods of time-based removal

    • The method of expireAfterAccess(long, TimeUnit) is to remove a key according to the time after the last access
    • expireAfterWrite(long, TimeUnit) this method is based on how long it takes to remove a key value pair after it is created or replaced
  • 3. Reference based removal:

This removal method is mainly based on java garbage collection mechanism, which is determined by the reference relationship of key or value

There are three ways to actively remove data:

  1. For separate removal Cache.invalidate(key)
  2. For batch removal Cache.invalidateAll(keys)
  3. Remove all Cache.invalidateAll()

If you need to take actions when removing data, you can also define a Removal Listener. However, it should be noted that the actions in the default Removal Listener are executed synchronously with the removal actions. If you need to change to asynchronous form, you can consider using the RemovalListeners.asynchronous(RemovalListener, Executor)

Topics: network Database Java