The difference between refreshAfterWrites and expireAfterWrite of Guava Cache LoadingCache

There are three ways to clean up or refresh cached data based on time:

expireAfterAccess: when the cache item is not read or written within the specified time period, it will be recycled.

expireAfterWrite: when the cache item is not updated within the specified time period, it will be recycled (remove the key) and will not be returned until a new value is obtained.

refreshAfterWrite: how long the cache item will be refreshed after the last update operation. The first request comes in and executes load to load the data into memory (synchronization process). Within the specified expiration time, such as 10 seconds, the data is read from the cache. After 10 seconds, no request comes in and the key will not be removed. reload is executed only when another request comes. In the process of background asynchronous refresh, if it is currently in the refresh state, the old value is accessed. During the refresh process, only one thread is performing the refresh operation, and multiple threads will not refresh the cache of the same key at the same time. When the throughput is very low, if there is no request for a long time, another request may get an old value (this old value may come from a long time ago), which will cause problems. (you can use expireAfterWrite and refreshAfterWrite together to solve this problem)

<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>23.0</version>
</dependency>

refreshAfterWrites example

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
 
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
 
/**
 *  Some data queries are time-consuming and relieve the pressure on the server. In high concurrency scenarios, you can use refreshAfterWrites to refresh the cache asynchronously.
 *	Question:
 *	1,Will it appear that the obtained value is an old value that has expired?
 *	2,With the rapid increase of requests, is the number of connections sufficient?
 *
 * LoadingCache refreshAfterWrites refresh mechanism
 * Add the data to the cache and refresh the data in the cache asynchronously
 * 
 * @author cenjianteng
 */
public class LoadingCacheDemo {
 
	public LoadingCache<String, Map<String, String>> cache;
 
	private static ExecutorService executorService = Executors.newFixedThreadPool(3);
 
	/**
	 * Data added to cache
	 */
	private Map<String, String> setData() {
		Map<String, String> result = new HashMap<>();
 
		result.put("result1", "Xiao Ming");
		result.put("result2", "Xiao Hei");
		result.put("result3", "Xiaobai");
		result.put("result4", "Xiao Huang");
 
		return result;
	}
 
	/**
	 * Build cache object
	 */
	private void buildCache() {
 
		cache = CacheBuilder.newBuilder().refreshAfterWrite(8, TimeUnit.SECONDS)
				.build(new CacheLoader<String, Map<String, String>>() {
					@Override
					public Map<String, String> load(String s) throws Exception {
						System.out.println("load ...");
						return setData();
					}
 
					@Override
					public ListenableFuture<Map<String, String>> reload(String key, Map<String, String> oldValue)
							throws Exception {
						System.out.println("reload ...");
						ListenableFutureTask<Map<String, String>> task = ListenableFutureTask
								.create(new Callable<Map<String, String>>() {
									@Override
									public Map<String, String> call() throws Exception {
										return setData();
									}
								});
						executorService.execute(task);
						return task;
					}
				});
 
	}
 
	/**
	 * test
	 */
	public static void main(String[] args) {
 
		LoadingCacheDemo demo = new LoadingCacheDemo();
		demo.buildCache();
 
		Runnable runnable1 = () -> {
			for (int i = 0; i < 100; i++) {
				try {
					System.out.println("Runnable1 Before Get Cache");
					System.out.println("Runnable1 " + demo.cache.get("test"));
					System.out.println("Runnable1 After Get Cache");
					Thread.currentThread().sleep(1000);
				} catch (ExecutionException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
		Runnable runnable2 = () -> {
			for (int i = 0; i < 100; i++) {
				try {
					System.out.println("Runnable2 Before Get Cache");
					System.out.println("Runnable2 " + demo.cache.get("test"));
					System.out.println("Runnable2 After Get Cache");
					Thread.currentThread().sleep(1000);
				} catch (ExecutionException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
		Thread thread1 = new Thread(runnable1);
		Thread thread2 = new Thread(runnable2);
		thread1.start();
		thread2.start();
 
	}
 
}

expireAfterWrite example

package com.cjt.demo;
 
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
 
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
 
public class CacheDemo2 {
 
    private static Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(1).
            expireAfterWrite(10, TimeUnit.SECONDS).build();
 
    public static void main(String[] args) {
 
        try {
 
            Runnable runnable1 = () -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        String str = cache.get("123", new Callable<String>() {
                            public String call() throws Exception {
                                cache.put("123","123");
                                return "111111111111111";
                            }
                        });
                        System.out.println(str);
 
                        Thread.currentThread().sleep(1000);
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            Runnable runnable2 = () -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        System.out.println(cache.get("456", new Callable<String>() {
                            public String call() throws Exception {
                                cache.put("456","456");
                                return "222222222222222";
                            }
                        }));
                        Thread.currentThread().sleep(1000);
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            Thread thread1 = new Thread(runnable1);
            Thread thread2 = new Thread(runnable2);
            thread1.start();
            thread2.start();
 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
 
}
# Use expireAfterWrite with refreshAfterWrite

```java
.refreshAfterWrite(20, TimeUnit.MINUTES) 
.expireAfterWrite(30, TimeUnit.MINUTES) 

The first request for load is to read the memory until 20 minutes. After 20 minutes, it depends on whether there is a request to read the cache to execute reload to refresh the memory value. If there is no request, it will be delayed for 30 minutes to see whether the memory has been refreshed (expireAfterWrite). If there is no refresh, the key will be removed and reloaded the next time to ensure that the recent new value is obtained. If there are always requests coming in, the key will not be removed and the asynchronous refresh of refreshAfterWrite will be performed

 CacheBuilder.newBuilder()
                .refreshAfterWrite(20, TimeUnit.MINUTES)
                .expireAfterWrite(30, TimeUnit.MINUTES)
                .maximumSize(1)
                .build(new CacheLoader<String, List<Map<String, Long>>>() {
                    @Override
                    public List<Map<String, Long>> load(String s) throws Exception {
                        return queryData();
                    }
 
                    @Override
                    public ListenableFuture<List<Map<String, Long>>> reload(String key, List<Map<String, Long>> oldValue)
                            throws Exception {
                        ListenableFutureTask<List<Map<String, Long>>> task = ListenableFutureTask
                                .create(() -> queryData());
                        executorService.execute(task);
                        return task;
                    }
                });

Keywords: Java memcached

Added by gtibok on Thu, 23 Sep 2021 04:58:34 +0300