缓存级别与缓存更新问题

标签: 数据库 | 发表时间:2017-03-19 18:06 | 作者:
出处:https://my.oschina.net/andylucc

There are only two hard things in Computer Science: cache invalidation and naming things.

-- Phil Karlton

缓存失效问题被认为是计算机科学中最难的两件事之一,这篇文章来自翻译,内容主要包括缓存级别与缓存更新常见的几种模式。

缓存应用模式

常见缓存应用模式

缓存常用来加快页面的加载速度,减少服务器或数据库服务的负载。缓存应用的常见模式如上图所示:

  1. 检索缓存,尝试查找之前相同请求的执行结果,如果找到了则返回,省去了重新执行的步骤;
  2. 如果缓存未命中,则重新执行计算逻辑并将结果保存至缓存;

数据库常常得益于对均匀分布的数据的读写,但是热点数据使得这种均匀被打破,从而出现了系统瓶颈。通过数据库服务前置缓存服务,可以有效吸收不均匀的负载和抵挡流量高峰。

缓存级别

缓存级别关注的问题是 什么时候做缓存(When)以及 什么地方做缓存(Where),下面介绍几种常见的缓存级别。

客户端缓存

缓存可以存储在客户端(操作系统或浏览器、服务端、或者是独立的缓存系统中。

CDN缓存

CDN也可以被认为是一种缓存。

Web服务器缓存

反向代理或者像Varnish这样的缓存服务可以直接保存静态的或动态的缓存内容。Web服务器也可以缓存请求直接响应客户端从而避免请求再次触达应用。

数据库缓存

我们的数据库服务在默认的配置或者稍微针对通用场景进行优化的情况下通常包含不同级别的缓存,针对特定的使用场景进行适当的调整可以进一步提高性能。

应用缓存

像Memcached和Redis这种内存key-value缓存服务,通常是置于应用和数据库服务之间,因为数据存储在内存中,因此这要比将数据存储在磁盘的数据库要快的多。但是内存与磁盘相比往往受限于空间,因此类似LRU(Least Recently Used)这种缓存淘汰算法应运而生,他们将相对较少访问的"冷"数据从内存置换出来将访问频率较高的“热”数据放入内存(将内存的使用价值最大化,译者注)。

Redis还有很多其他的功能,包括:

  • 持久化选项;
  • 内建数据结构(如sets、lists);

下面是针对 数据库查询级别对象级别的一般缓存:

  • 行级缓存;
  • 查询级别缓存;
  • 完全序列化的缓存;
  • 渲染后的HTML缓存;

值得一提的是,我们通常要避免文件级别的缓存,因为基于文件的缓存常常难于扩展和维护。

查询级别缓存:

每当我们查询数据库的时候,将查询(比如SQL)进行hash并作为key和查询结果关联存储,这种方法会遇到 缓存过期的问题:

  • 对于复杂的查询很难删除缓存的结果;
  • 缓存粒度较大,如果查询结果中只有丁点数据被更新,则整个查询都要过期;

对象级别缓存:

对象级别缓存是将数据看做对象:

  • 如果数据被修改则将数据从缓存中移除;
  • 使用异步的任务来更新缓存;

对象级别的缓存建议的使用场景:

  • 用户会话;
  • 渲染后的页面;
  • 活动流;
  • 用户图形数据;

缓存更新问题

因为内存受限于空间缓存只能存储有限的数据,因此我们需要决定在我们的应用场景中,使用何种 缓存更新策略,下面介绍几种常见的模式。

Cache-Aside

Cache-Aside模式

应用负责基于存储读写数据,缓存不直接和存储打交道,应用的行为如下:

  1. 检索缓存,缓存没有命中;
  2. 从数据库加载数据;
  3. 将数据更新至缓存;
  4. 返回结果;

代码示例如下:

def get_user(self, user_id):

     user = cache.get( "user.{0}" , user_id)

     if  user is None:

         user = db.query( "SELECT * FROM users WHERE user_id = {0}" , user_id)

         if  user is not None:

             cache.set(key, json.dumps(user))

     return  user

 

Memcached通常被应用于这种方式,这种模式对于接下来的数据读取将非常快,Cache-Aside也叫做 延迟加载,只有需要的数据被缓存,避免不需要的数据占用缓存空间。

这种模式的缺点如下:

  • 每次缓存没命中都增加系统之间的交互,这将会增加响应延迟;
  • 当对应数据库中的数据被更新之后将出现脏数据问题,这个问题可以通过设置过期时间(TTL)来缓解,当时间过期将发生强制更新缓存;
  • 当一个节点坏了之后,新的节点代替旧的节点,这个时候将出现大量的缓存穿透问题;

Write-Though

Write-Though模式

应用将缓存作为主要存储,读写都直接和缓存打交道,缓存负责基于存储进行读写:

  1. 应用基于缓存添加或删除记录;
  2. 缓存同步地将记录写入存储;
  3. 返回;

应用代码示例:

set_user( 12345 , { "foo" : "bar" })

缓存代码如下:

def set_user(user_id, values):

     user = db.query( "UPDATE Users WHERE id = {0}" , user_id, values)

     cache.set(user_id, user)

Write-Though对于所有的写操作都是比较慢的,但是对于读来说很快,用户通常需要容忍写延迟,但是不会出现脏数据。

这种模式的缺点如下:

  • 由于failure或者scaling带来的新增节点的时候,新增节点在下次更新数据之前将没有数据,这个问题可以结合Cache-Aside模式来缓解;
  • 对于很多写入的数据将永远不会读取到,这个问题可以通过设置过期时间解决;

Write-Behind(Write-Back)

 

Write-Behind模式

在这种模式下,应用的行为如:

  1. 直接读写缓存;
  2. 写操作通知任务来异步进行更新;

这种模式的缺点如下:

  • 如果在数据被更新到存储之前缓存挂了,则数据将会丢失;
  • 实现起来比Write-Though和Cache-Aside模式更为复杂;

Refresh-Ahead

Refresh-Ahead模式

我们可以配置缓存自动在最近访问的数据过期之前更新它们,如果可以准确预测将要访问的数据,Refresh-Ahead模式可以有效地减少读写的延迟。

这种模式的缺点如下:

  • 如果预测数据不准确,则比不做什么更有损性能;

缓存的缺点

一种解决方案通常会带来一些问题,我们来看看引入缓存带来的问题:

  • 缓存的引入带来了一致性问题,我们需要处理缓存中的数据与原数据不一致的问题;
  • 缓存的引入增加了软件架构的复杂性;;
  • 缓存过期是个难题,这个问题主要体现在何时更新缓存上;

扩展阅读

  • From cache to in-memory data grid
  • Scalable system design patterns
  • Introduction to architecting systems for scale
  • Scalability, availability, stability, patterns
  • Scalability
  • AWS ElastiCache strategies
  • Wikipedia

注:文中翻译如有错误,请多多包涵。

另外我开公众号啦,欢迎订阅:

相关 [缓存 缓存 更新] 推荐:

缓存更新的套路

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

缓存级别与缓存更新问题

- - Float_Luuu的博客
缓存失效问题被认为是计算机科学中最难的两件事之一,这篇文章来自翻译,内容主要包括缓存级别与缓存更新常见的几种模式. 缓存常用来加快页面的加载速度,减少服务器或数据库服务的负载. 缓存应用的常见模式如上图所示:. 检索缓存,尝试查找之前相同请求的执行结果,如果找到了则返回,省去了重新执行的步骤;. 如果缓存未命中,则重新执行计算逻辑并将结果保存至缓存;.

缓存算法

- lostsnow - 小彰
没有人能说清哪种缓存算法由于其他的缓存算法. (以下的几种缓存算法,有的我也理解不好,如果感兴趣,你可以Google一下  ). 大家好,我是 LFU,我会计算为每个缓存对象计算他们被使用的频率. 我是LRU缓存算法,我把最近最少使用的缓存对象给踢走. 我总是需要去了解在什么时候,用了哪个缓存对象.

Hibernate 缓存

- - ITeye博客
1数据缓存:(date caching) 是一种将数据暂时存于内存缓存去中的技术,缓存通常是影响系统性能的关键因素. 2.ORM的数据缓存策略有3中.   1.事务级缓存:  分为 数据库事务和 应用级事务,是基于Session的生命周期的实现,每个session都会在内部维持一个数据缓存, 随session的创建和消亡.

hibernate缓存,一级缓存,二级缓存,查询缓存

- - CSDN博客推荐文章
1、缓存是数据库数据在内存中的临时容器,它包含了库表数据在内存中的临时拷贝,位于数据库和访问层之间. 2、ORM在进行数据读取时,会根据缓存管理策略,首先在缓冲中查询,如果发现,则直接使用,避免数据库调用的开销. 事务级缓存:当前事务范围内的数据缓存. 应用级缓存:某个应用中的数据缓存. 分布式缓存:多个应用,多个JVM之间共享缓存.

缓存相关——缓存穿透、缓存并发、缓存失效、缓存预热、缓存雪崩、缓存算法

- - 编程语言 - ITeye博客
我们在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回. 这个时候如果我们查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询DB,这样缓存就失去了意义,在流量大时,可能DB就挂掉了. 要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞.

如何实现缓存系统的更新机制

- - 快课网
问题是这样的:假设要你设计一个缓存系统,你如何实现缓存的更新机制. 例如某网站具有高并发访问量,为提高性能,其中的一些网页页面都是存放在缓存中的,当需要更新缓存中的页面时,缓存系统如何完成缓存的更新. 在 给出解决方案之前,先看看内存数据库Redis中字典的设计. Redis 中的字典结构表示如下:.

Hibernate 二级缓存

- - CSDN博客推荐文章
很多人对二级缓存都不太了解,或者是有错误的认识,我一直想写一篇文章介绍一下hibernate的二级缓存的,今天终于忍不住了. 我的经验主要来自hibernate2.1版本,基本原理和3.0、3.1是一样的,请原谅我的顽固不化. hibernate的session提供了一级缓存,每个session,对同一个id进行两次load,不会发送两条sql给数据库,但是session关闭的时候,一级缓存就失效了.

App缓存管理

- - ITeye博客
无论大型或小型应用,灵活的缓存可以说不仅大大减轻了服务器的压力,而且因为更快速的用户体验而方便了用户. Android的apk可以说是作为小型应用,其中99%的应用并不是需要实时更新的,而且诟病于蜗牛般的移动网速,与服务器的数据交互是能少则少,这样用户体验才更好,这也是我们有时舍弃webview而采用json传输数据的原因之一.