如今缓存是随处可见了,如果你的程序还没有使用到缓存,那可能是你的程序并发量很低,或对实时性要求很低。我们公司的ERP在显示某些报表时,每次打开都需要花上几分钟的时间,假如搜索引擎也是这么慢,我想这家搜索引擎早就被淘汰了。

这些ERP报表是否该引入缓存加速一下呢……

使用缓存,就是在取出数据结果后,暂时将数据存储在某些可以快速存取的位置(例如各种NoSQL如Redis,HBase,又或MemoryCache等等),于是就可以让这些耗时的数据结果多次重复的利用,不必每次重复请求相同的数据,节省CPU和I/O,加速程序的响应。

使用缓存能让加载数据的延迟降低,I/O操作减少,从而性能得到提高。

缓存使用起来很容易,但是保持缓存的一致性却困难得多。有一些前人总结的经验和方法,我们可以借鉴一下。

缓存重点在于写入的时候,相关数据的更新问题,如果数据一直没有更新或删除操作,那缓存就不会存在脏数据一说了。

关于缓存写入,至少有4种写入的策略。

write-through

这个动作发生在Cache层。是指在写数据时,同时写入到缓存和DB中,这个写入操作认为是一个单一的事务,也就是说,在更新Cache时,我们先更新了Cache中的数据,然后接着更新Db中的数据。在DB的数据完成更新前,程序还不会返回,一直等到数据库存储的结果。可想而知,这种做法不会给写入数据带来更高的性能。

我用向下的箭头表示一个函数的调用,绿色的虚线表示函数的返回,我拙劣的语言能力还难以描述清楚,但如果把这个图写成程序,就像这个样子:

public class UserLogic
{
public void WriteToDb(UserEntity user)
{
CacheManager cache = GetCacheManager();
cache.WriteToDb(user);
}
}
public class CacheManager
{
public void WriteToDb(UserEntity user)
{
cacheStore.Set(user.cacheKey, user.ToJson());
dbManager.UpdateSqlServer(user);
}
}

UserLogic类放在Cache层之上,非Cache层,比如应用层、领域层、某某逻辑层之类……,它的WriteToDb函数内部并没有关于Db执行的代码,它不关心Db,它直接调用了Cache层的CacheManager的WriteToDb()。

CacheManager.WriteToDb()内部先去更新了Cache,然后并没有立刻返回,接着又将user写入了MSSQL里面。

所以我说,这种做法不会给写入数据带来更高的性能。

write-around

发生在Cache层,直接写入数据到数据库,不必写到缓存中。这个不必过多的解释,缓存的数据应该被立即过期(否则数据就会不一致了)

当我写这篇文章的时候,发现确实伪代码比我无力的中文解释更清晰,那么write-around的伪代码呢?

public class UserLogic
{
public void WriteToDb(UserEntity user)
{
cacheStore.Invalidate(user.caheKey);
dbManager.UpdateSqlServer(user);
}
}

应用层绕过了Cache层,直接调用Db写入了数据库。

write-behind

还是发生在Cache层,刚开始时,写入到缓存中,当设定的缓存容量达到上限,或等到一定的时间间隔后,再写到数据库中。

换句话说,写入到数据库是有条件的。当我们要存入数据库时,一定是有一批数据甚至是一大批数据都需要从缓存中写到数据库中。我们想象这是一个写入的队列,要写入的数据源源不断的放入到这个队列中。此时,我们可以使用服务器上的另外一个进程独立的处理这个队列。

如果在写入数据库前某些数据又发生了修改,因为该数据还没有被插入到数据库,所以这个队列中对应的那个元素也会发生相应的更改,以保证最后插入到数据库中的是最新的数据。

public class UserLogic
{
public void WriteToDb(UserEntity user)
{
CacheManager cache = GetCacheManager();
cache.WriteToDb(user);
}
}
public class CacheManager
{
public void WriteToDb(UserEntity user)
{
cacheStore.AddToQueue(user.cacheKey, user.ToJson());
}
}
public class DbDaemon
{
public void Watch()
{
while(someConditionIsOK())
{
UserEntity user = cacheStore.UserDequeue();
dbManager.WriteToDb(user);
}
}
}

在这种模式里,应用层UserLogic照例调用Cache层进行更新,但是CacheManager内的WriteToDb函数已经发生了变化,它将user的内容存放在一个队列中(当然了,同时也得更新缓存)。然后程序的就返回了。

可是,数据怎么放到Db里面去呢?

