Caching漫谈--关于Cache的几个理论
如今缓存是随处可见了,如果你的程序还没有使用到缓存,那可能是你的程序并发量很低,或对实时性要求很低。我们公司的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的几个理论的更多相关文章
- Caching漫谈--关于Cache的几个理论【转】
转自:https://www.cnblogs.com/asis/p/cache-pattern.html 如今缓存是随处可见了,如果你的程序还没有使用到缓存,那可能是你的程序并发量很低,或对实时性要求 ...
- [中英对照]Why Redis beats Memcached for caching | 在cache化方面,为何Redis胜过Memcached?
对Memcached和Redis有兴趣的同学不妨花几分钟读一读本文,否则请飘过. Why Redis beats Memcached for caching | 在cache化方面,为何Redis胜过 ...
- asp.net core 系列之Reponse caching之cache in-memory (2)
这篇文章(主要翻译于官网,水平有限,见谅)讲解asp.net core 中的 Cache in-memory (内存缓存). Cache in-memory in ASP.NET Core Cachi ...
- ABP理论学习之缓存Caching
返回总目录 本篇目录 介绍 ICacheManager ICache ITypedCache 配置 介绍 ABP提供了缓存的抽象,它内部使用了这个缓存抽象.虽然默认的实现使用了MemoryCache, ...
- 基于DDD的.NET开发框架 - ABP缓存Caching实现
返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...
- Cache的使用
公共方法Add 将指定项添加到 Cache 对象,该对象具有依赖项.过期和优先级策略以及一个委托(可用于在从 Cache 移除插入项时通知应用程序). Equals(从 Object 继承) 已重载. ...
- 采用EntLib5.0(Unity+Interception+Caching)实现项目中可用的Caching机制
看了园子里很多介绍Caching的文章,多数都只介绍基本机制,对于Cache更新和依赖部分,更是只简单的实现ICacheItemRefreshAction接口,这在实际项目中是远远不够的.实际项目中, ...
- Spring使用Cache、整合Ehcache
http://haohaoxuexi.iteye.com/blog/2123030 Spring使用Cache 从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring ...
- Implement Custom Cache Dependencies in ASP.NET 1.x
Code download available at:CuttingEdge0407.exe(128 KB) Contents What's a Cache Dependency, Anyway? ...
随机推荐
- 自学Linux Shell1.1-Linux初识
点击返回 自学Linux命令行与Shell脚本之路 1.1-Linux初识(架构.内核.shell) 1. Linux架构 Linux系统一般有4个主要部分:内核.shell.文件系统和应用程序.(有 ...
- 自学Linux Shell12.5-while、until命令
点击返回 自学Linux命令行与Shell脚本之路 12.5-while.until命令 until 循环与 while 循环在处理方式上刚好相反. while循环用于不断执行一系列命令,也用于从输入 ...
- Dominator Tree & Lengauer-Tarjan Algorithm
问题描述 给出一张有向图,可能存在环,对于所有的i,求出从1号点到i点的所有路径上的必经点集合. 什么是支配树 两个简单的小性质—— 1.如果i是j的必经点,而j又是k的必经点,则i也是k的必经点. ...
- 【转】CPU上下文切换的次数和时间(context switch)
http://iamzhongyong.iteye.com/blog/1895728 什么是CPU上下文切换? 现在linux是大多基于抢占式,CPU给每个任务一定的服务时间,当时间片轮转的时候,需要 ...
- luogu1345 奶牛的电信 (最小割)
虽然割点不好搞,但是可以变成割边呀 拆点,拆出来的边权给1,原图中的边权给inf,然后跑dinic就行了 #include<bits/stdc++.h> #define pa pair&l ...
- strace命令,read,write
strace + 运行的程序,可以查看程序运行的过程中调用的系统函数 read.write函数常常被称为Unbuffered I/O.指的是无用户及缓冲区.但不保证不使用内核缓冲区.
- 斯坦福大学公开课机器学习:Neural Networks,representation: non-linear hypotheses(为什么需要做非线性分类器)
如上图所示,如果用逻辑回归来解决这个问题,首先需要构造一个包含很多非线性项的逻辑回归函数g(x).这里g仍是s型函数(即 ).我们能让函数包含很多像这的多项式,当多项式足够多时,那么你也许能够得到可以 ...
- opencv 霍夫变换 实现图片旋转角度计算
在OCR实际开发中,证件照采集角度有很大的偏差,需要将图片进行旋转校正, 效果图: 在应用中发现应该加入高斯模糊,可以极大减少误差线条. 知道线条后 通过求斜率 得旋转角度 .(x1-x2)/(y1- ...
- DOJO常用的函数
DOJO常用的: 1,通过dojo.require以类似C编程中#include或者Java中import的方式加载所需的部件如dojo.require("dojo.parser" ...
- Unity做AR
Unity做AR呢这里借助了高通的AR包 这里是视频教程 http://www.tudou.com/programs/view/dnvEbIubNzI/ 这里是结果演示 http://www.tu ...
