深入解析:springcache缓存

缓存

本地缓存:一种将数据存储在应用程序本地内存中的技术,目的是减少对后端数据源(如数据库、远程服务)的访问次数,从而提高应用程序的性能和响应速度。本地缓存通常用于存储频繁访问且不经常变化的数据。
本地缓存的优势

  1. 提高性能:减少对后端数据源的访问次数,降低网络延迟,提高响应速度。
  2. 减轻后端压力:减少后端数据源的负载,提高系统的整体性能。
  3. 提高可用性:即使后端数据源不可用,缓存中的数据仍然可以提供服务。

本地缓存的局限性

  1. 数据一致性:缓存中的数据可能与后端数据源中的数据不一致,需要有效的缓存更新机制。
  2. 内存限制:本地缓存占用内存资源,需要合理管理缓存大小。
  3. 单点故障:本地缓存依赖于单个节点,如果节点故障,缓存中的数据会丢失

@EnableCaching:开启缓存注解功能,通常加在启动类上
@Cacheable:在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据,如果没有缓存数据,调用方法将方法返回值放到缓存中
@CachePut:将方法的返回值放到缓存
@CacheEvict:将一条或多条数据从缓存中删除

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

开启缓存功能

package com.itheima;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootConfiguration
@EnableCaching      // 开启缓存功能
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}

使用缓存

package com.itheima.service.impl;
import com.itheima.domain.User;
import com.itheima.service.UserService;
import org.springframework.cache.annotation.Cacheable;
public class UserServiceImpl implements UserService {
@Override
// 开启缓存,将查询到的数据放到id为key的缓存中,value = "cacheSpace"是缓存的位置
@Cacheable(value = "cacheSpace", key = "#id")
public User getById(Integer id) {
return bookDao.selectById(id);
}
}
package com.itheima.service.impl;
import com.itheima.domain.User;
import com.itheima.service.UserService;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
public class UserServiceImpl implements UserService {
@Override
// 开启缓存,将查询到的数据放到id为key的缓存中,value = "cacheSpace"是缓存的位置(有读取的功能)
@Cacheable(value = "cacheSpace", key = "#id")
@CachePut(value = "cacheSpace", key = "#id")        // 向缓存中放入缓存中
public User setById(Integer id) {
return User.selectById(id);
}
public User getById(Integer id) {
return User.selectById(id);
}
//查看缓存中是否有id为键的内容,如果有返回查到的内容,没有返回null
@Cacheable(value = "cacheSpace", key = "#id")
public String get(Integer id) {
return null;
}
}

缓存穿透

缓存穿透是指客户端请求数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库
理解:当一个缓存和数据库中都不存在的数据请求多次被提交,这些请求查缓存失败都会进行查数据库。
解决方法:

  1. 缓存空对象,将不存在的请求缓存成一个null
    优点:实现简单,维护方便
    缺点:
    可能造成短期的不一致
    会产生额外的内存消耗
  2. 布隆过滤
    在这里插入图片描述
    优点:内存占用较少,没有多余key
    缺点:
    实现复杂
    存在误判的可能

缓存雪崩

缓存雪崩:在同一时段大量的缓存key同时失效,或者redis服务当即,导致大量请求到达数据库带来巨大压力
解决方法:

  1. 给不同的Key的TTL添加随机值
  2. 利用Redis集群提高服务的可用性
  3. 给缓存业务添加降级限流策略
  4. 给业务添加多级缓存

缓存击穿

缓存击穿问题(热点key问题):一个被高并发访问并缓存重建业务交复杂的key突然失效,无数的请求访问会在瞬间给数据库带来巨大的冲击
问题:在缓存失效后,很多用户访问缓存失败,查询数据库,并进行缓存重建(只需一个用户进行缓存重建即可)
解决方案:

  1. 互斥锁
  2. 逻辑过期

在这里插入图片描述

互斥锁:
优点:

  1. 没有额外的内存消耗
  2. 保证一致性
  3. 实现简单

缺点:

  1. 线程需要等待,性能受影响
  2. 可能出现死锁

逻辑过期:将线程设置成永远有效,但是设置一个逻辑上的有效期,如果缓存过期了,则先用原来已经过期的缓存用着
优点:线程无需等待,性能较好
缺点:

  1. 不保证一致性
  2. 有额外内存消耗
  3. 实现复杂

互斥锁解决缓存击穿问题
在这里插入图片描述
可以通过redis中使用setnx来实现互斥锁
setnx可以对一个变量进行赋值,且只能进行赋值操作和删除操作,上锁就对这个值赋值,释放互斥锁只需要删除这个键值对
setnx进行获取锁

private boolean tryLock(String key) {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
return Boolean.TRUE.equals(flag);
}

释放锁

private void unlock(String key) {
stringRedisTemplate.delete(key);
}

逻辑过期解决缓存击穿问题
在这里插入图片描述

缓存更新策略

在这里插入图片描述
低一致性需求:使用内存淘汰机制。例如店铺类型的查询缓存
高一致性需求:主动更新,并以超时剔除作为兜底方案。例如店铺详情查询的缓存

好的,图中的内容是关于操作缓存和数据库时需要考虑的三个主要问题:

操作缓存和数据库时有三个问题需要考虑:

  1. 删除缓存还是更新缓存?
    更新缓存: 每次更新数据库都更新缓存,无效写操作较多。
    删除缓存: 更新数据库时让缓存失效,查询时再更新缓存。(更优)

  2. 如何保证缓存与数据库的操作的同时成功或失败?
    单体系统: 将缓存与数据库操作放在一个事务。
    分布式系统: 利用TCC等分布式事务方案。

  3. 先操作缓存还是先操作数据库?
    先删除缓存,再操作数据库。
    先操作数据库,再删除缓存。(更优)

posted @ 2025-12-21 17:27  gccbuaa  阅读(0)  评论(0)    收藏  举报