上一篇讲到了缓存击穿的问题,是说当一个热点数据在redis过期后,突然有大量请求同时来访问此数据,此时发现redis没有数据,这些大量的请求便都会打到数据库,给数据库造成压力这种是针对redis里没有这条数据,数据库里有这条数据的情况,我们可以用加锁来保证只有一个线程可以打到数据库,这个线程查询完数据库后将数据会写到redis,其它线程就可以直接从redis获取了,具体细节请查看上一篇:redis使用之四:缓存击穿但是上一篇同时也遗留了一个问题,就是这条数据在redis和数据都没有怎么办,这时候其它处于等待的线程,在一定时间内从redis中查不到数据就抛出异常,是否不太合理?而且还有一个问题,如果这些线程不是并发的,每个请求都是等前一个请求执行完并且释放锁后在发起,那这样每个请求都能获取到锁,并且打到数据库,仍然会给数据库造成压力,这种情况就是常说的缓存穿透,今天小编就来聊一聊关于快速掌握redis缓存?接下来我们就一起去研究一下吧!

快速掌握redis缓存(redis使用之五缓存穿透)

快速掌握redis缓存

上一篇讲到了缓存击穿的问题,是说当一个热点数据在redis过期后,突然有大量请求同时来访问此数据,此时发现redis没有数据,这些大量的请求便都会打到数据库,给数据库造成压力。这种是针对redis里没有这条数据,数据库里有这条数据的情况,我们可以用加锁来保证只有一个线程可以打到数据库,这个线程查询完数据库后将数据会写到redis,其它线程就可以直接从redis获取了,具体细节请查看上一篇:redis使用之四:缓存击穿。但是上一篇同时也遗留了一个问题,就是这条数据在redis和数据都没有怎么办,这时候其它处于等待的线程,在一定时间内从redis中查不到数据就抛出异常,是否不太合理?而且还有一个问题,如果这些线程不是并发的,每个请求都是等前一个请求执行完并且释放锁后在发起,那这样每个请求都能获取到锁,并且打到数据库,仍然会给数据库造成压力,这种情况就是常说的缓存穿透。

缓存穿透的解决,首先要对用户传来的参数做检验,通常情况下,数据库中的id,都是从1开始自增的,不可能存在小于1的id,如果用户传来了小于1的id,我们就可以参数是不合法的,直接把请求拒绝掉,不需要再去查redis和数据库。还有,假如我们数据表中只有一万条数据,并且不会有新增的数据,但是用户传来的id是十几万或者更大,那我们也可以认为参数是不合法的,直接把请求拒绝掉。总之,这一步的目的就是尽量把不合法的请求拒绝掉,但是没办法把所有不合法请求全部拒绝掉,比如数据表每天新增的数据特别大,这是你是没有办法去验证用户传来的id的上限的。

针对这种情况,有人说如果在数据库查询不到,也把这条数据存到缓存,只不过value存个空值,这种思路是正确的,但是问题是这个空值你怎么存,是存null,还是存空串呢?

首先来说一个存null值的问题,有人说redis的key和value都不能存null,key一定是不能存null的,但是value能不能存null我没有具体研究过,反正我是从来没有存过null的,也从来都不会有这样的需求让你存null,如果你真的需要null的话,那一定是你的设计有问题。但是话说回来,即使redis允许value存null,我们也不能存null,因为如果你存了null,那我们根据key去获取value时返回了null,那你怎么知道是因为key不存在返回了null,还是说key是存在的,只不过是value为null呢,你没有办法去区分这两种情况,但是你在代码种,还要根据这两种情况,做不同的处理,如果是因为key不存在,就需要获取锁去查询数据库,如果key存在但是value为null,就说明这条数据在数据库中都是不存在的,直接返回即可。但是你没办法区别这两种情况,就没办法处理,也许有人会说redis是有方法可以判断key是否存在的,但是这样做就给系统带来了复杂性,因为你需要两步操作,第一步判断key是否存在,第二步根据key获取value,这样做就麻烦了,我们没有必要做这么麻烦。

综上所述,value存null是不合理的,那就只能存个空串,我在实际开发时也是通过存个空串解决的,这样根据key获取value时,先判断返回结果是不是null,如果是null,也去获取锁去数据库查询,如果不存在,则说明此条数据在数据库和redis都不存在,直接返回即可,如果此两种情况都不是,就说明从redis中查到了数据,直接做反序列化,得到真正的实体对象即可。

这样看起来方案似乎比较完美了,但是实际上还是存在问题的,假如用户传来的id,目前在数据库中是不存在的,存了个空串,这样后面在根据这个id时,redis直接返回空串,我们就知道数据库里没有这条数据了。但是这个id可能只是暂时不存在,可能以后随着数据量的增长,这个id又存在了,这就需要在新增数据时,如果数据新增成功,要把redis中对应的数据删除,而且此时为了最大可能的保证数据库与redis的数据的一致性,我们需要利用threadlocal做个双删,具体细节请查看我前面写的文章,redis缓存使用之三:如何保证数据库数据与redis数据的一致性。但是这种双删的方案仍然有可能删除redis失败,所以我们在redis存储空串时,需要给对应的key加个比较短的过期时间,我一般都会加个三分钟的过期时间。

上面说的这种情况是说id暂时不存在,但是后面可能存在的情况,但是还有一种情况,就是这个id在数据库里是有的,只不过被打上了逻辑删除的标识,这代表着这条数据虽然在数据库中虽然存在,但是对用户来说是透明的,用户通过id查的时候,应该查不到这条数据才对,而且后面不管过了多长时间,通过这个id都是查不到这条数据的。针对这种情况,我们还是在redis中存个空串,只不过这时,我们在对应的key上加的过期时间会比较长,我通常都会加上一天的过期时间。

上面就是我在工作中,整个的解决redis缓存穿透的方案,大家有什么不同的看法或者更好的建议,欢迎评论区讨论[微笑]

,