这技巧放在DbDaemon里,DbDaemon位于另外一个exe中,是独立的应用程序(比如这是个Windows Service),当条件满足时,这个程序到队列里面去取值,取出来再放更新到数据库。

于是乎,UserLogic层只是更新了缓存而已,这会最大限度的保证写入的效率。

write-behind至少带来这几点好处:

  • 性能的提升,因为程序不再需要等待数据库的写入操作,当数据写入缓存成功后,程序立刻返回了。
  • 降低了数据库的负载,因为每次都是批量的插入到数据库中。如果在正式插入数据库前,数据还有更改,这会让它的优势更明显。例如,插入数据库是数据是{id:1, name:”张三”},这条数据先被放入队列但还没有被处理。后来这条数据又被更改成了李四王五赵六,无论被改了多少次,最终都是以最后的”赵六”为最终结果,数据库实际上只有一次插入操作。
  • 程序还提高了可靠性。想象如果数据库服务器发生了宕机,程序并不会立即出现问题,缓存的写入队列还在起作用。
  • 并发性能的提高,如果程序需要处理更多的并发,可以提高写入数据库的间隔,减少数据库的压力。

    但是,使用write-behind也是有挑战的,如果要使用write-behind,至少有这几点要考虑:
  • 由于数据库的更新是滞后于cache的,这意味着数据库的事务只能成功不能失败。我们想象一下客户已经提交了购买订单,cache更新成功,订单放入缓存的队列,但是20分钟后,尝试写入到SQL数据库时,出现了数据库插入失败的尴尬局面。
  • 如果第三方的应用,或者是人为原因去更新了数据库,这可能导致写入队列中的数据与数据库中数据出现冲突,从而导致数据回写失败。比如缓存中的一个主键是97033,正待写入到数据库。结果另外的某程序抢先一步插入了97033,那么就悲剧了。

cache-aside

发生在应用层,应用层保证缓存结果同DB的数据一致性,应用层来负责写入到数据库和整理缓存,缓存层则不必插手此事。

public class UserLogic
{
public void WriteToDb(UserEntity user)
{
cacheStore.Set(user.cacheKey, user);
dbManager.WriteToDb(user);
}
}

在这种模式里,所有动作就在应用层里发生,它自己更新Cache,自己写到Db,爱怎么干怎么干。

4种写入数据的方式各有各的特点,可以根据项目自身的特点加以选择。

数据的读取就简单得多了,有这样两种方式。

read-through

读取数据时,先尝试从缓存中取得,如果缓存中没有,那么再从数据库中读取,而后也将数据放入缓存中,以便下次读取。

refresh-ahead

简单的说就是在缓存数据过期前,能自动的刷新缓存数据。举个例子来说,某条数据在缓存中,过期时间是60秒。我们给他设置一个参数,比如是0.8,60x0.8=48秒,那么在前48秒访问该数据,就照正常的取法,直接返回缓存中的数据。当在48-60秒这个区间取数据时,缓存先将之前缓存的结果返回给外部应用程序,然后异步的再从数据库去更新缓存中的值,以尽可能的保证缓存的值是最新的。如果取数据的的时候超过了60秒,就安装read-through的方式。

Refresh-ahead是对未来数据的访问情形的估算,我们猜测这个数据在过期后,仍然可能被频繁的访问,那么这种设计的策略获得的优势会更明显。

但是,如果有大量的数据是用refresh-ahead策略,但是这个数据被重新缓存后,又一次都没有被访问过,那这个策略就是很失算的了。

那么,有人可能会问,既然如此,我把过期的时间延长不就好了,之前60秒过期,改成6000秒过期,这样就不会用Refresh-ahead策略来刷新数据了。

然而,事实是缓存的数据越久,出现脏数据的可能性也就越大,更重要的是,如果你的估算也是失误的,大量的超期缓存数据没有被实际访问,那么你就浪费了很多的内存,做了无用的事情,这也是应该避免的。

缓存策略介绍完毕。再加上伪代码,相信我还是说得比较清楚了。

小春 原创内容 本文地址:http://www.cnblogs.com/asis/p/cache-pattern.html https://1few.com/cache-pattern/

