沧海一粟

天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣。

0%

guava缓存

google guava-cache

redis做缓存很快,但是还有网络IO,在并发量大要求耗时低的情况下还是不满足需求,所以想试试本地缓存。

发现guava缓存不错,想试试

在本地demo测试的时候发现更新缓存时有毛刺出现

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package cn.wkq.cache;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.junit.Test;

import java.util.concurrent.*;

/**
* LocalCache
*
* @author: weikeqin.cn@gmail.com
* @date: 2020-05-10 17:08
**/
public class LocalCache {
/**
*
*/
private transient static final Logger log = LoggerFactory.getLogger(LocalCache.class);
/**
*
*/
private static LoadingCache<String, Object> cache = null;


static {

StringBuilder sb = new StringBuilder(1 << 20);
for (int i = 0; i < 102400; i++) {
sb.append(i);
}
String testString = sb.toString();
log.info("{}", testString.length());

cache = CacheBuilder.newBuilder()
//设置并发级别为8,并发级别是指可以同时写缓存的线程数
.concurrencyLevel(8)
//设置缓存容器的初始容量为10
.initialCapacity(10)
//设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
.maximumSize(100)
//是否需要统计缓存情况,该操作消耗一定的性能,生产环境应该去除
.recordStats()
//设置写缓存后n秒钟过期
.expireAfterWrite(1, TimeUnit.SECONDS)
//设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
//.expireAfterAccess(17, TimeUnit.SECONDS)
//只阻塞当前数据加载线程,其他线程返回旧值
.refreshAfterWrite(1, TimeUnit.SECONDS)
//设置缓存的移除通知
.removalListener(notification -> {
log.info(" 本地缓存key:{} 被移除,原因:{} ", notification.getKey(), notification.getCause());
//log.info(" 本地缓存key:{} 被移除,原因:{} 对应值:{}", notification.getKey(), notification.getCause(), notification.getValue());
})
//build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
.build(new CacheLoader<String, Object>() {
@Override
public Object load(String key) throws Exception {
log.info("重新查库");
return "获取的缓存的值" + testString;
}
});
}


/**
*
*/
@Test
public void cacheTest() {

ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 8, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

String key = "1";
int count = 100000;
for (int i = 0; i < count; i++) {

int finalI = i;
executor.execute(() -> {

long t1 = System.currentTimeMillis();
Object value = null;
try {
value = cache.get(key);
} catch (ExecutionException e) {
log.error("", e);
}
long t2 = System.currentTimeMillis();

log.info(" {} cost {} ms ", finalI, t2 - t1);
//log.info("{} 根据 {} 获取到的值 {} ", Thread.currentThread().getName(), key, ((String) value).length());

});

// try {
// Thread.sleep(1);
// } catch (InterruptedException e) {
// log.error("", e);
// }

}

executor.shutdown();
log.info("线程池关闭。");

while (!executor.isTerminated()) {
try {
TimeUnit.SECONDS.sleep(1);
log.info("线程池中线程数目:{},队列中等待执行的任务数目:{},已执行玩别的任务数目:{}", executor.getPoolSize(), executor.getQueue().size(), executor.getCompletedTaskCount());
} catch (InterruptedException e) {
log.error("", e);
}
log.info("还有线程未执行完成");
}

//缓存状态查看
log.info(cache.stats().toString());


}

}

少了网络IO,整体 TP99 比redis好了不少
但是在压测时发现毛刺更严重

References

[1] google/guava/wiki/CachesExplained
[2] [Google Guava] 3-缓存 并发编程网 - ifeve.com
[3] 缓存篇 : Guava cache 之全面剖析