题为“缓存的应用与设计模式”的技术分享活动准备文档

在公司举行的技术分享活动上, 这周轮到我进行一个主题的讲解了;在上周六开始在定下了“缓存” 为主题的内容,主要是有感于之前缓存在项目中的应用。主要思路就是在用户从浏览器发送请求,然后得到服务器的响应内容这样一个流程中,有哪些地方能够实现缓存的最大化利用,提高用户的体验以及服务器的响应速度。文档的主要构成是一些从网上找到的具体配置信息,概念性的内容;还有自己程序上的一些应用技巧,加上自己的理解;也借鉴了一些其他大牛的博客内容、理念等。

文稿如下:

在web应用的一个请求中,用户从浏览器发送请求,服务器接收到请求后进行数据的收集,根据参数信息调用对应的service或model,model从DB中查询检索到数据信息返回给服务器,服务器将得到的数据内容进行处理封装,响应发送给用户的浏览器。那在这个请求的流程中,缓存在不更改代码或少改代码情况下可以很好的提高服务器的响应处理速度,比如在服务器的缓存,内存层面的高速缓存,浏览器的本地缓存,程序应用上的缓存等。而有关php脚本优化以及源码编译后的opcode动态缓存的加速器如zend optimizer,eAccelerator等不在这次讨论范畴内。

服务器级缓存

Apache

mod_cache  mod_disk_cache  mod_mem_cache  mod_file_cache module的缓存实现

Apache 的缓存方式有两种,一种是基于硬盘文件的缓存,由mod_disk_cache实现,另一种是使用内存缓存,由mod_mem_cache实现,不过它们都是依赖mod_cache模块的,mod_cache模块提供了一些缓存配置的指令供它们使用,而mod_file_cache模块是搭配mod_mem_cache模块使用的

Mod_disk_cache以磁盘文件形式进行缓存

<IfModulecache_module>

CacheDefaultExpire  3600

CacheMaxExpire  86400

CacheLastModifiedFactor  0.1

<IfModuledisk_cache_module>

CacheEnable disk /

CacheRoot”D:\Program Files\Wamp\Apache2.2\cache”

CacheDirLevels 5

CacheDirLength 5

CacheMaxFileSize 1048576

CacheMinFileSize 10

</IfModule>

</IfModule>

使用缓存需要注意如下事项:

要使用缓存,必须使用指令CacheEnable启用它,目前可用的缓存类型为 disk 或mem,禁止缓存可以使用CacheDisable,如CacheDisable /private

待缓存的 URL 返回的状态值必须为: 200、203、300、301 或 410

URL 的请求方式必须是 GET 方式

发送请求时,头部中包含“Authorization: ”的字符串时,返回的内容将不会被缓存

URL 包含查询字符串,如问号?后的那些东西,除非返回的内容包含“Expires:”,否则不会被缓存

如果返回的状态值是 200,则返回的头部信息必须包含以下的一种才会被缓存:Etag、Last-Modified、Expires,除非设置了指令CacheIgnoreNoLastMod On

如果返回内容的头部信息“Cache-Control:”中包含“private”,除非设置了指令CacheStorePrivate On,否则不会被缓存

如果返回内容的头部信息“Cache-Control:”中包含“no-sotre”,除非设置了指令CacheStoreNoStore On,否则不会被缓存

如果返回内容的头部信息“Vary:”中包含了“*”,不会被缓存

配置完成后,重启apache后即可,测试一个脚本

<?php

Header(‘Last-Modified: ’. gmdate(‘D, d M Y H:i:s’, time()) . ’ GMT’);

Var_dump($_SERVER);

?>

在服务器指定的cachedir目录下面可以看到后缀为.data 与.header的文件;.data是当前请求页面的所有文档内容,而.header则是当前请求头信息。CacheDefaultExpire可以设定这些文件的过期时间。

Mod_file_cache缓存模块配合mod_mem_cache模块来使用

