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? ...
随机推荐
- CrossFire Round #481 div.3 978 打后感
虚拟赛,头一次打div.3感觉好TM水啊...... 一共7道题,我A了6道,第7题有思路但是没时间了. 结果还是排在700多名,可能其他人也觉得太水了吧. 逐一解析题目: A好简单,因为不想离散化我 ...
- 洛谷P3622 动物园
题意:给定一个n个元素的圈,m个条件.满足一个条件需要选某些元素或不选另一些元素. 问最多能满足多少条件.每个条件所关联的元素,最远的两个距离不会超过5. 解:想了半天...... 首先能想到断环成链 ...
- 关于表单----html杂记
前言:总结了一些关于表单的东西,发下内心的感慨,以前感觉自己什么都会,今天竟然连最基本的表单的东西都忘了,好丢人, 学习的过程中,切勿眼高手低,一定要做好自己的笔记,然后多写代码,多想为什么,我比较笨 ...
- bind&currying
1. bind 基本用法 bind()是ECMAScript5中新增的方法,这个方法主要作用是将函数绑定至某个对象.当在函数f()上调用bind()方法并传入一个对象o作为参数,这个方法将返回一个新函 ...
- Css的前世今生
Css的基础知识扫盲 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. HTML的用法没有什么技巧,就是死记硬背,当然你不需要都记下来,能记住20个常用的标签基本上就OK了,其他不常用 ...
- 远程升级云服务器系统 CentOS 6.x 至 CentOS 7.x
由于docker-ce不再支持centos6,所以觉得吧系统升级为centos7,以下是踩坑的过程 1.添加源 /etc/yum.repos.d/upgrade.repo [upgrade] name ...
- 条理清晰的搭建SSH环境之添加所需jar包
一.首先介绍要添加框架环境: JUnit Struts2 Hibernate Spring (1)配置JUnit /**-------------------------添加JUnit-------- ...
- python---tornado初识(1)
# coding:utf8 # __author: Administrator # date: 2018/3/6 0006 # /usr/bin/env python import tornado.i ...
- 学习windows编程 day4 之 绘制随机矩形和peekMessage
#include <windows.h> #include <strsafe.h> LRESULT CALLBACK WndProc(HWND hwnd, UINT messa ...
- 在windows环境下实现开机延迟启动tomcat
如果说我们的服务器断电了 开机之后还需要手动开下服务 还需要远程连接上 然后一个一个开启 是不是很麻烦 我们可以写一个bat脚本 然后设置开机5分钟之后启动tomcat 首先配置环境变量: ...