黄粱一梦终须醒,

无根无极本归尘。

金龙飞天归何处,

不如凡间做真人!

上个周忙活了一周上线工作,因为银行的行业性质,与国家安全、国家政策强相关。所以对上线版本的质量要求都很高,因而每一次上线前担惊害怕,上线中紧张揪心,上线后才如释重负,感觉身体被掏空。

上线后经过周末一天半的休息(本次上线,我们安排在了周六白天,要不估计又得上线到后半夜了),脑子好好放松了下,这个周就补一下前段时间的一个坑,介绍一下缓存设计中的Design Pattern,好好说一下除了我们介绍过的Cache Aside模式之外,还有的其他模式。

原来打算自己写一下,但是看到了一篇资深专家陈皓(著有专栏“左耳听风”)关于这方面的文章,大体上引用了陈老师的内容结合我自己的思考,给大家带来这一篇缓存专题的第二弹。中间穿插的红色字体部分是我对陈老师文章内容的补充,因为有些内容陈老师认为大家已经掌握,原文中已经忽略。

对陈老师专栏内容感兴趣的同学,可以通过链接:

https://coolshell.cn/articles/17416.html#Cache_Aside_Pattern

查看原文。

好啦,文章开始:

-----------------------------------begin-------------------------------------

首先这里,我们先不讨论更新缓存和更新数据这两个事是一个事务的事,或是会有失败的可能,我们先假设更新数据库和更新缓存都可以成功的情况(我们先把成功的代码逻辑先写对)。

更新缓存的的Design Pattern有四种:Cache aside, Read through, Write through, Write behind caching,我们下面一一来看一下这四种Pattern。

1. Cache Aside Pattern

这是最常用最常用的pattern了。其具体逻辑如下:

  • 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。

  • 命中:应用程序从cache中取数据,取到后返回。

  • 更新:先把数据存到数据库中,成功后,再让缓存失效。

注意,我们的更新是先更新数据库,成功后,让缓存失效。那么,这种方式是否有问题呢?我们可以脑补一下。

此处,陈老师关于Cache Aside解决的问题以及还存在的问题没有太详细的介绍,关于Cache Aside模式能解决的问题,以及本身存在的问题,可以出门左拐,查看这篇文章《技术提升(1) -- 缓存专题》中第三大部分的第四小点 -- 先更新数据库再删除缓存,此处我简单贴一下这一部分异常情况的流程图,以及出现问题的原因。

异常场景

在 读-写这种场景下,在读线程获取到缓存中数据为空,需要更新缓存之前,写线程已经删除了缓存,会导致最终结果读线程将缓存覆盖成旧数据。导致缓存不一致,但是这种情况首先是缓存存在过期时间,过期后就会从数据库中重新获取,另外这种场景要求读线程的处理过程要慢于写线程的时间,这种情况几率很小。

Cache Aside这是标准的design pattern,包括Facebook的论文《Scaling Memcache at Facebook》也使用了这个策略。为什么不是写完数据库后更新缓存?你可以看一下Quora上的这个问答《Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend?》,主要是怕两个并发的写操作导致脏数据。

关于更新策略使用先更新数据库,然后更新缓存的策略,在写 - 写这种场景下,也是会出现缓存不一致的问题,具体问题,大家也可以参考《技术提升(1) -- 缓存专题》中第三大部分的第二小点 -- 先更新数据库再更新缓存,我这里也是简单贴一下问题的流程图以及原因解释。

异常场景

来了两个写线程,线程A首先完成了写数据库的操作(操作1,2),在该线程没有完成更新缓存操作之前(操作7,8);线程B已经完成了更新数据库以及缓存的操作(操作3,4,5,6)。可以看到最终的处理结果数据库中为线程B的更新值,缓存为线程A的更新值,导致缓存不一致(这个不一致的缓存最起码得保存到这个缓存过期的时候)。

在这里,陈老师的结论跟我上一篇总结的一致,同样是认为:Cache Aside也会出现并发问题,但这个case理论上会出现,不过,实际上出现的概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。

所以,这也就是Quora上的那个答案里说的,要么通过2PC或是Paxos协议保证一致性,要么就是拼命的降低并发时脏数据的概率,而Facebook使用了这个降低概率的玩法,因为2PC太慢,而Paxos太复杂。当然,最好还是为缓存设置上过期时间。

2. Read/Write Through Pattern

我们可以看到,在上面的Cache Aside套路中,我们的应用代码需要维护两个数据存储,一个是缓存(Cache),一个是数据库(Repository)。所以,应用程序比较啰嗦。而Read/Write Through套路是把更新数据库(Repository)的操作由缓存自己代理了,所以,对于应用层来说,就简单很多了。可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的Cache。

