当前位置:网站首页>Guava cache usage summary

Guava cache usage summary

2022-06-23 07:44:00 Xujingfeng

gossip

The original article has been broken 2 It's been months , It's not because I'm busy , Mainly lazy . But I also feel that there are fewer and fewer technical points to share with you , On the one hand, I have been engaged in some “ Internal projects ” The development of , Even if I want to share , I can't move to the official account & Blog up ; On the one hand, I am not very good at some technical points , When I was a beginner , I dare to write , And after a certain number of years of work , On the contrary, there are some burdens , Would my readers mind ? reasoning , I recalled the original intention of writing , Isn't it just to record your learning process ? So , I still recorded this article according to my previous style of writing , In order to avoid becoming a blogger who breaks the blog .

Here is the text .

Preface

“ cache ” It has always been the kind of technical point that our programmers talk about most , Such as Redis、Encache、Guava Cache, You've heard of at least one . What needs to be acknowledged is that , Whether it's the eight part essay style of interview , Or the frequency of actual use ,Redis Distributed caching is indeed the most popular caching technology at present , But at the same time , From my personal project experience , Local caching is also a common technique .

analysis Redis There are many cached articles , for example Redis An avalanche 、Redis Expiration mechanism, etc , Such titles of official account are not uncommon in my circle of friends timeline in , However, there are few articles analyzing the local cache in my image .

In a recent project , A new colleague used Guava Cache To a person RPC Interface , I am here review Its code just found an unreasonable way to write , So there is this article .

This article will introduce Guava Cache Some common operations of : Basics API Use , Expiration strategy , Refresh strategy . And according to my writing habits , Some summaries of actual development will be attached . What needs to be stated in advance is , I haven't read Guava Cache Source code , The introduction is just some experience or best practices , There won't be too much in-depth parsing .

Just a quick introduction Guava Cache, It is Google Packaged base kit guava A memory cache module in , It mainly provides the following capabilities :

  • Encapsulates the process of caching and data source interaction , Make development more focused on business operations
  • Provide thread safe access operations ( By analogy ConcurrentHashMap)
  • Provide common cache expiration policies , Cache refresh policy
  • Provide cache hit rate monitoring

Based on using

Use an example to introduce Guava Cache The basic method of use – Cache the return value of case conversion .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private String fetchValueFromServer(String key) {
return key.toUpperCase();
}

@Test
public void whenCacheMiss_thenFetchValueFromServer() throws ExecutionException {
LoadingCache<String, String> cache =
CacheBuilder.newBuilder().build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
return fetchValueFromServer(key);
}
});

assertEquals(0, cache.size());
assertEquals("HELLO", cache.getUnchecked("hello"));
assertEquals("HELLO", cache.get("hello"));
assertEquals(1, cache.size());
}

Use Guava Cache The benefits of have jumped onto the paper , It decouples cache access from business operations .CacheLoader Of load Method can be understood as an entry for loading raw data from a data source , When calling LoadingCache Of getUnchecked perhaps get When the method is used ,Guava Cache The behavior is as follows :

  • When cache misses , A synchronous invocation load Interface , Load into cache , Return cache value
  • A cache hit , Directly return the cached value
  • When multithreaded cache misses ,A Threads load when , It will block B Thread request , Until the cache is loaded

be aware ,Guava Two are provided getUnchecked perhaps get Loading method , No big difference , No matter which one you use , All need to pay attention to , Data sources, whether they are RPC The return value of the interface is still the database , Consider access timeout or failure , Do exception handling .

Preload cache

Common usage scenarios for preloading cache :

  • The commonplace seckill scene , Pre cache warm up , Add hot items to cache ;
  • After system restart , Load the cache in advance , Avoid real requests from crashing the cache

Guava Cache Provides put and putAll Method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void whenPreloadCache_thenPut() {
LoadingCache<String, String> cache =
CacheBuilder.newBuilder().build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
return fetchValueFromServer(key);
}
});

String key = "kirito";
cache.put(key,fetchValueFromServer(key));

assertEquals(1, cache.size());
}

Operation and HashMap As like as two peas .

There is a mistake here , And the new colleague just stepped on , It's also my original intention to write this article , Be sure to use only in the scenario of preloading cache put, Any other scenario should use load To trigger the load cache . Look at this Opposite the sample

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//  Note that this is a negative example 
@Test
public void wrong_usage_whenCacheMiss_thenPut() throws ExecutionException {
LoadingCache<String, String> cache =
CacheBuilder.newBuilder().build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
return "";
}
});

String key = "kirito";
String cacheValue = cache.get(key);
if ("".equals(cacheValue)) {
cacheValue = fetchValueFromServer(key);
cache.put(key, cacheValue);
}
cache.put(key, cacheValue);

assertEquals(1, cache.size());
}