Caching漫谈--关于Cache的几个理论的更多相关文章

  1. Caching漫谈--关于Cache的几个理论【转】

    转自:https://www.cnblogs.com/asis/p/cache-pattern.html 如今缓存是随处可见了,如果你的程序还没有使用到缓存,那可能是你的程序并发量很低,或对实时性要求 ...

  2. [中英对照]Why Redis beats Memcached for caching | 在cache化方面,为何Redis胜过Memcached?

    对Memcached和Redis有兴趣的同学不妨花几分钟读一读本文,否则请飘过. Why Redis beats Memcached for caching | 在cache化方面,为何Redis胜过 ...

  3. asp.net core 系列之Reponse caching之cache in-memory (2)

    这篇文章(主要翻译于官网,水平有限,见谅)讲解asp.net core 中的 Cache in-memory (内存缓存). Cache in-memory in ASP.NET Core Cachi ...

  4. ABP理论学习之缓存Caching

    返回总目录 本篇目录 介绍 ICacheManager ICache ITypedCache 配置 介绍 ABP提供了缓存的抽象,它内部使用了这个缓存抽象.虽然默认的实现使用了MemoryCache, ...

  5. 基于DDD的.NET开发框架 - ABP缓存Caching实现

    返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...

  6. Cache的使用

    公共方法Add 将指定项添加到 Cache 对象,该对象具有依赖项.过期和优先级策略以及一个委托(可用于在从 Cache 移除插入项时通知应用程序). Equals(从 Object 继承) 已重载. ...

  7. 采用EntLib5.0(Unity+Interception+Caching)实现项目中可用的Caching机制

    看了园子里很多介绍Caching的文章,多数都只介绍基本机制,对于Cache更新和依赖部分,更是只简单的实现ICacheItemRefreshAction接口,这在实际项目中是远远不够的.实际项目中, ...

  8. Spring使用Cache、整合Ehcache

    http://haohaoxuexi.iteye.com/blog/2123030 Spring使用Cache 从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring ...

  9. Implement Custom Cache Dependencies in ASP.NET 1.x

    Code download available at:CuttingEdge0407.exe(128 KB)   Contents What's a Cache Dependency, Anyway? ...

随机推荐

  1. 自学Linux Shell12.8-循环实例

    点击返回 自学Linux命令行与Shell脚本之路 12.8-循环实例 待定. 3 fi bash shell的if语句会运行if后面的那个命令. 如果该命令的退出状态码是0 (该命令成功运行),位于 ...

  2. 洛谷 P2774 方格取数问题 解题报告

    P2774 方格取数问题 题目背景 none! 题目描述 在一个有 \(m*n\) 个方格的棋盘中,每个方格中有一个正整数.现要从方格中取数,使任意 2 个数所在方格没有公共边,且取出的数的总和最大. ...

  3. 洛谷 P2317 [HNOI2005]星际贸易 解题报告

    P2317 [HNOI2005]星际贸易 题目描述 输入输出格式 输入格式: 输出格式: 如果可以找到这样的方案,那么输出文件output.txt中包含两个整数X和Y.X表示贸易额,Y表示净利润并且两 ...

  4. JAVA多线程之中断机制(如何处理中断?)

    一,介绍 这篇文章主要记录使用 interrupt() 方法中断线程,以及如何对InterruptedException进行处理.感觉对InterruptedException异常进行处理是一件谨慎且 ...

  5. SDL源码阅读笔记(1) 基本模块

    write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie 前言 对于大牛来说,写关于阅读源码的文章都会叫源码剖析或者深入浅出啥的,对于我,自己阅读阅读源码,写 ...

  6. bzoj3209 花神的数论题——数位dp

    题目大意: 花神的题目是这样的 设 sum(i) 表示 i 的二进制表示中 1 的个数.给出一个正整数 N ,花神要问你 派(Sum(i)),也就是 sum(1)—sum(N) 的乘积. 要对1000 ...

  7. 有趣的filter

    js中的filter就是过滤的意思,比如,我们以什么样的方式进行过滤,得到我们想要的结果. 对,我们要的就是这个结果. 给定一个数组,我们要的是Burger(汉堡) const restaurants ...

  8. Java 多线程间通信

    JDK 1.5 以后, 将同步和锁封装成了对象, 并将操作锁的隐式方法定义到了该对象中, 将隐式动作变成了显示动作. Lock 接口 Lock 接口, 位于 java.util.concurrent. ...

  9. WCF开发实战系列三:自运行WCF服务

    WCF开发实战系列三:自运行WCF服务 (原创:灰灰虫的家 http://hi.baidu.com/grayworm)上一篇文章中我们建立了一个WCF服务站点,为WCF服务库运行提供WEB支持,我们把 ...

  10. 酷炫的SVG 动态图标

                                                      在  loading.io 上能看到好多效果惊艳的loading图标.它们都是用svg写成的,寥寥几 ...