高并发系统实战课
徐长龙
前微博架构师、极客时间架构师
11663 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 30 讲
结束语&结课测试 (2讲)
高并发系统实战课
15
15
1.0x
00:00/00:00
登录|注册

02|缓存一致:读多写少时,如何解决数据更新缓存不同步?

异步脚本遍历数据库刷新缓存
识别主要实体ID刷新缓存
版本号缓存设计
订阅数据库更新消息
人工维护缓存方式
更新缓存的代码示例
小容量Redis缓解热点高并发查询
临时缓存+长期热缓存的实现
缓存穿透问题
关系型和统计型数据缓存刷新
单条实体数据缓存刷新
临时缓存的实现代码示例
频繁查询的数据放入缓存
评估缓存是否有效
热点数据放入缓存
思考题
总结
长期热数据缓存
缓存更新不及时问题
临时热缓存
缓存性价比
缓存一致:读多写少时,如何解决数据更新缓存不同步?

该思维导图由 AI 生成,仅供参考

你好,我是徐长龙,我们继续来看用户中心性能改造的缓存技巧。
上节课我们对数据做了归类整理,让系统的数据更容易做缓存。为了降低数据库的压力,接下来我们需要逐步给系统增加缓存。所以这节课,我会结合用户中心的一些业务场景,带你看看如何使用临时缓存或长期缓存应对高并发查询,帮你掌握高并发流量下缓存数据一致性的相关技巧。
我们之前提到过,互联网大多数业务场景的数据都属于读多写少,在请求的读写比例中,写的比例会达到百分之一,甚至千分之一。
而对于用户中心的业务来说,这个比例会更大一些,毕竟用户不会频繁地更新自己的信息和密码,所以这种读多写少的场景特别适合做读取缓存。通过缓存可以大大降低系统数据层的查询压力,拥有更好的并发查询性能。但是,使用缓存后往往会碰到更新不同步的问题,下面我们具体看一看。

缓存性价比

