跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • 并发情况下写入缓存
未分類
3 2 月 2021

并发情况下写入缓存

并发情况下写入缓存

資深大佬 : rocky114 2

比如在 1000 并发的状态下,缓存过期了,这个时候需要读取数据库重新写入缓存,只有获取锁的线程才能读取数据库,其它没有拿到锁的线程如何处理呢?
第一种方案:sleep(200) 睡眠 200 毫秒,重新到缓存中取数据,取到返回给客户端
第二种方案:直接返回空数据给客户端,提示稍后重试
大佬有話說 (46)

  • 資深大佬 : Dabaicong

    看程序怎么对这个缓存数据的利用了,如果要求准确数据,那就得等缓存重建完成;要求不高可以直接用过期的缓存数据

  • 主 資深大佬 : rocky114

    @Dabaicong 过期的缓存已经没数据了,这个时候要直接返回空吗?

  • 資深大佬 : imdong

    快要过期的时候,就更新缓存。
    有一个线程锁定去读即可,其他的锁不住就直接返回缓存。

    典型的缓存击穿,缓存血崩案例。

  • 資深大佬 : yty2012g

    一般套路不是缓存过期就去读库,然后发送回源消息,另一个应用接收回源消息读库写缓存么。这样做保持最终一致性是不需要加锁的。另外看你的数据重要程度吧,重要的数据一般是不允许返回空的

  • 資深大佬 : JKeita

    这看你对数据容忍度吧,可以接受返回空,就返回空。

  • 資深大佬 : JKeita

    即使是正常情况下都可能出现网络异常导致客户端请求失败的情况,所以重试机制这种应该客户端去判断。

  • 資深大佬 : netnr

    要过期前就调更新缓存,保证缓存数据始终有效,避免多次调更新可以加锁

  • 資深大佬 : netnr

    异步更新

  • 資深大佬 : artikle

    可以加个缓存标识,这个缓存标识时间比原缓存时间小,要是缓存标识过期,就直接读取缓存返回同时后台读取数据库数据更新缓存。

  • 主 資深大佬 : rocky114

    @netnr 缓存太多,定期更新不可维护吧?要是说每天的凌晨执行一次缓存热更新这个还能接受

  • 資深大佬 : ksco

    可以使用 singleflight 。

    https://github.com/golang/groupcache/blob/master/singleflight/singleflight.go

  • 資深大佬 : wqhui

    如果是不会经常变的数据直接设置不过期,然后自己维护。对于过期的缓存,其它也要读这个数据的线程可以阻塞掉,然后其它线程获取到锁后,再尝试去缓存获取数据,有点类似双检锁。

  • 資深大佬 : ksco

    补充一下,假设有三个线程同时读取一个过期的 key,singleflight 可以保证只有一个线程读库更新缓存,其他的线程会等待此线程执行完成,然后拿到和此线程相同的返回值。

    实现上也比较简单,可以看看上面贴的源码。用其他语言改写应该也问题不大。

  • 資深大佬 : darkleave

    建议了解下缓存更新策略,你这种情况按照 cache aside 或者 read through 的方式去处理就行了

  • 資深大佬 : bingoshe

    我有点不明白,这里去数据库取数据的时候,为什么需要加锁?这个数据是独占性资源吗?是的话为什么 1000 个并发要维护各自的缓存?
    将 1000 个并发生成不 expireTime+random 数,这样就不会在一瞬间都过期了;
    任务扫描更新缓存;

  • 資深大佬 : ksco

    @darkleave #14 cache aside 或者 read through 只是解决了正确性,并没有解决并发读的缓存击穿问题。

  • 資深大佬 : pangleon

    上说的好,为啥非得等查不到采取更新,如果真的是特别热点的数据快过期就去更新

  • 資深大佬 : rrfeng

    前段时间才了解到 go 官方 sync 包里有个叫 singleflight 的玩意儿,专做这个。

  • 資深大佬 : admol

    @pangleon 一种兜底策略吧,要是去提前更新的线程出现问题了呢?

  • 資深大佬 : wy315700

    缓存过期这个词有点歧义。

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

  • 資深大佬 : xwander

    缩小锁粒度吧,既然是读取数据后写入缓存,读没必要锁,锁的是缓存区,这个锁是整个缓存区的全局锁?

  • 資深大佬 : enihcam

    布隆过滤器锁,布尔改整数,取 2 代表此 entry 正在访问实体。原子化操作这个表。

  • 資深大佬 : enihcam

    顺便可以做成 circuit breaker,一石二鸟。

  • 主 資深大佬 : rocky114

    @wy315700 这里的缓存过期是指缓存不存在了

  • 主 資深大佬 : rocky114

    @pangleon 定期更新比较难维护,要是缓存 key 比较少的还好,要是有几百个类型的缓存都要定期维护就有点麻烦了

  • 資深大佬 : pangleon

    @rocky114 我意思取数的时候不光取数据,也包括 TTL,发现要过期了就更新

  • 主 資深大佬 : rocky114

    @ksco 这个支持分布式吗

  • 主 資深大佬 : rocky114

    @pangleon 你这个方案好,感谢啊

  • 資深大佬 : justforlook44444

    缓存击穿

  • 資深大佬 : Varobjs

    @pangleon 我意思取数的时候不光取数据,也包括 TTL,发现要过期了就更新
    ———-

    也需要加锁的吧,比如 1000 并发请求,都发现快要过期了(例如 ttl<120 ),都去更新读数据库,效果其实和获取不到缓存数据的时候再更新是一样的。

  • 資深大佬 : axbx

    写缓存的时候加一个更新间隔时间,比缓存失效时间短,每次读取的时候去判断一下是否已经过了这个间隔时间,过了的话异步去更新缓存。

  • 資深大佬 : cassyfar

    一般直接开一个单独线程更新 cache 。当前所有的 cache miss 全部去读数据库。你不停查看 TTL 然后更新只会让你代码特别肿胀,而且如果更新 cache 失败,不还是会失效然后会遇到老问题吗?

  • 資深大佬 : xxy973211

    @pangleon 这种数据为啥不直接设置成不过期呢?即使数据库有更新,刷新缓存就行了吧

  • 資深大佬 : petercui

    过期或者修改了数据,只需要让缓存失效就行了,然后下次读取的时候再写入缓存。

  • 資深大佬 : keepeye

    1.sleep 不是不行,就怕雪崩,具体要看并发量和持续时间以及刷新缓存耗时
    2.直接返回错误给客户端,让客户端自己重试,这个是可行的,但只适用普通场景
    3.若要始终保证缓存有效,那只能单独一个线程,在缓存快要过期前,提前更新缓存

  • 資深大佬 : pangleon

    @xxy973211 如果你们数据量少,可以这么干。
    但是假如你们有 1000W 数据,REDIS 占用的内存有多少考虑过么?可以通过这个网站计算 http://www.redis.cn/redis_memory/
    所以全部数据不过期适合全部数据量小的情况。
    也可以只设置热点数据永不过期,前提是你要知道哪些是热点数据以及热点数据量小的情况。相应的有了 REDIS 缓存预热的说法。

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

    主的问题是,业内常见的处理他不想用,正常查不到缓存就返回空前端处理一下,就留一个获取到锁的线程去更新。
    主不想返回空,那么那么多线程在那里轮询类似自旋,就比较烦躁了。
    还有一种方案就是 2 套 REDIS,一套过期时间长一些作为备份缓存,过期时间短的查不到去查这个备份的。
    问题是 REDIS 在云服务商那不便宜啊,如果数据量一大成本是个问题。

  • 資深大佬 : luzhh

    Java 的话,用 FutureTask,1000 个请求过来,只有一个请求实际区读取数据库,其他的请求等待第一个请求拿到结果之后返回结果即可。

  • 資深大佬 : Foredoomed

    都不是,没拿到锁的线程等待

  • 資深大佬 : imjamespond

    react 模式加队列即可

  • 主 資深大佬 : rocky114

    @Foredoomed php+redis 分布式锁没法实现阻塞等待吧,php+mysql 实现的锁倒是可以阻塞等待

  • 資深大佬 : vindurriel

    两个方案刚好是 CAP 定理中选 C 还是 A 的问题 方案一选 C 问题是 200ms 不一定够 还得加随机数削峰 方案二选 A 增加了客户端 /使用者的负担

  • 資深大佬 : hxndg

    我没实现过分布式,不过设计过单机的线程缓存操作之类的。。。。。提个自己的想法
    创建一个缓存的队列,命名为缓冲垫,表示没命中,目前正在从数据库拿数据。

    如果工作者线程发现缓存没命中,这个数据也没在缓冲垫里,直接去数据库那数据就完了,然后一次性更新数量多一些的数据,如果有局部性可言。

    如果工作者线程发现缓存没命中,这个数据在缓冲垫里,那就直接返回,先去做别的事务,等待已经去数据库取数据的工作者线程把数据取回来,再继续执行。

    总之就是减少忙等,除非忙等的时间特别短。

  • 主 資深大佬 : rocky114

    @hxndg 这里是不允许多个线程直接读取数据库的,因为流量都直接到数据库容易把数据库搞奔溃了,所以需要增加一把锁,我这里的疑问点是其它拿不到锁的线程应该怎么处理?前排给出的方案是存储的缓存值增加 ttl 信息,这样每次读取缓存时判断下快过期的就重新设置一下缓存,这样就保证了缓存不会过期。可能有人会说一段时间没有访问缓存失效了,一下子并发上来还是会遇到问题,我认为一段时间没有访问的缓存不属于热点缓存,访问量应该不大。最后配合上凌晨缓存热更新应该能基本解决这些问题

  • 資深大佬 : sujin190

    加锁就是了,搞个超高性能的锁服务,如果锁服务也挂了就返回让客户端重试,而且只需要在无缓存的时候才加锁从数据库加载,指单纯用于加锁的话,设计好搞个十倍 redis 性能的,妥妥的

  • 資深大佬 : sujin190

    @xxy973211 #33 不过期有个极大问题是一致性维护太难了,写错了就麻烦死了,内存管理也很麻烦,缓存的话过了缓存时间就会从数据库加载,等同于系统有自动修复能力,维护会容易太多了

  • 資深大佬 : hxndg

    @rocky114
    如果单纯从缓存热点的角度来考虑,你使用 ttl 是可以做的,但是具体 ttl 的值哪种更合适这个只能测出来,因为不同的流量,ttl 的值可能需要动态变化。

    一段时间没被访问的缓存走数据库是没问题的,但是要防备热点更新,这种比方说很多人每天早上醒过来的时候会看新闻这种。

    你提的实际上就是 lru 算法的各种实现,搜搜看?

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具