【转帖】文件缓存相关

 

 

文件缓存系统的设计与实现
2008-09-23 15:22

 

 

作者:刘晨光 QQ:64452627 MSN:liuchenguang_pro@hotmail.com 转载请注明出处 2008-09-23

 

 

 

 

 

 

 

 

一、项目背景
    
随着系统运营时间的增加,数据量与日俱增,数据库系统单张表的数量超过百万以上,数据的慢查询日志频频出现慢查询语句。根据观察得出以下规律:

 

大部分是查询的历史数据且重复率不高。

 

现行处理办法:

方法

可扩展性

复杂度

效果

查询优化

结构调整

Memcache


      

二、系统架构

 

三、文件缓存系统说明
   
系统由唯一md5(global unique key)生成的零星小文件组成,每个文件存储一条数据库记录。存储系统暂时分成三级目录存储,每个目录由md5(key)的单一两个字符组成。

1.
总容量:
((10 + 26) * (10 + 26)) * ((10 + 26) * (10 + 26)) * ((10 + 26) * (10 + 26)) * 1024 = 2229025112064


2.
缓存文件路径规则:
$md5 = md5($key)

$cache_file = $path.”/”.substr($md5,0,2)."/".substr($md5,3,2)."/".substr($md5, 5, 2)."/".$md5.".php";

3.
数据存储格式:
/cache/6d/4a/f3/6d04af37caec85c561772d40a0540746.php
<?php
$arr_post_list[] = array (
0 => '26135',
1=> '~
值得收藏的美图~-6',
2 => '2001456573',
3 => '
张金鑫',
4 => 'fs4.cyworld.com.cn/data6',
5 => '1165344437538443_file.jpg',
6 => '3',
);
?>

4.使用
$sql = “SELECT id FROM table”;
$data = $db->getAll($sql);
$arr_post_list = array();
foreach($data as $record) {
    $cache_file = getLocalCacheFile($record[“id”]);
    require_once($cache_file);

}

5.
服务器部署
    
Web服务器与文件缓存服务器之间通过nfs mount进行通信, Web服务器与文件缓存服务器之间是一对多的关系,每个Web服务器上有多个mount点已解决单点故障问题。每次数据更新是更新到所有的cache节点上。

 

6.数据表id作为主索引

因为数据表id作为主索引,因此查询时至通过索引就可以得到所需数据。

 

 

四、测试
以一张记录总数为277376条数据的表为例进行测试。
测试工具webbench


结构比较

 

结构

SQL + Memcache( 前 n 页 )

SQL + File Cache( 所有数据 )

SQL

SELECT id,… FROM table …

SELECT id FROM table

      

       结果 (时长30)    speed(pages/min) transfer(bytes/sec)  

并发

speed

transfer

requets

slow log

speed

transfer

requets

slow log

1

170

221430

85

228

294333

114

5

272

354360

136

有 3

382

493089

191

100

242

134336

121

有 5

236

117174

118

500

196

141882

98

有 12

120

154856

60

1000

140

174381

70

有 23

146

154074

73


       
结论 :

       系统改造后的方案比改造前随并发量(或运行时长)的增加对数据库产生压力增长缓慢。


 

 

五、文件散列
散列结果相对均衡,都在9-10M之间

9.4M    ./cache/b8

9.8M    ./cache/7a

9.3M    ./cache/7c


 

六、问题与挑战
       1.
生成的缓存文件占用硬盘较大,未压缩处理时大致十倍于数据库。

2.把对数据库的压力转嫁到磁盘IO上,产生大量的随机读取,磁盘损害几率加大。

3.大量零星的小文件存储可能遇到linux inode问题。

4.合适的文件系统未测试证实。

 

说明:本方案测试用例非通用,需结合自身系统自行设置业务逻辑。

 

 

 

 

 

PHP文件缓存的性能测试

前言
在开发MooPHP的过程中,为了寻找更为高效的缓存方式,对两种最常用的缓存方式进行了测试。