This way of writing , stay load Method has a null value set , Subsequently, manually put + get Using the cache , This habit is more like operating a HashMap, But it is not recommended to Cache Use in . I introduced get coordination load By Guava Cache To ensure thread safety , When multiple threads are guaranteed to access the cache , The first request loads the cache at the same time , Blocking subsequent requests , In this way HashMap The usage is neither elegant , In extreme cases, it can also cause cache breakdown 、 Thread safety and so on .

Please make sure that only put Method is used as a preload cache scene .

Cache expiration

The previous introduction is still in use ConcurrentHashMap The category of ,Cache Its first difference is “ Cache expiration ” This scene can be reflected . This section describes Guava Some common cache expiration behaviors and Strategies .

Cache a fixed number of values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void whenReachMaxSize_thenEviction() throws ExecutionException {
LoadingCache<String, String> cache =
CacheBuilder.newBuilder().maximumSize(3).build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
return fetchValueFromServer(key);
}
});

cache.get("one");
cache.get("two");
cache.get("three");
cache.get("four");
assertEquals(3, cache.size());
assertNull(cache.getIfPresent("one"));
assertEquals("FOUR", cache.getIfPresent("four"));
}

Use ConcurrentHashMap One of the biggest problems in caching , That is, we have no simple and effective means to stop its unlimited growth , and Guava Cache Can be initialized by LoadingCache The process of , To configure maximumSize , To ensure that cached content does not cause your system to appear OOM.

It is worth noting that , The test cases I use here are in addition to getgetUnchecked The third way to get the cache , As the literal meaning describes ,getIfPresent When the cache does not exist , It doesn't trigger load Method to load the data source .

LRU Expiration strategy

Still use the above example , We are setting the capacity to 3 when , Learn only LoadingCache Can be stored 3 It's worth , But I didn't know that 4 After saving values , Which old value needs to be eliminated , Make room for new values . actually ,Guava Cache By default LRU Cache retirement strategy .Least Recently Used Least recently used , You may not have implemented this algorithm , But you must have heard of , stay Guava Cache in Used The semantics of represents any access , for example put、get. Continue with the following example .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void whenReachMaxSize_thenEviction() throws ExecutionException {
LoadingCache<String, String> cache =
CacheBuilder.newBuilder().maximumSize(3).build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
return fetchValueFromServer(key);
}
});

cache.get("one");
cache.get("two");
cache.get("three");
// access one
cache.get("one");
cache.get("four");
assertEquals(3, cache.size());
assertNull(cache.getIfPresent("two"));
assertEquals("ONE", cache.getIfPresent("one"));
}

Notice the difference between this example and the example in the previous section : The fourth time get visit one after ,two Becomes the longest unused value , When the fourth value four After the deposit , The object of elimination has become two, Instead of one 了 .

Cache fixed time

Set the expiration time for the cache , Also distinguish HashMap and Cache An important feature of .Guava Cache Provides expireAfterAccessexpireAfterWrite The plan , by LoadingCache Set the expiration time for the cache value in .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void whenEntryIdle_thenEviction()
throws InterruptedException, ExecutionException {

LoadingCache<String, String> cache =
CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.SECONDS).build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
return fetchValueFromServer(key);
}
});

cache.get("kirito");
assertEquals(1, cache.size());

cache.get("kirito");
Thread.sleep(2000);

assertNull(cache.getIfPresent("kirito"));
}

Cache invalidation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void whenInvalidate_thenGetNull() throws ExecutionException {
LoadingCache<String, String> cache =
CacheBuilder.newBuilder()
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
return fetchValueFromServer(key);
}
});

String name = cache.get("kirito");
assertEquals("KIRITO", name);

cache.invalidate("kirito");
assertNull(cache.getIfPresent("kirito"));
}

Use void invalidate(Object key) Remove a single cache , Use void invalidateAll() Remove all caches .

Cache refresh

Cache refresh is often used to overwrite the old cache value with the new value of the data source ,Guava Cache Two types of refresh mechanisms are provided : Manual refresh and scheduled refresh .

Manually refresh

1
cache.refresh("kirito");

refresh Method will trigger load Logic , Try loading the cache from the data source .

It should be noted that ,refresh Method does not block get Method , So in refresh period , The old cache values will still be accessed , until load complete , See the following example .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Test
public void whenCacheRefresh_thenLoad()
throws InterruptedException, ExecutionException {

LoadingCache<String, String> cache =
CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.SECONDS).build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws InterruptedException {
Thread.sleep(2000);
return key + ThreadLocalRandom.current().nextInt(100);
}
});

String oldValue = cache.get("kirito");

new Thread(() -> {
cache.refresh("kirito");
}).start();

// make sure another refresh thread is scheduling
Thread.sleep(500);

String val1 = cache.get("kirito");

assertEquals(oldValue, val1);

// make sure refresh cache
Thread.sleep(2000);

