从0开始学架构
李运华
资深技术专家
立即订阅
38968 人已学习
课程目录
已完结 59 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 照着做,你也能成为架构师!
免费
基础架构 (13讲)
01 | 架构到底是指什么?
02 | 架构设计的历史背景
03 | 架构设计的目的
04 | 复杂度来源:高性能
05 | 复杂度来源:高可用
06 | 复杂度来源:可扩展性
07 | 复杂度来源:低成本、安全、规模
08 | 架构设计三原则
09 | 架构设计原则案例
10 | 架构设计流程:识别复杂度
11 | 架构设计流程:设计备选方案
12 | 架构设计流程:评估和选择备选方案
13 | 架构设计流程:详细方案设计
高性能架构模式 (8讲)
14 | 高性能数据库集群:读写分离
15 | 高性能数据库集群:分库分表
16 | 高性能NoSQL
17 | 高性能缓存架构
18 | 单服务器高性能模式:PPC与TPC
19 | 单服务器高性能模式:Reactor与Proactor
20 | 高性能负载均衡:分类及架构
21 | 高性能负载均衡:算法
高可用架构模式 (10讲)
22 | 想成为架构师,你必须知道CAP理论
23 | 想成为架构师,你必须掌握的CAP细节
24 | FMEA方法,排除架构可用性隐患的利器
25 | 高可用存储架构:双机架构
26 | 高可用存储架构:集群和分区
27 | 如何设计计算高可用架构?
28 | 业务高可用的保障:异地多活架构
29 | 异地多活设计4大技巧
30 | 异地多活设计4步走
31 | 如何应对接口级的故障?
可扩展架构模式 (6讲)
32 | 可扩展架构的基本思想和模式
33 | 传统的可扩展架构模式:分层架构和SOA
34 | 深入理解微服务架构:银弹 or 焦油坑?
35 | 微服务架构最佳实践 - 方法篇
36 | 微服务架构最佳实践 - 基础设施篇
37 | 微内核架构详解
架构实战 (13讲)
38 | 架构师应该如何判断技术演进的方向?
39 | 互联网技术演进的模式
40 | 互联网架构模板:“存储层”技术
41 | 互联网架构模板:“开发层”和“服务层”技术
42 | 互联网架构模板:“网络层”技术
43 | 互联网架构模板:“用户层”和“业务层”技术
44 | 互联网架构模板:“平台”技术
45 | 架构重构内功心法第一式:有的放矢
46 | 架构重构内功心法第二式:合纵连横
47 | 架构重构内功心法第三式:运筹帷幄
48 | 再谈开源项目:如何选择、使用以及二次开发?
49 | 谈谈App架构的演进
50 | 架构实战:架构设计文档模板
特别放送 (7讲)
架构专栏特别放送 | “华仔,放学别走!”第1期
架构专栏特别放送 | “华仔,放学别走!” 第2期
如何高效地学习开源项目 | “华仔,放学别走!” 第3期
架构师成长之路 | “华仔,放学别走!” 第4期
架构师必读书单 | “华仔,放学别走!” 第5期
新书首发 | 《从零开始学架构》
致「从0开始学架构」专栏订阅用户
结束语 (1讲)
结束语 | 坚持,成就你的技术梦想
从0开始学架构
登录|注册

17 | 高性能缓存架构