PHP常用缓存方式
第一种,把需要缓存的数据进行处理,形成PHP可以直接执行的文件。在需要缓存数据的时候,通过include方式引入,并使用。
第二种,把需要的数据通过serialize函数序列化后直接保存到文件。在需要使用缓存数据的时候,通过反序列化读入文件内容并复制给需要的变量,然后使用。

测试结果
通过测试我们发现,第二种也就是serialize缓存数据的方式更加高效。(数据略去,最后提供了文章地址下载,大家可以自行测试)

原因分析
include方式读取缓存的时候,PHP需要执行几个过程
1.读取文件
2.解析所Include的文件
3.执行,给变量赋值

而serialize序列化方式读取缓存的时候:
1.读取数据
2.反序列化数据内容
3.给变量赋值

从以上内容对比的话,可能是由于解析PHP文件内的数组需要的时间超过unserialize反序列化数组的时间。如果你有兴趣可以查看《PHP filesystem相关函数和include require的性能效率研究》:http://www.ccvita.com/163.html

测试文件代码
下载地址:MooPHP-CacheTest.zip
原创地址:http://www.ccvita.com/311.html 有新的研究心得会在这里更新。
CacheTest_IncludeFile.php

< ?php

$t1 = gettimeofday();

for ($i = 0; $i < 10000; $i++){
include("CacheTest_IncludeData.php");
}

$t2 = gettimeofday();

echo ($t2['sec'] - $t1['sec']) * 1000 + ($t2['usec'] - $t1['usec']) / 1000 . "/n";

CacheTest_SerializeFile.php

< ?php

function read_cache($filename) {

if(@$fp = fopen($filename, 'r')) {
@$data = fread($fp,filesize($cachefile));
fclose($fp);
}
return $s;
}

$t1 = gettimeofday();

for ($i = 0; $i < 10000; $i++){
$x = read_cache("CacheTest_SerializeData.php");
$x_r = unserialize($x);
}

$t2 = gettimeofday();

echo ($t2['sec'] - $t1['sec']) * 1000 + ($t2['usec'] - $t1['usec']) / 1000 . "/n";

 

总结分析
第一种,include缓存的方式
优点:增加数据的保密性,和安全性,缓存内容不会被外界发现。
缺点:速度相对较慢。
用途:保存禁止系统外部得知的数据,比如web系统的设置,甚至MySQL信息等的保存

第二种,serialize序列化缓存的方式
优点:速度较快。
缺点:缓存系统文件路径一旦曝光,缓存内容会泄露。
用途:缓存最新文章,相关文章等不担心外部得知的数据的时候,可以使用这种方式。