如果你的网站有几个文件的访问非常频繁而又不经常变动,则可以在 Apache 启动的时候就把它们的内容缓存到内存中(当然要启用内存缓存系统),使用的是mod_file_cache模块,具体如下:

有多个文件可以用空格格开

MMapFile  var/wwwml/index.html var/wwwml/articles/index.html

上面是缓存文件的内容到内存中,除此之外,还可以只缓存文件的打开句柄到内存中,当有请求进来的时候,Apache 直接从内存中获取文件的句柄,返回内容,和MMapFile指令很像,具体如下:

CacheFile var/wwwml/index.html var/wwwml/articles/index.html

上面两个指令所缓存的文件如果有修改的话,必须重启 Apache 或使用 graceful 之类的方式强制使 Apache 更新缓存数据,否则当用户访问的时候获取的不是最新的数据。

有时候需要根据某些特殊的头部信息来决定是否进行缓存,则可以使用如下指令:

当头部信息中包含 Set-Cookie 时则跳过不进行缓存操作

CacheIgnoreHeaders Set-Cookie

有时候需要缓存的时候跳过 URL 中的查询字符串?使用如下指令:

CacheIgnoreQueryString On

Apache 还有一个模块mod_expires,这个模块控制header中的cache-control参数的max-age指令

ExpiresActive On

#ExpiresDefault A600

ExpiresByType image/x-icon A2592000

ExpiresByType application/javascript A2592000

ExpiresByType text/css A604800

ExpiresByType image/gif A2592000

ExpiresByType image/png A2592000

ExpiresByType image/jpeg A2592000

ExpiresByType text/plain A86400

ExpiresByType application/x-shockwave-flash A2592000

ExpiresByType video/x-flv A2592000

ExpiresByType application/pdf A2592000

#ExpiresByType text/html A600

可以看到js文件

Cache-Control     max-age=2592000

Connection Keep-Alive

Date  Thu, 21 Mar 2013 07:21:31 GMT

Expires       Sat, 20 Apr 2013 07:21:31 GMT

Keep-Alive timeout=5, max=100

Server        Apache/2.2.19 (Win32) PHP/5.3.6

Vary  Accept-Encoding

X-Pad         avoid browser bug

自动加上了Expires过期时间了

扩展:

压缩模块

除了缓存服务器轻松配置一下mod_deflate gzip就能返回压缩后的数据,压缩率可以达到60%-75%之间。

扩展:

Nginx也有对应的fastcgi-cache /HttpHeadersModule /gzipmodule

内存级缓存

Memcache

Memcache是一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。简单的说就是将数据调用到内存中,然后从内存中读取,从而大大提高读取速度。

Memcache现在的版本是v1.4.15,使用memcache的网站一般流量较大;为了缓解数据库的压力,让memcache作为前端数据的一个缓存区域,将部分信息保存到内存中,让内存作为硬盘的高速缓存。因为单台服务器的memcache缓存容量是有限的,不过memcache是支持分布式的,所以我们可以根据实际情况进行数据的分布存储,比如社交类的网站以user为主,可以考虑把userid 为1开头的存放到编号为1的memcache服务器上,userid为2的放到编号为2的memcache服务器上。只是这样的话在程序业务上面就需要对userid进行判断了。

Memcache服务器端是直接通过客户端连接后操作的,没有进行任何的验证过程;在平时开发环境中可以这样,但是生产环境中就不能这样了。Memcache不能直接暴露在互联网上,否则很容易被人查看,或者服务器被入侵。所以我们可以以

1、   内网访问memcache服务器,服务器一般有两个网卡,一内一外;我们就让web服务器通过机房内网来访memcache。 Memcache的服务器上启动的时候就监听内网的IP地址和端口,内网间的访问能够有效阻止其他非法的访问。

# memcached -d -m 1024 -u root -l 192.168.0.200 -p 11211 -c 1024 -P /tmp/memcached.pid

