13 | 缓存的使用姿势(一):如何选择缓存的读写策略?
13 | 缓存的使用姿势(一):如何选择缓存的读写策略?
讲述:唐扬
时长12:40大小11.60M
Cache Aside(旁路缓存)策略
Read/Write Through(读穿 / 写穿)策略
Write Back(写回)策略
课程小结
一课一思
赞 26
提建议
精选留言(77)
- 小可置顶2019-10-16工作中老师说的这几种缓存策略基本都用到了,特别是统计接口响应时间那个例子和我们的场景一样。管理平台统计一百多个节点的上报到队列中的数据,原来是按消费一批统计完直接批量入库,数据量太大(每秒两三千),压力全在数据库,系统也比较卡,并且如果入库不及时就会数据积压,后续都跟不上。现在是消费统计和入库分开,消费统计先放缓存,每分钟再将缓存同步到数据库,同步成功再提交消费offset,目前看还算稳定。
作者回复: 👍
共 9 条评论37 - 任鹏斌2019-10-16读到这里突然想到一个开源项目https://github.com/apache/ignite,内存数据库,结合了关系型数据库和缓存的优点,如果只当缓存使用的话,可以自动加载和写入关系型数据库中的数据。完美解决一致性问题。但是好像国内使用的人不多。
作者回复: 好滴,我关注一下~
共 6 条评论45 - 岁寒2019-10-16缓存一定会引入不一致的。。
作者回复: 是的 所以解决的办法需要权衡一致性和性能
30 - 无形2019-11-04文中提到的第一个第一个缓存和数据不一致的问题,我认为这个问题的原因是,多个客户端更新缓存和数据库之间是无序的、并发的操作,这样必然导致数据不一致的问题,因此我们采用了监听binlog的方式,把Binlog扔到消息队列中,由一个leader来消费,负责更新缓存,保证了写缓存操作之间的顺序性,保证了缓存的准确性,避免了频繁读库。
作者回复: 这样确实是一个比较好的方式,只是会稍微复杂
共 15 条评论21 - 长期规划2019-10-24老师,我理解WriteBack策略相当于缓存和缓冲区合二为一了,对吧。据我所知,MySQL的buffer pool使用了WriteBack策略,但为了防止系统崩溃后数据丢失,MySQL使用了WAL(Write-Ahead Logging)机制,写先日志。好像WAL在HBase等系统也在用
作者回复: 是的
15 - yc2019-10-17write back策略读请求时“如果缓存不是脏的,则由缓存组件将后端存储中的数据加载到缓存中”,是不是写错了,如果缓存不是脏的,直接从缓存返回即可,为什么还要从后端记载数据到缓存然后返回?我看留言很多人都有同样的疑问,请老师解释一下,谢谢。
作者回复: write back策略其实不算数据库和mc之间的策略,而是计算机体系结构中的策略,比如磁盘文件的缓存。它的完整读策略是这样的:如果缓存命中,则直接返回;如果缓存不命中,则重新找一个缓存块儿,如果这个缓存块儿是脏的,那么写入后端存储,并且把后端存储中的数据加载到缓存中;如果不是脏的,那么就把后端存储中的数据加载到缓存,然后标记缓存非脏。 是我的讲述不太清晰,感谢你的提问
13 - 约书亚2019-10-16Cache Aside对缓存命中率两种解决方案中的1,可能是我没看懂,感觉没解决问题啊? 这里说在“更新数据时也更新缓存”,我理解就是先更新DB再更新缓存,这样除非在更新DB之前加分布式锁,否则在更新DB之后加分布式锁,再更新缓存,依然较高可能出现不一致的情况。 实际中我们确实用在更新缓存时用分布式锁或本地锁,只不过是发现缓存为空而去读DB时,为了解决穿透问题。 纯个人见解,除了cache aside,另外两种更贴近底层系统开发而不是商业应用开发。因为我们大多数人做的系统,都是低速存储都是数据库,是有复杂的业务逻辑约束的,比如唯一性等,不是那种简单的page/cpu cahce。我们经常的写操作一般都要借助数据库来检验这些约束并且在出错之后返回给用户。而如果直接与缓存打交道,且不论有些缓存的实现并不保证数据可靠性,也不能依靠缓存检验这些约束。 其实现在很多系统用的一种缓存模式是类似CQRS,写直接修改DB,异步更新到缓存,读只从缓存读数据。适合对数据不一致窗口可以容忍的场景。展开
作者回复: 1. 是在更新数据库前加锁,锁的粒度是大了一些 2. 确实是更偏重底层开发
共 3 条评论13 - 老菜2020-07-03老师讲到的缓存策略在工作中都或多或少用到过或者见到过。不过除了老师讲的的策略之外,我们项目中用到了另外的缓存读写策略。 背景是这样的:”商品中心“维护了商品基础信息包括价格信息,作为微服务对外提供统一 API 访问入口,在前端比如购物车里需要调用“商品中心”提供的 API 获取价格信息渲染到页面,但是每次获取价格信息都涉及到网络调用,并且商品价格等信息一单写入修改频率是非常低的。基于以上原因在前端增加一层关于商品价格等信息的缓存。 写入策略是这样的:首先全量拷贝“商品中心”的所有商品价格信息到前端的缓存中,当然这个是一个非常耗时的操作并且这个操作只有在最开始初始缓存时执行且仅执行一次。然后订阅“商品中心”的改价操作的binlog,在发生商品信息修改时,更新前端缓存的价格信息,还有一个兜底策略就是定时任务轮询商品中心发生改价的商品信息,然后更新前端缓存的价格信息。 读缓存策略是这样的:在前端查询商品价格信息时,只会与缓存交互。 这样做的目的就是将购物车里商品查询性能做到极致,因为在活动期特别是双 11 时候购物车或商品详情访问可达亿级 PV ;另外就是商品变价都是运营人员提前 1-2 天配置好的,比如商品 A 原价是 10 元,双 11 期间改价为 8.9 元,价格生效期是在双 11 期间,这种缓存读取策略既提高了查询性能,又不会受数据短暂时间延迟的影响。展开共 5 条评论9
- 程序水果宝2019-10-22使用写回策略,如果在缓存更新到数据库之前设备掉电了,那这样数据岂不是丢失了,请问这是怎么解决的呢,通过主备机制吗,缓存数据写两份?
作者回复: 是有这个问题,比如pagecache在机器掉电之后就都是数据了。一个办法是将写入缓存的操作写入log里,类似lsm树的write ahead log
共 2 条评论6 - 七号叽2019-10-16老师你好,请问一下write back策略为什么读请求时是“如果缓存不是脏的,则由缓存组件将后端存储中的数据加载到缓存中”,而不是直接返回?谢谢
作者回复: 否则缓存块就可能永远是脏的了
共 10 条评论6 - 王大伟2019-10-16Read/Write Through策略与MySQL的Buffer Pool的机制很相似啊共 3 条评论6
- fdconan2019-10-23Cache Aside(旁路缓存)策略,对于读多写少场景,当一个写操作更新db后同时删除缓存。然后多个读就会回源,这不会造成db压力么?
作者回复: 会的,这就是狗桩效应嘛~
5 - Geek_49305e2019-10-18老师,1. 一种做法是在更新数据时也更新缓存,只是在更新缓存前先加一个分布式锁,因为这样在同一时间只允许一个线程更新缓存,就不会产生并发问题了。 这个解决方案应该有些不严谨的地方,如有A,B两个线程,A先更新数据库的值为20,而后A获取到更新缓存的分布式的锁,但未释放锁,此时B更新数据库的值为21,更新后尝试获取锁,此时获取锁一定会失败,抛出异常,终止更新缓存。 最后缓存中的数据为A更新的的值20
作者回复: 这种情况下,在更新数据库之前就要加锁
共 4 条评论5 - 饭团2019-10-16老师问您一个问题!其实如果是使用.Cache Aside方式的话。在写的时候时候因为更新数据后,删除了缓存。在高并发情况下。那么可能会出现以下情况: 主从同步的情况下,从库没来得及同步。大量的读请求返回的是从库的旧数据。而这个时候读的数据会被动写入缓存。那就存在很大的问题!这种应该怎么处理!如果是这样的话?是不是只能依靠分布式锁来实现了!
作者回复: 是的 这样只能更新缓存,然后使用分布式锁来控制
共 4 条评论4 - MoonGod2019-10-16老师请问一个问题,在cache aside策略中,如果先更新数据库,再删除缓存。这样如果读请求访问量很大,会短时间出现大量请求穿透到数据库,这里有好的办法优化吗?
作者回复: 如果更新不频繁的话,其实还OK 如果更新频繁,可以加分布式锁,让单一线程可以更新这条数据;或者设置短的过期时间,让可能出现的不一致数据尽快过期
共 3 条评论3 - null2020-07-20原文:Write Back 策略的核心思想是在写入数据时只写入缓存,并且把缓存块儿标记为“脏”的。而脏块儿只有被再次使用时才会将其中的数据写入到后端存储中。 问题一: 再次使用时,这里的“使用”是指 read 或 write 操作,还是仅是 write 操作? ----- 原文:在“Write Miss”的情况下,我们采用的是“Write Allocate”的方式,也就是在写入后端存储的同时要写入缓存,这样我们在之后的写请求中都只需要更新缓存即可,而无需更新后端存储了,我将 Write back 策略的示意图放在了下面。 问题二: Write Back 策略,当数据不在缓存时,需要要写入后端存储和缓存。为什么后续的写请求,只需要更新缓存(并且把缓存标记为“脏”),不需要更新后端存储呀? 我的理解是:这好像回到了问题一的场景了,再次使用时(后续的写请求),把被标记为“脏”的数据写回后端存储。 问题三: 下方的示意图,如果为脏,写后端存储。之后又写缓存,并标记为脏。 这里应该是将上一次的脏数据(本次写请求之前的脏数据)刷到存储。而本次写请求的修改,是体现在写后端存储之后的写缓存操作。本次的写修改,需要等待下一次使用时,才会写入到后端存储。是这样么? ----- 原文:Write Back 策略的读的策略也有一些变化了。我们在读取缓存时如果缓存不命中则寻找一个可用的缓存块儿, 如果这个缓存块儿是“脏”的,就把缓存块儿中之前的数据写入到后端存储中,并且从后端存储加载数据到缓存块儿, 如果不是脏的,则由缓存组件将后端存储中的数据加载到缓存中,最后我们将缓存设置为不是脏的,返回数据就好了。 问题四: 读的时候,如果不命中缓存,为啥要寻找一个可用的缓存块?这个缓存块是本次读请求业务相关的么?如果相关,那不是缓存不命中了么?如果不相关,那咋知道该如何刷回后端存储呀? 问题五: 还是没能理解,当读操作时,既然缓存不命中了,何来缓存块“脏”与“不脏”这一说呢? 问题六: 假设我理解了缓存不命中,并且缓存块是“脏”的场景。 此时将缓存块的数据写入后端存储,为什么还需要从后端存储加载数据到缓存块?此时后端存储的数据,不应该和缓存块里的数据是一致的么? 问题七: 假设我理解了缓存不命中,并且缓存块是不“脏”的场景。 为什么还需要从后端存储加载数据到缓存,数据不是不脏么,不脏就是没有被修改过,那应该也是和后端存储的数据是一致的吧? ----- 谢谢老师,期待您的回复!!展开共 2 条评论2
- 长期规划2019-12-18老师,写穿策略中先写缓存再同步DB,此过程若是多线程,需要加锁吧,如果不加,也存在写覆盖问题
作者回复: 是的,可以加分布式锁
2 - 长期规划2019-12-17Cache Aside在更新频繁时的两种解决策略应该是很经典的CAP问题吧,要保证C就要一定程度上牺牲A,反之亦然。看哪个更重要
作者回复: 是的
共 2 条评论2 - 你净瞎说~2019-10-29脏是针对缓存块来说的吧?缓存也有脏不脏吗?
作者回复: 指的是缓存使用的那块内存有未被刷新到后端存储中的数据,就认为是脏的
共 3 条评论2 - 拒绝2019-10-17老师你好,请问一下write back策略为什么读请求时是“如果缓存不是脏的,则由缓存组件将后端存储中的数据加载到缓存中”,而不是直接返回?,这里您说: 否则缓存块就可能永远是脏的了。 对此表示疑惑,既然不是脏数据,难道不是直接返回就好了?
作者回复: write back策略其实不算数据库和mc之间的策略,而是计算机体系结构中的策略,比如磁盘文件的缓存。它的完整读策略是这样的:如果缓存命中,则直接返回;如果缓存不命中,则重新找一个缓存块儿,如果这个缓存块儿是脏的,那么写入后端存储,并且把后端存储中的数据加载到缓存中;如果不是脏的,那么就把后端存储中的数据加载到缓存,然后标记缓存非脏。 是我的讲述不太清晰,感谢你的提问
2