本文目的:囊括介绍市面通行的缓存方案,为选型做准备。具体集成在文末有推介阅读
缓存是什么?缓存是为了减轻数据库的压力而存在。
比如查询某个数据时,首先从缓存中查找,缓存中没有,才会从数据库中查询,也就一定程度减轻了数据库的压力
/**
* 这个例子仅仅作为演示,市面上的缓存框架通常都提供通过注解的方式来创建/获取/更新缓存的数据,很方便
**/
puulic UserEntity getUserInfo(){
// 第一级本地缓存: 从缓存查询
UserEntity user = CacheService.use('user','getUser','123456')
if(user==null){
// 第二级远程缓存: 从Redis查询
user = redisService.get("userId","123456")
if(user==null){
// 缓存中都没数据: 从数据库查询
user = UserService.getByid("123456")
}
}
return user;
}
缓存又分为两种,JVM虚拟机(本地)缓存和远程缓存.其实就是根据
数据存储位置
进行分类。
本地缓存
远程缓存
springboot 中默认提供了以通用的缓存接口,因为JAVA语言缓存是有协议规范的,市面上的缓存框架都是根据通行协议编写,所以spring中可以很方便切换具体的缓存实现。
通常的缓存方案都是: 本地缓存 + Redis(远程)缓存 +数据库
本地缓存仅仅介绍一具自实现的具体实现访问,其他方案仅介绍特性:
缓存的本质就是存储在内存中的KV数据结构,对应的就是jdk中的HashMap,但是要实现缓存,还需要考虑并发安全性、容量限制等策略,下面简单介绍一种利用LinkedHashMap实现缓存的方式。
LinkedHashMap维持了一个链表结构,用来存储节点的插入顺序或者访问顺序(二选一),并且内部封装了一些业务逻辑,只需要覆盖removeEldestEntry方法,便可以实现缓存的LRU淘汰策略。
此外我们利用读写锁,保障缓存的并发安全性。需要注意的是,这个示例并不支持过期时间淘汰的策略。
自实现缓存的方式,优点是实现简单,不需要引入第三方包,比较适合一些简单的业务场景。对于比较复杂的场景,建议使用比较稳定的开源工具。
public class LRUCache extends LinkedHashMap {
/**
* 可重入读写锁,保证并发读写安全性
*/
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private Lock readLock = readWriteLock.readLock();
private Lock writeLock = readWriteLock.writeLock();
/**
* 缓存大小限制
*/
private int maxSize;
public LRUCache(int maxSize) {
super(maxSize + 1, 1.0f, true);
this.maxSize = maxSize;
}
@Override
public Object get(Object key) {
readLock.lock();
try {
return super.get(key);
} finally {
readLock.unlock();
}
}
@Override
public Object put(Object key, Object value) {
writeLock.lock();
try {
return super.put(key, value);
} finally {
writeLock.unlock();
}
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return this.size() > maxSize;
}
}
基于Guava Cache实现本地缓存。Guava是Google团队开源的一款 Java 核心增强库
特性:
<dependency>
<groupId>com.google.guavagroupId>
<artifactId>guavaartifactId>
<version>30.1.1-jreversion>
dependency>
Caffeine是以 GuavaCache 为原型而开发的一个本地缓存框架, 相对GuavaCache, 它有更高的性能与命中率, 更强大的功能, 更灵活的配置方式
特性:
EhCache是一个轻量级开源的缓存框架, Hibernate使用的默认缓存框架就是EhCache, 它支持多种缓存模型, 将缓存管理在 堆内 / 堆外 / 磁盘 / 远程 多地.
特性:
JetCache 是阿里开源的通用缓存访问框架, 它统一了多级缓存的访问方式, 封装了类似于SpringCache的注解, 以及GuavaCache类似的Builder, 来简化项目中使用缓存的难度
特性:
上面介绍了几种本地缓存方案,选哪种你完全可以根据自己的熟练度来,如果都不熟悉就是用默认spring cache即可。
但其实在工作中,很多人偷懒,本地缓存也不加,只添加redis来做一级远程缓存,如果redis中没有,那么再从数据库中查询。
因为使用了缓存技术后,通常都涉及一个问题:缓存一致性
.
这个问题很头疼,涉及到你对系统架构的理解,以及根据架构去选择合适的一致性方案。所以通常能少上一点缓存手段,对于开发就能少一点事儿.
如何解决这个问题,推介阅读文末文章。
介绍一下 Encache 的注解使用方式
@Cacheable
:启用缓存,首先从缓存中查找数据,如果存在,则从缓存读取数据;如果不存在,则执行方法,并将方法返回值添加到缓存@CachePut
:更新缓存,如果 condition 计算结果为 true,则将方法返回值添加到缓存中@CacheEvict
:删除缓存,根据 value 与 key 字段计算缓存地址,将缓存数据删除
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
@Override
@Cacheable(value = "user", key = "#userId")
public User findById(Integer userId) {
return userDAO.findById(userId);
}
@Override
@CachePut(value = "user", key = "#user.id", condition = "#user.id != null")
public User save(User user) {
user.setUpdateTime(new Date());
userDAO.save(user);
return userDAO.findById(user.getId());
}
@Override
@CacheEvict(value = "user", key = "#userId")
public boolean deleteById(Integer userId) {
return userDAO.deleteById(userId);
}
@Override
public List<User> findAll() {
return userDAO.findAll();
}
}
------ 如果文章对你有用,感谢右上角 >>>点赞 | 收藏 <<<