缓存级别与缓存更新问题

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

缓存应用模式

常见缓存应用模式

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

  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. 返回结果;

代码示例如下:

1
2
3
4
5
6
7
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. 返回;

应用代码示例:

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

缓存代码如下:

1
2
3
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
原文出处: Float_Luuu
from: http://www.importnew.com/23967.html
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring框架提供了多个缓存级别供开发者选择和配置。其中包括: 1. 类级别缓存:通过在类级别上使用`@CacheConfig`注解,可以为整个类的方法启用缓存。这意味着类中的所有方法都将使用相同的缓存配置。 2. 方法级别缓存:通过在方法上使用`@Cacheable`注解,可以为特定的方法启用缓存。这样,每次调用该方法时,Spring会首先检查缓存中是否存在相应的结果,如果存在,则直接返回缓存中的值,而不执行方法体。 3. 更新缓存:通过`@CachePut`注解,可以在方法执行后将结果存储到缓存中。这对于需要更新缓存的方法非常有用,例如在更新数据库记录后,同时更新缓存。 4. 清除缓存:通过`@CacheEvict`注解,可以清除指定的缓存。这对于需要在方法执行后清除缓存的情况非常有用,例如在删除数据库记录后,同时清除缓存。 需要注意的是,Spring框架本身并不提供具体的缓存实现,而是通过与各种缓存方案的整合来实现缓存功能。开发者可以根据自己的需求选择合适的缓存实现,如Caffeine、Guava Cache、Ehcache等。通过配置`CacheManager`组件,可以将这些缓存实现集成到Spring框架中。 综上所述,Spring框架提供了多个缓存级别,包括类级别缓存、方法级别缓存更新缓存和清除缓存。开发者可以根据需要选择和配置适合的缓存实现。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* [缓存-SpringCache](https://blog.csdn.net/weixin_68829137/article/details/127164634)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [最通俗的方法让你搞懂spring缓存机制](https://blog.csdn.net/qq_29235677/article/details/121685290)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值