高性能本地缓存
在将本地缓存前你肯定在想,本地缓存有么好讲的,不就是一个map么。把要缓存的数据存入map中,自己就能实现。但是这里有几个点我们要考虑:
- 并发-使用普通的Map还是线程安全的ConcurrentMap?
- 容量-Map的容量需要有多大?
- 过期策略-Map里的数据如果很久不用是不是需要定时清除?
- 驱逐策略-如果数据还没有过期,但是容量满了该怎么处理?
如果你也有这些问题那么请看下面的文章。
之前我介绍了Google的本地缓存Guava Cache,有兴趣的可以看看我的这篇文章:
“Google的这个本地缓存真好用”
但是我觉得这个本地缓存用起来不是很方便,性能也并不高。因此我推荐一个高性能本地缓存框架-Caffeine,Guava Cache的升级版,使用起来很方便。下面我们来看看它的使用示例。
性能
性能展示:
Read(100%)
Write(100%)
由上图我们看到:无论是读还是写,Caffeine的性能有多强。
使用示例
首先导入Maven依赖:
代码语言:javascript复制<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.8.4</version>
</dependency>
测试代码:
我们先来创建一个缓存cache
,这里我们定义了一些属性:
“
maximumSize():
这里可以设置缓存数量上限。expireAfterWriter():
表示从最后一次写入后多久会过期。weakKeys():
key使用了弱引用。weakValues():
value值使用了弱引用,防止内存泄漏。removeListener():
缓存失效时的监听。 ”
Caffeine使用了线程安全,并发下性能优良的ConcurrentMap
我们开始从缓存中获取:
代码语言:javascript复制String valueFromCache = cache.get("lvshen", key -> getValue(key));
如上代码,我们从cache
中获取key为lvshen
的值,如果从cache
中获取不到,我们就从方法getValue(key)
中获取。一般来说查缓存有3步:
- 从缓存中查询,如果缓存中有值,返回
- 如果缓存中没有值,则从数据库中获取,并返回
- 将数据库中的值存入缓存中
Caffeine本地缓存也是这个逻辑,为了模拟这个逻辑,我们两次从缓存中获取key为lvhsen
的值。第一次是从数据库中获取,会调用getValue()
方法,第二次会直接从缓存中获取。
运行结果如下:
代码语言:javascript复制调用了getValue方法....
测试结果:lvshen
测试结果2:lvshen
运行结果印证了上述逻辑。
除了自动清理缓存,我们也可以手动清理缓存:
代码语言:javascript复制cache.invalidate("lvshen");
Guava Cache和Caffeine很大的区别在于使用的淘汰算法,Guava Cache采用的LRU算法,而Caffeine使用的LFU算法的升级版TinyLFU算法,至于这LRU和LFU算法的区别,可以看看我写的这篇文章:
“【技术干货】淘汰算法LRU与LFU”
TinyLFU作为LFU算法的升级版,在性能上更加强大(高命中率、低内存占用),具体实现这里就不做详述。
当然我们也可以结合SpringBoot使用:
首先配置config
使用的时候直接注入:
代码语言:javascript复制@Autowired
Cache<String, Object> caffeineCache;
...
public void fun(String key) {
caffeineCache.put(key,"I am Lvshen");
}
我们也可以定义成注解的形式使用:
引入maven依赖
代码语言:javascript复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
配置config
使用如下:
代码语言:javascript复制@CachePut(key = "#userInfo.id")
public void addUser(User userInfo) {
//这里模拟存入数据库中
userMap.put(userInfo.getId(), userInfo);
}
@Cacheable(key = "#id")
public User getByName(Integer id) {
//先从缓存中获取,缓存中没有,再从数据库中获取
return userMap.get(id);
}
这样我们也能实现SpringBoot和Caffeine的融合使用。
官方文档
本篇文章只是对Caffeine做一个入门的介绍,想要深入了解的童鞋可以看看官方文档。
官方文档
Caffeine的官方使用文档如下:
“(https://github.com/ben-manes/caffeine/wiki/Policy-zh-CN) ”