高并发系统设计40问
唐扬
美图公司技术专家
立即订阅
9202 人已学习
课程目录
已更新 38 讲 / 共 40 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 为什么你要学习高并发系统设计?
免费
基础篇 (6讲)
01 | 高并发系统:它的通用设计方法是什么?
02 | 架构分层:我们为什么一定要这么做?
免费
03 | 系统设计目标(一):如何提升系统性能?
04 | 系统设计目标(二):系统怎样做到高可用?
05 | 系统设计目标(三):如何让系统易于扩展?
06 | 面试现场第一期:当问到组件实现原理时,面试官是在刁难你吗?
演进篇 · 数据库篇 (5讲)
07 | 池化技术:如何减少频繁创建数据库连接的性能损耗?
08 | 数据库优化方案(一):查询请求增加时,如何做主从分离?
09 | 数据库优化方案(二):写入数据量增加时,如何实现分库分表?
10 | 发号器:如何保证分库分表后ID的全局唯一性?
11 | NoSQL:在高并发场景下,数据库和NoSQL如何做到互补?
演进篇 · 缓存篇 (6讲)
12 | 缓存:数据库成为瓶颈后,动态数据的查询要如何加速?
13 | 缓存的使用姿势(一):如何选择缓存的读写策略?
14 | 缓存的使用姿势(二):缓存如何做到高可用?
15 | 缓存的使用姿势(三):缓存穿透了怎么办?
16 | CDN:静态资源如何加速?
加餐 | 数据的迁移应该如何做?
演进篇 · 消息队列篇 (6讲)
17 | 消息队列:秒杀时如何处理每秒上万次的下单请求?
18 | 消息投递:如何保证消息仅仅被消费一次?
19 | 消息队列:如何降低消息队列系统中消息的延迟?
20 | 面试现场第二期:当问到项目经历时,面试官究竟想要了解什么?
用户故事 | 从“心”出发,我还有无数个可能
期中测试 | 10道高并发系统设计题目自测
演进篇 · 分布式服务篇 (9讲)
21 | 系统架构:每秒1万次请求的系统要做服务化拆分吗?
22 | 微服务架构:微服务化后,系统架构要如何改造?
23 | RPC框架:10万QPS下如何实现毫秒级的服务调用?
24 | 注册中心:分布式系统如何寻址?
25 | 分布式Trace:横跨几十个分布式组件的慢请求要如何排查?
26 | 负载均衡:怎样提升系统的横向扩展能力?
27 | API网关:系统的门面要如何做呢?
28 | 多机房部署:跨地域的分布式系统如何做?
29 | Service Mesh:如何屏蔽服务化系统的服务治理细节?
演进篇 · 维护篇 (5讲)
30 | 给系统加上眼睛:服务端监控要怎么做?
31 | 应用性能管理:用户的使用体验应该如何监控?
32 | 压力测试:怎样设计全链路压力测试平台?
33 | 配置管理:成千上万的配置项要如何管理?
34 | 降级熔断:如何屏蔽非核心系统故障的影响?
高并发系统设计40问
登录|注册

13 | 缓存的使用姿势(一):如何选择缓存的读写策略?

唐扬 2019-10-16
上节课,我带你了解了缓存的定义、分类以及不足,你现在应该对缓存有了初步的认知。从今天开始,我将带你了解一下使用缓存的正确姿势,比如缓存的读写策略是什么样的,如何做到缓存的高可用以及如何应对缓存穿透。通过了解这些内容,你会对缓存的使用有深刻的认识,这样在实际工作中就可以在缓存使用上游刃有余了。
今天,我们先讲讲缓存的读写策略。你可能觉得缓存的读写很简单,只需要优先读缓存,缓存不命中就从数据库查询,查询到了就回种缓存。实际上,针对不同的业务场景,缓存的读写策略也是不同的。
而我们在选择策略时也需要考虑诸多的因素,比如说,缓存中是否有可能被写入脏数据,策略的读写性能如何,是否存在缓存命中率下降的情况等等。接下来,我就以标准的“缓存 + 数据库”的场景为例,带你剖析经典的缓存读写策略以及它们适用的场景。这样一来,你就可以在日常的工作中根据不同的场景选择不同的读写策略。

Cache Aside(旁路缓存)策略

