缓存三要素:
- 命中率
- 缓存更新策略
- 缓存最大数据量
命中率
- 定义:命中率指请求缓存次数和缓存返回正确结果次数的比例
- 例子:mysql的 query cache 用来缓存和query相关的数据。具体来说,query cache 缓存客户端提交给mysql的select语句以及该语句的结果集。就是将select语句和语句的结果作hash映射关系后保存在一定的内存区域中(可以通过执行 SHOW GLOBAL STATUS 查看 GLOBAL STATUS)。按照SQL Cache规则,只有SELECT 被缓存,如果数据需要频繁更新,可能没有必要设置缓存。
缓存更新策略
- MySQL的 Query Cache 缓存更新策略很简单。在Mysql中,可以设置Query Cache所使用的总内存,MySQL会把默认可以进行缓存的SQL语句的结果集进行缓存,一旦内存塞满后,就会剔除老的Query Cache 对象。当表中的数据有任何变换时,包括新增、修改、删除等,都会使所有引用到该表的SQL 的 Query Cache 失效。
- 缓存更新策略归纳:
- FIFO(First In First Out)及Second Chance
- FIFO:最先进入缓存的数据在缓存空间不够的情况下(超出最大元素限制时)会被首先清理出去。
- Second Chance 是通过FIFO修改而来的,比FIFO好的地方是改善了成本。它会检查即将被踢出的对象有没有之前被使用过的标志(1 一个 bit 表示),如果没有被使用过,就把他踢出。否则,这个标志位将被清除,然后把这个缓存对象当作新增缓存对象加入队列
- LFU (Less Frequently Used)
- 最少使用的元素会被清理掉。这要求缓存的元素有 hit 属性,在缓存空间不够的情况下,hit值最小的将被清除出缓存。
- 标准LRU (Least Recently Used)
- 最近使用最少的元素被清理。缓存的元素有一个时间戳,当缓存容量满了,现有缓存元素中时间戳距离当前时间最远的元素会被清除出缓存。
- 近似/变异LRU算法
- LRU在大多数情况下表现是不错的,但是由于只考虑了访问时间而没有考虑访问频率,存在顺序扫描问题。在顺序扫描的情况下,LRU没有命中,此外标准的LRU成本也很高。近似LRU算法则是LRU算法的一种微调算法,往往通过牺牲精度来获得更好的性能,通常使用肥标准的LRU师兄或过期删除策,比如不严格按时间删除过期键而是随机删除。
- 还有一类算法属于变异LRU算法,比如LRU2算法以及2Q算法。LRU2会把被两次访问过的对象放入缓存池,当缓存池满了之后,把有两次最少使用的缓存对象踢走,也即需要跟踪对象两次。
- 2Q(Two Queues)算法把被访问的数据放到LRU的缓存中,如果这个对象再一次被访问,就可以把它转移到第二个、更大的LRU缓存。
- ARC 算法(Adaptice Replacement Cache)
- ARC 介于LRU 和 LFU之间,为了提高效果,ARC由两个LRU组成,第一个,也就是L1,包含的条目是最近只被使用过一次的,而第二个LRU,包含的是最近被使用过两次的条目。因此L1放的是新对象,而L2放的是常用的对象。
- MRU算法(Most Recently Used)
- MRU 和 LRU 是对应的,MRU 会移除最近最多被使用的对象,适用于高并发、但命中率低的场合。
- 其他缓存策略
- Sliding time-based expiration、Random Cache、LIRS等使用场景比较罕见的算法。
缓存最大数据量
- 定义:在缓存中能处理元素的最大个数或所能使用的最大存储空间。通常各种缓存机制会有限定(可能由操作系统决定)。
- 例子:
- MySQL 的 Query Cache 缓存最大数据量由 query_cache_size 参数决定,并且可以修改。
- Memcached 缓存最大数据量可使用内存由操作系统决定,默认64MB,每次最大可申请内存为2MB
- 超过缓存机制所允许的最大数据量系统会进行相应处理
- 停止缓存服务,所有缓存数据被清空
- 拒绝写入,不再对缓存数据进行更新
- 根据缓存更新策略清除旧数据。
文件缓存
- 定义:把缓存数据存储到文件系统即硬盘中。
- 磁盘容量大,可以存放足够多的数据。
- 磁盘与内存相比更稳定更可靠,断电后数据不丢失,存储也比较简单可靠。
- 固态硬盘的读取和速度极高,可达500Mb/s
- 扩展容易。可以使用磁盘阵列、分布式处理等进行大规模的存储和管理。
- 文件缓存机制
- 文件缓存机制逻辑很清晰,模板的作用之一就是把动态的PHP代码“编译”成静态的HTML文件,当下次读取时不要再“编译”,直接读取静态文件。
- 下面的简单的模板引擎使用最简单的文件缓存:
- 根据配置文件判断是否要进行缓存,若需要缓存,则跳转下一步。若不需要,则include 加载php文件。
- 判断对应的静态文件是否存在,存在,判断对应静态文件是否过期,若未过期则读取,否则重新编译。若不存在,则进行编译,将编译内容保存为静态的HTML文件。
- 尽管文件存储受到磁盘I/O效率的影响,但再web中,类似模板编译后存储到静态文件这样的做法由很大的优势。
public function show($file){
$this->file = $file;
$PIG_TPL_FILE = $this->arrayConfig['templateDir'].$file.$this->arrayConfig['suffix'];
//判断缓存是否过期,以及缓存是否存在
if(is_file($this->path())){
//判断缓存是否过期,1是过期 0是未过期
$old = $_SERVER['REQUEST_TIME'] - filetime($this->path()) >= $this->arrayConfig['cache_time']?1:0;
}else{
$old = 0;
}
//true是需要静态编译(缓存)
if(true == $this->arrayConfig['cache_htm']){
//判断缓存是否存在且过期没
if(is_file($this->path()) && !$old){
readfile($this->path());//静态编译文件存在且还没过期
}
else{
if(is_file($PIG_TPL_FILE )){
ob_start();
extract($this->value);
include($PIG_TPL_FILE);
$this->compile();//开始编译
}else{
die('找不到模板文件');
}
}
}else{
//不需要静态编译(缓存)
if(if_ifle($PIG_TPL_FILE)){
extract($this->value);
include($PIG_TPL_FILE);
}else{
die('找不到模板文件:'.$PIG_TPL_FILE);
}
}
}
//有时,会把一些常用的或比较耗费数据库资源的大变量缓存起来
$data = $db->fetchAll($sql);
file_put_contents(ROOT.'cache/cat.PHP',var_export($data,TRUE));
//需要的时候打开文件
eval("\$data = $data;");
客户端缓存
- 缓存最后一层,是直接面对客户端的客户端缓存。通常也吧这部分称为web缓存。web缓存位于客户端。缓存会根据进来的请求保存输出内容的副本。例如html页面、图片、文件等。然后当下个请求到来时,如果是相同的URL缓存直接使用副本响应访问请求,而不是向源服务器再次发送请求。
- web缓存是由浏览器实现。浏览器在计算机上开辟一块硬盘空间用于存储已经看过的网站的副本。
- 浏览器缓存:在同一个会话过程中(即当前浏览器没有被关闭之前)会检查一次并确定缓存的副本足够新。这个缓存对于用户单击“后退”或者单击刚访问过的链接特别有用。
客户端缓存规则
- 前端页面缓存主要遵循HTTP协议和客户端的设置工作。
- 如果响应头信息告诉缓存器不要保留缓存,缓存器就不会缓存相应内容。
- 如果请求信息需要认证或者安全加密,相应内容也不会被缓存。
- 如果在回应中不存在校验器(ETag或者Last-Modified头信息),缓存服务器会认为缺乏直接的更新度信息,内容将会被认为不可缓存
- 一个缓存的副本如果由以下信息,将会被认为足够新
- 含有完整的过期时间和寿命控制头信息,并且内容仍在保鲜期内。
- 浏览器已经使用过缓存副本,并且在一个会话中已经检查过内容的新鲜度。
- 缓存代理服务器近期内已经使用过缓存副本,并且内容的最后更新时间在上次试用期之前
- 够新的副本将直接从缓存中送出,而不会向源服务器发送请求。
- 如果缓存的副本已经太旧,缓存服务器将向源服务器发出校验请求,用于确定是否可以继续使用当前拷贝继续服务。
HTTP协议中的缓存使用
- HTML文档中的<head>区域中加入描述文档的各种属性,这些Meta标签常常被用于标记文档不可以被缓存或者标记多长时间后过期
- 客户端第二次请求同一个URL时,根据HTTP协议的规定,浏览器会向服务器传送报头(HTTP Request Header),服务器响应请求,并查询到标记文件没有发生改动,服务端返回304,浏览器接收到此状态码后,可以直接从本地缓存中读取。
- HTTP/1.x 304 Not Modified
- Date:Tue,03 Mar 2009 05:03:56 GMT
- Content - Type:image/jepg
- Content - Length:83185
- Last - Modified:Tue,24 Feb 2009 08:01:04 GMT
- Cache - Control :: max - age = 2592000
- Expires : Thu,02 Apr 2009 05:14:08 GMT
- Etag:"5d8c72a5edda8d6a:3239"
- 其中 Last-Modi、Expires和ETag是页面缓存标识
- 参数简介
- Expires(过期时间),告诉缓存器相关副本在多长时间内是新鲜的。过了这个时间,缓存器就好向服务器发送请求,检查文档是否被修改。Expire是头信息对设置静态图片文件可缓存特别有用。过期时间头信息属性值只能是HTTP格式的日期时间,其他的都会被解析成当钱时间之前,副本会过期。
- Cache - Control:通过这个属性可以让网站发布者全面控制内容,并定位过期时间的限制
- 有用的cache-control响应头信息
- max-age = [秒]:从请求时间开始到过期时间之间的秒数
- s-maxage = [秒]:应用于共享缓存
- public:标记认证内容也可以被缓存。一般经认证的内容才能被缓存,输出是自动不可以被缓存的
- no-store:强制缓存在任何情况下都不要保留任何副本
- must-revalidate:每次使用该资源都要确认资源新鲜性
- proxy-pevalidate:指对缓存代理服务器起作用
- Cache-Control:max-age=300,must-revalidatte
- Last-Modified:文档最后修改时间。
- If-Modified-Since:询问该时间之后的文件是否有被修改过。
- 如果服务端的资源没有发生变化,则自动返回HTTP3-4状态码,内容为空,这样就节省了传输数据量。当服务端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时相类似。
- 如果If-Modified-Since的时间比服务器当前时间(request_time)还晚,会认为是个非法请求
- ETag:服务器生成的唯一标识符ETag,每次副本的标签都会发生变化。HTTP协议规格说明定义ETag为“被请求变量的实体标记”。简单点说即服务器响应时给请求URL标记,并在HTTP响应头中将其传送到客户端,类似服务器端返回的格式。
- 如果ETag没变,返回状态304。在客户端发出请求后,HTTP Reponse Header中包含Etag:"5d8c72a5edda8d6a:3239" 标识,等于告诉Client端,你拿到的这个资源又表示ID:"5d8c72a5edda8d6a:3239",下次需要发情期并索要同一个URI时,浏览器同时发出一个If-None-Match报头(request),此时包头信息包含上次访问得到的ETag:标识。
- 这样Client端相当于缓存两份,服务端会比对二者的ETag。如果If-None-Match为False,返回304。
- 小窍门
- 不经常改变的图片/页面启用缓存,使用Cache-Control:max-age属性设置一个较长的过期时间
- 定期更新的内容设置一个缓存服务器可以识别的max-age属性或过期时间
- 尽量避免使用post,post模式的返回内容大部分缓存服务器都不会保存。
- 不要在URL中加入针对每个用户的识别信息
HTTP缓存实例
- 只有出现三种情况浏览器才会更新缓存
- 缓存到期
- 缓存被清除
- F5强制刷新
- 有些浏览器会缓存静态资源,并且普通用户很少对缓存进行清理,这会导致虽然开发人员已经在服务器上更新了文件,但是用户一直没有看到更新情况。这种情况下,需要用户强制刷新或者清空浏览器本地缓存才能使缓存失效。此时我们通常采取在静态资源后附加一个时间戳的方式避免缓存:
- <script src = "link.js?d = 1923454332"></script>
- 在JavaScript文件后面加上问号的那一串字符没有实际意义,只是为了避免JavaScript被缓存到本地而不能及时更新,其算是给JavaScript文件加的一个“版本号”,通常为时间戳或者版本标记(如link.js?Version=1.2.0)。这样浏览器就会认为这是一个新资源,从而发起请求,更新本地缓存。
- 需要注意的是,如果有的服务器加了代理服务器,其忽略静态文件名后面的字符串,对请求不予以响应。这时,最好直接更改文件名,如link_20122.js
<?php
//test.php
//header('Cache-Control:no-cache,must-revalidate');//告诉浏览器不用缓存
//header(Pragma:no-cache');//告诉浏览器不用缓存
header('Cache-Control:max-age=86400,must-revalidate');//24小时
header('Last-Modified:'.gmdate('D,d M Y H:i:s').'GMT');
//header('Expires'.gmdate('D, d M Y H:i:s',time()+'86400').'GMT');
echo'我不刷新';
?>
<!DOCTYPE html>
<html>
<body>
haha,<a href = test.php>go</a>
</body>
</html>
HTML5中的Application Cache
- HTML5中新增一个特性Application Cache接口,用来处理离线应用中的一些问题。使用这个接口会给应用带来三方面的优势:
- 离线浏览:用户在不能联网时依然能浏览整个站点
- 高速:缓存资源是存储在本地的,能更快加载
- 更小的服务器负载:浏览器只需从服务器端下载有改变的资源,相同资源不重复下载
- Application Cache指定浏览器需要保存哪个文件。用户在离线的情况下,即使按了刷新按钮,应用也能正确加载和工作。
- Application Cache配置:
- <html manifest = "example.mf"></html>
- 如果一个页面没有manifest属性,将不会被缓存(除非在Manifest文件中显示定义这个页面)。这意味着只要用户访问的页面包含Manifest属性,都将被加入Application Cache中。这样,不用在Manifest文件中指定需要缓存哪些页面
- Manifest属性可以指定一个绝对URL或者一个相对路径,但是,一个绝对URL需要和Web App同源。一个Manifest文件可以是任何扩展类型的文件,但必须要有正确的MIME-Type。可以在Web服务端的配置文件中加上这个属性。
- 以Apache为例,在httpd.conf里加上一句话:
- AddType text/cache - manifest.mf
- Manifest文件内容如下
- CACHE MANIFEST
- index.html
- style.css
- images/logo.jpg
- scripts/main.js
- 需要注意,第一行“CACHE MANIFEST”字符串是必不可少的。在文件中可以用#号注释。 一个应用只有在Manifest文件发生变化时才会更新缓存。例如:如果编辑图像或者改写一个JavaScript函数,缓存并不会发生更新。必须改写Manifest文件本身来通知浏览器需要更新缓存文件,缓存才会更新。
- 一个应用在离线情况下会保持它的缓存状态,除非有以下事件发生则会更改其缓存状态:
- 用户清除浏览器中存储的站点数据
- Manifest file被修改
- 注意:修改里在Manifest文件中列出的某个文件并不会让浏览器重新缓存资源。必须是Manifest文件本身改变 了,才会重新进行缓存。
- App Cache通过编程更新
- 要在脚本中更新缓存也比较简单,代码如下
var appCache = window.applicationCache;
var status = appCache.status;//缓存状态,0-5之间
console.log(status);
appCache.update();//更新缓存
if(appCache.status == window.applicationCache.UPDATEREADY){
appCache.swapCache();//如果获取成功,清除旧数据,用新数据替代
}
web服务器缓存
- 在web层面上的缓存,除了基于HTTP协议加浏览器实现外,还可以通过一些Web服务器自带的缓存组件,以及服务器和浏览器之间的代理服务器提供的缓存功能实现。
- Apache缓存
- Apache的Expires和Cache-Control模块包含控制缓存的信息。这些模块需要和Apache一起编译。
- 默认是没有启动的
- 如何确定相应模块以及被启用
- 找到httpd程序并运行httpd-1会列出可用模块,要用的模块是mod_expires和mod_headers。
- 一旦启用了模块,就可以在.htaccess文件或者服务器的access.conf文件中,通过mod_expires设置副本过期时间了,包括设置控制应答时的Expires头内容和Cache-Control头的max-age
//方式一:设置头内容
ExpiresActive On
ExpiresByType image/gif"access plus 1 month"
ExpiresByType image/jpg"access plus 1 month"
ExpiresByType text/js"access plus 30 minutes"
ExpiresByType application/x-javascript"access plus 30 minutes"
ExpiresBytype application/x-shockwave-flash"access plus 30 minutes"
//方式二:
<ifmodule mod_expires.c>
<filesmatch"\.(jpg|gif|png|css|js)$">
ExpiresActive on
ExpiresDefault "access plus 1 year"
</filesmatch>
</ifmodule>