在项目中我们经常使用缓存架构,来缓存我们的数据,比如redis、caffeine等。那么redis和caffeine有什么区别?作用又有哪些不同呢?
caffeine详情
redis和caffeine的区别?
相同点就不用说,广义上都是缓存的方式。咱们就说说不同。
- redis是将数据存储到内存里;caffeine是将数据存储在本地应用里
- caffeine和redis相比,没有了网络IO上的消耗
那么在高并发场景中,一般我们都是结合使用,形成一二级缓存。caffeine作为一级缓存,redis作为二级缓存。
使用流程大致如下:去一级缓存中查找数据(caffeine-本地应用内)如果没有的话,去二级缓存中查找数据(redis-内存)再没有,再去数据库中查找数据(数据库-磁盘)。
caffeine的使用
Caffeine 相当于一个缓存工厂,可以创建出多个缓存实例 Cache。这些缓存实例都继承了 Caffeine 的参数配置,Caffeine 是如何配置的,这些缓存实例就具有什么样的特性和功能。
Caffeine 是目前性能最好的本地缓存,因此,在考虑使用本地缓存时,直接选择 Caffeine 即可。
Caffeine.newBuilder().maximumSize(6000).expireAfterAccess(10, TimeUnit.MINUTES)
.expireAfterWrite(10, TimeUnit.MINUTES).removalListener(new RemovalListener<String, CacheUserInfo>() {
@Override
public void onRemoval(@Nullable String key, @Nullable CacheUserInfo value,
@NonNull RemovalCause cause) {
if (value != null) {
log.info("user profile has removed. uid {}, name {}", key, value.getName());
}
}
}).recordStats().build(new CacheLoader<String, CacheUserInfo>() {
@Override
public @Nullable CacheUserInfo load(@NonNull String telephone) throws Exception {
try {
return CacheUserInfo.getLoggedUserProfile(telephone, false);
} catch (Exception e) {
log.error("load user profile error", e);
return null;
}
}
});
}
caffeine缓存属性
initialCapacity 缓存初始容量
整数,表示能存储多少个缓存对象。
为什么要设置初始容量呢?因为如果提前能预估缓存的使用大小,那么可以设置缓存的初始容量,以免缓存不断地进行扩容,致使效率不高。
maximumSize 最大容量
如果缓存中的数据量超过这个数值,Caffeine 会有一个异步线程来专门负责清除缓存,按照指定的清除策略来清除掉多余的缓存。注意:比如最大容量是 2,此时已经存入了2个数据了,此时存入第3个数据,触发异步线程清除缓存,在清除操作没有完成之前,缓存中仍然有3个数据,且 3 个数据均可读,缓存的大小也是 3,只有当缓存操作完成了,缓存中才只剩 2 个数据,至于清除掉了哪个数据,这就要看清除策略了。
maximumWeight 最大权重
存入缓存的每个元素都要有一个权重值,当缓存中所有元素的权重值超过最大权重时,就会触发异步清除。下面给个例子。
class Student{
Integer score;
String name;
}
Caffeine<String, Student> caffeine = Caffeine.newBuilder()
.maximumWeight(100)
.weigher((String key, Person value)-> value.getScore());
Cache<String, Student> cache = caffeine.build();
cache.put("one", new Student(40, "one"));
cache.put("two", new Student(60, "two"));
cache.put("three", new Student(50, "three"));
Thread.sleep(10);
System.out.println(cache.estimatedSize());
System.out.println(cache.getIfPresent("two"));
运行结果:
2
null
要使用权重来衡量的话,就要规定权重是什么,每个元素的权重怎么计算,weigher 方法就是设置权重规则的,它的参数是一个函数,函数的参数是 key 和 value,函数的返回值就是元素的权重,比如上述代码中,caffeine 设置了最大权重值为 100,然后将每个 Student对象的 socre成绩作为权重值,所以整个意思就是:缓存中存储的是 Student对象,但是限制所有对象的 score总和不能超过 100,否则就触发异步清除缓存。
特别要注意一点:最大容量 和 最大权重 只能二选一作为缓存空间的限制。
CacheStats 默认的缓存状态收集器
默认情况下,缓存的状态会用一个 CacheStats 对象记录下来,通过访问 CacheStats 对象就可以知道当前缓存的各种状态指标,那究竟有哪些指标呢?
先说一下什么是“加载”,当查询缓存时,缓存未命中,那就需要去第三方数据库中查询,然后将查询出的数据先存入缓存,再返回给查询者,这个过程就是加载。
caffeine 过期策略
在Caffeine中分为两种缓存,一个是有界缓存,一个是无界缓存,无界缓存不需要过期并且没有界限。在有界缓存中提供了三个过期API:
- expireAfterWrite:代表着写了之后多久过期。(上面列子就是这种方式)
- expireAfterAccess:代表着最后一次访问了之后多久过期。
- expireAfter:在expireAfter中需要自己实现Expiry接口,这个接口支持create,update,以及access了之后多久过期。注意这个API和前面两个API是互斥的。这里和前面两个API不同的是,需要你告诉缓存框架,他应该在具体的某个时间过期,也就是通过前面的重写create,update,以及access的方法,获取具体的过期时间。
caffeine 更新策略
何为更新策略?就是在设定多长时间后会自动刷新缓存。
Caffeine提供了refreshAfterWrite()方法来让我们进行写后多久更新策略:
LoadingCache<String, String> build = CacheBuilder.newBuilder().
refreshAfterWrite(1, TimeUnit.DAYS).build(new CacheLoader<String, String>() {
@OverridepublicString
load(String key) {
return "";
}
});
}
caffeine 打点监控
在Caffeine中提供了一些的打点监控策略,通过recordStats()Api
进行开启,默认是使用Caffeine自带的,也可以自己进行实现。在StatsCounter
接口中,定义了需要打点的方法目前来说有如下几个:
- recordHits:记录缓存命中
- recordMisses:记录缓存未命中
- recordLoadSuccess:记录加载成功(指的是CacheLoader加载成功)
- recordLoadFailure:记录加载失败
- recordEviction:记录淘汰数据
通过上面的监听,我们可以实时监控缓存当前的状态,以评估缓存的健康程度以及缓存命中率等,方便后续调整参数。
caffeine 淘汰监听
有很多时候我们需要知道Caffeine中的缓存为什么被淘汰了呢,从而进行一些优化?这个时候我们就需要一个监听器,代码如下所示:
Cache<String, String> cache = Caffeine.newBuilder().removaListener(((key, value, cause) -> {
System.out.println(cause);
})).build();
在Caffeine中被淘汰的原因有很多种:
- EXPLICIT: 这个原因是,用户造成的,通过调用remove方法从而进行删除。
- REPLACED: 更新的时候,其实相当于把老的value给删了。
- COLLECTED: 用于我们的垃圾收集器,也就是我们上面减少的软引用,弱引用。
- EXPIRED:过期淘汰。
- SIZE: 大小淘汰,当超过最大的时候就会进行淘汰。