一、缓存问题与解决
缓存穿透
缓存穿透是指查询缓存和DB中都不存在的数据
。
通过id查询,id一般大于0,攻击者会故意传id为-1去查询,由于缓存是不命中则从DB中获取数据,这将会导致每次缓存都不命中数据导致每个请求都访问DB,造成缓存穿透
缓存穿透示例:
public Station findProjectStation(Long stationId) {
//从缓存中查询
Station station = (Station)redisTemplate.boundHashOps("project_station").get(stationId);
if(station==null){
//缓存中没有,从数据库查询
Station st = stationMapper.selectByPrimaryKey(stationId);
if(st!=null){
station = st;
redisTemplate.boundHashOps("project_station").put(stationId,station);
}
}
return station;
}
解决方案:
1.接口层增加校验,如鉴权校验
2.id做校验,id<=0的直接拦截;
3.从缓存取不到的数据,在数据库中也没有取到,就将key-value对写为key-空对象。以此防止攻击者反复用同一个id暴力攻击。
4. 使用缓存预热,缓存预热就是将数据提前加入到缓存中,当数据发生变更,再将最新的数据更新到缓存。
5. 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
6. 利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回
7. 设置短的过期时间
解决缓存穿透示例:
public Station findProjectStation(Long stationId) {
//从缓存中查询
Station station = (Station)redisTemplate.boundHashOps("project_station").get(stationId);
if(station==null){
//缓存中没有,从数据库查询
Station st = stationMapper.selectByPrimaryKey(stationId);
if(st!=null){
station = st;
redisTemplate.boundHashOps("project_station").put(stationId,station);
}else {
redisTemplate.boundHashOps("project_station").put(stationId,new Station());
// 随机时间
redisTemplate.boundHashOps("project_station").expire(60, TimeUnit.SECONDS);
}
}
return station;
}
缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据
。
由于某个时刻并发用户量非常大,同时读缓存没读到数据,又同时去数据库获取数据,引起数据库压力瞬间增大,造成过大压力。
可能产生缓存击穿示例:
public List<Station> findStationList() {
//从缓存中查询
List<Station> stationList = (List<Station>)redisTemplate.boundValueOps("station_list").get();;
if(stationList==null){
//缓存中没有,从数据库查询
Example example=new Example(Station.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("type","1");
List<Station> findStationList = stationMapper.selectByExample(example);
if(findStationList!=null){
stationList = findStationList;
redisTemplate.boundValueOps("station_list").set(stationList);
}
}
return stationList;
}
解决方案:
1.设置热点数据永远不过期。
2.缓存预热
3.数据不设置过期时间,在缓存的对象上添加一个属性标识过期时间,每次获取到数据时,校验对象中的过期时间属性,如果数据即将过期,则异步发起一个线程主动更新缓存中的数据,当然也可能拿到过期的值,看具体需求。
4.加锁。本地锁与分布式锁。
解决示例:
public List<Station> findStationList() {
//从缓存中查询
List<Station> stationList = (List<Station>) redisTemplate.boundValueOps("station_list").get();
;
if (stationList == null) {
//缓存中没有,加锁从数据库查询
synchronized (this) {
Example example = new Example(Station.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("type", "1");
List<Station> findStationList = stationMapper.selectByExample(example);
if (findStationList != null) {
stationList = findStationList;
redisTemplate.boundValueOps("station_list").set(stationList);
}
}
}
return stationList;
}
缓存雪崩
缓存雪崩是指缓存数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至宕机
。
缓存中如果大量缓存在一段时间内集中过期了,这时候会发生大量的缓存击穿现象,所有的请求都落在了DB上,由于查询数据量巨大,引起DB压力过大甚至导致DB宕机
解决方案:
1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。如果Redis是集群部署,将热点数据均匀分布在不同的Redis库中也能避免全部失效的问题
2.设置热点数据永远不过期。
3.使用缓存预热
4.使用互斥锁,但是该方案将导致吞吐量明显下降
三者区别
缓存穿透:通常请求携带有参数,不断发起请求。
缓存击穿:通常是某个时刻并发大量请求,并发查询同一条数据。
缓存雪崩:缓存不同的数据大批量到过期时间,很多数据都查不到从而查数据库。
缓存预热
缓存预热是将数据提前加入到缓存中,当数据发生变更,再将最新的数据更新到缓存。
实现缓存预热方法:
创建RedisInit 类,实现InitializingBean接口并重写afterPropertiesSet方法,然后编写逻辑,启动项目时就会自动执行相应逻辑。
import org.springframework.beans.factory.InitializingBean;
@Component
public class RedisInit implements InitializingBean {
@Autowired
private IStationService stationService;
/**
* 缓存预热
*
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
stationService.saveStationListToRedis();
}
}
二、缓存淘汰机制
Redis可以对存储在Redis中的缓存数据设置过期时间,但是并非key过期时间到了就一定会被Redis给删除。
Redis可以设置最大缓存即最大内存,当内存已使用率到达,则开始清理缓存
maxmemory <bytes> :设置最大内存 maxmemory 500mb
删除策略
设置了expire的key缓存过期了,但是服务器的内存还是会被占用,这是因为redis是基于删除策略进行删除
Redis删除策略主要有三种:
1.定期删除
Redis默认是每隔 100ms 就随机抽取一些设置了过期时间的 Key,检查其是否过期,如果过期就删除。
2.惰性删除
定期删除由于是随机抽取可能会导致很多过期 Key 到了过期时间并没有被删除。在从缓存获取数据的时候,redis会检查这个key是否过期了,如果过期就删除这个key。也就是说在查询的时候将过期key从缓存中清除。
3.主动删除
当内存满了,依据配置的淘汰策略进行删除
内存淘汰机制
仅使用定期删除 + 惰性删除机制存在一个严重的隐患:如果定期删除留下了很多已经过期的key,并且长时间都没有使用过这些过期key,会导致过期key无法被惰性删除,从而导致过期key一直堆积在内存里,最终造成Redis内存块被消耗殆尽。
Redis内存淘汰机制应运而生。Redis内存淘汰机制提供了八种不同的内存淘汰策略,4.0前有6种。
volatile-lru : 从已设置过期时间的缓存中,淘汰最近最少使用的数据,推荐使用策略
volatile-ttl: 在设置了过期时间的缓存中,挑选将要过期的数据,直接淘汰
volatile-random: 从已设置过期时间的缓存中,随机淘汰数据
allkeys-lru: 所有数据,淘汰最近最少使用的,即清除最少用的旧缓存,然后保存新的缓存,推荐使用
allkeys-random: 在所有的缓存中随机删除,不推荐
noeviction: 旧缓存永不过期,新缓存设置不了,不淘汰,内存满了返回错误,默认策略
volatile-lfu: 从已设置过期时间的缓存中,淘汰使用频率最低的数据
allkeys-lfu: 所有数据,淘汰使用频率最低的数据
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/137045.html