Memcache服务器端设置监听通过内网的192.168.0.200的ip的11211端口,占用1024MB内存,并且允许最大1024个并发连接

2、   设置防火墙也是简单有效的方式,如果memcache需要通过外网ip来进行访问,那么我们可以设置服务器的iptables

# iptables -F

# iptables -P INPUT DROP

# iptables -A INPUT -p tcp -s 192.168.0.2 –dport 11211 -j ACCEPT

# iptables -A INPUT -p udp -s 192.168.0.2 –dport 11211 -j ACCEPT

这里的意思就是指定只允许来自192.168.0.2的服务器进行访问。

Redis

Redis是一种面向“键/值”对类型数据的分布式NoSQL数据库系统,特点是高性能,持久存储,适应高并发的应用场景。目前最新版本2.6.11

Redis是一个NOSQL数据库系统,支持各种数据存储类型,普通字符串,list,set,order sets;

那么redis与memcache都是内存级别的数据缓存,他们之间有什么区别呢?应该怎么选择呢。

1、memcache是一个高性能分布式的缓存系统,而redis是高性能分布式NOSQL数据库系统。

2、memcache数据常驻内存,memcached挂掉后数据消失;而redis数据内容也是常驻内存,且还可以定期的保存到磁盘中

3、memcache数据不可恢复,而redis数据丢失后可以通过aof恢复

4、redis有着更丰富的数据结构选择,在redis有着更丰富的操作;在memcache中只能拿到客户端中进行类似的操作在set进去,加大网络的IO次数

等等

Memcache与redis各有各的优势,memcache内存利用率高,数据存储维持简单搞笑的键值对模式,可以单纯的作为一个cache层,对于前后台的数据传输有着一个中转站的作用,而且有着成熟的分布式方案,同事各个语言都有着优秀的客户端。Redis是面向对象的键值对高性能分布式的数据库系统,适用于高并发,持久存储的场景,有着完善的数据存储类型且数据操作都是支持incr原子操作的,redis客户端也覆盖了各种语言,能很方便的使用。

对于数据同步以及持久化有很高要求的可以使用redis,对于仅仅是作为一个数据缓存层且不想维持复杂的数据类型结构那么可以选择memcache。

浏览器本地缓存

HTTP网页缓存

Cache-Control     max-age=2592000

Connection Keep-Alive

Date  Mon, 25 Mar 2013 07:31:50 GMT

Expires       Wed, 24 Apr 2013 07:31:50 GMT

一般一个http响应的header信息,包括如Etag  Last-modified Cache-control Expires 等这些信息,那么浏览器根据这些信息一并与网页内容进行本地缓存。

If-None-Match   “427fe7b6442f2096dff4f921548565486”

If-Modified-Since   Fri, 27 Jul 2012 01:35:39 GMT

浏览器再本地的缓存中发现last-modified信息,那么就以If-Modified-Since加入到header信息中发送至服务器,询问下服务器上面最后的一次访问更新是什么时候呢,那么apache服务器就会核对一下这个文件的最后更新时间,如果大于这个时间那么就会返回一个200的响应,重新下载这个文件。如果文件还是没有更新那么服务器就返回一个304 Not Modified,浏览器还是继续用本地缓存。

http的缓存,不仅可以节省服务器处理资源;还减少了网页的下载次数,节约网络带宽。但有时候我们经常是动态编程,比较少会整体缓存整个网页,不过对于网页所需要的附属内容如css、js、图片等内容,网站结构所需的图片js等内容我们可以缓存时间长点如30day,而用户图片数据内容可以进行5day的有效缓存。

如google这样的智能搜索引擎可以识别资源的状态信息,使用http cache之后,只有在文本内容真正变化后才会被爬取,那么我们可以直接对爬虫返回304 not modified,这样不但降低服务器负载与爬虫造成的网络带宽消耗,实际上也提高了google爬虫的效率。

