记一次redis缓存击穿问题

末蓝、 2022-03-26 03:16 310阅读 0赞

前言:

首先先介绍下项目环境:SpringBoot+Redis+JPA等。之所以写这篇文章是为了总结下线上的一个击穿问题,以便于对redis有更为深刻的认识和理解。

这里我就不使用项目中的代码了,使用自己的测试代码来说明主要问题。

一、业务场景:

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyOTY3NjY1_size_16_color_FFFFFF_t_70

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyOTY3NjY1_size_16_color_FFFFFF_t_70 1

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

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyOTY3NjY1_size_16_color_FFFFFF_t_70 2

结果:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyOTY3NjY1_size_16_color_FFFFFF_t_70 3

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyOTY3NjY1_size_16_color_FFFFFF_t_70 4

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

二、如何避免缓存击穿

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

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyOTY3NjY1_size_16_color_FFFFFF_t_70 5

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyOTY3NjY1_size_16_color_FFFFFF_t_70 6

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyOTY3NjY1_size_16_color_FFFFFF_t_70 7

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyOTY3NjY1_size_16_color_FFFFFF_t_70 8

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyOTY3NjY1_size_16_color_FFFFFF_t_70 9

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

发表评论

表情:
评论列表 (有 0 条评论,310人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Redis缓存击穿

    缓存击穿 缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。 解决方案: 1

    相关 redis缓存击穿问题

    需要缓存的业务考虑缓存的两种用法模式 1.读模式,如何读取一个数据,应该遵循先从缓存中读取,     如果缓存中没有,再在数据库读取,如果在数据库查到数据则再放到缓存

    相关 redis缓存击穿问题

    前言: 首先先介绍下项目环境:SpringBoot+Redis+JPA等。之所以写这篇文章是为了总结下线上的一个击穿问题,以便于对redis有更为深刻的认识和理解。 这里我