许式伟的架构课
许式伟
七牛云CEO
立即订阅
19913 人已学习
课程目录
已更新 71 讲 / 共 77 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 怎样成长为优秀的软件架构师?
免费
基础平台篇 (21讲)
01 | 架构设计的宏观视角
02 | 大厦基石:无生有,有生万物
03 | 汇编:编程语言的诞生
04 | 编程语言的进化
05 | 思考题解读:如何实现可自我迭代的计算机?
06 | 操作系统进场
07 | 软件运行机制及内存管理
08 | 操作系统内核与编程接口
09 | 外存管理与文件系统
10 | 输入和输出设备:交互的演进
11 | 多任务:进程、线程与协程
12 | 进程内协同:同步、互斥与通讯
13 | 进程间的同步互斥、资源共享与通讯
14 | IP 网络:连接世界的桥梁
15 | 可编程的互联网世界
16 | 安全管理:数字世界的守护
17 | 架构:需求分析 (上)
18 | 架构:需求分析 (下) · 实战案例
19 | 基础平台篇:回顾与总结
加餐 | 我看Facebook发币(上):区块链、比特币与Libra币
加餐 | 我看Facebook发币(下):深入浅出理解 Libra 币
桌面开发篇 (16讲)
20 | 桌面开发的宏观视角
21 | 图形界面程序的框架
22 | 桌面程序的架构建议
23 | Web开发:浏览器、小程序与PWA
24 | 跨平台与 Web 开发的建议
25 | 桌面开发的未来
26 | 实战(一):怎么设计一个“画图”程序?
27 | 实战(二):怎么设计一个“画图”程序?
28 | 实战(三):怎么设计一个“画图”程序?
29 | 实战(四):怎么设计一个“画图”程序?
30 | 实战(五):怎么设计一个“画图”程序?
31 | 辅助界面元素的架构设计
课外阅读 | 从《孙子兵法》看底层的自然法则
加餐 | 想当架构师,我需要成为“全才”吗?
32 | 架构:系统的概要设计
33 | 桌面开发篇:回顾与总结
服务端开发篇 (14讲)
34 | 服务端开发的宏观视角
35 | 流量调度与负载均衡
36 | 业务状态与存储中间件
37 | 键值存储与数据库
38 | 文件系统与对象存储
39 | 存储与缓存
40 | 服务端的业务架构建议
41 | 实战(一):“画图”程序后端实战
42 | 实战(二):“画图”程序后端实战
43 | 实战(三):“画图”程序后端实战
44 | 实战(四):“画图”程序后端实战
45 | 架构:怎么做详细设计?
46 | 服务端开发篇:回顾与总结
加餐 | 如何做HTTP服务的测试?
服务治理篇 (11讲)
47 | 服务治理的宏观视角
48 | 事务与工程:什么是工程师思维?
49 | 发布、升级与版本管理
50 | 日志、监控与报警
加餐 | 怎么保障发布的效率与质量?
51 | 故障域与故障预案
52 | 故障排查与根因分析
53 | 过载保护与容量规划
54 | 业务的可支持性与持续运营
55 | 云计算、容器革命与服务端的未来
56 | 服务治理篇:回顾与总结
架构思维篇 (8讲)
57 | 心性:架构师的修炼之道
用户故事 | 站在更高的视角看架构
58 | 如何判断架构设计的优劣?
59 | 少谈点框架,多谈点业务
60 | 架构分解:边界,不断重新审视边界
加餐 | 实战:“画图程序” 的整体架构
61 | 全局性功能的架构设计
62 | 重新认识开闭原则 (OCP)
许式伟的架构课
登录|注册

39 | 存储与缓存

许式伟 2019-09-06
你好,我是七牛云许式伟。
前面接连三讲我们介绍了存储中间件的由来,以及最为常见的存储中间件,如:键值存储(KV Storage)、数据库(Database)、对象存储(Object Storage)。
当然,它们并不是全部。常见的存储中间件还有很多,比如消息队列(MQ)、搜索引擎(Search Engine)等等。
限于篇幅,我们不能一一对它们进行分析。今天,我们聊一聊缓存(Cache)。

memcached