String val2 = cache.get("kirito");
assertNotEquals(oldValue, val2);

}

In any case , The cache value may be inconsistent with the data source , At the business level, fault-tolerant logic for accessing old values should be well prepared .

Automatically refresh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void whenTTL_thenRefresh() throws ExecutionException, InterruptedException {
LoadingCache<String, String> cache =
CacheBuilder.newBuilder().refreshAfterWrite(1, TimeUnit.SECONDS).build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
return key + ThreadLocalRandom.current().nextInt(100);
}
});

String first = cache.get("kirito");
Thread.sleep(1000);
String second = cache.get("kirito");

assertNotEquals(first, second);
}

And the previous section refresh The mechanism is the same ,refreshAfterWrite It won't block either get Threads , There is still the possibility of accessing old values .

Cache hit Statistics

Guava Cache By default, no statistics will be made on hits , Need to build CacheBuilder Time explicit configuration recordStats.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void whenRecordStats_thenPrint() throws ExecutionException {
LoadingCache<String, String> cache =
CacheBuilder.newBuilder().maximumSize(100).recordStats().build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
return fetchValueFromServer(key);
}
});

cache.get("one");
cache.get("two");
cache.get("three");
cache.get("four");

cache.get("one");
cache.get("four");

CacheStats stats = cache.stats();
System.out.println(stats);
}
---
CacheStats{hitCount=2, missCount=4, loadSuccessCount=4, loadExceptionCount=0, totalLoadTime=1184001, evictionCount=0}

Notification mechanism for cache removal

In some business scenarios , We want to do some monitoring of cache invalidation , Or do some callback processing for the expired cache , You can use RemovalNotification Mechanism .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void whenRemoval_thenNotify() throws ExecutionException {
LoadingCache<String, String> cache =
CacheBuilder.newBuilder().maximumSize(3)
.removalListener(
cacheItem -> System.out.println(cacheItem + " is removed, cause by " + cacheItem.getCause()))
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
return fetchValueFromServer(key);
}
});

cache.get("one");
cache.get("two");
cache.get("three");
cache.get("four");
}
---
one=ONE is removed, cause by SIZE

removalListener You can give LoadingCache Add a callback handler ,RemovalNotification The instance contains the cached key value pairs and the reason for removal .

Weak Keys & Soft Values

Java The concepts of weak reference and soft reference in the foundation are believed to have been learned by everyone , Here is a review for you

  • Soft citation : If an object has only Soft citation , be Enough memory when , Garbage collector Just Can't Recycle it ; If Out of memory , will Recycling These objects . As long as the garbage collector doesn't recycle it , This object can be used by the program
  • Weak reference : Only have Weak reference Owned by Shorter Of Life cycle . In the process of the garbage collector thread scanning the memory area it governs , Once found, only Weak reference The object of , Whether or not the current Is there enough memory space , Metropolis Recycling Its memory .

stay Guava Cache in ,CacheBuilder Provides weakKeys、weakValues、softValues Three methods , Match the cached key value pairs with JVM The garbage collection mechanism is associated .

This operation may have its applicable scenarios , For example, maximum use JVM Memory cache , But rely on GC clear , The performance is expected to be low . In short, I will not rely on JVM Mechanism to clean up the cache , So I dare not use this feature , Online stability comes first .

If you need to set a cleanup policy , You can refer to the two schemes of fixed quantity and fixed time in the cache expiration summary , Combined use ensures high performance with caching , Don't hang up the memory .

summary

This paper introduces Guava Cache Some commonly used API 、 Usage examples , And some misuses that need to be vigilant .

In choosing to use Guava when , I usually use it in combination with actual scenarios , Make the following considerations :

  1. Why not Redis?

    If the local cache can solve , I don't want to introduce an additional middleware .

  2. If you guarantee the consistency between the cache and the data source ?

    A situation , I will use caching in scenarios with low data sensitivity , So a brief disagreement can be tolerated ; Other cases , I will set the mechanism of periodically refreshing the cache and manually refreshing the cache . for instance , There is a display application on the page developer List function , Only the application name is stored locally ,developer The list is through a RPC Interface query , And because of the other side's restrictions , The interface qps Very low tolerance , You can consider caching developer list , And configuration maximumSize as well as expireAfterAccess. If a user is developer New data is added to the data source , It leads to data inconsistency , The page can also be set with a sync button , Let users take the initiative refresh; perhaps , If it is judged that the current user is not developer list , You can also program refresh once . All in all, it's very flexible , Use Guava Cache Of API It can meet the caching requirements of most business scenarios .

  3. Why Guava Cache, How about its performance ?

    I'm mainly thinking about stability , The project has been using Guava Cache. It is said that there are more than Guava Cache Fast local cache , But my system doesn't care about that performance .

原网站

版权声明
本文为[Xujingfeng]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/174/202206230701559569.html