李运华 2018-06-05
虽然我们可以通过各种手段来提升存储系统的性能,但在某些复杂的业务场景下,单纯依靠存储系统的性能提升不够的,典型的场景有:
需要经过复杂运算后得出的数据,存储系统无能为力
例如,一个论坛需要在首页展示当前有多少用户同时在线,如果使用 MySQL 来存储当前用户状态,则每次获取这个总数都要“count(*)”大量数据,这样的操作无论怎么优化 MySQL,性能都不会太高。如果要实时展示用户同时在线数,则 MySQL 性能无法支撑。
读多写少的数据,存储系统有心无力
绝大部分在线业务都是读多写少。例如,微博、淘宝、微信这类互联网业务,读业务占了整体业务量的 90% 以上。以微博为例:一个明星发一条微博,可能几千万人来浏览。如果使用 MySQL 来存储微博,用户写微博只有一条 insert 语句,但每个用户浏览时都要 select 一次,即使有索引,几千万条 select 语句对 MySQL 数据库的压力也会非常大。
缓存就是为了弥补存储系统在这些复杂业务场景下的不足,其基本原理是将可能重复使用的数据放到内存中,一次生成、多次使用,避免每次使用都去访问存储系统。
缓存能够带来性能的大幅提升,以 Memcache 为例,单台 Memcache 服务器简单的 key-value 查询能够达到 TPS 50000 以上,其基本的架构是:
缓存虽然能够大大减轻存储系统的压力,但同时也给架构引入了更多复杂性。架构设计时如果没有针对缓存的复杂性进行处理,某些场景下甚至会导致整个系统崩溃。今天,我来逐一分析缓存的架构设计要点。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《从0开始学架构》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(96)

  • bluefantasy
    我们的系统就出现过类似的问题,开始的时候没有缓存,每次做活动访问量大的时候就会导致反应特别慢。后来通过加redis缓存解决了问题。

     对于缓存雪崩问题,我们采取了双key策略:要缓存的key过期时间是t,key1没有过期时间。每次缓存读取不到key时就返回key1的内容,然后触发一个事件。这个事件会同时更新key和key1。

    作者回复: 很有创意👍👍

    2018-06-05
    5
    173
  • 王磊
    经常我说到缓存的时候,面试官问我,数据库自身不是有缓存吗,标准答案是怎么回击他?

    作者回复: 我对mysql比较熟,以下仅限mysql:
    1. mysql第一种缓存叫sql语句结果缓存,但条件比较苛刻,程序员不可控,我们的dba线上都关闭这个功能,具体实现可以查一下
    2. mysql第二种缓存是innodb buffer pool,缓存的是磁盘上的分页数据,不是sql的查询结果,sql的执行过程省不了。而mc,redis这些实际上都是缓存sql的结果,两种缓存方式,性能差很远。

    因此,可控性,性能是数据库缓存和独立缓存的主要区别

    2018-06-05
    2
    42
  • loveluckystar
    讲一个头两天发生的事情,我们的一个业务背后是es做db,之前是通过redis做缓存,缓存一段时间后失效再从es读取,是业务访问加载缓存的方式。有一天线上es集群机器单台出现问题,返回慢,由于分布式的缘故,渐渐拖满了所有请求,缓存失效来查询es发生了超时,加载失败,于是下次访问还是直接访问es。最终缓存全部失效,qps翻了好多倍,直接雪崩,es集群彻底没有响应了。。。之后我们只好先下线这个缓存加载功能,让集群活过来,最终改造缓存加载方式,用后台进程去更新缓存,而不用业务访问加载。

    作者回复: 现学现用的案例,很赞👍👍

    2018-06-05
    3
    36
  • 三月沙@wecatch
    好的缓存方案应该从这几个方面入手设计:
    1.什么数据应该缓存
    2.什么时机触发缓存和以及触发方式是什么
    3.缓存的层次和粒度( 网关缓存如 nginx,本地缓存如单机文件,分布式缓存如redis cluster,进程内缓存如全局变量)
    4.缓存的命名规则和失效规则
    5.缓存的监控指标和故障应对方案
    6.可视化缓存数据如 redis 具体 key 内容和大小

    作者回复: 确实,细节不少,可以写本书了😃

    2018-06-05
    30
  • 一叶
    笔记:
    书籍:
    《高性能Mysql》
    《unix编程艺术》:宁花机器一分,不花程序员一秒

    提升性能:
    先单机,有压力后优先考虑sql优化、db参数调优,还有硬件性能(32核/16G/SSD)优化,不行还可以再考虑业务逻辑优化、缓存。不要一上来就读写分离、集群等,能单库搞定的就毫不犹豫的单库。

    ---

    主从读写分离
    适用于单机无法应付所有请求,且读比写多时,读写分离还可以分别针对读写节点建索引来优化。
    对实时性要求不高:刚写入就读会有延迟,同步数据特别大时,延迟可能达到分钟级(可用缓存解决:2-8原则,挑选占访问量80%的前20%来缓存)。
    TODO主从还能设置自增长key不一样?

    分库分表(甚用,增加很多复杂度)
    几千万或上亿
    分库时机:单机性能瓶颈,1业务不复杂,但整体数据量影响到数据库性能;2业务复杂,需要分系统由不同团队开发,使用分库减少团队耦合。(分库导致不能join和事务(有方案但性能太低用了跟没分库差别不大,用最终一致性/事件驱动))
    分表时机:单表数据量大拖慢了sql性能,做垂直(将常用和不常用字段分开)或水平拆分(id分段、hash路由、添加路由表等)提高速度。(那么join、count、分页排序等就变得复杂)
    TODO环状hash 一致性hash?

    nosql
    (nosql——not noly sql 本质上是牺牲ACID中的某个或某几个属性,以解决关系数据库某些复杂的问题)
    关系数据库:强大的sql功能和ACID属性,发展了几十年技术相当成熟 Mysql / Postgresql
    k-v存储:解决关系数据库无法存储数据结构的问题 Redis / Memcache (redis不太适合key的value特别大,这种情况会导致整个redis变慢,这种场景mc更好->参考IO模型 redis单Reactor单进程读写大value会阻塞所有业务 持久化也会)
    文档数据库:解决关系数据库强schema约束,查询不存在的列会报错,扩充很麻烦还会长时间锁表 MongoDB
    列式数据库:解决关系数据库处理大数据分析或统计时IO高的问题,关系数据库即使只处理某列也会把整行查询到内存中 HBase / Greenplum
    全文搜索引擎:解决关系数据库全文搜索like扫描全表性能问题 ElasticSearch / solr
    LevelDB 内存型?
    时序数据库?:实时计算统计实时监控 influxDB
    OLAP OLTP HTAP?

    缓存(千万千万不要设计复杂的缓存,到时候各种不一致问题烦死你)
    cdn、nginx缓存、网关缓存、数据层缓存redis、db本身也有缓存(sql结果缓存、读取的磁盘分页缓存)
    缓存穿透:1本身无数据(添加默认值缓存/布隆过滤器[整型自增key?]) 2未生成缓存(识别爬虫并禁止 但可能影响seo)
    缓存雪崩:缓存实效后大家都在更新缓存导致系统性能急剧下降(1消息队列通知后台更新、2使用分布式更新锁)
    缓存热点:大部分业务都会命中的同一份缓存,比如1000w+粉丝的微博消息,复制多分缓存副本,key里面加副本编号将请求分散,且设置过期范围,而不是所有副本固定同一过期时间。
    缓存框架看一下设计思路:echcache、网友分享https://github.com/qiujiayu/AutoLoadCache


    作者回复: 很用心,赞👍

    2018-09-23
    18
  • mapping
    计算机界两大难题:命名和缓存过期。没用缓存的时候,想着怎么用缓存提升性能,用了缓存又担心数据更新不及时。技术上希望所有的请求都能命中缓存,业务上又恨不得数据实时最新。所以就会引入各种缓存过期策略,如设置过期时间,按规则删除,打版本。这些应该在前期设计缓存系统时规划好,我们最早是将 sql md5 作 key 查询结果存入缓存,结果业务系统数据不一致,要清除缓存简直是噩梦,只能祭出绝招重启 memcache,后面改成按规则删除,在 key 中加上业务和用户的前缀,可以很方便删除某个业务或某个用户的缓存。以上过期策略在前端浏览器也是这样,最简单就是 web 服务器设置静态资源缓存过期时间,如果业务频繁发新版本,过期时间不宜设置太长,但其实每次变动的文件很少,这种策略会导致大部分缓存命中率不高。按规则删除,早期很多网站上会有诊断助手类的东西,页面加载错误点下诊断助手就帮你清除缓存,原理就是对静态文件逐一带上 no-cache 请求头发送 ajax 请求强制覆盖缓存(跟 DevTools 中 disable cache 原理一样)。打版本其实就相当于让浏览器请求一个新版本文件,对于老版本文件就让它在缓存中自生自灭。

    作者回复: 你们的缓存设计有点复杂,还不如调整业务,越复杂的方案越容易出错,参考架构设计原则

    2018-06-05
    17
  • 家榆
    我是实现了个框架:https://github.com/qiujiayu/AutoLoadCache,用于解决一下问题:
    1. 缓存操作与业务代码耦合问题;
    2. 缓存穿透问题;
    3. 异步在缓存快要过期时,异步刷新缓存;
    4. 使用“拿来主义机制”,降低回源并发量;
    2018-07-05
    1
    13
  • 醇梨子
    华仔,请教一下,针对这种高并发缓存架构设计中,缓存和存储系统一致性问题怎么保证?比如说商品浏览人数,需要存库,然后又需要放缓存,需要频繁更新数据库。

    作者回复: 没法保证,这类数据允许一定的不一致,一定范围内的对用户也没有影响,不要只从技术的角度考虑问题,结合业务考虑技术

    2018-06-17
    12
  • 公号-代码荣耀
    上缓存架构的时候,结合以前的实际经历,会有几个值得注意的地方:

    1 哪些数据才真正的需要缓存?缓存也并非银弹。既然允许数据缓存,那么在你是可以接受在一定时间区间内的数据不一致性的。(当然可以做到最终一致性)

    2 确定好1后,就需要会数据类型进行分类,比如业务数据缓存,http缓存等

    3 根据数据类型及访问特点的不同选择不同缓存类型的技术方案。

    请问华仔,热点数据存在相当的突发性,临时的扩容似乎也来不及,能否从缓存架构角度如何避免类似微博宕机的事件?

    作者回复: 1. 限流
    2. 容器化+动态化
    3. 业务降级,例如限制评论

    2018-06-05
    9
  • 倔强小小🐤
    我们系统是做美术馆的3d展示的,后台配置的数据有点多,画框数据有几m,单个模型数据有几百k,最早直接mysql直接读取,后来用redis但是感觉效果不理想,又引入ehcache,发现不经过网络传输性能很好,经过网络传输后网络一般还是卡的很,后来又在前端用local storage进行缓存,后端通过rabbitmq进行消息通知前端清除本地缓存,缓存设置有效期都是一天,请问下我们这种情况有更好的方案吗

    作者回复: 前后端分离,在node上缓存和渲染试试

    2018-06-05
    7
  • jacy
    商品列表中,像商品描述等信息,缓存更新不及时影响不大,但某些重要数据,如价格,需要及时更新的数据,有没什么好的办法做到刷新。对于价格这种关键数据,不缓存,直接从数据库查询,是否可行。或者在用户查看商品详情时再去数据库查询价格,但可能出现列表中的价格和详情页中的价格不一致。

    作者回复: 通常有几种做法:
    1. 同步刷新缓存:当更新了某些信息后,立刻让缓存失效。
    这种做法的优点是用户体验好,缺点是修改一个数据可能需要让很多缓存失效
    2. 适当容忍不一致:例如某东的商品就是这样,我查询的时候显示有货,下单的时候提示我没货了
    3. 关键信息不缓存:库存,价格等不缓存,因为这类信息查询简单,效率高,关系数据库查询性能也很高

    2018-06-05
    6
  • cqc
    1个问题:关于后台更新,既然缓存服务器内存不足,需要剔除数据,那么后台更新再次触发查询,是否又会导致其他一些缓存数据被剔除,这感觉像是陷入一个循环了
    1个思路:我们之前的web项目,对于缓存热点数据,为了减少服务器的压力,在客户端引入了缓存:CDN加local storage,感觉对服务器端压力分散还是很有效果的。

    作者回复: 1.是的,所以加内存才是根本解决方式
    2. 这是分级缓存策略

    2018-06-05
    6
  • 刘磊
    对缓存的key也需要进行valid,避免无效的key查询缓存
    2018-06-05
    6
  • 🔰夏天的味道
    线上遇到的一个错误:业务查询的结果序列化后放到redis,下次从redis取出来时报错。原来是结果类虽然实现了Serializable接口,但是没有重写serialVersionUID,导致不能成功反序列化。
    2018-06-06
    5
  • 鹅米豆发
    1、最早也是采用后台用数据库,前台用关系型数据库+被动缓存的模式。结果是经常的性能抖动,且缓存一致性问题很难解决。后来我们的多数系统,都采用了前后台分离的模式——后台原始数据仍然是关系型数据库,前台使用缓存作为数据源,两者之间数据实时同步+定时同步+人工触发结合。
           这个模式,基本根除了穿透,雪崩,不一致,性能抖动这些。但带来了新的问题,比如数据丢失且不可恢复。我们的做法是,让缓存具备相对可靠的持久化机制+运维体系。
           2、遇到过几次热点问题,感觉这个更加棘手些。第一种情况,单Key数据结构本身过大,单个分片出现热点,单次访问的复杂度变大。这个相对容易,可以对key进行拆分,使用hashtag机制分片。第二种情况,数据分片普遍不均衡,较少遇到,遇到就比较棘手。第三种情况,数据分片均衡,但访问不均衡,可以增加副本数量。

    作者回复: 缓存持久化是一个不错的方法

    2018-06-05
    5
  • Sic Pavis
    刚好前段时间就有一个问题。
    产品反馈:后台更新的配置不生效,需要过几小时到半天不等的时间才能生效。
    从数据库确认持久化数据没问题,因此怀疑是缓存问题。

    我们的缓存设计是:业务key加一个缓存版本号做key,每次更新数据对应缓存的版本号就+1。
    前台定期扫描缓存版本号来实现缓存的更新。

    于是我查到了线上最新的版本号去缓存服务器查询,见鬼,数据也是正常的。排查了几天没有结果,无奈上线一个版本打了debug日志,终于找到原因了。

    我们的服务是双中心的,两边各存有一份缓存版本号,不知道什么原因,导致一边的缓存版本号比另外一个集群的多了3。由于我们的更新策略不是删掉无效的缓存,而是更新缓存的key,因此旧版本的缓存数据实际上还在缓存服务器上。更要命的是缓存服务器做了双中心同步(为啥同步暂且不提),这样就导致实际上一边集群读取的实际上是另外一个集群几个版本前留下的缓存数据。需要等待这个数据过期后,才能正常从数据库加载数据。

    作者回复: 这种做法会经常出现一些线上小问题

    2018-08-23
    4
  • 感觉概念理解上有点差异,缓存穿透出现在缓存失效要去查数据库。缓存击穿则是一个key失效导致高并发访问数据库压力增大。缓存雪崩则是大量缓存key失效或者缓存宕机了,导致数据库并发压力增大。感觉老师您把缓存穿透说成了击穿,击穿说成了雪崩。不知道我是否理解有问题?

    作者回复: 穿透和击穿是一个概念,雪崩不是你理解的那样,雪崩的意思就是开始一个小问题越来越大越来越严重,缓存宕机导致的问题算缓存穿透,就是指缓存没作用了

    2018-11-23
    3
  • 何磊
    遇到过一次缓存失效导致缓存穿透,很多请求的压力直接到了db。为了处理这种情况,我采用的方案就是:key永不过期,后台有个进程定时更新所有缓存。
    文中提到的结合消息队列来更新更具有时效性,非常棒,看到评论中的有一个双key机制,设计很巧妙,不过成本太高了。相当于成本翻倍。

    作者回复: 除非特殊场景,一般我还是建议尽量用简单直观甚至粗暴的方案😂

    2018-06-06
    3
  • june peng
    你好,在缓存雪崩后台更新策略里,比如1000个同时访问一个失效的缓存key,如果给这个key加读写锁,这样保证只有一个访问存储系统,其它999个人虽然慢点,但是至少能保证业务不会挂。如果用消息队列,就是前台只拿缓存key里的数据(不能访问存储系统),如果key不存在就发给消息队列更新,如果启多个进程去接受这些消息,依然不能避免后台击穿存储系统,难道只有启用一个进程?这样又太慢,可否进一步说明这种方案的复杂点在哪?谢谢

    作者回复: 你的分析很对👍
    具体在实现的时候,后台更新线程既不能只有一个,也不能和业务线程一样多,一般8~32个就差不多了,因为缓存更新并不会非常频繁。
    假如8个线程后台更新也可能导致缓存雪崩,那就要做更多事情了,例如:后台线程更新前先读取一下缓存,存在就不更新。

    2018-06-06
    3
  • 100kg
    你好,我想请教下,如果两台mysql互为主从,其中一台主键自增步长设置为2,另一个为1,这样做会影响性能吗?比如索引的连贯性之类的

    作者回复: 不会,索性是对已经存在的值建立索性数据结构,值的连续性和大小对数据结构本身没有影响,值的数量才会影响索性的大小和性能

    2018-06-06
    1
    3
收起评论
96
返回
顶部