品味Spring Cache设计之美(三)
4 自定义二级缓存
4.1 应用场景
笔者曾经在原来的项目,高并发场景下多次使用多级缓存。多级缓存是一个非常有趣的功能点,值得我们去扩展。
多级缓存有如下优势:
1.离用户越近,速度越快;
2.减少分布式缓存查询频率,降低序列化和反序列化的CPU消耗;
3.大幅度减少网络IO以及带宽消耗。
进程内缓存做为一级缓存,分布式缓存做为二级缓存,首先从一级缓存中查询,若能查询到数据则直接返回,否则从二级缓存中查询,若二级缓存中可以查询到数据,则回填到一级缓存中,并返回数据。若二级缓存也查询不到,则从数据源中查询,将结果分别回填到一级缓存,二级缓存中。
来自《凤凰架构》缓存篇
Spring Cache并没有二级缓存的实现,我们可以实现一个简易的二级缓存DEMO,加深对技术的理解。
4.2 设计思路
1.MultiLevelCacheManager:多级缓存管理器;
2.MultiLevelChannel:封装Caffeine和RedissonClient;
3.MultiLevelCache:实现org.springframework.cache.Cache接口;
4.MultiLevelCacheConfig:配置缓存过期时间等;
MultiLevelCacheManager是最核心的类,需要实现getCache和getCacheNames两个接口。
创建多级缓存,第一级缓存是:Caffeine , 第二级缓存是:Redisson。二级缓存,为了快速完成DEMO,我们使用Redisson对Spring Cache的扩展类RedissonCache 。它的底层是RMap,底层存储是Hash。我们重点看下缓存的「查询」和「存储」的方法:
@Override
public ValueWrapper get(Object key) {
Object result = getRawResult(key);
return toValueWrapper(result);
}
public Object getRawResult(Object key) {
logger.info("从一级缓存查询key:" + key);
Object result = localCache.getIfPresent(key);
if (result != null) {
return result;
}
logger.info("从二级缓存查询key:" + key);
result = RedissonCache.getNativeCache().get(key);
if (result != null) {
localCache.put(key, result);
}
return result;
}
「查询」数据的流程:
1.先从本地缓存中查询数据,若能查询到,直接返回;
2.本地缓存查询不到数据,查询分布式缓存,若可以查询出来,回填到本地缓存,并返回;
3.若分布式缓存查询不到数据,则默认会执行被注解的方法。
下面来看下「存储」的代码:
public void put(Object key, Object value) {
logger.info("写入一级缓存 key:" + key);
localCache.put(key, value);
logger.info("写入二级缓存 key:" + key);
RedissonCache.put(key, value);
}
最后配置缓存管理器,原有的业务代码不变。执行下getUserById方法,查询用户编号为1的用户信息。
- 从一级缓存查询key:1
- 从二级缓存查询key:1
- ==> Preparing: select * FROM user t where t.id = ?
- ==> Parameters: 1(Long)
- <== Total: 1
- 写入一级缓存 key:1
- 写入二级缓存 key:1
第二次执行相同的动作,从日志可用看到从优先会从本地内存中查询出结果。
- 从一级缓存查询key:1
等待30s , 再执行一次,因为本地缓存会失效,所以执行的时候会查询二级缓存
- 从一级缓存查询key:1
- 从二级缓存查询key:1
一个简易的二级缓存就组装完了。
5 什么场景选择Spring Cache
在做技术选型的时候,需要针对场景选择不同的技术。
笔者认为Spring Cache的功能很强大,设计也非常优雅。特别适合缓存控制没有那么细致的场景。比如门户首页,偏静态展示页面,榜单等等。这些场景的特点是对数据实时性没有那么严格的要求,只需要将数据源缓存下来,过期之后自动刷新即可。这些场景下,Spring Cache就是神器,能大幅度提升研发效率。
但在高并发大数据量的场景下,精细的缓存颗粒度的控制上,还是需要做功能扩展。
1.多级缓存;
2.列表缓存;
3.缓存变更监听器;
笔者也在思考这几点的过程,研读了 j2cache , jetcache相关源码,受益匪浅。它们的设计思想很多可以用于扩展Spring Cache。后续的文章会重点分享下笔者的心得。