Redis - 缓存穿透、缓存击穿、缓存雪崩

缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为小于0的数据或id为特别大等不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。举例代码:

/**
 * 通过分类id查询广告集合
 * @param categoryId 广告分类id
 * @return 广告集合
 */
public List<Content> findContentById(Long contentCatId){
	// 首先根据分类id到Redis中去获取数据 - Constants.CONTENT_LIST_REDIS为Key
	List<Content> contentList = (List<Content>) redisTemplate.boundHashOps(Constants.CONTENT_LIST_REDIS).get(contentCatId);
	if(contentList == null){
		// 如果没有,就去数据库查询
		List<Content> contentList = contentDao.findContentById(categoryId);
		// 然后再存一份到Redis
		redisTemplate.boundHashOps(Constants.CONTENT_LIST_REDIS).put(contentCatId,contentList);
	}
	return contentList;
}

上面代码就存在缓存穿透的问题,如果用户查询的分类id为-8,Redis中不存在这个数据,那么它就会去MySQL数据库中查询。MySQL里面也不存在这个数据,所以返回的结果是null。然后把这个null以key-value存入到Redis中。即Redis中存一个null值,Redis是不会存储null值的。因此,下一次从Redis中查询依旧是null值,则根据代码逻辑又会去查MySQL,形成一个无限循环,就会导致MySQL压力巨大,这就是缓存穿透问题。

解决方案:

①在接口层增加校验,如对用户进行鉴权校验(是否能访问这个接口),对查询的id做基础校验,如id<=0,直接进行拦截。但这种方法遗漏一个问题,那就是id值过大(大到数据库中依旧不存在),我们没办法对id值过大进行校验。

②在上面方法进行改进,当MySQL中也查询不到的时候,我们就往Redis中存一个不为null的值,这样可以防止攻击用户反复用同一个id进行暴力攻击。这种方法依旧存在问题,那就是如果攻击用户每次攻击的id递减或是递增,每次的id不同,还是会去访问MySQL,然后还会使Redis中存储的数据飞涨。

/**
 * 通过分类id查询广告集合
 * @param categoryId 广告分类id
 * @return 广告集合
 */
public List<Content> findContentById(Long contentCatId){
	// 首先根据分类id到Redis中去获取数据
	List<Content> contentList = (List<Content>) redisTemplate.boundHashOps("content_list").get(contentCatId);
	if(contentList == null){
		// 如果没有,就去数据库查询
		contentList = contentDao.findContentById(categoryId);
		if(contentLIST == null){
			// 如果MySQL中依旧不存在,则创建一个空的集合
			contentList = new ArrayList<>();
		}
		// 然后再存一份到Redis
		redisTemplate.boundHashOps("content_list").put(contentCatId,contentList);
	}
	return contentList;
}

缓存预热,缓存预热就是将数据提前加入到缓存中,当数据发生变更,再将最新的数据更新到缓存。即我们在Redis中查询不到的时候也不再访问MySQL数据库去查询了,Redis中没有,那么MySQL中必定也没有。

缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据。这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

缓存穿透和缓存击穿的区别:

1、一般缓存穿透是由恶意攻击造成的,而缓存击穿则是由Redis中不存在数据,数据库中存在,且在某一时刻并发量较大造成。

2、一般缓存穿透的方法都会传一个id之类的值过来,而缓存击穿的方法不会传参数过来。

举例代码:

扫描二维码关注公众号,回复: 10390264 查看本文章
/**
 * 查询所有菜单集合(导航栏数据)
 * @return 菜单集合
 */
public List<String> findCatTreeName(){
	// 从Redis中查询所有菜单名
	List<String> catTreeList = redisTemplate.boundValueOps("categoryTree").get();
	// 如果查询结果为null
	if(catTreeList == null){
		// 从数据库中查询
		catTreeList = categoryDao.findAllCatTree();
		// 然后存入Redis,并设置一个过期时间60天
		redisTemplate.boundValueOps("categoryTree").set(catTreeList,60,TimeUnit.DAYS);
	}
	return catTreeList;
}

上面代码就存在缓存击穿问题,而这个缓存击穿问题往往就是因为过期时间造成的。当Redis中存的数据过期之后,在某一刻,10000个人同时访问这个方法,由于Redis中不存在数据,则这10000个人都会去访问MySQL数据库,导致数据库压力巨大。

解决方案:

1、设置数据永不过期

2、缓存预热

缓存雪崩

缓存雪崩是指缓存数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至宕机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案:

1、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

2、设置数据永不过期

3、缓存预热

发布了100 篇原创文章 · 获赞 25 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_40885085/article/details/104444662