边缘缓存模式(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. 解决 document.getElementsByClassName 在 IE8 下的兼容下的问题

    H5中新增了一个令人期待已久的方法:getElementsByClassName,这个方法让我们可以通过 class 属性中的类名来访问元素,这是极好的. but! 这个方法在IE9以下的浏览器是不支 ...

  2. vue.js带复选框表单的增删改查

    近段时间由于公司项目要求,前端开始使用VUE框架进行开发,最近刚开始学习,做了一个表单的增删改查,和大家分享一下. 页面模型代码设计如下 <template> <div id=&qu ...

  3. 技术派-不用sqrt手工计算平方根

    题目:任意长度数串,不使用sqrt函数,手工计算平方根?   要求只准用加/减/乘/除四则运算,不准使用power/sqrt等函数.   算法如下: 1.以小数点为中心往两边每2位分隔为一组: 2.然 ...

  4. spring 注解验证@NotNull等使用方法

    @Null 被注释的元素必须为null@NotNull 被注释的元素不能为null@AssertTrue 被注释的元素必须为true@AssertFalse 被注释的元素必须为false@Min(va ...

  5. C++单继承、多继承情况下的虚函数表分析

    C++的三大特性之一的多态是基于虚函数实现的,而大部分编译器是采用虚函数表来实现虚函数,虚函数表(VTAB)存在于可执行文件的只读数据段中,指向VTAB的虚表指针(VPTR)是包含在类的每一个实例当中 ...

  6. NDK jni mk文件 so文件 巴啦啦 初体验

    概念JNI(Java Native Interface,Java本地接口),实现了Java和其他语言的交互(主要是C/C++),如:Java程序通过JNI调用C/C++编写的在Windows上运行的D ...

  7. dubbo负载均衡是如何实现的?

    dubbo的负载均衡全部由AbstractLoadBalance的子类来实现 RandomLoadBalance 随机 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀 ...

  8. 【原创】JAVA进程突然消失的原因?

    引言 值此七夕佳节,烟哥放弃了无数妹纸的邀约,坐在电脑面前码字,就是为了给读者带来新的知识,这是一件伟大的事业! 好吧,实际情况是没人约.为了化解尴尬,我决定卖力写文章,嗯,一定是我过于屌丝! 好了, ...

  9. vue过滤器的使用

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. curl工具使用实例

    curl是一个命令行工具,其基于libcurl库,用于发送网络请求,获取并展示响应数据,下面来看curl的具体用法. 1.下载网页源码 curl命令直接接URL,用于下载指定URL的网页源码,并将其显 ...