历史帖子,一段时间后就很少人访问了;但是爬虫还是会每天记录访问造成服务器的高消耗,使用http cache后,爬虫爬过一次后,以后无论多少次爬取都可直接返回304了。

HTML5 DOM Storage本地存储API

在web storage api出现之前,服务器端需要存储客户端与浏览器之间交换的数据信息,如ajax请求的数据信息。而现在浏览器可以将需要重复请求的数据内容存储到web storage上,同时在关闭浏览器之后还可以恢复数据。

那同样是进行浏览器数据存储的cookie与web storage有什么区别?我们知道cookie是服务器端与浏览器进行文本传输的内置机制,我们可以用cookie来存储服务器生成当前连接的会话标识,可以用来实现购物网站的购物车,可以用来存储数据供接下来的页面使用。Cookie对于web开发程序确实有很大的用处,但是cookie也是有一些缺点的

1、   Cookie大小受限制,4KB数据

2、   只要有请求浏览器就会自动将cookie进行传输,cookie数据在网络上是可见的,存在安全风险;同时无论加载哪个url,cookie的数据都会消耗网络带宽。

而web storage目前可以存储高达5MB的数据内容,主流浏览器都支持。Web storage 有两种主要实现sessionstorage与localstorage,sessionstorage主要是用于会话的数据存储,关闭浏览器之后自动失效;而localstorage是永久存储在浏览器中的。

通过调用对象的setItem()与getItem()方法进行设置与获取的实现

window.sessionStorage.setItem(‘k’, ‘v’);

window.sessionStorage.getItem(‘k’);

window.sessionStorage.removeItem(‘k’);

也可以通过对象属性方式进行设置获取

window.sessionStorage.firstKey = ‘val’;

alert(window.sessionStorage.firstKey);

那么这样与普通的javascript数据对象有什么区别呢。最大的一个区别就是在于“作用域”普通的js对象在当前页面刷新后丢失已经不奇怪了,但是对于sessionstorage只要不关闭标签或浏览器,一个页面设置的sessionstorage值,只要是同源的网页中,其他页面可以依据Key获取到值。但是因为sessionstorage不能持久保存所以对于不是很重要的数据,但是短时间内存在的流程(比如对话框、向导),多个页面需要该数据的情况下可以存储在其中,而在以前这些数据需要用form或cookie进行来回传递的。

Localstorage代码使用上没有区别,但是要注意的是localstorage是持久化存储,除非用户删除是永远不会删除的。

扩展

IndexedDB

大容量索引型的数据结构存储可以看看IndexedDB,以K/V键值对形式进行数据存储。

程序应用层缓存

在应用开发过程中,需要了解缓存可以有效的提高程序的响应能力与处理能力。

那哪些数据写入缓存?

大量非实时更新的数据内容,如文章,站点信息,tag分类,rss list信息

Mpf魔炫网上面一个专题里面有着视频信息、专题信息、相关文章信息、微博信息等内容。

在项目上线运营之初没有考虑到具体的情况,后来分析发现在视频详细页与专题页是有着最高的访问量的,最高是达到了1万人同时在线访问,那段时间导致后台压力巨大,用户打开页面缓慢;之后做出调整,在视频、专题信息填写完成之后很少进行改动,所以我们决定把一些如视频信息,专题信息,相关文章信息等内容加入到缓存区,用户在进行访问是可以说服务器几乎不用查询mysql了。能够很大的提高用户的访问量,服务器数据能快速的返回。

ORM对象,大量的sql的查询主要作用于服务器磁盘的IO,在一定程度上mysql的反应速度体现在磁盘IO的速度上。那么在实现ORM对象缓存是,需要考虑的是

我们是以减少服务器磁盘IO为目的,不是为了减少发送到数据库的sql条数;使用ORM会增减sql语句条数。

既然决定采用ORM模式作为主要的数据读写操作,那么在数据库表结构的设计上尽量越细越好,经常查询访问的大数据量单独列表。

