边缘缓存模式(Cache-Aside Pattern),即按需将数据从数据存储加载到缓存中。此模式最大的作用就是提高性能减少不必要的查询。

1 模式

  1. 先从缓存查询数据
  2. 如果没有命中缓存则从数据存储查询
  3. 将数据写入缓存
  代码形如:
        public async Task<MyEntity> GetMyEntityAsync(int id)
{
// Define a unique key for this method and its parameters.
var key = string.Format("StoreWithCache_GetAsync_{0}", id);
var expiration = TimeSpan.FromMinutes();
bool cacheException = false;
try
{
// Try to get the entity from the cache.
var cacheItem = cache.GetCacheItem(key);
if (cacheItem != null)
{
return cacheItem.Value as MyEntity;
}
}
catch (DataCacheException)
{
// If there is a cache related issue, raise an exception
// and avoid using the cache for the rest of the call.
cacheException = true;
}
// If there is a cache miss, get the entity from the original store and cache it.
// Code has been omitted because it is data store dependent.
var entity = ...;
if (!cacheException)
{
try
{
// Avoid caching a null value.
if (entity != null)
{
// Put the item in the cache with a custom expiration time that
// depends on how critical it might be to have stale data.
cache.Put(key, entity, timeout: expiration);
}
}
catch (DataCacheException)
{
// If there is a cache related issue, ignore it
// and just return the entity.
}
}
return entity;
} public async Task UpdateEntityAsync(MyEntity entity)
{
// Update the object in the original data store
await this.store.UpdateEntityAsync(entity).ConfigureAwait(false);
// Get the correct key for the cached object.
var key = this.GetAsyncCacheKey(entity.Id);
// Then, invalidate the current cache object
this.cache.Remove(key);
} private string GetAsyncCacheKey(int objectId)
{
return string.Format("StoreWithCache_GetAsync_{0}", objectId);
}

2 关注点

2.1 缓存数据的选择

  对于相对静态的数据或频繁读取的数据,缓存是最有效的。

2.2 缓存数据的生命周期

  过期时间短,会导致频繁查询数据源而失去缓存的意义;设置时间过长,可能发生缓存的数据过时不同步的情况。

2.3 缓存过期策略的选择

  一般缓存产品都有自己内置的缓存过期策略,最长使用的是“最近最不常使用”算法,需要在使用时,了解产品的默认配置和可配置项,根据实际需求选择。

2.4 本地缓存与分布式缓存的选择

  本地缓存的查询速度更快,但在一个分布式环境中,需要保证不同机器的本地缓存统一,这需要我们在程序中处理;分布式缓存性能不如本地缓存,但大多数时候,分布式缓存更适合大型项目,分布式缓存产品家族自带的管理和诊断工具十分适合运维和性能监控。

2.5 一致性问题

  实现边缘缓存模式不能保证数据存储和缓存之间的实时一致性。数据存储中的某个项可能随时被外部进程更改,并且此更改可能在下次将项加载到缓存中之前不会反映在缓存中。

3 一致性

3.1 淘汰还是更新缓存

淘汰缓存:数据写入数据存储,并从缓存删除
优点:简单
缺点:缓存增加一次miss,需要重新加载数据到缓存
  更新缓存:数据写入数据存储,并更新缓存
  优点:缓存不会增加一次miss,命中率高
  缺点:更新缓存比淘汰缓存复杂
  淘汰还是更新缓存主要取决于——更新缓存的复杂度,数据查询时间以及更新频率。
  如果更新缓存复杂度低,从数据存储查询查询数据有比较耗时,更新频率又高,则为了保证缓存命中率,更新缓存比较适合。此外大大多数情景,都可以用淘汰缓存,来满足需求。

3.2 操作顺序

  先操作数据存储,再操作缓存
  先操作缓存,再操作数据存储

3.3 一致性问题

非并发场景——场景为Application修改数据(考虑操作失败的影响)

先操作缓存,再操作数据存储

淘汰缓存时:
step 1:淘汰缓存成功
step 2:更新数据存储失败
结果:数据存储未修改,缓存已失效
修改缓存时:
step 1:修改缓存成功
step 2:更新数据存储失败
结果:数据不一致
先操作数据存储,再操作缓存
  淘汰缓存时:
step 1:更新数据存储成功
step 2:淘汰缓存失败
结果:数据不一致
  修改缓存时:
step 1:更新数据存储成功
step 2:修改缓存失败
结果:数据不一致

并发场景——场景位Application1修改数据,Application2读取数据,(不考虑操作失败的影响,仅考虑执行顺序的影响)

先操作缓存,再操作数据存储
  淘汰缓存时:
