1
Dabaicong 2021-02-02 08:47:53 +08:00
看程序怎么对这个缓存数据的利用了,如果要求准确数据,那就得等缓存重建完成;要求不高可以直接用过期的缓存数据
|
3
imdong 2021-02-02 08:53:02 +08:00 via iPhone 1
快要过期的时候,就更新缓存。
有一个线程锁定去读即可,其他的锁不住就直接返回缓存。 典型的缓存击穿,缓存血崩案例。 |
4
yty2012g 2021-02-02 08:53:50 +08:00
一般套路不是缓存过期就去读库,然后发送回源消息,另一个应用接收回源消息读库写缓存么。这样做保持最终一致性是不需要加锁的。另外看你的数据重要程度吧,重要的数据一般是不允许返回空的
|
5
JKeita 2021-02-02 08:54:20 +08:00
这看你对数据容忍度吧,可以接受返回空,就返回空。
|
6
JKeita 2021-02-02 08:56:29 +08:00
即使是正常情况下都可能出现网络异常导致客户端请求失败的情况,所以重试机制这种应该客户端去判断。
|
7
netnr 2021-02-02 09:00:39 +08:00 via Android
要过期前就调更新缓存,保证缓存数据始终有效,避免多次调更新可以加锁
|
8
netnr 2021-02-02 09:04:13 +08:00 via Android
异步更新
|
9
artikle 2021-02-02 09:04:55 +08:00
可以加个缓存标识,这个缓存标识时间比原缓存时间小,要是缓存标识过期,就直接读取缓存返回同时后台读取数据库数据更新缓存。
|
11
ksco 2021-02-02 09:32:14 +08:00 1
|
12
wqhui 2021-02-02 09:45:38 +08:00
如果是不会经常变的数据直接设置不过期,然后自己维护。对于过期的缓存,其它也要读这个数据的线程可以阻塞掉,然后其它线程获取到锁后,再尝试去缓存获取数据,有点类似双检锁。
|
13
ksco 2021-02-02 09:46:51 +08:00
补充一下,假设有三个线程同时读取一个过期的 key,singleflight 可以保证只有一个线程读库更新缓存,其他的线程会等待此线程执行完成,然后拿到和此线程相同的返回值。
实现上也比较简单,可以看看上面贴的源码。用其他语言改写应该也问题不大。 |
14
darkleave 2021-02-02 09:56:16 +08:00
建议了解下缓存更新策略,你这种情况按照 cache aside 或者 read through 的方式去处理就行了
|
15
bingoshe 2021-02-02 09:59:40 +08:00
我有点不明白,这里去数据库取数据的时候,为什么需要加锁?这个数据是独占性资源吗?是的话为什么 1000 个并发要维护各自的缓存?
将 1000 个并发生成不 expireTime+random 数,这样就不会在一瞬间都过期了; 任务扫描更新缓存; |
16
ksco 2021-02-02 10:10:02 +08:00
@darkleave #14 cache aside 或者 read through 只是解决了正确性,并没有解决并发读的缓存击穿问题。
|
17
pangleon 2021-02-02 10:31:19 +08:00
楼上说的好,为啥非得等查不到采取更新,如果真的是特别热点的数据快过期就去更新
|
18
rrfeng 2021-02-02 10:46:35 +08:00 via Android
前段时间才了解到 go 官方 sync 包里有个叫 singleflight 的玩意儿,专做这个。
|
20
wy315700 2021-02-02 10:53:49 +08:00
缓存过期这个词有点歧义。
两层含义: 1 缓存存在,但是里面的数值或者时间戳过期了,这种情况下可以先返回过期数据,然后另开一个线程去更新缓存。 2 缓存不存在了,最好避免这种情况以免数据库被击穿,可以另开一个循环线程去定期更新缓存。 |
21
xwander 2021-02-02 11:01:46 +08:00
缩小锁粒度吧,既然是读取数据后写入缓存,读没必要锁,锁的是缓存区,这个锁是整个缓存区的全局锁?
|
22
enihcam 2021-02-02 11:41:53 +08:00 via Android
布隆过滤器锁,布尔改整数,取 2 代表此 entry 正在访问实体。原子化操作这个表。
|
23
enihcam 2021-02-02 11:43:35 +08:00 via Android
顺便可以做成 circuit breaker,一石二鸟。
|
25
rocky114 OP @pangleon 定期更新比较难维护,要是缓存 key 比较少的还好,要是有几百个类型的缓存都要定期维护就有点麻烦了
|
29
justforlook44444 2021-02-02 14:13:20 +08:00
缓存击穿
|
30
Varobjs 2021-02-02 14:45:55 +08:00
@pangleon 我意思取数的时候不光取数据,也包括 TTL,发现要过期了就更新
---------- 也需要加锁的吧,比如 1000 并发请求,都发现快要过期了(例如 ttl<120 ),都去更新读数据库,效果其实和获取不到缓存数据的时候再更新是一样的。 |
31
axbx 2021-02-02 15:57:07 +08:00
写缓存的时候加一个更新间隔时间,比缓存失效时间短,每次读取的时候去判断一下是否已经过了这个间隔时间,过了的话异步去更新缓存。
|
32
cassyfar 2021-02-02 16:12:45 +08:00
一般直接开一个单独线程更新 cache 。当前所有的 cache miss 全部去读数据库。你不停查看 TTL 然后更新只会让你代码特别肿胀,而且如果更新 cache 失败,不还是会失效然后会遇到老问题吗?
|
34
petercui 2021-02-02 16:15:57 +08:00
过期或者修改了数据,只需要让缓存失效就行了,然后下次读取的时候再写入缓存。
|
35
keepeye 2021-02-02 16:25:35 +08:00
1.sleep 不是不行,就怕雪崩,具体要看并发量和持续时间以及刷新缓存耗时
2.直接返回错误给客户端,让客户端自己重试,这个是可行的,但只适用普通场景 3.若要始终保证缓存有效,那只能单独一个线程,在缓存快要过期前,提前更新缓存 |
36
pangleon 2021-02-02 16:31:45 +08:00
@xxy973211 如果你们数据量少,可以这么干。
但是假如你们有 1000W 数据,REDIS 占用的内存有多少考虑过么?可以通过这个网站计算 http://www.redis.cn/redis_memory/ 所以全部数据不过期适合全部数据量小的情况。 也可以只设置热点数据永不过期,前提是你要知道哪些是热点数据以及热点数据量小的情况。相应的有了 REDIS 缓存预热的说法。 大部分场景下热点数据其实就那么多,大部分是冷数据。所以目前有很多冷热数据的解决方案。这是另一个问题就不在这里讨论了。 楼主的问题是,业内常见的处理他不想用,正常查不到缓存就返回空前端处理一下,就留一个获取到锁的线程去更新。 楼主不想返回空,那么那么多线程在那里轮询类似自旋,就比较烦躁了。 还有一种方案就是 2 套 REDIS,一套过期时间长一些作为备份缓存,过期时间短的查不到去查这个备份的。 问题是 REDIS 在云服务商那不便宜啊,如果数据量一大成本是个问题。 |
37
luzhh 2021-02-02 17:52:18 +08:00
Java 的话,用 FutureTask,1000 个请求过来,只有一个请求实际区读取数据库,其他的请求等待第一个请求拿到结果之后返回结果即可。
|
38
Foredoomed 2021-02-02 17:58:25 +08:00
都不是,没拿到锁的线程等待
|
39
imjamespond 2021-02-02 19:08:35 +08:00 via Android
react 模式加队列即可
|
40
rocky114 OP @Foredoomed php+redis 分布式锁没法实现阻塞等待吧,php+mysql 实现的锁倒是可以阻塞等待
|
41
vindurriel 2021-02-02 20:41:36 +08:00 via iPhone
两个方案刚好是 CAP 定理中选 C 还是 A 的问题 方案一选 C 问题是 200ms 不一定够 还得加随机数削峰 方案二选 A 增加了客户端 /使用者的负担
|
42
hxndg 2021-02-02 21:18:12 +08:00
我没实现过分布式,不过设计过单机的线程缓存操作之类的。。。。。提个自己的想法
创建一个缓存的队列,命名为缓冲垫,表示没命中,目前正在从数据库拿数据。 如果工作者线程发现缓存没命中,这个数据也没在缓冲垫里,直接去数据库那数据就完了,然后一次性更新数量多一些的数据,如果有局部性可言。 如果工作者线程发现缓存没命中,这个数据在缓冲垫里,那就直接返回,先去做别的事务,等待已经去数据库取数据的工作者线程把数据取回来,再继续执行。 总之就是减少忙等,除非忙等的时间特别短。 |
43
rocky114 OP @hxndg 这里是不允许多个线程直接读取数据库的,因为流量都直接到数据库容易把数据库搞奔溃了,所以需要增加一把锁,我这里的疑问点是其它拿不到锁的线程应该怎么处理?前排给出的方案是存储的缓存值增加 ttl 信息,这样每次读取缓存时判断下快过期的就重新设置一下缓存,这样就保证了缓存不会过期。可能有人会说一段时间没有访问缓存失效了,一下子并发上来还是会遇到问题,我认为一段时间没有访问的缓存不属于热点缓存,访问量应该不大。最后配合上凌晨缓存热更新应该能基本解决这些问题
|
44
sujin190 2021-02-03 10:29:41 +08:00
加锁就是了,搞个超高性能的锁服务,如果锁服务也挂了就返回让客户端重试,而且只需要在无缓存的时候才加锁从数据库加载,指单纯用于加锁的话,设计好搞个十倍 redis 性能的,妥妥的
|