缓存(Cache)是数据交换过程中的缓冲区,通常被称为数据缓冲区。它通常从数据库中获取数据,并将其存储在本地代码中,以提高访问速度和系统性能。对于那些被频繁访问的热点数据,缓存可以显著减少对后端数据库的访问压力,从而降低响应时间(例如,数据库如 MySQL 通常存储在磁盘中,而 Redis 则存储在内存中),提升系统的整体吞吐能力和稳定性。本文介绍了两种redis缓存的实现方式,一种是直接手动操作redis,另一种是使用SpringCache。
一、用 Redis 作缓存的一个简易流程如下
1.1 数据读取流程(查询缓存)
- 客户端发起请求:用户通过客户端向应用服务器发送数据请求
- 缓存检查:应用服务器首先检查Redis缓存中是否存在所需数据
- 缓存命中路径:
- 如果缓存中有数据(缓存命中)
- 直接从Redis获取数据
- 立即返回缓存数据给客户端
- 缓存未命中路径:
- 如果缓存中没有数据(缓存未命中)
- 查询数据库获取原始数据
- 将查询到的数据写入Redis缓存
- 为缓存数据设置过期时间(可选,一般像是验证码之类的就会设置)
- 返回数据库数据给客户端
1.2 数据更新流程
- 数据更新请求:当需要修改数据时,首先更新数据库
- 缓存同步策略:更新数据库后需要处理缓存一致性,这里提供两种简单策略:
- 删除策略:直接删除Redis中的相关缓存,下次查询时重新加载
- 更新策略:同时更新Redis缓存中的数据,保持数据同步
- 完成更新:无论采用哪种策略,最终完成数据更新操作
1.3 用 Redis 作缓存的一个简易流程图如下所示
二、实现
以springboot项目为例(该springboot就包含简单的User增删改查),代码目录结构如下:
数据库User表结构图如下所示
2.1 手动用Redis实现(采用删除策略)
2.1.1 查询方法。先查缓存,命中直接返回,未命中就查数据库,然后将数据放入缓存再返回。
2.1.2 新增用户、删除用户、更新用户信息,需要删除缓存。
2.1.3 验证效果
一开始没有缓存
查询了ID为1的用户,缓存更新了
获取所有用户信息后的缓存情况
新增用户后,list缓存被删除了
更新ID为1的用户信息后,缓存里原key为1的缓存被删除了
2.2 使用SpringCache
**@Cacheable **方法执行前先检查缓存,如果缓存中有数据则直接返回,否则执行方法并将结果缓存。
// 根据ID查询用户,缓存key为用户ID
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
return userMapper.selectById(id);
}
// 查询所有用户,缓存key为固定字符串
@Cacheable(value = "users", key = "'all'")
public List<User> getAllUsers() {
return userMapper.selectList(null);
}
@CacheEvict 方法执行后删除指定的缓存数据。
// 删除指定用户的缓存
@CacheEvict(value = "users", key = "#id")
public boolean deleteUser(Long id) {
return userMapper.deleteById(id) > 0;
}
// 删除所有用户相关缓存
@CacheEvict(value = "users", allEntries = true)
public void clearAllCache() {
// 清空所有缓存
}
// 删除多个缓存
@Caching(evict = {
@CacheEvict(value = "users", key = "#user.id"),
@CacheEvict(value = "users", key = "'all'")
})
public boolean updateUser(User user) {
return userMapper.updateById(user) > 0;
}
@CachePut 方法每次都会执行,并将结果更新到缓存中。
// 更新用户信息,同时更新缓存
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
userMapper.updateById(user);
return user; // 返回值会更新到缓存中
}
// 创建用户并缓存
@CachePut(value = "users", key = "#result.id")
public User createUser(User user) {
userMapper.insert(user);
return user;
}
@Caching 可以同时使用多个缓存注解,实现复杂的缓存操作。
// 组合使用:更新用户时,既要更新单个用户缓存,又要删除列表缓存
@Caching(
put = @CachePut(value = "users", key = "#user.id"),
evict = @CacheEvict(value = "users", key = "'all'")
)
public User updateUserAndRefreshCache(User user) {
userMapper.updateById(user);
return user;
}
// 删除用户时,删除多个相关缓存
@Caching(evict = {
@CacheEvict(value = "users", key = "#id"),
@CacheEvict(value = "users", key = "'all'"),
@CacheEvict(value = "userDetails", key = "#id")
})
public boolean deleteUserWithDetails(Long id) {
return userMapper.deleteById(id) > 0;
}
三、总结
springcache适合一些简单的缓存操作,比较方便,但是粒度相对比较粗;而手动实现的话,可以做到更细粒度,比如要针对删除符合某些复杂条件的缓存springcache就不好弄了,手动实现的却可以。
相关代码放在了https://gitee.com/tzm-hua/redis-cache中,其中manual分支为手动实现redis,springcache分支为使用springcache。