step 1:Application1先淘汰缓存
step 2:Application2从缓存读取数据,但是没有命中缓存
step 3:Application2从数据存储读取旧数据
step 4:Application1完成数据存储的更新
step 5:Application2把旧数据写入缓存,造成缓存与数据存储不一致
结果:数据不一致
  更新缓存时:
step 1:Application1先更新缓存
step 2:Application2从缓存读取到新数据
step 3:Application2
step 4:Application1更新数据存储成功
step 5:Application2
结果:数据一致
先操作数据存储,再操作存储
  
  淘汰缓存时:
step 1:Application1更新数据存储完成
step 2:Application2从缓存读取数据,查询到旧数据
step 3:Application1从缓存中删除数据
结果:缓存已淘汰,下次加载新数据
  更新缓存时:
step 1:Application1更新数据存储完成
step 2:Application2从缓存读取数据,查询到旧数据
step 3:Application1从更新缓存数据
结果:数据一致
  由此可见,不管我们如何组织,都可能完全杜绝不一致问题,而影响一致性的两个关键因素是——“操作失败”和“时序”。
  对于“淘汰还是更新缓存”、“先缓存还是先数据存储”的选择,不应该脱离具体需求和业务场景而一概而论。我们把关注点重新回到“操作失败”和“时序”问题上来.
  对于“操作失败”,首先要从程序层面提高稳定性,比如“弹性编程”,“防御式编程”等技巧,其次,要设计补偿机制,在操作失败后要做到保存足够的信息(包括补偿操作需要的数据,异常信息等),并进行补偿操作(清洗异常数据,回滚数据到操作前的状态),通过补偿操作,实现最终一致性。
  对于“时序”问题,需要根据程序结构,梳理出潜在的时序问题。本文例子中,不设计补偿操作,如果引入的话,操作组合的时序图可能会更加复杂。解决的思路就是对于单个用户来说操作应该是“原子的”在分布式环境中,多个用户的操作应该是“串行”的。最简单的思路,就是使用分布式锁,来保证操作的串行化。当然,我们也可以通过队列来进行异步落库,实现最终一致性。

4 缓存穿透、缓存击穿、缓存雪崩

4.1 缓存穿透

  缓存穿透是指用户频繁查询数据存储中不存在的数据,这类数据,查不到数据所以也不会写入缓存,所以每次都会查询数据存储,导致数据存储压力过大。
  解决方案:
    • 增加数据校验
    • 查询不到时,缓存空对象

4.2 缓存击穿

  高并发下,当某个缓存失效时,可能出现多个进程同时查询数据存储,导致数据存储压力过大。
  解决方案:
      • 使用二级缓存
      • 通过加锁或者队列降低查询数据库存储的并发数量
      • 考虑延长部分数据是过期时间,或者设置为永不过期

4.3 缓存雪崩

  高并发下,大量缓存同时失效,导致大量请求同时查询数据存储,导致数据存储压力过大。
  解决方案:
      • 使用二级缓存
      • 通过加锁或者队列降低查询数据库存储的并发数量
      • 根据数据的变化频率,设置不同的过期时间,避免在同一时间大量失效
      • 考虑延长部分数据是过期时间,或者设置为永不过期

5 缓存体系

   
   
位置 缓存技术
客户端 浏览器缓存
客户端应用缓存
客户端网络 代理服务器开启缓存
广域网 使用代理服务器(含CDN)
使用镜像服务器
使用P2P技术
源站及源站网络 使用接入层提供的缓存机制
使用应用层提供的缓存机制
使用分布式缓存
静态化、伪静态化
使用服务器操作系统提供的缓存机制

  总之,设计不能脱离具体需求和业务场景而存在,这里没有最优的组合方式,以上对该模式涉及问题的讨论,旨在发掘潜在的问题,以便合理应对。

参考资料

  《CloudDesignPatternsBook》

  《亿级流量网站架构核心技术》