缓存(Cache)是什么?
简单说,缓存是存储(Storage)的加速器。加速的原理通常是这样几种方法:
最常见的是用更高速的硬件来加速。比如,用 SSD 缓存加速 SATA 存储,用内存缓存加速基于外存的存储。
还有一种常见的方法是用更短的路径。比如,假设某个计算 y = F(x) 非常复杂,中间涉及很多步骤,发生了一系列的存储访问请求,但是这个计算经常会被用到,那么我们就可以用一个 x => y 的内存缓存来加速。
可见,缓存的数据结构从实现上来讲只需要是一个键值存储。所以它的接口可以非常简单:
type Cache {
...
}
func (cache *Cache) Get(key []byte) (val []byte, err error)
func (cache *Cache) Set(key, val []byte) (err error)
func (cache *Cache) Delete(key []byte) (err error)
第一个被广泛应用的内存缓存是 memcached。通常,我们会使用多个 memcached 实例构成一个集群,通过 Hash 分片或者 Range 分片将缓存数据分布到这些实例上。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《许式伟的架构课》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(14)

  • Geek_88604f
    对于一个确定的 x 值,如果 F(x) 永远不变,这就没问题。但如果 F(x) 值是不确定的,那就有可能会出现并发的两个 F(x) 请求得到的结果不同,从而导致缓存中的值和存储中的值不一致。
            这段描述我是这么理解的,老师的意思是不是两个线程同时去get同一个key,发现key不在缓存中,此时两个线程都会去计算key对应的value,当线程A拿到x完成计算后准备将计算结果刷到缓存(但还未刷新到缓存),线程B拿到了已经改变过的x(如果x表示select某个数据表的返回值,线程c有可能在线程B计算之前改变了x)也计算了值并先于线程A将值刷新到缓存,然后线程A才缓过劲来将它的计算结果刷新到缓存,这样导致的最终结果是缓存和存储中的值不一致。
            对于这种情况我觉得可以考虑以下几个方案去改进或规避:(1)考虑缓存的key为空的情况毕竟占少数,因此可以考虑当读取到缓存为空时随机等待几个毫秒的延迟后再次读取,如果key还为空则计算y=f(x)。(2)缓存更新序列化,将y=f(x)的计算从fastf(x)中移除,当缓存没有命中时向消息队列发起异步更新消息,消费者从消息队列中取消息计算f(x)并刷新缓存(如果短时间内有大量的更新消息,可以考虑只处理最新的消息),当然业务侧要做读取缓存的重试。(3)将y=f(x)的计算从fastf(x)中移除,设置缓存不老化,fastf(x)只负责读取数据,当缓存没有命中的时候从存储中读取。由x的变化发起者或者定时任务来计算f(x),当x发生变化的时候完成计算并刷新缓存。

    作者回复: 你的理解是对的,当存在并行的Set请求,自然存在时序问题,导致存储和缓存数据不一致。解决方案来说:
    1、sleep不能解决多任务协同问题,所以这个方案不可行。
    2、是可行的思路,把 F(x) 和 Set 一起串行执行。不过这会导致在缓存未命中时 F(x) 执行两遍。你说的缓存未命中从存储读,本质上是执行 F(x) 的意思。
    3、存储中 x 对应的数据发生变化时,我们通常的做法是把 x 从缓存中清除(Delete),而不是执行 F(x) 和 Set。原因是缓存空间是有限的,所以要给 Get 次数比较多的数据缓存,而不是一发生变更就缓存,这样非常可能反而降低了缓存命中率。

    2019-09-07
    7
  • Tachyon
    缓存雪崩并不是说缓存大量宕机,而是大量key几乎同时过期导致请求直接打到后端存储上。

    作者回复: 成因可以多样化,同时过期的效果和宕机显然类似。

    2019-09-20
    1
  • mickey
    祝许老师以及极客全体老师教师节快乐,工作顺利,身体健康!
    2019-09-10
    1
  • Aaron Cheung
    groupcache 学习了 打卡39
    2019-09-09
    1
  • Charles
    所以许老师怎么去评估一个系统是否应该上缓存,假设目前存储都可以顶住负载?谢谢

    作者回复: 要分析清楚压力和和效率瓶颈。压力大了,可以考虑加缓存,当然也可以考虑存储扩容。效率瓶颈,通常只能用缓存解决。

    2019-09-08
    1
  • 醉雪飘痕
    不好意思,突然明白了,应该是指缓存满进行的淘汰。而分不同的group后,各个group不相互影响,可独立进行淘汰,控制粒度更细。
    请忽略我上面的提问,谢谢。
    2019-09-07
    1
  • 醉雪飘痕
    “F(x)、G(x) 在同一个内存缓存集群就意味着它们相互之间会淘汰对方?”
    许老师,这里有些不理解,为何F(x)、G(x)会相互淘汰对方?
    2019-09-07
    2
    1
  • Dean
    如何理解groupcache值不可变就解决了一致性问题,此时如果存储的值变了,缓存中的相应的计算结果如果不变的话,不是也不一致么?

    作者回复: 你可以这么理解:以键值存储为例,它存储的是 key => (value, ver),而 groupcache 存储的是 (key, ver) => value。这样就可以做到存储的值改变而 groupcache 值不变。只不过怎么用,这个需要好好考虑一下。

    2019-09-16
  • JACK
    还是不太懂为什么groupcache解决了一致性问题,如果fx的实现易变,那fx,gx的组合也会变吧
    2019-09-10
  • Geek_88604f
    func FastF(x TypeX) (y TypeY) {
      key := toBytes(x)
      hash := hashOf(key)
      i := hash % countOf(memcaches)
      val, err := memcaches[i].Get(key)
      if err != nil {
        y = F(x)
        val = toBytes(y)
        memcaches[i].Set(key, val)
      } else {
        y = fromBytes(val)
      }
      return
    }
    这段代码第六行判断条件是=还是≠,没太明白整个逻辑。

    作者回复: err != nil 表示失败

    2019-09-06
  • leslie
    课程讲到现在算是明白为何redis、memcache的考虑因素了:之前很多地方的讲解没有涉及到底层算法,故而让我们觉得好像类似,但是实际上完全不同的;原来是忽略了底层算法。
          老师今天的课从不一样的角度去解释:彻底明白为何差不多的的东西其实各种称呼方式,大多数情况下其实很多时候没有强调 缓存与内存,这个概念被统称了。谢谢老师的分享。
    2019-09-06
  • 诗泽
    如果 F(x) 值是不确定的,这种情况下放缓存里也就没意义了吧?

    作者回复: 这里说的不确定不是太准确,正确应该说会改变,有多个版本

    2019-09-06
  • CoderLim
    缓存热数据可以有效缓解存储的压力,提高响应速度,但是设计时需要考虑扩缩容是否影响 hash 映射,是否重试友好,是否有持久性的需求
    2019-09-06
  • 风清扬
    许老师,缓存血崩的原因是命中率降低,大量请求直达后端,后端性能极速下降导致,解决办法是抛弃过多的请求。想到的一个是应用层既网关层限流。能详细讲解下吗?

    作者回复: 后面还会细聊这些问题

    2019-09-06
收起评论
14
返回
顶部