Read Through

Read Through 套路就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或LRU换出),Cache Aside是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对应用方是透明的。

Write Through

Write Through 套路和Read Through相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)

下图自来Wikipedia的Cache词条。其中的Memory你可以理解为就是我们例子里的数据库。

3. Write Behind Caching Pattern

Write Behind 又叫 Write Back。一些了解Linux操作系统内核的同学对write back应该非常熟悉,这不就是Linux文件系统的Page Cache的算法吗?是的,你看基础这玩意全都是相通的。所以,基础很重要,我已经不是一次说过基础很重要这事了。

Write Back套路,一句说就是,在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作飞快无比(因为直接操作内存嘛 ),因为异步,write backg还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的。

但是,其带来的问题是,数据不是强一致性的,而且可能会丢失(我们知道Unix/Linux非正常关机会导致数据丢失,就是因为这个事)。在软件设计上,我们基本上不可能做出一个没有缺陷的设计,就像算法设计中的时间换空间,空间换时间一个道理,有时候,强一致性和高性能,高可用和高性性是有冲突的。软件设计从来都是取舍Trade-Off。

另外,Write Back实现逻辑比较复杂,因为他需要track有哪数据是被更新了的,需要刷到持久层上。操作系统的write back会在仅当这个cache需要失效的时候,才会被真正持久起来,比如,内存不够了,或是进程退出了等情况,这又叫lazy write。

在wikipedia上有一张write back的流程图,基本逻辑如下:

4. 再多唠叨一些

1)上面讲的这些Design Pattern,其实并不是软件架构里的mysql数据库和memcache/redis的更新策略,这些东西都是计算机体系结构里的设计,比如CPU的缓存,硬盘文件系统中的缓存,硬盘上的缓存,数据库中的缓存。基本上来说,这些缓存更新的设计模式都是非常老古董的,而且历经长时间考验的策略,所以这也就是,工程学上所谓的Best Practice,遵从就好了。

2)有时候,我们觉得能做宏观的系统架构的人一定是很有经验的,其实,宏观系统架构中的很多设计都来源于这些微观的东西。比如,云计算中的很多虚拟化技术的原理,和传统的虚拟内存不是很像么?Unix下的那些I/O模型,也放大到了架构里的同步异步的模型,还有Unix发明的管道不就是数据流式计算架构吗?TCP的好些设计也用在不同系统间的通讯中,仔细看看这些微观层面,你会发现有很多设计都非常精妙……所以,请允许我在这里放句观点鲜明的话——如果你要做好架构,首先你得把计算机体系结构以及很多老古董的基础技术吃透了。

3)在软件开发或设计中,我非常建议在之前先去参考一下已有的设计和思路,看看相应的guideline,best practice或design pattern,吃透了已有的这些东西,再决定是否要重新发明轮子。千万不要似是而非地,想当然的做软件设计。

4)上面,我们没有考虑缓存(Cache)和持久层(Repository)的整体事务的问题。比如,更新Cache成功,更新数据库失败了怎么吗?或是反过来。关于这个事,如果你需要强一致性,你需要使用“两阶段提交协议”——prepare, commit/rollback,比如Java 7 的XAResource,还有MySQL 5.7的 XA Transaction,有些cache也支持XA,比如EhCache。当然,XA这样的强一致性的玩法会导致性能下降,关于分布式的事务的相关话题,你可以看看《分布式系统的事务处理》一文。

【全文完】

----------------------------end------------------------------

5. 缓存专题总结

陈老师的文章内容基本就是这样了,中间我补充了一些陈老师认为大家都已经掌握的内容,原文中略去不写了,我进行了适当的补充。在文章最后,适当给大家简单总结一下缓存专题这两部分的内容。看下面的思维导图:

从图中可以看到,缓存更新模式,按照不同的角度有不同的划分方式:

从应用角度来看,一部分认为缓存与数据库是两个实体,需要应用都要进行相应的操作,这其中缓存一致性出现概率最低的就是延迟双删策略,但是比较复杂,一般常用的模式就是Cache Aside模式;另一部分认为缓存与数据库对于应用来说,它们是一个东西,内部缓存与数据库的更新对外部应用透明,应用不关心。这其中对于缓存与数据库两部分的更新,同样又存在两种策略,分别是同步更新,还是异步更新。异步更新就是Write Back模式,这也是linux系统中常用的Page Cache常用的策略。而同步更新策略从更新的时机又可以分成Read Through,Write Through两种。

好啦,今天缓存专题的第二弹:缓存更新模式的介绍就到这里啦,各位老板们学会了么?

----------------------这回真的end啦-------------------