我们来考虑一种最简单的业务场景,比方说在你的电商系统中有一个用户表,表中只有 ID 和年龄两个字段,缓存中我们以 ID 为 Key 存储用户的年龄信息。那么当我们要把 ID 为 1 的用户的年龄从 19 变更为 20,要如何做呢?
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《高并发系统设计40问》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(40)

  • 小可 置顶
    工作中老师说的这几种缓存策略基本都用到了,特别是统计接口响应时间那个例子和我们的场景一样。管理平台统计一百多个节点的上报到队列中的数据,原来是按消费一批统计完直接批量入库,数据量太大(每秒两三千),压力全在数据库,系统也比较卡,并且如果入库不及时就会数据积压,后续都跟不上。现在是消费统计和入库分开,消费统计先放缓存,每分钟再将缓存同步到数据库,同步成功再提交消费offset,目前看还算稳定。

    作者回复: 👍

    2019-10-16
    8
    6
  • 任鹏斌
    读到这里突然想到一个开源项目https://github.com/apache/ignite,内存数据库,结合了关系型数据库和缓存的优点,如果只当缓存使用的话,可以自动加载和写入关系型数据库中的数据。完美解决一致性问题。但是好像国内使用的人不多。

    作者回复: 好滴,我关注一下~

    2019-10-16
    11
  • 七号叽
    老师你好,请问一下write back策略为什么读请求时是“如果缓存不是脏的,则由缓存组件将后端存储中的数据加载到缓存中”,而不是直接返回?谢谢

    作者回复: 否则缓存块就可能永远是脏的了

    2019-10-16
    4
    4
  • 岁寒
    缓存一定会引入不一致的。。

    作者回复: 是的 所以解决的办法需要权衡一致性和性能

    2019-10-16
    4
  • 王大伟
    Read/Write Through策略与MySQL的Buffer Pool的机制很相似啊
    2019-10-16
    2
    4
  • Geek_49305e
    老师,1. 一种做法是在更新数据时也更新缓存,只是在更新缓存前先加一个分布式锁,因为这样在同一时间只允许一个线程更新缓存,就不会产生并发问题了。 这个解决方案应该有些不严谨的地方,如有A,B两个线程,A先更新数据库的值为20,而后A获取到更新缓存的分布式的锁,但未释放锁,此时B更新数据库的值为21,更新后尝试获取锁,此时获取锁一定会失败,抛出异常,终止更新缓存。 最后缓存中的数据为A更新的的值20

    作者回复: 这种情况下,在更新数据库之前就要加锁

    2019-10-18
    1
    3
  • 约书亚
    Cache Aside对缓存命中率两种解决方案中的1,可能是我没看懂,感觉没解决问题啊?
    这里说在“更新数据时也更新缓存”,我理解就是先更新DB再更新缓存,这样除非在更新DB之前加分布式锁,否则在更新DB之后加分布式锁,再更新缓存,依然较高可能出现不一致的情况。
    实际中我们确实用在更新缓存时用分布式锁或本地锁,只不过是发现缓存为空而去读DB时,为了解决穿透问题。
    纯个人见解,除了cache aside,另外两种更贴近底层系统开发而不是商业应用开发。因为我们大多数人做的系统,都是低速存储都是数据库,是有复杂的业务逻辑约束的,比如唯一性等,不是那种简单的page/cpu cahce。我们经常的写操作一般都要借助数据库来检验这些约束并且在出错之后返回给用户。而如果直接与缓存打交道,且不论有些缓存的实现并不保证数据可靠性,也不能依靠缓存检验这些约束。
    其实现在很多系统用的一种缓存模式是类似CQRS,写直接修改DB,异步更新到缓存,读只从缓存读数据。适合对数据不一致窗口可以容忍的场景。

    作者回复: 1. 是在更新数据库前加锁,锁的粒度是大了一些
    2. 确实是更偏重底层开发

    2019-10-16
    3
  • fdconan
    Cache Aside(旁路缓存)策略,对于读多写少场景,当一个写操作更新db后同时删除缓存。然后多个读就会回源,这不会造成db压力么?

    作者回复: 会的,这就是狗桩效应嘛~

    2019-10-23
    2
  • 程序水果宝
    使用写回策略,如果在缓存更新到数据库之前设备掉电了,那这样数据岂不是丢失了,请问这是怎么解决的呢,通过主备机制吗,缓存数据写两份?

    作者回复: 是有这个问题,比如pagecache在机器掉电之后就都是数据了。一个办法是将写入缓存的操作写入log里,类似lsm树的write ahead log

    2019-10-22
    2
  • 饭团
    老师问您一个问题!其实如果是使用.Cache Aside方式的话。在写的时候时候因为更新数据后,删除了缓存。在高并发情况下。那么可能会出现以下情况:
    主从同步的情况下,从库没来得及同步。大量的读请求返回的是从库的旧数据。而这个时候读的数据会被动写入缓存。那就存在很大的问题!这种应该怎么处理!如果是这样的话?是不是只能依靠分布式锁来实现了!

    作者回复: 是的 这样只能更新缓存,然后使用分布式锁来控制

    2019-10-16
    3
    2
  • Fourty Seven
    老师,写回策略中,读请求未命中,为啥要找可用缓存块,可用是指什么?有数据?没数据?这块没太理解
    2019-11-07
    1
    1
  • 无形
    文中提到的第一个第一个缓存和数据不一致的问题,我认为这个问题的原因是,多个客户端更新缓存和数据库之间是无序的、并发的操作,这样必然导致数据不一致的问题,因此我们采用了监听binlog的方式,把Binlog扔到消息队列中,由一个leader来消费,负责更新缓存,保证了写缓存操作之间的顺序性,保证了缓存的准确性,避免了频繁读库。

    作者回复: 这样确实是一个比较好的方式,只是会稍微复杂

    2019-11-04
    3
    1
  • 你净瞎说~
    脏是针对缓存块来说的吧?缓存也有脏不脏吗?

    作者回复: 指的是缓存使用的那块内存有未被刷新到后端存储中的数据,就认为是脏的

    2019-10-29
    2
    1
  • 长期规划
    老师,我理解WriteBack策略相当于缓存和缓冲区合二为一了,对吧。据我所知,MySQL的buffer pool使用了WriteBack策略,但为了防止系统崩溃后数据丢失,MySQL使用了WAL(Write-Ahead Logging)机制,写先日志。好像WAL在HBase等系统也在用

    作者回复: 是的

    2019-10-24
    1
  • 小喵喵
    Read Through/Write Through...策略的示意图是不是画错了?
    缓存命中?否---------->读缓存-------------->数据库加载数据到缓存
    缓存都没有命中,再去读缓存也无法命中啊。中间步骤(读缓存)是不是多余的呢?

    作者回复: 这个读缓存想表达的意思是由缓存将数据加载到缓存中的

    2019-10-20
    1
  • yc
    write back策略读请求时“如果缓存不是脏的,则由缓存组件将后端存储中的数据加载到缓存中”,是不是写错了,如果缓存不是脏的,直接从缓存返回即可,为什么还要从后端记载数据到缓存然后返回?我看留言很多人都有同样的疑问,请老师解释一下,谢谢。

    作者回复: write back策略其实不算数据库和mc之间的策略,而是计算机体系结构中的策略,比如磁盘文件的缓存。它的完整读策略是这样的:如果缓存命中,则直接返回;如果缓存不命中,则重新找一个缓存块儿,如果这个缓存块儿是脏的,那么写入后端存储,并且把后端存储中的数据加载到缓存中;如果不是脏的,那么就把后端存储中的数据加载到缓存,然后标记缓存非脏。
    是我的讲述不太清晰,感谢你的提问

    2019-10-17
    1
  • Keith
    你好, 关于Write Back策略:
    1. Write Back只是说明写的策略, 没有说明读的策略吧?
    2. 关于"我们在读到缓存数据后,...,如果缓存不是脏的,则由缓存组件将后端存储中的数据加载到缓存中,最后我们将缓存设置为不是脏的,返回数据就好了。", 如果遇到连续的读操作, 缓存中的数据一直都是"不是脏的", 并且每次读操作都要"由缓存组件将后端存储中的数据加载到缓存中", 这样不是增加了缓存与存储之间的读次数吗?

    作者回复: 1. 后面有写明
    2. 是的 但是读后会将缓存标记为不脏,在读多些少的场景下,不会增加很多

    2019-10-17
    2
    1
  • MoonGod
    老师请问一个问题,在cache aside策略中,如果先更新数据库,再删除缓存。这样如果读请求访问量很大,会短时间出现大量请求穿透到数据库,这里有好的办法优化吗?

    作者回复: 如果更新不频繁的话,其实还OK
    如果更新频繁,可以加分布式锁,让单一线程可以更新这条数据;或者设置短的过期时间,让可能出现的不一致数据尽快过期

    2019-10-16
    1
    1
  • 欢喜哥
    2. 另一种做法同样也是在更新数据时更新缓存,只是给缓存加一个较短的过期时间,这样即使出现缓存不一致的情况,缓存的数据也会很快地过期,对业务的影响也是可以接受。

    老师请教一下,如果时间短,不会不缓存很快过期,反复读数据库?

    作者回复: 会的,不过要看具体的命中率,是否可以接受

    2019-12-09
  • 退役的球童
    老师,你好。cache aside策略理论上来说可以有效避免缓存跟数据库的数据不一致问题。但是还是存在客户端读取到的数据与数据库中数据不一致的情况啊。万一客户端会依赖读到的数据做二次运算,这种情况咋处理?
    2019-12-08
收起评论
40
返回
顶部