边缘缓存模式(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. linux初学者-文件的归档和传输

      1.文件归档 因为linux系统都是以文件的形式存在,所以在处理文件时有时候因为文件太多导致传输速度慢等问题,为了提高方便并且提高效率,常把文件归档,文件归档就是把多个文件变成一个归档文件. 文件 ...

  2. 如何实现Kali linux系统下的U盘启动(小白指导)

    一.准备工作: 声明:这个“操作”并不会影响你原装的系统,真正的即插即用的哦. (1)4GB的U盘<读写速度比较快的> (2)Kali linux镜像文件 (3)软件Universal-U ...

  3. 程序员的长安十二时辰:Java实现从Google oauth2.0认证调用谷歌内部api

    最近公司在做一个app购买的功能,主要思路就是客户在app上购买套餐以后,Google自动推送消息到Java后端,然后Java后端通过订单的token获取订单信息,保存到数据库. Java后端要获取订 ...

  4. web设计_9_CSS常用布局,响应式

    一个完整的页面和其中的组件该如何具备灵活性. 怎么样利用CSS来实现无论屏幕.窗口以及字体的大小如何变化,都可以自由扩展和收缩的分栏式页面. 要决定使用流动布局.弹性布局还是固定宽度的布局,得由项目的 ...

  5. iOS 注释

    1) 参数的注释: UIButton *btnSend;/**< 发送按钮 */ 效果: 2) 方法的注释: type1(无参数): /** table 相关设置 */ -(void)confi ...

  6. codeforces 322 A Ciel and Dancing

    题目链接 题意: 有n个男孩和m个女孩,他们要结对跳舞,每对要有一个女孩和一个男孩,而且其中一个要求之前没有和其他人结对,求出最大可以结多少对. 如图,一条线代表一对,只有这样三种情况. #inclu ...

  7. 100天搞定机器学习|Day13-14 SVM的实现

    昨天我们学习了支持向量机基本概念,重申数学推导原理的重要性并向大家介绍了一篇非常不错的文章.今天,我们使用Scikit-Learn中的SVC分类器实现SVM.我们将在day16使用kernel-tri ...

  8. 33行代码爬取妹子图片(bs4+urllib)

    from bs4 import BeautifulSoupimport urllib2import urllibimport lxmlimport os def get_imgs(): image_c ...

  9. Layui多文件上传进度条

    Layui原生upload模块不支持文件上传进度条显示,百度,谷歌找了一下不太适用.后面找到一个别人修改好的JS,替换上去,修改一下页面显示即可使用,一下是部分代码 HTML: <div cla ...

  10. java并发编程(二十)----(JUC集合)CopyOnWriteArrayList介绍

    这一节开始我们正式来介绍JUC集合类.我们按照List.Set.Map.Queue的顺序来进行介绍.这一节我们来看一下CopyOnWriteArrayList. CopyOnWriteArrayLis ...