缓存击穿有相似之处,都是直接绕过Redis缓存去访问数据库,但是场景不一样。缓存击穿应用的场景主要是热点数据或者被频繁访问的数据。缓存击穿指的是某个热点数据过期或者被删除,导致大量请求直接绕过缓存去访问数据库,导致数据库压力瞬间升高,甚至崩溃。解决缓存击穿这个问题,可以使用互斥锁或者分布式的方式,保证同一时间里面,只有一个线程去查询和重建缓存。
下面是模拟两个线程在争夺互斥锁的场景,在同一台服务器下,当线程1拿到互斥锁后,JVM内部的锁监视器就会检测到有线程在运行,其他线程就不能执行了。等到线程1查询数据且重建完缓存后,就会去释放锁。一旦线程1释放锁,线程2就可以拿到互斥锁,接着查询缓存发现已经存在,就会直接返回数据并且释放锁。这样就可以解决在单体服务器下,缓存击穿的问题。(集群服务器使用分布式锁)
1、使用互斥锁(Mutex):
在缓存失效或者被删除的瞬间,使用互斥锁进行资源控制,只允许一个请求进入数据库查询,其他请求等待。这样可以避免大量请求同时查询数据库,从而导致数据库崩溃。
这里利用Redis命令setnx
唯一性的特性来做互斥锁,只有创建锁成功,才能执行下面的操作,否则就只能等待重试。这个方法是实现简单,保证一致性,且维护方便。缺点是只能在单体服务器下有效运行。
1 | public Result QueryRedisById(Long id) { |
Redis命令setnx
如果key不存在才能被设置,这个就像一把互斥锁,等到获取到新的数据并且返回数据后,就删除这把锁del
,可以利用这一特性来实现这个功能;但是这个程序或者代码块有可能会出现错误导致锁无法被释放(即删除),那么在设置锁的时候,根据业务执行的耗时,给它加一个TTL,如10s过期时间来自动释放锁,防止死锁。
CACHE_SHOP_KEY,CACHE_SHOP_TTL,CACHE_NULL_TTL、LOCK_SHOP_TTL这些自定义常量都是自己维护的,防止在其他地方引用出错而导致出现其他的问题。
2、使分布式锁:
这里引入Redisson来做分布式锁,实现较上一个稍复杂且集群服务器下也有效。
pom文件导入依赖:
1 | <!-- https://mvnrepository.com/artifact/org.redisson/redisson --> |
创建一个配置文件,注入Bean,交由Spring来管理。
1 | @Configuration |
useSingleServer:单体Redis服务器,使用集群可以使用useClusterServers方法,或者多注入几个单体RedissonClient来组成集群。
1 | private final StringRedisTemplate stringRedisTemplate; |
逻辑与使用Redis命令的setnx
的方法基本一致,只是省去了自定义的步骤,且在集群服务器也可生效。
3、基于逻辑过期解决缓存击穿:
这个方法的优点是无需线程等待,就可以直接获取。缺点也较为明显,不能保证一致性,会有额外的内存消耗,且实现复杂。
1 | @Override |
1 | @Data |
这个方法的思路就是,先把热点数据预加载到Redis服务器,然后这个方法会有一个逻辑过期时间expireTime
存储在RedisData
,等待客户端访问热点数据的时候,会直接从Redis服务器中查询数据,拿到数据后直接转换成目标对象返回,同时会判断逻辑过期时间是否已经过期,如果已经过期了,就会查询数据库并且更新数据。所以,在逻辑有效期内,如果数据发生了改变,就会导致数据不一致的问题。
这里必须注意的是,需要提前把热点数据封装成RedisData对象并存储进 Redis 服务器。
以上就是今天分享的三种关于解决缓存击穿的方法。
本文链接: https://longzas.github.io/2023/08/24/Redis-%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!