并发情况下写入缓存

2021-02-02 08:43:30 +08:00
 rocky114
比如在 1000 并发的状态下,缓存过期了,这个时候需要读取数据库重新写入缓存,只有获取锁的线程才能读取数据库,其它没有拿到锁的线程如何处理呢?
第一种方案:sleep(200) 睡眠 200 毫秒,重新到缓存中取数据,取到返回给客户端
第二种方案:直接返回空数据给客户端,提示稍后重试
4934 次点击
所在节点    编程
46 条回复
Dabaicong
2021-02-02 08:47:53 +08:00
看程序怎么对这个缓存数据的利用了,如果要求准确数据,那就得等缓存重建完成;要求不高可以直接用过期的缓存数据
rocky114
2021-02-02 08:50:07 +08:00
@Dabaicong 过期的缓存已经没数据了,这个时候要直接返回空吗?
imdong
2021-02-02 08:53:02 +08:00
快要过期的时候,就更新缓存。
有一个线程锁定去读即可,其他的锁不住就直接返回缓存。

典型的缓存击穿,缓存血崩案例。
yty2012g
2021-02-02 08:53:50 +08:00
一般套路不是缓存过期就去读库,然后发送回源消息,另一个应用接收回源消息读库写缓存么。这样做保持最终一致性是不需要加锁的。另外看你的数据重要程度吧,重要的数据一般是不允许返回空的
JKeita
2021-02-02 08:54:20 +08:00
这看你对数据容忍度吧,可以接受返回空,就返回空。
JKeita
2021-02-02 08:56:29 +08:00
即使是正常情况下都可能出现网络异常导致客户端请求失败的情况,所以重试机制这种应该客户端去判断。
netnr
2021-02-02 09:00:39 +08:00
要过期前就调更新缓存,保证缓存数据始终有效,避免多次调更新可以加锁
netnr
2021-02-02 09:04:13 +08:00
异步更新
artikle
2021-02-02 09:04:55 +08:00
可以加个缓存标识,这个缓存标识时间比原缓存时间小,要是缓存标识过期,就直接读取缓存返回同时后台读取数据库数据更新缓存。
rocky114
2021-02-02 09:23:31 +08:00
@netnr 缓存太多,定期更新不可维护吧?要是说每天的凌晨执行一次缓存热更新这个还能接受
ksco
2021-02-02 09:32:14 +08:00
wqhui
2021-02-02 09:45:38 +08:00
如果是不会经常变的数据直接设置不过期,然后自己维护。对于过期的缓存,其它也要读这个数据的线程可以阻塞掉,然后其它线程获取到锁后,再尝试去缓存获取数据,有点类似双检锁。
ksco
2021-02-02 09:46:51 +08:00
补充一下,假设有三个线程同时读取一个过期的 key,singleflight 可以保证只有一个线程读库更新缓存,其他的线程会等待此线程执行完成,然后拿到和此线程相同的返回值。

实现上也比较简单,可以看看上面贴的源码。用其他语言改写应该也问题不大。
darkleave
2021-02-02 09:56:16 +08:00
建议了解下缓存更新策略,你这种情况按照 cache aside 或者 read through 的方式去处理就行了
bingoshe
2021-02-02 09:59:40 +08:00
我有点不明白,这里去数据库取数据的时候,为什么需要加锁?这个数据是独占性资源吗?是的话为什么 1000 个并发要维护各自的缓存?
将 1000 个并发生成不 expireTime+random 数,这样就不会在一瞬间都过期了;
任务扫描更新缓存;
ksco
2021-02-02 10:10:02 +08:00
@darkleave #14 cache aside 或者 read through 只是解决了正确性,并没有解决并发读的缓存击穿问题。
pangleon
2021-02-02 10:31:19 +08:00
楼上说的好,为啥非得等查不到采取更新,如果真的是特别热点的数据快过期就去更新
rrfeng
2021-02-02 10:46:35 +08:00
前段时间才了解到 go 官方 sync 包里有个叫 singleflight 的玩意儿,专做这个。
admol
2021-02-02 10:49:45 +08:00
@pangleon 一种兜底策略吧,要是去提前更新的线程出现问题了呢?
wy315700
2021-02-02 10:53:49 +08:00
缓存过期这个词有点歧义。

两层含义:
1 缓存存在,但是里面的数值或者时间戳过期了,这种情况下可以先返回过期数据,然后另开一个线程去更新缓存。
2 缓存不存在了,最好避免这种情况以免数据库被击穿,可以另开一个循环线程去定期更新缓存。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/750489

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX