并发情况下写入缓存

2021-02-02 08:43:30 +08:00
 rocky114
比如在 1000 并发的状态下,缓存过期了,这个时候需要读取数据库重新写入缓存,只有获取锁的线程才能读取数据库,其它没有拿到锁的线程如何处理呢?
第一种方案:sleep(200) 睡眠 200 毫秒,重新到缓存中取数据,取到返回给客户端
第二种方案:直接返回空数据给客户端,提示稍后重试
4730 次点击
所在节点    编程
46 条回复
xwander
2021-02-02 11:01:46 +08:00
缩小锁粒度吧,既然是读取数据后写入缓存,读没必要锁,锁的是缓存区,这个锁是整个缓存区的全局锁?
enihcam
2021-02-02 11:41:53 +08:00
布隆过滤器锁,布尔改整数,取 2 代表此 entry 正在访问实体。原子化操作这个表。
enihcam
2021-02-02 11:43:35 +08:00
顺便可以做成 circuit breaker,一石二鸟。
rocky114
2021-02-02 13:28:49 +08:00
@wy315700 这里的缓存过期是指缓存不存在了
rocky114
2021-02-02 13:34:03 +08:00
@pangleon 定期更新比较难维护,要是缓存 key 比较少的还好,要是有几百个类型的缓存都要定期维护就有点麻烦了
pangleon
2021-02-02 13:52:21 +08:00
@rocky114 我意思取数的时候不光取数据,也包括 TTL,发现要过期了就更新
rocky114
2021-02-02 13:56:29 +08:00
@ksco 这个支持分布式吗
rocky114
2021-02-02 13:57:24 +08:00
@pangleon 你这个方案好,感谢啊
justforlook44444
2021-02-02 14:13:20 +08:00
缓存击穿
Varobjs
2021-02-02 14:45:55 +08:00
@pangleon 我意思取数的时候不光取数据,也包括 TTL,发现要过期了就更新
----------

也需要加锁的吧,比如 1000 并发请求,都发现快要过期了(例如 ttl<120 ),都去更新读数据库,效果其实和获取不到缓存数据的时候再更新是一样的。
axbx
2021-02-02 15:57:07 +08:00
写缓存的时候加一个更新间隔时间,比缓存失效时间短,每次读取的时候去判断一下是否已经过了这个间隔时间,过了的话异步去更新缓存。
cassyfar
2021-02-02 16:12:45 +08:00
一般直接开一个单独线程更新 cache 。当前所有的 cache miss 全部去读数据库。你不停查看 TTL 然后更新只会让你代码特别肿胀,而且如果更新 cache 失败,不还是会失效然后会遇到老问题吗?
xxy973211
2021-02-02 16:15:28 +08:00
@pangleon 这种数据为啥不直接设置成不过期呢?即使数据库有更新,刷新缓存就行了吧
petercui
2021-02-02 16:15:57 +08:00
过期或者修改了数据,只需要让缓存失效就行了,然后下次读取的时候再写入缓存。
keepeye
2021-02-02 16:25:35 +08:00
1.sleep 不是不行,就怕雪崩,具体要看并发量和持续时间以及刷新缓存耗时
2.直接返回错误给客户端,让客户端自己重试,这个是可行的,但只适用普通场景
3.若要始终保证缓存有效,那只能单独一个线程,在缓存快要过期前,提前更新缓存
pangleon
2021-02-02 16:31:45 +08:00
@xxy973211 如果你们数据量少,可以这么干。
但是假如你们有 1000W 数据,REDIS 占用的内存有多少考虑过么?可以通过这个网站计算 http://www.redis.cn/redis_memory/
所以全部数据不过期适合全部数据量小的情况。
也可以只设置热点数据永不过期,前提是你要知道哪些是热点数据以及热点数据量小的情况。相应的有了 REDIS 缓存预热的说法。

大部分场景下热点数据其实就那么多,大部分是冷数据。所以目前有很多冷热数据的解决方案。这是另一个问题就不在这里讨论了。

楼主的问题是,业内常见的处理他不想用,正常查不到缓存就返回空前端处理一下,就留一个获取到锁的线程去更新。
楼主不想返回空,那么那么多线程在那里轮询类似自旋,就比较烦躁了。
还有一种方案就是 2 套 REDIS,一套过期时间长一些作为备份缓存,过期时间短的查不到去查这个备份的。
问题是 REDIS 在云服务商那不便宜啊,如果数据量一大成本是个问题。
luzhh
2021-02-02 17:52:18 +08:00
Java 的话,用 FutureTask,1000 个请求过来,只有一个请求实际区读取数据库,其他的请求等待第一个请求拿到结果之后返回结果即可。
Foredoomed
2021-02-02 17:58:25 +08:00
都不是,没拿到锁的线程等待
imjamespond
2021-02-02 19:08:35 +08:00
react 模式加队列即可
rocky114
2021-02-02 20:11:31 +08:00
@Foredoomed php+redis 分布式锁没法实现阻塞等待吧,php+mysql 实现的锁倒是可以阻塞等待

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

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

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

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

© 2021 V2EX