When a project needs to use local cache, it usually implements LRU cache based on ConcurrentHashMap or LinkedHashMap. In the process of building wheels, it is generally necessary to solve the following problems:
1. Memory is limited, so you need to limit the maximum size of the cache.
2. How do I clear cache entries that are too old?
3. How to deal with concurrent read and write operations.
4. Cache data transparency: hit ratio, failure rate, etc.
The strength of a cache depends on how elegantly and efficiently these problems are addressed. Guava Cache solves these problems well. It is a very good local cache, thread-safe, fully functional, easy to use, and good performance. Overall, Guava cache is the local cache of choice.
Here’s a simple 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 {
returncreateExpensiveGraph(key); }});Copy the code
Next, we’ll explain how to use the Guava cache from several perspectives.
Create cache
Remote cache or local cache
User user = cache.get(usernick); if(user == null){ user = userDao.getUser(usernick); cache.put(usernick, user); } return user;
Namely the if cached, return; Otherwise create/load/compute, cache and return.
Guava Cache implements this logic in a more elegant way in two ways:
1. From A CacheLoader
2. From A Callable
Caches created using these two methods are different from regular map caches in that they both implement the above “if cached, return; Otherwise create/load/compute, cache and return “. The difference is that cache reader is defined broadly for the entire cache and can be regarded as a uniform load method based on the key value. Callable is more flexible, allowing you to specify when you get. Give two chestnuts to illustrate how to use these two methods
The From CacheLoader:
LoadingCache<String, String> graphs = CacheBuilder.newBuilder().maximumSize(1000) .build(new CacheLoader<String, String>() {public String load(String key) {"no cache,load from db"); return "123"; }}); String val1 = graphs.get("key"); System.out.println("1 value is: " + val1); String val2 = graphs.get("key"); System.out.println("2 value is: "+ val2); From Callable: Cache<String, String> Cache = cacheBuilder.newBuilder ().maximumsize (1000).build(); String val1 = cache.get("key", new Callable<String>() {
public String callPrintln () {// Here is the key to the actual value of the method, such as the key to the database or through the complex time-consuming calculation of system.out.println () {// here is the key to the actual value of the method, such as the key to the database or through the complex time-consuming calculation of system.out.println ()"val call method is invoked"); return "123"; }}); System.out.println("1 value is: " + val1); String val2 = cache.get("testKey", new Callable<String>() {
public String callPrintln () {// Here is the key to the actual value of the method, such as the key to the database or through the complex time-consuming calculation of system.out.println () {// here is the key to the actual value of the method, such as the key to the database or through the complex time-consuming calculation of system.out.println ()"val call method is invoked"); return "123"; }}); System.out.println("2 value is: " + val2);Copy the code
Note that all Guava Caches, loader or not, support the GET (Key,Callable
) method.
) method.
Inserted Directly:Values can also be Inserted Directly into the cache via cache.put(key,value). This method overwrites the entry corresponding to the key.
Cache removal
Memory is limited, so you can’t load everything into memory, and a large local cache is a nightmare for any Java application. Therefore, local cache must provide a different mechanism to clear “unnecessary” cache entries and balance memory usage and hit ratio. Guava Cache provides three types of Cache removal strategies: size-based eviction, time-based eviction, and Reference-based eviction.
Size-based eviction: Cache capacity based removal. If your cache is not allowed to expand beyond its maximum value, use cacheBuilder.maxmusize (long). In this case, the cache itself frees entries memory that has not been used recently or infrequently. Two points to note here:
1. It is not the case that entries are deleted when the limit is exceeded, but when the limit is reached, which you should be careful about, as it is clear that the cache will still delete entries even if the limit is not reached.
2. If a key-entry has been removed, when you call get(key) again, if CacheBuilder is in CacheLoader mode, it will still be loaded once from the CacheLoader.
Also, if entries in your cache have a different memory footprint, if your cache values have a different memory footprint, you can use CacheBuilder. Weigher (weigher) to weigh each entry. Then use the CacheBuilder. MaximumWeight (long) set a maximum. In TPN, the subscription information of users to message categories is cached by local cache. Some users subscribe to more message categories and occupy more memory, while some users subscribe to fewer message categories and naturally occupy less memory. I can use the following method to set different weights based on the number of message categories that users subscribe to, so that the cache can cover as many users as possible without changing the cache size:
LoadingCache<Key, User> Users= CacheBuilder.newBuilder()
.maximumWeight(100000) .weigher(new Weigher<Key, User>() {
public int weigh(Key k, User u) {
if(u.categories().szie() >5){
return 2; }else{ return 1; } } }) .build( new CacheLoader<Key, User>() {
public Userload(Key key) { // no checked exception
returncreateExpensiveUser(key); }});Copy the code
PS: This example may not be very appropriate, but it is sufficient to illustrate the use of weight.
Time-based eviction: Timing based removal. Guava Cache provides two ways to implement this logic:
1. expireAfterAccess(long, TimeUnit)
Timing starts from the last access (read or write) and releases the entries after this specified time. Note: The order of deleted entries is very similar to size-based eviction.
2. expireAfterWrite(long,TimeUnit)
Entries are timed from the point at which they were created or last modified, and if a specified period of time has elapsed since then, entries will be deleted. This is smart, because data gets older and older over time.
If you want to test the Timed Eviction, use the Ticker interface and cacheBuilder.ticker (Ticker) methods to set a time to your cache, you won’t need to wait for system time.
Reference-based eviction: Reference based removal. Guava has a garbage collector for entries, which can use weak reference for keys or values and soft reference for values.
1. Cachebuilder.weakkeys (): Stores keys through weak reference. In this case, if keys are not referenced by strong or soft, entries will be garbage collected.
2. Cachebuilder.weakvalues () : Stores values by weak Referene. In this case, if valves are not referenced by Strong or Soft, entries will be garbage collected.
Note that the garbage collector in this case is based on references, which would result in the entire cache comparing two keys using == instead of equals();
In addition to the above three ways to remove enties from a cache, there are three ways to actively release enties:
Cache. Invalidate (key)
Cache. InvalidateAll (keys)
3. Remove all applications: cache.invalidateAll ()
You can also define a Removal Listener if you want to do something while removing data, but note that by default the Removal Listener is performed synchronously with the Removal action. If you want to do it asynchronously, Consider using RemovalListeners. Asynchronous (RemovalListener, Executor).
Finally, let’s take a look at when the Guava Cache performs the cleanup. A cache created using the CacheBuilder does not automatically clear or remove entries, nor does it immediately clear an entry when it expires. Instead, it performs cleanup with a small number of operations while performing writes or reads (in the case of very few writes). The reason for this is that if we are constantly cleaning and removing the cache, we need to create a thread whose business will compete with the user’s actions for the shared lock. In addition, some environments restrict the creation of cleanup threads, which prevents CacheBuilder from being used in that environment. Therefore, Guava Cache gives the user the choice of when to clean. If your cache is geared towards high-throughput applications, you don’t have to worry about performing cache maintenance, cleaning up expired entries, etc. If your Cache is read oriented, you can create your own maintenance thread to call cache.cleanup () every once in a while, such as ScheduledExecutorService.
If you want to schedule regular maintenance of the cache there are few writes in the cache, just schedule repairs with ScheduledExecutorService.
Iii. Statistical Functions:
Statistics is a very useful feature of Guava Cache. The cacheBuilder.recordStats () method can be used to initiate cache data collection:
Cache.stats(): Returns a CacheStats object that provides some data methods
2. HitRate (): request clicks
3. AverageLoadPenalty (): The time it takes to load the new value, in nanosecondes
4. EvictionCount (): indicates the number of clearances