缓存更新的套路

看到好些人在写更新缓存数据代码时,先删除缓存,然后再更新数据库,而后续的操作会把数据再装载的缓存中。然而,这个是逻辑是错误的。试想,两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还一直这样脏下去了。

我不知道为什么这么多人用的都是这个逻辑,当我在微博上发了这个贴以后,我发现好些人给了好多非常复杂和诡异的方案,所以,我想写这篇文章说一下几个缓存更新的Design Pattern(让我们多一些套路吧)。

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

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

Cache Aside Pattern

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

  • 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
  • 命中:应用程序从cache中取数据,取到后返回。
  • 更新:先把数据存到数据库中,成功后,再让缓存失效。

Cache-Aside-Design-Pattern-Flow-Diagram

Updating-Data-using-the-Cache-Aside-Pattern-Flow-Diagram-1

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

一个是查询操作,一个是更新操作的并发,首先,没有了删除cache数据的操作了,而是先更新了数据库中的数据,此时,缓存依然有效,所以,并发的查询操作拿的是没有更新的数据,但是,更新操作马上让缓存的失效了,后续的查询操作再把数据从数据库中拉出来。而不会像文章开头的那个逻辑产生的问题,后续的查询操作一直都在取老的数据。

这是标准的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?》,主要是怕两个并发的写操作导致脏数据。

那么,是不是Cache Aside这个就不会有并发问题了?不是的,比如,一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,所以,会造成脏数据。

但,这个case理论上会出现,不过,实际上出现的概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。

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

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你可以理解为就是我们例子里的数据库。

Write-through_with_no-write-allocation

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的流程图,基本逻辑如下:

Write-back_with_write-allocation

 

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这样的强一致性的玩法会导致性能下降,关于分布式的事务的相关话题,你可以看看《分布式系统的事务处理》一文。

摘录:http://coolshell.cn/articles/17416.html

转载于:https://www.cnblogs.com/xhyouyou/p/6061916.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/270490.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

怎样获取当前页面值php,想要得到当前页面的所有url参数信息怎么用PHP来实现?...

本篇文章主要给大家介绍怎么使用php获取完整url。首先给新手小白们简单介绍下什么是url。百度百科上是这么解说的,统一资源定位符是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯…

@PostConstruct注解学习,最详细的分享教程

该注解可以实现在运行工程时,自动运行该注解下的方法; PostConstruct是java自带的注解,指的是在项目启动的时候执行这个方法,也可以理解为在spring容器启动的时候执行,可作为一些数据的常规化加载,比如数据…

电脑知识:分享实用的电脑维护小常识

目录 常识1:杜绝直接拔电脑电源强行关机 常识2:不要用电池玩游戏 常识3:开不了机可以尝试插拔内存条 常识4:电脑散热的处理方法 常识5:避免在电脑工作时拔插头 今天小编就为大家带来一些电脑日常实用中的维护小知识&am…

Sentinel实现黑白名单控制详细教程来了

一:新建一个IpRequestOriginParser类,实现RequestOriginParser接口,配置如下 public class IpRequestOriginParser implements RequestOriginParser {/*** Parse the origin from given HTTP request.** param request HTTP request* return …

D. Anton and Chess 模拟题 + 读题

http://codeforces.com/contest/734/problem/D 一开始的时候看不懂题目,以为象是中国象棋那样走,然后看不懂样例。 原来是走对角线的,长知识了。 所以我们就知道,王有八个方向,所以每个方向选一个来做代表就行了。 那么…

电脑知识:常见电脑蓝屏代码识别与处理方法

电脑蓝屏怎么办? 相信大家都遇到过蓝屏的问题, 有时候电脑用着用着就突然蓝屏了, 或者某天开机突然蓝屏了…… 电脑蓝屏的原因非常的多, 到底是什么引起的呢? 收集了一些最常见的几种蓝屏代码, 大家只需要如…

sentinel的@SentinelResource注解使用

客户自定义限流处理逻辑 创建ExceptionUtil类用于自定义限流处理逻辑 自定义限流处理类: ExceptionUtil 二:新增 SentinelResource注解 配置 SentinelResource(value “hello2”, blockHandler “exHandler”, blockHandlerClass {ExceptionUtil.class}) 通过…

办公技巧:10个WORD神操作,值得收藏

目录 1、F4键 2、Ctrl字母快捷键 3、巧用“文档比较” 4、巧用替换功能 5、特殊字体保存 6、Word表格随心粘 7、去除超链接 8、Word图片轻松移 9、截图 10、格式刷不停 在日常办公当中, Word文档就是我们最常用的软件之一。用它我们写论文、写方案、写小说等等。 但…

MYSQL学习:GROUP BY分组取最新的一条记录

日常开发当中,经常会遇到查询分组数据中最新的一条记录,比如统计当前系统每个人的最新登录记录、外卖系统统计所有买家最新的一次订单记录、图书管理系统借阅者最新借阅书籍的记录等等。今天给大家介绍一下如何实现以上场景的SQL写法,希望对大…

@SentinelResource注解实现热点限流

下图中请求url中param参数为axb,如果QPS超过5,就会限流 一:如下代码 RestController public class ParamController {GetMapping("/param")SentinelResource(value "param", blockHandler "exHandler")p…

硬件知识:固态硬盘4K对齐知识介绍

目录 1、什么是4K对齐呢? 2、怎么查看硬盘是否4K对齐呢? 3、怎么4K对齐呢? 现在大家基本都有一个固态硬盘,而在固态硬盘分区中4K对齐是非常重要的。 1、什么是4K对齐呢? “4K对齐”就是符合“4K扇区”定义格式化过的硬…

【spring cloud】注解@SpringCloudApplication和@SpringBootApplication的区别

SpringCloudApplication注解 注解SpringCloudApplication包括:SpringBootApplication、EnableDiscoveryClient、EnableCircuitBreaker,分别是SpringBoot注解、注册服务中心Eureka注解、断路器注解。对于SpringCloud来说,这是每一微服务必须应…

网络知识:路由器常见故障分析及处理方法

目录 1.路由器的部分功能无法实现 2.网络频繁掉线 3.无法浏览网页 4.某些应用无法使用 5.网络带宽达不到合同带宽或相差甚远 6.局域网内存在多个路由器,因人为原因出现二级路由 对当前的大多数网络来说,无论是实现网络互连还是访问Internet&a…

硬件技巧:如何隐设置的你的电脑U盘不可见

有时候电脑里面有重要内容,在不联网的情况下,还需要禁用U盘,下面介绍禁用U盘的方法,原创文章,转载注明出处即可。 第一步,首先在电脑上点击开始按钮,或者直接按下快捷键组合"WinR"&am…

前端知识:如何创建自己的Iconfont图标库

在日常的开发过程中,前端页面经常会引用一些图标,iconfont图标库是前端开发者非常友好的在线字体图标库。大家可以根据平常所涉及的项目,收藏自己需要的图标库,方便在后续的项目中使用,今天小编给大家介绍如何通过icon…

硬件:固态硬盘SSD的基础知识及安装注意事项

固态硬盘就是用固态电子存储芯片阵列而制成的硬盘,相对于机械硬盘,固态硬盘的读写速度更快,但是固态硬盘的缺点是寿命不如机械硬盘。 固态硬盘有写入寿命,平均起来约为3000次P/E,1P/E为硬盘存储上限,相当于…

C# Redis实战(二)

二、Redis服务 在C# Redis实战(一)中我将所有文件拷贝到了D盘redis文件夹下,其中redis-server.exe即为其服务端程序,双击即开始运行,如图可以将此服务设置为windows系统服务,下载Redis服务安装软件,安装即可。安装完成…

matlab仿真超声波测距,超声波测距仪制作-Arduino中文社区 - Powered by Discuz!

本帖最后由 xiebb5688 于 2017-12-4 09:06 编辑虽然学的是机械,可也接触过C语言,MATLAB等程序,每次编程的时候,能够把BUG一个个解决掉,会带来不小的成就感。于是感觉到自己骨子还是挺喜欢代码的。于是也不知何时了解了…