缓存穿透
缓存穿透是指缓存和数据库中都没有数据,而用户不断发起请求这些请求会穿透直接访问数据库,如果发起id为-1或者id特别大的数据,就可以利用这个漏洞,对数据库造成压力。甚至压垮数据库
解决方案:
设置缓存空对象:创建空对象并且将其缓存起来,同时设置一个过期时间(避免控制占用更多的储存空间),之后在访问这个数据得时候,则会从缓存中获取,这样保护了后端的数据源
代码示例
先从redis中查,查不到在从mysql查,mysql查不到,返回一个空对象存入redis并设置缓存时间,然后返回,以后这个请求来都访问redis中的空对象。
@Override
public TbItem selectItemInfo(Long itemId) {
//查询缓存
TbItem tbItem = (TbItem) redisClient.get(ITEM_INFO + ":" + itemId + ":"+ BASE);
if(tbItem!=null){
return tbItem;
}
tbItem = tbItemMapper.selectByPrimaryKey(itemId);
/********************解决缓存穿透************************/
if(tbItem == null){
//把空对象保存到缓存
redisClient.set(ITEM_INFO + ":" + itemId + ":"+ BASE,new TbItem());
//设置缓存的有效期
redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ BASE,30);
return tbItem;
}
//把数据保存到缓存
redisClient.set(ITEM_INFO + ":" + itemId + ":"+ BASE,tbItem);
//设置缓存的有效期
redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ BASE,ITEM_INFO_EXPIRE);
return tbItem;
}
/**
* 根据商品 ID 查询商品介绍
* @param itemId
* @return
*/
@Override
public TbItemDesc selectItemDescByItemId(Long itemId) {
//查询缓存
TbItemDesc tbItemDesc = (TbItemDesc) redisClient.get(
ITEM_INFO + ":" + itemId + ":"+ DESC);
if(tbItemDesc!=null){
return tbItemDesc;
}
TbItemDescExample example = new TbItemDescExample();
TbItemDescExample.Criteria criteria = example.createCriteria();
criteria.andItemIdEqualTo(itemId);
List<TbItemDesc> itemDescList =
this.tbItemDescMapper.selectByExampleWithBLOBs(example);
if(itemDescList!=null && itemDescList.size()>0){
//把数据保存到缓存
redisClient.set(ITEM_INFO + ":" + itemId + ":"+ DESC,itemDescList.get(0));
//设置缓存的有效期
redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ DESC,ITEM_INFO_EXPIRE);
return itemDescList.get(0);
}
/********************解决缓存穿透************************/
//把空对象保存到缓存
redisClient.set(ITEM_INFO + ":" + itemId + ":"+ DESC,new TbItemDesc());
//设置缓存的有效期
redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ DESC,30);
return null;
}
缓存击穿
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对一个key不停的进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,然后直接请求数据库了,有点像水滴石穿,主要特点就是大量请求并且持续,就像在一个屏障上开了一个洞。
解决方案
1.使用Redis分布式锁
2.设置热点数据在redis中永远不过期
基于redis分布式锁实现的思路是:当大量请求访问一个key时,使用该分布式锁让大量中请求中的一个请求获取该锁独占该key,然后这个请求执行业务,执行业务后释放锁,如果不释放会可能会由于业务处理失败,导致redis没有数据而死锁,导致其他的请求无限的等待。然后没有获得该锁的请求等待该业务处理后回调自身。
实例代码
public TbItemDesc selectItemDescByItemId(Long itemId) {
TbItemDesc tbItemDesc = (TbItemDesc) redisClient.get(ITEM_INFO+":"+itemId+":"+DESC);
if(tbItemDesc != null){
return tbItemDesc;
}
/******************解决缓存击穿问题*******************/
if(redisClient.setnx(SETNX_LOCK_DESC+":"+itemId, itemId)) {
try {
tbItemDesc = tbItemDescMapper.selectByPrimaryKey(itemId);
/*********解决缓存穿透问题*******/
if (tbItemDesc == null) {
tbItemDesc = new TbItemDesc();
redisClient.set(ITEM_INFO + ":" + itemId + ":" + DESC, tbItemDesc);
redisClient.expire(ITEM_INFO + ":" + itemId + ":" + DESC, 30);
return tbItemDesc;
}
redisClient.set(ITEM_INFO + ":" + itemId + ":" + DESC, tbItemDesc);
redisClient.expire(ITEM_INFO + ":" + itemId + ":" + DESC, ITEM_INFO_EXPIRE);
}finally {
//释放分布式锁
redisClient.del(SETNX_LOCK_DESC+":"+itemId);
}
return tbItemDesc;
}else{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return selectItemDescByItemId(itemId);
}
}
上述代码意思就是首先查询redis中的key值是否存在,如果不存在,在多个请求中,使用分布式锁锁到一个请求处理业务,然后释放锁,同时这个请求在处理业务的时候,也有其他请求进来,但是无法执行业务代码(因为当前锁被占有,进入else中睡眠一秒),等待被锁请求执行完业务,然后回调方法,此时业务执行完redis中有数据,直接返回数据**。只有是redis中该key不存在,才使用分布式锁,从mysql中查询出来然后缓存到redis返回**
缓存雪崩
缓存雪崩。指的是在某一时间端,缓存集中过期失效
解决方案:
缓存数据的过期时间设置随机或不同分类商品缓存不同周期或热门类目的商品缓存永不过期