目录
一、缓存是什么?
二、缓存穿透
1.缓存穿透是什么
2.解决方案
三、缓存击穿
缓存击穿是什么
2.解决方案
四、缓存雪崩
1.什么是缓存雪崩
2.解决方式
五、使用redis实现分布式锁
总结
一、缓存是什么?
缓存是数据交换的缓冲区,是储存数据的临时的读写性能高的场所。
通俗的来说,数据也就是前端与数据库交互的时候中间的缓冲区。我们可以将一些经常被从数据库中查询的数据存放到缓存中,从而来减轻数据库的访问压力
我们经常使用redis作为缓存数据库使用,但是缓存虽好,但是在实际开发中,会出现各种各样的问题。最经常需要解决的问题就是:缓存穿透,缓存击穿,缓存雪崩。
二、缓存穿透
1.缓存穿透是什么
缓存穿透是指在企业开发中,可能会收到黑客的攻击,其中一种攻击手段就是,重复查询redis以及数据库中没有的值。当很多线程以及并发做这样的查询的时候,会造成数据库压力过大,造成数据库瘫痪。
如下图所示,用户重复访问一个不存在的key,redis中没有查询到也就频繁的访问数据库。如果是不怀好意的使用一些压力测试工具,短时间内做成百上千次这个动作很可能会导致我们数据库瘫痪。
2.解决方案
1.缓存空对象
在用户访问了一个不存在的key后,我们从数据库中访问也没有查询到值,就往缓存中存入一个空值,并设置较短过期时间。这样可以极大程度避免缓存穿透现象
2.使用布隆过滤器
布隆过滤器会将数据库中的值以二进制加密保存,并且在用户访问redis之前进行过滤,如果发现不存在的数据则会直接拦截请求。缺点是布隆过滤器的命中率比较低,依然有可能放过很多不合法请求进入redis中。
三、缓存击穿
缓存击穿是什么
缓存击穿经常发生在一个热门的key上,也就是这个key被经常访问。当这个key的定时时间到了,或者这个key突然在redis中消失了。这个时候外部极大的访问压力一下子进入MySQL中,大概率导致数据库直接瘫痪。
例如:在双11活动中,某个商品十分火热很多用户查询这个商品,如果突然这个商品在redis中的缓存消失了会导致大量请求直接到数据库中,导致数据库瘫痪
2.解决方案
1.进行数据的预热
在热点事件来临之前,预先存入数据到redis中,并且设置长过期时间
2.实时监控
人为监控哪些数据访问量正在加速上升。实时调整过期时间
3.使用分布式锁
当缓存时效的时候,及时使用redis中的setnx设置一个锁,只有得到锁的线程才能进行访问操作。这样就避免了高并发场景下请求涌入数据库的事件发生。在下文会详细说明分布式锁的使用
四、缓存雪崩
1.什么是缓存雪崩
缓存雪崩与缓存击穿很相似,区别在于缓存雪崩是大多数key同时失效,或者超时,导致数据库压力陡增。
2.解决方式
1.将缓存失效的时候分散开
在给每一个key添加缓存的时候,都在原有固定过期时间上增加上一个随机值。保证多数缓存不会在同一时间失效
2.使用分布式锁,或者redis中的事务
redis中的事务是命令队列,与分布式锁的作用相同。防止不会有大量的线程对数据库一次性进行读写操作。这种方式不适合高并发的场景下使用
五、使用redis实现分布式锁
使用redis实现分布式锁的核心命令是 setnx,这个命令创建的数据只在键不存在时,才对键进行设置操作。
实施步骤:
使用redis先创建一个变量并且设置定时事件,值我们使用UUID生成。这样可以保证线程直接释放锁的时候不会冲突。在实际开发中如果不适用UUID表示锁可能会出现a线程释放了b线程的锁。
场景如下: a线程获取到了锁,进行业务,这个时候a线程突然卡住了。由于我们给锁设置了过时时间。这个时候b线程得到了新生成的锁。这个时候a线程重新工作了,业务完成后如果没有使用UUID表示锁就可能将b线程的锁释放。
实例代码
@Autowiredprivate StringRedisTemplate stringRedisTemplate;@GetMapping("/testRedis")public void testRedis(){//使用uuid对锁进行标识,防止互相释放锁String uuID = UUID.randomUUID().toString();String lockKey = "lock:id:"+uuID;//加锁并且设置过时时间为10秒Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", lockKey, 3, TimeUnit.SECONDS);//如果成功加锁if (lock){String num = stringRedisTemplate.opsForValue().get("num");if (Strings.isBlank(num)){//如果查询到的值为空则直接返回return;}stringRedisTemplate.opsForValue().increment("num");//操作完成后释放锁//判断锁的idString lock1 = stringRedisTemplate.opsForValue().get("lock");if (lockKey.equals(lock1)){//如果锁的id为正确,则释放锁stringRedisTemplate.delete("lock");}else {//如果不正确,直接结束操作return;}}//如果没有得到锁else {try {Thread.sleep(1000);this.testRedis();} catch (InterruptedException e) {e.printStackTrace();}}}
}
总结
概述了redis缓存使用中可能出现的应用问题