记一次线上Redis缓存击穿

Posted 润青

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次线上Redis缓存击穿相关的知识,希望对你有一定的参考价值。

前言:

首先介绍下项目环境:SpringBoot+Redis+JPA+ZK等技术。由一个线上redis击穿问题引发的一些思考。这里我就不使用项目中的代码了,使用自己的测试代码来说明主要问题。需要注意的是:本文只是提供一种解决思路,当然可以采用其他办法。

一、业务场景

使用redis的理由是在用户访问量很大的时候,如果一次次去从数据库中读取数据,无疑会增加数据库的负担(重要的数据当然还是要从库中读),但对于诸如用户浏览记录或者xxx列表之类的数据,数据量是巨大的,如果从库中读显然不可行,所以考虑存到redis中,大致的流程是这样,注意:此图和实际业务无关只是为了说明问题

好了接下来来看下代码,由于使用的是springboot因此在整合redis的时候使用了redisTemplete

记一次线上Redis缓存击穿

嗯,看上去没有什么问题,来测试一下,为了模拟环境,我们使用25条线程执行10万条请求,来看是否像我们的预想结果即:如果是第一次查询的话,默认去查数据库,由于之后将信息放到了缓存,因此第二次应该查缓存。

为了验证次问题我将之前在redis中存的数据全部移除,然后我们来执行一下测试程序:

记一次线上Redis缓存击穿

结果:

记一次线上Redis缓存击穿

我们再来看一下redis客户端中有没有缓存到数据,可以看到缓存了数据,说明逻辑是对的。

记一次线上Redis缓存击穿

但是,我们可以看到结果并没有向我们想象的那样第一次查数据库,而是有部分数据都是从数据库中读取的,后面才开始查缓存的,如果并发再大点的话,查数据库的数量会更多,那么我们该如何避免这种情况呢?


二、如何避免缓存击穿

我们先来分析一下问题出在哪里,10w条查询请求进来,由于是第一次查询所以这个时候redis中是不存在缓存数据的,这个时候因为是并发执行,可能有1000条请求进来,发现redis中没有,于是就去读数据库查了出来,然后将数据设置到了redis中,1001条以后的请求进来后发现redis中已经有了缓存,所以就不去读取数据库而是直接去redis中读取缓存,因此就会出现上面的那种结果。

如何避免呢?我们很容易就想到使用互斥锁,即synchronized它可以保证在某一时刻只有一个对象拥有锁,也就是说当第一条请求进入的时候它拥有了一把锁,那么此时请求流程是怎样的呢?看图~

记一次线上Redis缓存击穿


和之前相同的是所有的请求都可以进入到判断是否为空的逻辑,当判断redis为空后去数据库中查的时候,采用synchronized来锁定当前的对象,也就是用户1的请求,这个时候其他的请求只能等,那么用户1接着就会去数据库中读取,然后把读取到的信息放入到redis中去,当它执行完这一系列流程之后,请求2才开始执行,这时它发现redis中已经有了数据,所以就不去数据库中读取,直接从redis中拿出来显示,所以此时就可以避免缓存击穿的问题,先来看下代码:

记一次线上Redis缓存击穿

可以看到this也就是当前对象被锁定,其他对象就无法继续进入执行,接着我们来清除redis中原有的缓存来测试一下结果:

记一次线上Redis缓存击穿

此时的redis中是没有缓存数据的,接着我们来执行程序:

可以看到此刻虽然redis中没有数据,但是只有第一次从数据库中查询,之后的请求都是从缓存中读取出来,我们再来看下redis缓存中:


三、总结

当然你也可以将synchronized作用在方法上,这样也可以保证结果的正确性,但是需要注意的是这样的话性能会大大降低,因此当你调用方法的时候其他请求会进行等待,此时方法是阻塞的,而且效率也是低下的,而作用在方法块上的效率显然要比方法上好的多。


以上是关于记一次线上Redis缓存击穿的主要内容,如果未能解决你的问题,请参考以下文章

缓存击穿、穿透、雪崩及Redis分布式锁

REDIS12_缓存雪崩缓存穿透基于布隆过滤器解决缓存穿透的问题缓存击穿基于缓存击穿工作实际案例

REDIS12_缓存雪崩缓存穿透基于布隆过滤器解决缓存穿透的问题缓存击穿基于缓存击穿工作实际案例

REDIS08_缓存雪崩缓存穿透基于布隆过滤器解决缓存穿透的问题缓存击穿基于缓存击穿工作实际案例

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

Laravel 中使用 Redis 锁解决缓存击穿问题