备注
当装了ea、apc等PHP内存缓存之后,第一种通过include读取缓存的方式速度会高于第二种serialize序列化缓存的方式。所以在MooPHP框架中,我们对于非敏感信息采用第二种方式缓存;敏感信息采用第一种方式缓存。有关MooPHP的介绍可以参考《MooPHP框架简介》这篇文章(地址:http://www.ccvita.com/295.html

 

 

 

 

CSDN因为访问量巨大,所以采用了文件缓存机制,即将帖子内容生成XML文件,再在客户端有XSLT解析,确实可以很大的减轻数据库压力。于是我也想了想,如果采用这种文件缓存的方式来增强论坛、Blog的性能,减轻数据库压力,要注意哪些问题呢?于是仔细想了想一些可能的问题和如何来解决:

在这之前我先对本文的术语作一个简单解释:第一篇帖子和它的所有回复都称之为一个主题(Thread),第一篇帖子和它后面的每篇回复都叫做帖子(Post)。另外一个前提是本文中,类似于CSDN的情况:即XML文件缓存是辅助的,在数据库中同时存有一份数据,访问时先访问数据库获取关键信息,然后在显示帖子内容时读取XML文件。

缓存的单位:CSDN的缓存是以一个Thread为单位,将Thread下所有的Posts都生成一个XML,这样一个好处就是生成的文件比较少,缺点就是文件更新较频繁(每有回帖就必须更新一下文件),不方便支持帖子修改/删除功能,客户端下载时需要整个XML下载完才能显示出HTML。我个人倾向于以Post为单位,每个Post生成一个XML,这样做的缺点是生成的文件较多,优点是调用更加灵活,不用频繁更新XML,在客户端解析时可以支持单个Post加载完就显示。

缓存的时机:生成缓存文件主要有两种时机——帖子创建时即生成XML、第一次访问帖子时生成XML。我个人倾向于第二种方案:有利于多台服务器负载均衡,如果缓存文件丢失,可以方便再次生成。CSDN好像是采用的在有新帖子创建时生成/修改XML,不确认。

支持帖子修改:CSDN是不支持帖子修改和删除的(版主应该可以吧),具体原因我不清楚,如果他们的缓存文件时在帖子创建时生成和更新,那么可能是不想频繁更新文件,如果是第一次访问时,那么需要有一个时间戳来记录帖子是不是修改了,如果修改了那么需要更新XML。基于前面两个问题我选择的方案,在第一次访问时生成缓存的XML文件,并且以Post为单位生成XML文件,那么当帖子修改后,我在下次访问时需要更新这个XML文件,而不是访问旧的XML文件。
那么怎么样才能在每次访问时知道帖子更新了呢?如果是帖子修改时就直接更新XML文件当然简单,但是现在不是这样,那么是不是每次先服务器端读取一遍XML,然后对比一下XML中的帖子最后更新时间和数据库中的帖子最后更新时间是不是一致的,如果不是就表示需要更新XML了,这样当然可行,但是效率太低了,不过这倒是给了我一点启发:为什么不直接以帖子最后更新时间(精确到秒)命名,这样在找缓存文件时,如果帖子更新了,而缓存文件没有更新,那么就会找不到缓存文件,再次生成就好了。这样有个问题是会导致过期的文件还存在,倒不见得是坏事情——相当于对每个帖子都有个历史存档了。

权限问题:如果XML路径是很有规律的,并且可以被直接访问到,那么就没有什么权限和隐私可言了,一种比较直接有效的方法,就是通过HttpHandler来隐藏缓存的XML文件的真实路径,并且判断用户是否有权限看到。如果你担心XML文件路径可能被猜出来,那么可以将缓存文件禁止直接外部访问,或者对上面提到的XML文件命名方案进行改进:对于Post增加一个属性——缓存文件名,第一次创建时生成一个随机数作为文件名,如果后来帖子有修改,同时更新这个属性。

文件保存路径:因为每个Post一个文件这种方案会让文件很多,如果一个目录下文件太多,检索效率会非常低的,那么应该对文件进行分区,分成四级目录:第一级将帖子文件按照所属板块(Forum)分类,例如按照板块的ID生成目录;第二级按照Thread的日期来分类,这个可以根据帖子量来决定日期划分,如果帖子量巨大甚至可以每天一个目录;第三级按照Thread来分类,例如按照Thread.ThreadId来生成目录,第四级按照Post来分类,因为每个Post每更新一次就会生成一个新的XML,参考命名方式:
string cacheFilePath = string.Format("Forum{0}//{1}//{2}//Thread{3}//Post{4}//{5}.xml",?post.ForumId, post.ThreadDate.ToString("yyyy"), post.ThreadDate.ToString("MM"), post.ThreadId, post.PostId, post.CacheFileName);

换皮肤问题:XSLT本身就可以很方便的实现换肤功能——每种皮肤一个XSLT文件即可,不过这样的一个问题就是需要对XSLT比较熟悉。如果要在传统的论坛换皮肤基础上实现这个功能,可以考虑一个折中的方案:将每个缓存后的Post的XML作为XML数据岛嵌入在帖子显示的位置,本身IE对XML数据岛就支持非常好,即使是FireFox之类不支持XML数据岛的浏览器(貌似是不支持的),也可以结合XSLT来做,这个XSLT只要解析显示帖子内容这部分就好了,相对难度小很多。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值