一、项目背景
随着系统运营时间的增加,数据量与日俱增,数据库系统单张表的数量超过百万以上,数据的慢查询日志频频出现慢查询语句。根据观察得出以下规律:
大部分是查询的历史数据且重复率不高。
现行处理办法:
方法 |
可扩展性 |
复杂度 |
效果 |
查询优化 |
低 |
中 |
中 |
结构调整 |
中 |
高 |
高 |
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.合适的文件系统未测试证实。
说明:本方案测试用例非通用,需结合自身系统自行设置业务逻辑。
Linux文件系统设计了三类的Cache以用来支持多类型的文件系统。
一、 VFS索引节点缓存 Inode Cache
VFS索引节点是一个Hash表,它不断地读取。VFS提供的索引节点缓存可以加快对文件系统的存取。每次从索引节点缓存中读取一个VFS索引节点,这样系统就可以节省读取物理设备的存取时间。
当VFS访问索引节点时,它首先查找VFS索引节点缓存。为了在VFS索引节点缓存中查找一个索引节点,系统首先计算它对应的哈希值,然后将其作为索引值进入索引节点哈希表。然后再通过读取这个拥有相同哈希值的索引节点链表逐个匹配索引节点,直到找到具有指定设备号和inode号的索引节点。如果从缓存中找到了索引节点,该索引节点的计数值加1。否则申请一空闲的索引节点。
二、 目录缓存 Inode Directory Cache
为了加快对常用的目录的存取,由于是根据路径访问文件的,Linux维护了表达路径与索引节点对应关系的目录缓存,被文件系统使用过的目录将会存入到该目录缓存中。这样,同一目录被再次访问时,可直接从缓冲区得到,不必重复访问存储文件系统的设备。
当真实的文件系统读取一个目录时,目录的详细信息被添加到目录缓存中。这样,同一目录被再次访问时,可直接从缓冲区找到此目录的有关信息。只有短于15个字符的目录才能保存在目录缓存中。
目录缓存由一张哈希表组成,其中每个表项指向具有同样哈希值的目录缓存链表的一个指针,哈希函数使用文件系统的设备号和目录名来计算哈希值,以便快速地找到一个目录项。为了保持一个最新的、正确的缓冲,VFS使用基于LRU(Least Recently Used)最近最少算法的目录缓冲链表。
三、 缓冲区缓存 Buffer Cache
当进程使用安装的文件系统时,它们产生很多对块设备数据块的读写请求,如下图所示。这些块读写请求会通过标准核心过程以Buffer_head数据结构的形式给出设备驱动程序需要的几乎全部信息。操作系统将设备块上的数据看作具有同样大小的数据块的线性列表。Buffer_head中的b_dev和b_blocknr属性值惟一指明了向什么设备读写第几个数据块。
(缓冲区缓存结构)见最下方的图
为了加快物理块设备的存取,Linux维护一组块缓冲区的缓存,称作Buffer Cache。它被所有物理块设备所共享。在任一时刻,由若干缓冲区为若干设备工作。数据块一经使用,就会在Buffer Cache留下备份,而且经常使用的数据将一直留在Buffer Cache中,下次再访问该数据时,如果从缓冲区缓存中可以得到有效的数据,则将节省系统去访问物理设备的时间。Buffer Cache的性能直接影响文件系统的性能。
块缓冲区高速缓存由两个功能部分组成。第一部分是空闲的块缓冲区列表。第二部分则是缓存自身。一个散列表包含了指向具有同样散列表索引的缓冲区链的指针。散列索引是由设备标志符合数据块的块号产生的。一个块缓冲不是在空闲表中就是在缓存中。缓存中的块缓冲同时被插入到最近最少使用(LRU)列表中。对应于每一种缓冲区类型都有一个LRU列表。
目前Linux支持以下5种缓冲区类型:
Clean 未使用、新创建的缓冲区
Locked 被锁住、等待被回写
Dirty 包含最新的有效数据,但还没有被回写
Shared 共享的缓冲区
Unshared 原来被共享但现在不共享
Buffer Cache独立于任何类型的文件系统。文件系统中凡涉及到磁盘读写,几乎都通过Buffer Cache,有些甚至直接利用Buffer Cache保存信息。Linux中精心设计的Buffer Cache系统,它是由一个hash表和若干个以Buffer_Cache结构为节点的链表构成。
像所有的缓存一样,必须高效地维护缓冲区缓存,以便它有效地、公平地为块设备分配缓存表项。Linux使用bdflush监控程序执行这些Cache的看护工作。
可见,这三类的Cache分别针对索引节点、目录和块设备数据块而设计的,它们可以有效地支持Linux的文件系统。这里Linux通过对索引节点、目录和块设备数据块等各个环节而设立cache,在各个环节上都立足于提高存取的速度,这使得linux有一个高效的文件系统了。