同时在构建sql语句是尽量避免多表关联查询,拆成多表的主键查询。(某个location附近的event)

保存缓存的数据同步

加入缓存后,就存在这样的问题;要保存缓存与数据库数据的一致性!否则数据不一致会加大程序排除问题的难度。那么数据一致性通常是在insert,update,delete时。可以以回调的方式实现缓存的同步。

Insert数据到数据库时,在insert成功后,将得到的最新last_insertid作为key,当前的数据内容作为value存入到缓存。数据在被调用时,直接就是获取缓存了。

Update 对于缓存的数据更新可以有两种方式。

1、   主动更新

2、   被动更新

主动更新就是update记录成功后,直接回调update缓存机制。

$cache->Delete($id);

$cache->set($id, $data);

先删除,后写入保持数据的最新。

被动更新则是,在用户访问这个页面时程序调用发现没有缓存,第一次去数据库读取内容返回,且写入缓存;用户第二次访问这个页面时则返回的是缓存数据内容了。

那么我个人推荐的是第一种,即在更新数据时直接同时回调将缓存数据内容也更新,这样避免在用户第一次访问时可能会得到一个较慢的响应。

案例:

某个网站的文章缓存是以文章id+文章的updatetime来作为key进行数据缓存的,在文章更新时updatetime改变,用户访问程序进行缓存查询时没有找到,进而查询数据库返回并且写入到cache中,注意这时原先的缓存并没有删除且也可能没有过期,那么就是一直存在其中缓存系统之中,直到缓存系统自动的垃圾回收。

1、   这样需要用户访问,被动式更新

2、   加重缓存系统的压力,需要管理更多的实际上对程序来说已经过期了的数据内容。

缓存的过期数据处理

缓存的过期数据处理的实现主要是两个方面的

1、数据库记录在删除时,同时回调删除缓存中的数据内容。

2、缓存对象系统的自动清除

建议是在数据库记录delete成功后回调清除cache中的记录信息,保持缓存系统中数据的清洁度。

Kohana中的缓存实现

Kohana内核框架只有一个简单的file cache方式,core.php里面的kohana:cache($key, $data, $lifetime = NULL)方法。实现方式就是当前时间与文件内容修改时间比对。

If($data === NULL){

If( (time() – fimemtime( $file)) < $lifetime ){

Return unserialize(file_get_content($file));

}else{

// delete expires cache file

}

Return NULL;

}

file_put_contents($file, serialize($data)); //写入

在项目复杂的业务逻辑中,这样的缓存机制肯定是不够用的了。所以kohana采用cache module用来专门的做缓存实现。且缓存实现在框架内核中多次用到,所以内核框架是有几个必须依赖的module的,cache就是其中之一。

今cache module实现的缓存驱动有apc,file,memcache,sqlite,wincache这几种,而我们常用的就是file,memcache。

Kohana中的缓存应用主要体现在

1、   类实例对象的缓存

2、   配置信息的缓存应用

分享总结

昨天讲解完毕之后,反响不咋地;你自己理解不一定能完整清晰的表达出来,有些问题模糊不清,自己也没弄明白,确实没有讲好;整体内容很全面,但是没有具体到哪一点,好像什么都概括了,但是没有侧重某一点深入讲开。

而且演讲的技巧要加强啊,整个过程就是自己个人概述没有一个活跃的气氛,让人昏昏欲睡。

总结下来就是几点

1、  整体内容比较多,较平。介绍用户一个请求过程中哪些地方可加入缓存机制,没有侧重某一点。

2、  内容概念多,实质涉及代码较少;需要把概念与代码结合起来。

3、  有些地方的问题较模糊,讲述不够清楚。

4、  事前对于提出需求的人想了解的内容,没有深入进行讲解。事先沟通没有做好。

5、  缓存应用的程序深度应用需要加强,内容较为浅显;案例分析代码讲解较少。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*