缓存可以滥用吗?在对用户中心优化时,一开始就碰到了这个有趣的问题。
就像刚才所说,我们认为用户信息放进缓存可以快速提高性能,所以在优化之初,我们第一个想到的就是将用户中心账号信息放到缓存。这个表有 2000 万条数据,主要用途是在用户登录时,通过用户提交的账号和密码对数据库进行检索,确认用户账号和密码是否正确,同时查看账户是否被封禁,以此来判定用户是否可以登录:
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了在读多写少场景下,解决数据更新缓存不同步问题的技巧。作者首先强调了评估缓存有效性的重要性,提出了临时热缓存的概念,以及如何通过临时缓存有效降低数据库查询压力。此外,文章详细讨论了缓存更新不及时的问题,并提出了针对单条实体数据缓存刷新的解决方案。还介绍了关系型和统计型数据缓存刷新的常用方法,包括人工维护缓存、订阅数据库变化、版本号缓存设计等。另外,还探讨了长期热数据缓存的实现方式,以及如何避免缓存穿透问题。最后,通过代码示例展示了一种“临时缓存+长期热缓存”混用的实现方式,以及如何通过异步脚本定期扫描热缓存列表来主动推送缓存。整体而言,本文通过实际业务场景,深入浅出地介绍了在高并发查询下缓存数据一致性的相关技巧,对于需要解决类似问题的读者具有一定的参考价值。文章内容涵盖了缓存有效性评估、临时热缓存、缓存更新、长期热数据缓存、缓存穿透问题等技术特点,为读者提供了全面的解决方案和实际操作示例。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《高并发系统实战课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(22)

  • 最新
  • 精选
  • Daniel
    置顶
    1. 使用 Bloom Filter 识别热点 key 时,有时会识别失误,进而导致数据没有找到,那么如何避免这种情况呢? 通过我的 “机器学习的经验”,我觉得是这个布隆过滤器的哈希算法有点过拟合了,也就是说容错率高了,在资金充足的情况下先试着调低“容错率“(超参数)提升容量试试(不知道工业界上布隆过滤器的容错率能设置成 0%吗?但后期可能随着数据量的增长也是一个无限扩容的”吞金兽“呀),但我感觉我这个想法在工业界应该不成立。 第二种方法,我想到的是,如果这个 key 被误识别为”hotkey”的话,就在内存中记入“not_hotkey”列表,每次数据进来的时候,先用 缓存里的 not_hotkey里的列表来筛,要是不是hotkey就做成临时缓存,要是这个key是hotkey的话,就进行长期缓存来处理。 2. 使用 Bloom Filter 只能添加新 key,不能删除某一个 key,如果想更好地更新维护,有什么其他方式吗? 对于长时间不用的 key ,我认为可以设置一个“失效时间”,比如 一周内不用,就自动清除掉这个key。 之后在新的一周,把失效的key清理出去,再重新整理好一个列表,重新更新一遍这个布隆过滤器的新的哈希算法表。 (但感觉这个方法貌似不是最优的,也要在半夜用户量访问少的时间点去做变更处理) 老师想请教一个问题,对于 hotkey (热点数据)这个工业界的评价标准是不是不同行业会不一样呀? 比较想知道工业界上是用什么方法(一般统计方法?机器学习聚类?深度学习网络?)和工具(数据埋点?用户操作行为分析?),来做 “热点数据”的 判别的?

    作者回复: 你好,Daniel,很高兴收到你的留言 第一种方法,如果使用集合检测也可以达到同样效果,你提到的方法缺陷在于not_hotkey会很大,这里需要记录所有不是hot key的所有key。目前bloomfilter只是个模糊筛选,用小量数据换更好的性能,但是他确实误判概率多一些。 第二个问题也是一个办法,但是需要我们能够精准控制这一批数据过期时间,但是我们在这节课用它主要是为了判断本地缓存中是否有这个缓存,由于无法判断所以需要每次询问,会导致系统更加复杂。 第三个问题 主要是访问量,我们可以将一些key 做一些count统计,数据埋点就足够了,机器学习和深度学习的QPS有些低,我印象里,一个1w元的显卡做发音评分,一秒钟只能处理4个请求,场景不太适合,有点小才大用了。

    2022-10-27归属地:北京
    3
    3
  • peter
    置顶
    请教老师几个问题: Q1:缓存都是有超时时间的,从这个意义上说,都是“临时”的,为什么本文还要分为“临时”缓存和“长期”缓存? Q2:“临时”缓存和“长期”缓存在实现上可以用同一个软件吗? 比如,两者都可以用Redis实现?或者,“临时缓存”是用一个组件实现(非Redis)而“长期缓存”用Redis实现? 或者,“临时缓存”在代码中实现而“长期缓存”用Redis? Q3:人工维护缓存,怎么操作? 缓存数据一般都比较多,人工怎么能够维护一堆数据?具体是怎么操作的? 有一个界面,通过此界面来操作吗?

    作者回复: 你好,peter,又见面了,感谢你的留言~ Q1:由于我们的大多数数据都是有时效性的,我们很少去做永久的内存缓存,毕竟内存还是很贵的,我们需要考虑性价比。长期缓存可以是一天,临时TTL是30秒。同时长期的更新是定期脚本刷新,临时是用到才会放进去一会儿。再来看,长期的访问很频繁,如果放开会导致数据库压力很大,但是临时的由于访问量小所以不用特意防击穿。 Q2:如果缓存压力不大可以用一个,如果很大会再做个L1缓存,在每台业务服务器上,这样能缓解核心缓存压力。 Q3:这个属于人工写代码,如我更新了用户的昵称,那么我会刷新这个用户的所有帖子的缓存,以及这个用户的所有最近留言缓存,以及这个用户的个人信息缓存。这是一种,还有一种就是你说的界面配置规则,但是这些都是要能快速定位的才可以。你可能会碰到,用户昵称以rick开头的账号有多少个,当我们改昵称为这个的时候,对于这种条件多样的,刷新哪个缓存不好确定,只能临时缓存30秒等他过期后刷新

    2022-10-26归属地:北京
    3
    6
  • 1. Bloom Filter 存在误报,会把不是热点的 key 识别成热点key, 所以需要一个 0误报的算法,数据结构 所以 Cuckoo Filter 布谷鸟过滤器来了 2. 可以定期或者其他策略 重新构造 Bloom Filter > 其实上面的2个问题,都可以使用 Cuckoo Filter 来解决

    作者回复: 你好,很高兴收到你的回复,没错!确实Cuckoo Filter能够解决所有问题!同时补充提醒:他也有一些缺点使用的时候要注意,性能没有bf高,同时删除存在误删情况~

    2022-11-03归属地:北京
    3
    4
  • 传输助手
    读取数据库设置缓存的时候,为了不受数据库主从延迟的影响,是不是需要强制读主库?

    作者回复: 你好,传输助手,很高兴收到你的留言,这里有一个前提,就是我们加缓存的服务基本都是读并发高的服务,对于MySQL主库来说,问题就是全局只有一个主库,所以他是单点,同时更脆弱,理论上这种读压力尽量不要压到主库上~

    2022-10-26归属地:北京
    2
    3
  • 一颗苹果
    布隆过滤器的缺点,可以用hashmap那个链表解决,遇到冲突了延伸出一个链表,但得标注清楚这个元素是哪个key的。链表是空,直接set和普通布隆过滤器一样,不是空就追加一个元素(标注好哪个key)。删除的话遇到链表也遍历,根据匹配的key来删除。如果足够稀疏,那性能和一般布隆一样,数据越密集性能越下降。要准确性就只能牺牲性能来换吧

    作者回复: 你好,一颗苹果,很高兴收到你的思路,这是个不错的办法。简单翻译一下,hashmap的hash环如果不够大,很多key都会碰撞在一个hash块内,而碰撞在一起的数据会在hashmap块内用链表保存,链表每次查找都要遍历所以会有一定性能下降,所以如果key少链表小的话会很快,当然这里没提及hash计算的时间复杂度。

    2022-12-22归属地:北京
    2
  • Elvis Lee
    1. 使用 Bloom Filter 识别热点 key 时,有时会识别失误,进而导致数据没有找到,那么如何避免这种情况呢? 布隆可以判断一定不存在的数据,那么是否可以认为,只要插入不成功,即为热数据,但在设计布隆的时候需要根据业务来设置好容量和容错率。同时布隆删除操作在生产上不建议,最好是持久化后用版本号去区分。如果是离线链路,更推荐生成布隆文件,推送去客户端。实时的,目前接触是保存在Redis,Redis7的版本好像已经不需要插件

    作者回复: 你好,Elvis Lee,很高兴收到你的留言,不建议这样使用bloomfilter,主要原因在于,他的识别和md5计算结果一样,有的时候不同的key返回的结果是一致的,所以这样是拿不到准确结果的。

    2022-10-26归属地:北京
    4
    2
  • hhh
    没有抢到锁的sleep 1s然后去查询,这样接口耗时不是就会肯定大于1s吗 ,假如超时配置小于1s,这次请求不是必定会超时嘛

    作者回复: 你好,hhh,时间不是绝对的这个可以根据情况进行调整,以前我们普遍是200ms左右,相对的这个方式比直接打沉中心数据库好那么一点,前提我们前端服务器足够多。

    2022-11-11归属地:北京
    3
    1
  • 门窗小二
    老师!但是课后题有答疑篇吗?

    编辑回复: 建议先自己尝试回答,课后题答案后续看回答情况再公布。

    2022-10-28归属地:北京
    1
  • 黄堃健
    老师,看了留言:说用布谷鸟过滤器,f = fingerprint(x); i1 = hash(x); i2 = i1 ⊕ hash( f); 但是 由于存的是指纹, 用的是hash算法,不排除 data1,data2 计算的tingerprint(x),hash(x) 都相同。 这样可能存在误删,也存在误判情况

    作者回复: 是的,但是,这个方式可以减少大量计算,当误判后再用一个算法复核一下就好了

    2024-03-01归属地:广东
  • 黄堃健
    不过,通过队列更新消息这一步,我们还会碰到一个问题——条件批量更新的操作无法知道具体有多少个 ID 可能有修改 这会有什么问题? 一条条更新不就是可以吗? 因为没有接触过这块,不理解为什么要合并更新?

    作者回复: 你好,这里是因为如果我缓存的是一批符合特定条件的列表数据,其中有几个数据状态发生了改变,由于缓存是被动的,当基础数据发生变化,我无法准确知道当前还有那些缓存保存的是过去的数据,所以无法对他们进行更新,这对数据并发高且准确性要求高的上层服务来说很致命

    2024-03-01归属地:广东
收起评论
显示
设置
留言
22
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部