缓存更新的Design Pattern -- 缓存专题(2)相关推荐

  1. 搞懂分布式技术15:缓存更新的套路

    缓存更新的套路 看到好些人在写更新缓存数据代码时,先删除缓存,然后再更新数据库,而后续的操作会把数据再装载的缓存中.然而,这个是逻辑是错误的.试想,两个并发操作,一个是更新操作,另一个是查询操作,更新 ...

  2. 两难!先更新数据库再删缓存?还是先删缓存再更新数据库?

    前言 当我们在做数据库与缓存数据同步时,究竟更新缓存,还是删除缓存,究竟是先操作数据库,还是先操作缓存?本文带大家深度分析数据库与缓存的双写问题,并且给出了所有方案的实现代码方便大家参考. 本篇文章主 ...

  3. session.merge 缓存不更新_这几个缓存更新的设计你都知道吗?

    前言 Hello,everybody,我是asong,上一篇文章我们一起聊一聊了面试中几个常见的缓存问题,今天我依然聊一聊缓存,不过今天我们聊的不是面试了,我们一起来看一看我们在系统中缓存更新的设计, ...

  4. 缓存更新策略概览(Caching Strategies Overview)

    缓存是一种用于更快数据检索的数据存储技术.从某种意义上说,它比从其主存储(如数据库)获取数据更快.为了实现这一点,我们通常缓存频繁请求或计算的数据.现在,让我们仔细看看可能需要考虑的不同缓存策略.请记 ...

  5. 缓存级别与缓存更新问题

    缓存失效问题被认为是计算机科学中最难的两件事之一,这篇文章来自翻译,内容主要包括缓存级别与缓存更新常见的几种模式. 缓存应用模式 常见缓存应用模式 缓存常用来加快页面的加载速度,减少服务器或数据库服务 ...

  6. 为什么你的缓存更新策略是先更新数据库后删除缓存,讲讲其他的情况有什么问题?...

    2019独角兽企业重金招聘Python工程师标准>>> 问题:怎么保持缓存与数据库一致? 要解答这个问题,我们首先来看不一致的几种情况.我将不一致分为三种情况 数据库有数据,缓存没有 ...

  7. Redis入门到实战(实战篇)缓存更新、穿透、雪崩、击穿!

    Redis基础篇 Java面试宝典-redis 实战篇Redis 开篇导读 亲爱的小伙伴们大家好,马上咱们就开始实战篇的内容了,相信通过本章的学习,小伙伴们就能理解各种redis的使用啦,接下来咱们来 ...

  8. 【Redis】缓存更新策略

    1. 缓存更新策略综述 内存淘汰 不用自己维护,利用 Redis 自己的内存淘汰机制 (内存不足时,触发策略,默认开启,可自己配置),其可在一定程度上保持数据一致性 超时剔除 给数据添加 TTL,到期 ...

  9. 讲讲 Redis 缓存更新一致性

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:Finley www.cnblogs.com/Finley/ ...

最新文章

  1. NLP任务非Transformer不可?
  2. NTFS的交换数据流ADS应用
  3. Ubuntu 10.10 安装配置 Ruby on Rails
  4. Ubuntu 12.04嵌入式交叉编译环境arm-linux-gcc搭建过程图解
  5. python时间计算_python datetime库使用和时间加减计算
  6. tp3 普通模式url模式_[tp3.2.1]开启URL(重写模式),省略URL中的index.php
  7. 缴满15年能领多少钱 养老金计算公式网上疯传
  8. 在 SharePoint 2010 中访问数据
  9. 使用OStressSQL Server压力测试
  10. python如何设置api接口_python如何使用api接口
  11. Linux下卸载vmware
  12. python| map()函数应用详解
  13. matlab广义误差分布,基于Copula函数的多源径流预报误差联合分布研究
  14. fdfs和springboot的整合
  15. LED背光源的使用寿命多久?
  16. 想要走上巅峰?练好演讲也是必不可少的技能
  17. HTB[Tier 0]Appointment题解
  18. 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法?(递归与动态规划)
  19. 网站seo优化3-7天快速上百度首页靠谱吗?怎么做
  20. Core Data详解

热门文章

  1. centos7:ceph-fuse挂载方法
  2. 个人资料卡片CSS样式
  3. linux虚拟机安装并配置网络
  4. 用HTML5制作买火车票的页面,基于HTML5手机预订火车票系统的设计与实现_问答库...
  5. java上传文件到linux文件下
  6. 【PR】关于字幕的一系列设置
  7. vue之校验身份证号码
  8. [RHEL5企业级Linux服务攻略]--第4季 DNS服务全攻略
  9. 数商云B2B工业用品采购系统解决方案:打造B2B工业采购平台用户与分销管理体系
  10. 微信小程序中的页面跳转(通过点击按钮、调用方法的形式)