缓存击穿及解决方案
这是我参与更文挑战的第6天,活动详情查看: 更文挑战
问题起源与某微信群大佬说自己原本支持singleflight的代码被改了~
本着程序员能有不掌握的技术,不能有不了解的名词,去搜索singleflight~进而搜索到“缓存击穿”这个名词,相关名词还有缓存雪崩。
概念
先来了解一下名词的概念:
缓存击穿:指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到db[1]。此时缓存起不到作用,就像被“击穿”了。缓存击穿会引起数据库瞬间压力增大。
缓存雪崩:指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。
解决方案
互斥锁
当缓存失效时,给从数据库取数据添加一个锁,只能有一个线程(线程A)可以进去读取数据库,其他线程等待;当线程A读取数据库数据成功后,将数据更新到缓存中,其他等待线程直接去读取缓存获取数据。Go的singleflight用的就是这种思想。我们首先贴一段Java-redis的代码:
其中的redis.setnx
方法为加锁。
setnx
, SET if Not eXists 的缩写。只在键 key
不存在的情况下, 将键 key
的值设置为 value
。若键 key
已经存在, 则 SETNX
命令不做任何动作。命令在设置成功时返回 1
, 设置失败时返回 0
。
1 | String get(String key) { |
Go的singleflight采用的也是这种互斥锁的方案,实现上singleflight使用一个临时的map
存储第一个协程读取数据库的数据,后续方法直接从map
中获取,连缓存都不用读。singleflight全部代码并不多,其github地址。
其源代码可在Go安装路径,如C:\Program Files\Go\src\internal\singleflight
查看。
异步构建缓存
简单来说,就是获取始终从缓存获取;缓存中存储数据与过期时间,每次过期后启动另外的线程获取数据更新缓存。无法保证数据的一致性,但大多数情况下可以满足需求。
布隆过滤器
布隆过滤器的作用是能够迅速判断一个元素是否在一个集合。
应用布隆过滤器到缓存击穿中,就是维护一个数据库key的集合,每次请求,首先取缓存中获取数据,缓存中没有则通过布隆过滤器判断查询的key是否可能在数据库中,若不在则不请求数据库[3]。
[1]缓存击穿-百度百科,https://baike.baidu.com/item/%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF