漫谈缓存更新之道

许多人在更新缓存时,先删除缓存,然后再更新数据库,而后续的操作会把数据再装载入缓存中。

然而,这个逻辑是错误的!!!

试想,两个并发操作,一个更新,一个查询,更新删除缓存后,查询没有命中缓存,先把旧数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是旧数据,导致缓存中持续地产生脏数据.

因此,让我们来总结一下缓存更新的几个Design Pattern.

这里,我们先不讨论更新缓存和更新数据这两个事是一个事务的事,或是会有失败的可能,我们先假设更新数据库和更新缓存都可以成功的情况

更新缓存的的Design Pattern有四种

1 Cache Aside Pattern

最常用的pattern。具体逻辑如下:

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

Cache-Aside-Design-Pattern-Flow-Diagram

Updating Data using the Cache-Aside Pattern - Flow Diagram

注意,我们的更新是先更新数据库,成功后,让缓存失效

一个查询操作,一个更新操作的并发

首先,没有了删除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这个就不会有并发问题了?

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

这个情形理论上会出现,不过,实际上出现的概率可能非常低,因为需要发生在读缓存时缓存失效,而且并发着有一个写操作。

而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大

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

2 Read/Write Through Pattern

上面的Cache Aside,应用代码需要维护两个数据存储,一个是缓存,一个是数据库,应用程序比较啰嗦。

Read/Write Through是把更新数据库的操作由缓存自己代理,所以,对于应用层来说,就简单很多。

可理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的Cache。

2.1 Read Through

Read Through 就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或LRU换出)

  • Cache Aside是由调用方负责把数据加载入缓存
  • Read Through则用缓存服务自己来加载,从而对应用方是透明的

2.2 Write Through

和Read Through相仿,不过是在更新数据时发生

当有数据更新时

  • 如果没有命中缓存,直接更新数据库,然后返回
  • 如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)

下图中的Memory可以理解为就是我们例子里的数据库

A write-through cache with no-write allocation

3 Write Behind Caching Pattern

Write Behind 又叫 Write Back

在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库

这个设计的好处就是

  • 让数据的I/O操作飞快无比(因为直接操作内存嘛 )
  • 因为异步,write back还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的

但是,其带来的问题是,数据不是强一致性的,而且可能会丢失(我们知道Unix/Linux非正常关机会导致数据丢失,就是因为这个事)。

另外,Write Back实现逻辑比较复杂,因为他需要track哪些数据是被更新的,需要刷到持久层上。

操作系统的write back会在仅当这个cache需要失效的时候,才会被真正持久化,比如,内存不够了,或是进程退出了等情况,这又叫lazy write

A write-back cache with write allocation

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏逸鹏说道

【推荐】C#线程篇---你所不知道的线程池(4)

线程的创建和销毁都要耗费大量的时间,有什么更好的办法?用线程池! 太多的线程浪费内存资源,有什么更好的办法?用线程池! 太多线程有损性能,有什么更好的办法?用线...

3748
来自专栏用户画像

3.2 虚拟内存管理

②当大量作业要求运行时,由于内存不足以容纳所有作业,只能使少数作业先运行,导致多道程序度的下降。

762
来自专栏挖掘大数据

如何高效地合并Spark社区PR到自己维护的分支

最近刚刚忙完Spark 2.2.0的性能测试及Bug修复,社区又要发布2.1.2了,国庆期间刚好有空,过了一遍2.1.2的相关JIRA,发现有不少重要修复2.2...

3758
来自专栏数据和云

如何提高Linux下块设备IO的整体性能?

编辑手记:本文主要讲解Linux IO调度层的三种模式:cfp、deadline和noop,并给出各自的优化和适用场景建议。 作者简介: ? 邹立巍 Linux...

6624
来自专栏烙馅饼喽的技术分享

我的CMS开发记-4 介绍一下DotNetNuke的系统执行流程

       有朋友说应该写个大致结构出来。想想也有道理,那么我就来介绍一下Dotnetnuke的执行流程。基本上我这个就是照搬他的 基本思路     一个站点...

3388
来自专栏JavaEdge

压测软件Jmeter使用实例(WIN7环境)百科我们为什么使用JmeterJmeter安装配置Sampler监听器(Listener)点击启动按钮,开启测试Jmeter自定义变量Redis的压测

3525
来自专栏用户2442861的专栏

使用ThinkPHP框架快速开发网站(多图)

http://blog.csdn.net/ruby97/article/details/7574851/

2.2K2
来自专栏运维

服务稳定性及应用防护方案

日志收集推荐使用Elastic Stack协议栈,可以满足收集海量日志需求,而且便于后续分析、报表、报警操作

1151
来自专栏owent

理解Raft算法

之前已经有Paxos算法,用于解决分布式系统最终一致性问题,而且已经有了zookeeper这个成熟的开源实现。那么这个Raft算法有啥用呢?按照Raft官网的说...

4733
来自专栏小灰灰

QuickTask动态脚本支持框架整体介绍篇

一个简单的动态脚本调度框架,支持运行时,实时增加,删除和修改动态脚本,可用于后端的进行接口验证、数据订正,执行定时任务或校验脚本

1212

扫码关注云+社区

领取腾讯云代金券