边缘缓存模式(Cache-Aside Pattern)的更多相关文章

  1. 缓存实践Cache Aside Pattern

    Cache Aside Pattern旁路缓存,是对缓存应用的一个总结,包括读数据方案和写数据方案. 读数据方案 先读cache,如果命中则返回 如果miss则读db 将db的数据存入缓存 写数据方案 ...

  2. Cache Aside Pattern

    Cache Aside Pattern 即旁路缓存是缓存方案的经验实践,这个实践又分读实践,写实践 对于读请求 先读cache,再读db 如果,cache hit,则直接返回数据 如果,cache m ...

  3. Cache-Aside Pattern(缓存模式)

    Load data on demand into a cache from a data store. This pattern can improve performance and also he ...

  4. 缓存模式(Cache Aside、Read Through、Write Through、Write Behind)

    目录 概览 Cache-Aside 读操作 更新操作 缓存失效 缓存更新 Read-Through Write-Through Write-Behind 总结 参考 概览 缓存是一个有着更快的查询速度 ...

  5. 微软BI 之SSIS 系列 - Lookup 组件的使用与它的几种缓存模式 - Full Cache, Partial Cache, NO Cache

    开篇介绍 先简单的演示一下使用 Lookup 组件实现一个简单示例 - 从数据源表 A 中导出数据到目标数据表 B,如果 A 数据在 B 中不存在就插入新数据到B,如果存在就更新B 和 A 表数据保持 ...

  6. yii中缓存(cache)详解

    缓存是用于提升网站性能的一种即简单又有效的途径.通过存储相对静态的数据至缓存以备所需,我们可以省去生成这些数据的时间.在 Yii 中使用缓存主要包括配置和访问缓存组件 . 内部方法 一.缓存配置: 1 ...

  7. yii中缓存(cache)详解 - 彼岸あ年華ツ

    缓存是用于提升网站性能的一种即简单又有效的途径.通过存储相对静态的数据至缓存以备所需,我们可以省去生成 这些数据的时间.在 Yii 中使用缓存主要包括配置和访问缓存组件 . 内部方法 一.缓存配置: ...

  8. SQLite剖析之异步IO模式、共享缓存模式和解锁通知

    1.异步I/O模式    通常,当SQLite写一个数据库文件时,会等待,直到写操作完成,然后控制返回到调用程序.相比于CPU操作,写文件系统是非常耗时的,这是一个性能瓶颈.异步I/O后端是SQLit ...

  9. Handler+ExecutorService(线程池)+MessageQueue模式+缓存模式

    android线程池的理解,晚上在家无事 预习了一下android异步加载的例子,也学习到了一个很重要的东东 那就是线程池+缓存  下面看他们的理解.[size=1.8em]Handler+Runna ...

随机推荐

  1. 异步请求xhr、ajax、axios与fetch的区别比较

    目录 1. XMLHttpRequest对象 2. jQuery ajax 3. axios 4. fetch 参考 why: 为什么会出现不同的方法呢? what: 这些都是异步请求数据的方法.在不 ...

  2. 一文了解:Redis基础类型

    Redis基础类型 Redis特点 开源的,BSD许可高级的key-value存储系统 可以用来存储字符串,哈希结构,链表,集合 安装 windows:https://github.com/micro ...

  3. Kafka消息队列初识

    一.Kafka简介 1.1 什么是kafka kafka是一个分布式.高吞吐量.高扩展性的消息队列系统.kafka最初是由Linkedin公司开发的,后来在2010年贡献给了Apache基金会,成为了 ...

  4. python_Tensorflow学习(三):TensorFlow学习基础

    一.矩阵的基本操作 import tensorflow as tf   # 1.1矩阵操作 sess = tf.InteractiveSession() x = tf.ones([2, 3], &qu ...

  5. 安装CUDA9.0及对应版本的tensorflow-gpu详细过程(Windows server 2012R2版本也可以)

    由于最近跑机器学习相关代码的时候CPU运算速度跟不上,这才利用GPU来运算代码,显然使用GPU来运算速度明显要快很多,但是搭配GPU的使用环境是真的麻烦且头疼.网上有很多牛人的搭建过程,虽然他们都成功 ...

  6. Spring 常犯的十大错误,答应我 打死都不要犯好吗?

    1. 错误一:太过关注底层 我们正在解决这个常见错误,是因为 “非我所创” 综合症在软件开发领域很是常见.症状包括经常重写一些常见的代码,很多开发人员都有这种症状. 虽然理解特定库的内部结构及其实现, ...

  7. Ubuntu 18.04 LTS版本 GoldenDict安装与配置

    为何安装? GoldenDict是一款Linux下很好用的词典软件,其具有的关于词典的裁剪功能使得用户能够方便地对各种词典进行添加或删除,其具有的屏幕取词功能能够帮助用户方便地进行翻译,其具有的网络源 ...

  8. alluxio源码解析-层次化存储(4)

    层次化存储-特性介绍: https://www.alluxio.org/docs/1.6/cn/Tiered-Storage-on-Alluxio.html 引入分层存储后,Alluxio管理的数据块 ...

  9. web面试

    什么是面向对象? 程序中的事物都是用对象结构来描述的,所有的程序都是用面向对象的思想,管理数据和功能的. 面向对象三大特点:封装 继承 多态 什么是封装? 创建一个对象结构,用来保存一个事物的属性和方 ...

  10. AutoCAD C#二次开发

    https://www.cnblogs.com/gisoracle/archive/2012/02/19/2357925.html using System; using System.Collect ...