互联网应用架构:专注编程教学,架构,JAVA,Python,微服务,机器学习等领域,欢迎关注,一起学习。
前言
现在可以说缓存是 Redis 横行的时代,无论什么项目一提到缓存就上 Redis,好比无论啥项目一来就是微服务架构。其实这些想法都无可否非,但是关键是如何因地制宜去处理现有的项目问题,主要是看应用的多大。
缓存如何选
技术方面:在面对一些分布式系统的时候,我们这时候由于需要作缓存,这时可以采用 redis 来作为缓存的中间存储地,如果面对一些简单的项目应用,我们可以考虑 Spring 官方推荐的 Caffeine(咖啡因)来实现。
项目方面:很多时候项目的开发进度都是非常赶的,如何在有限的时间做出当前最优化的选择,这是我们需要考虑的问题。
资源方面:很多公司在人力资源,技术资源,服务器资源等的沉淀并没有,进行架构设计的时候需要考虑这些因素进去,举个例子,只有 2 台服务器,4~5 个开发人员,项目比较紧,此时就要考虑人员的学习成本,服务器的资源成本,毕竟网络消耗也是需要我们考虑进去的。
官方推荐 Caffeine
在 Caffeine 出现之前,本地缓存经常使用 Google Guava 来实现,它提供了一个非常简便易用的本地缓存来实现,这个实现是基于 LRU 算法,同时支持多种缓存过期的策略。Caffeine 是在 Java8 里面,有人采用 Java8 对缓存进行重写的版本,可以理解为一个升级版,同样基于 LRU 算法并且支持多种缓存过期策略,在 SpringBoot2.0 的时代,官方把它作为默认的缓存工具。
在更早之前还有用 EhCache,这个也是非常优秀的进程内缓存框架,是 Hibernate 默认的集成工具。
Caffeine 驱逐策略
这里着重讲一下驱逐策略,笔者认为这一块会比较重要,主要涉及到缓存的命中问题,这才是缓存的根本所在。缓存中的驱逐策略主要是预测了哪些数据可以在短期之内被再次用到,因而增加了缓存的命中率,LRU(最少最近使用)算法是现在最流行的驱逐策略,但是这个算法有一定的确定,因为默认会认为最后到达的数据最有可能被访问,因此会授予它最高级别的优先,这对其他数据是不公平的,这个特点看起来有点像分类里面经常遇到的不平衡分类问题。
针对这种情况,Caffeine 也进行了改进,提供了三种驱逐策略
1、大小策略
Caffeine 针对大小策略提供了两种方式,一种是基于缓存大小,另外一种是基于权重。
注意:maximumSize 与 maximumWeight 这两个不能同时使用
// 基于缓存大小
Caffeine.maximumSize(long)
// 基于权重
Caffeine.maximumWeight(long)
复制代码
2、时间策略
时间策略主要有三个方法,
// 自缓存条目最后一次读取或写入时开始,如果超过了该方法设定的时长,标记条目过期
Caffeine.expireAfterAccess()
// 自缓存条目创建或最后一次写入的时间点开始,如果超过了该方法设定的时长,标记条目过期
Caffeine.expireAfterWrite()
// 在可变持续时间过去之后标记条目过期
Caffeine.expireAfter(Expiry)
复制代码
3、引用策略
在开始介绍引用策略之前,先来了解一下各个引用之间的区别,从表格我们
针对不同的引用,我们设置不同的策略进行驱逐。
自动化缓存
现在很多时候,项目总是与 MyBatis 进行整合对数据库进行操作,那我们现在针对 Caffeine 进行整合,让每次查询的时候都可以走缓存
引入需要的项目包
<!-- SpringBoot缓存组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- 本地缓存caffeine组件 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<!--mybatis整合 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
复制代码
配置 Caffeine
@Configuration
@EnableCaching
public class CaffeineConfig {
@Bean
public Cache<String, Object> caffeineCache() {
return Caffeine.newBuilder()
// 设置最后一次访问多长时间后过期
.expireAfterAccess(20, TimeUnit.SECONDS)
// 初始化缓存空间大小
.initialCapacity(2048)
// 初始化缓存最大条数
.maximumSize(2000)
.build();
}
}
复制代码
继承 mybatis 的 cache 并采用 Caffeine 进行实现
@SuppressWarnings("unchecked")
public class MybatisCaffeineCache implements org.apache.ibatis.cache.Cache{
@Autowired
private Cache<String, Object> caffeineCache;
// 读写锁
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
private final String id;
public MybatisCaffeineCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
if (caffeineCache == null) {
// MybatisRedisCache没有注入,采用手动获取caffeineCache对象
caffeineCache = (Cache<String, Object>) SpringUtils.getBean("caffeineCache");
}
caffeineCache.put(key.toString(), value);
}
@Override
public Object getObject(Object key) {
if (caffeineCache == null) {
// MybatisRedisCache没有注入,采用手动获取caffeineCache对象
caffeineCache = (Cache<String, Object>) SpringUtils.getBean("caffeineCache");
}
return caffeineCache.getIfPresent(key.toString());
}
@Override
public Object removeObject(Object key) {
if (caffeineCache == null) {
// MybatisRedisCache没有注入,采用手动获取caffeineCache对象
caffeineCache = (Cache<String, Object>) SpringUtils.getBean("caffeineCache");
}
return caffeineCache.asMap().remove(key.toString());
}
@Override
public void clear() {
if (caffeineCache == null) {
// MybatisRedisCache没有注入,采用手动获取caffeineCache对象
caffeineCache = (Cache<String, Object>) SpringUtils.getBean("caffeineCache");
}
caffeineCache.cleanUp();
}
@Override
public int getSize() {
if (caffeineCache == null) {
// MybatisRedisCache没有注入,采用手动获取caffeineCache对象
caffeineCache = (Cache<String, Object>) SpringUtils.getBean("caffeineCache");
}
final Long size = caffeineCache.estimatedSize();
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
}
复制代码
如何使用
1. 在Mapper类上加入以下信息
@CacheNamespace(implementation = MybatisCaffeineCache.class, eviction = MybatisCaffeineCache.class)
参考:
@Mapper
@CacheNamespace(implementation = MybatisCaffeineCache.class, eviction = MybatisCaffeineCache.class)
public interface IDataDictDataDao extends BaseMapper<DataDictData>
2. 在XML文件上加入缓存Mapper,避免缓存无效
<cache-ref namespace="xx.xxx.xxx.xxx.xx.IDataDictDataDao"/>
复制代码
这样子一个本地缓存就完成了。
缓存拓展
在使用 Caffeine 的时候虽然很简便,但是也存在一个问题就是分布式问题,那我们这里可以进行拓展,采用 redis 做二级缓存,同样的直接实现 mybatis 的 cache 类即可,与本地缓存类似,这里就不一一拓展开来,有兴趣的同学可以自行实验。
--END--
作者:@互联网应用架构
原创作品,抄袭必究
如需要源码或转发,关注后私信我
部分图片或代码来源网络,如侵权请联系删除,谢谢!
评论