第7章.磁盘缓存基础
7.1 cache_dir指令
cache_dir指令是squid.conf配置文件里最重要的指令之一。它告诉squid以何种方式存储cache文件到磁盘的什么位置。cache_dir指令取如下参数:
cache_dir scheme directory size L1 L2 [options]
7.1.1 参数:Scheme
Squid支持许多不同的存储机制。默认的(原始的)是ufs。依赖于操作系统的不同,你可以选择不同的存储机制。在./configure时,你 必须使用--enable-storeio=LIST选项来编译其他存储机制的附加代码。我将在8.7章讨论aufs,diskd,coss和null。 现在,我仅仅讨论ufs机制,它与aufs和diskd一致。
7.1.2 参数:Directory
该参数是文件系统目录,squid将cache对象文件存放在这个目录下。正常的,cache_dir使用整个文件系统或磁盘分区。它通常不介意是 否在单个文件系统分区里放置了多个cache目录。然而,我推荐在每个物理磁盘中,仅仅设置一个cache目录。例如,假如你有2个无用磁盘,你可以这样 做:
# newfs /dev/da1d
# newfs /dev/da2d
# mount /dev/da1d /cache0
# mount /dev/da2d /cache1
然后在squid.conf里增加如下行:
cache_dir ufs /cache0 7000 16 256
cache_dir ufs /cache1 7000 16 256
假如你没有空闲硬盘,当然你也能使用已经存在的文件系统分区。选择有大量空闲空间的分区,例如/usr或/var,然后在下面创建一个新目录。例如:
# mkdir /var/squidcache
然后在squid.conf里增加如下一行:
cache_dir ufs /var/squidcache 7000 16 256
7.1.3 参数:Size
该参数指定了cache目录的大小。这是squid能使用的cache_dir目录的空间上限。计算出合理的值也许有点难。你必须给临时文件和swap.state日志,留出足够的自由空间(见13.6章)。我推荐挂载空文件系统,可以运行df:
% df -k
Filesystem 1K-blocks Used Avail Capacity Mounted on
/dev/da1d 3037766 8 2794737 0% /cache0
/dev/da2d 3037766 8 2794737 0% /cache1
这里你可以看到文件系统有大约2790M的可用空间。记住,UFS保留了部分最小自由空间,这里约是8%,这就是squid为什么不能使用全部3040M空间的原因。
你也许试图分配2790M给cache_dir。如果cache不很繁忙,并且你经常轮转日志,那么这样做也许可行。然而,为安全起见,我推荐保留10%的空间。这些额外的空间用于存放squid的swap.state文件和临时文件。
注意cache_swap_low指令也影响了squid使用多少空间。我将在7.2章里讨论它的上限和下限。
底线是,你在初始时应保守的估计cache_dir的大小。将cache_dir设为较小的值,并允许写满cache。在squid运行一段时间 后,cache目录会填满,这样你可以重新评估cache_dir的大小设置。假如你有大量的自由空间,就可以轻松的增加cache目录的大小了。
7.1.3.1 Inodes
Inodes(i节点)是unix文件系统的基本结构。它们包含磁盘文件的信息,例如许可,属主,大小,和时间戳。假如你的文件系统运行超出了i节 点限制,就不能创造新文件,即使还有空间可用。超出i节点的系统运行非常糟糕,所以在运行squid之前,你应该确认有足够的i节点。
创建新文件系统的程序(例如,newfs或mkfs)基于总空间的大小,保留了一定数量的i节点。这些程序通常允许你设置磁盘空间的i节点比率。例 如,请阅读newfs和mkfs手册的-i选项。磁盘空间对i节点的比率,决定了文件系统能实际支持的文件大小。大部分unix系统每4KB创建一个i节 点,这对squid通常是足够的。研究显示,对大部分cache代理,实际文件大小大约是10KB。你也许能以每i节点8KB开始,但这有风险。
你能使用df -i命令来监视系统的i节点,例如:
% df -ik
Filesystem 1K-blocks Used Avail Capacity iused ifree %iused Mounted on
/dev/ad0s1a 197951 57114 125001 31% 1413 52345 3% /
/dev/ad0s1f 5004533 2352120 2252051 51% 129175 1084263 11% /usr
/dev/ad0s1e 396895 6786 358358 2% 205 99633 0% /var
/dev/da0d 8533292 7222148 628481 92% 430894 539184 44% /cache1
/dev/da1d 8533292 7181645 668984 91% 430272 539806 44% /cache2
/dev/da2d 8533292 7198600 652029 92% 434726 535352 45% /cache3
/dev/da3d 8533292 7208948 641681 92% 427866 542212 44% /cache4
如果i节点的使用(%iused)少于空间使用(Capacity),那就很好。不幸的是,你不能对已经存在的文件系统增加更多i节点。假 如你发现运行超出了i节点,那就必须停止squid,并且重新创建文件系统。假如你不愿意这样做,那么请削减cache_dir的大小。
7.1.3.2 在磁盘空间和进程大小之间的联系
Squid的磁盘空间使用也直接影响了它的内存使用。每个在磁盘中存在的对象,要求少量的内存。squid使用内存来索引磁盘数据。假如你增加了新 的cache目录,或者增加了磁盘cache大小,请确认你已有足够的自由内存。假如squid的进程大小达到或超过了系统的物理内存容量,squid的 性能下降得非常块。
Squid的cache目录里的每个对象消耗76或112字节的内存,这依赖于你的系统。内存以StoreEntry, MD5 Digest, 和LRU policy node结构来分配。小指令(例如,32位)系统,象那些基于Intel Pentium的,取76字节。使用64位指令CPU的系统,每个目标取112字节。通过阅读cache管理的内存管理文档,你能发现这些结构在你的系统 中耗费多少内存(请见14.2.1.2章)。
不幸的是,难以精确预测对于给定数量的磁盘空间,需要使用多少附加内存。它依赖于实际响应大小,而这个大小基于时间波动。另外,Squid还为其他数据结构和目的分配内存。不要假设你的估计正确。你该经常监视squid的进程大小,假如必要,考虑削减cache大小。
7.1.4 参数:L1和L2
对ufs,aufs,和diskd机制,squid在cache目录下创建二级目录树。L1和L2参数指定了第一级和第二级目录的数量。默认的是16和256。图7-1显示文件系统结构。
Figure 7-1. 基于ufs存储机制的cache目录结构
某些人认为squid依赖于L1和L2的特殊值,会执行得更好或更差。这点听起来有关系,即小目录比大目录被检索得更快。这样,L1和L2也许该足够大,以便L2目录的文件更少。
例如,假设你的cache目录存储了7000M,假设实际文件大小是10KB,你能在这个cache_dir里存储700,000个文件。使用16个L1和256个L2目录,总共有4096个二级目录。700,000/4096的结果是,每个二级目录大约有170个文件。
如果L1和L2的值比较小,那么使用squid -z创建交换目录的过程,会执行更快。这样,假如你的cache文件确实小,你也许该减少L1和L2目录的数量。
Squid 给每个cache目标分配一个唯一的文件号。这是个32位的整数,它唯一标明磁盘中的文件。squid使用相对简单的算法,将文件号转换位路径名。该算法 使用L1和L2作为参数。这样,假如你改变了L1和L2,你改变了从文件号到路径名的映射关系。对非空的cache_dir改变这些参数,导致存在的文件 不可访问。在cache目录激活后,你永不要改变L1和L2值。
Squid在cache目录顺序中分配文件号。文件号到路径名的算法(例如, storeUfsDirFullPath( )),用以将每组L2文件映射到同样的二级目录。Squid使用了参考位置来做到这点。该算法让HTML文件和它内嵌的图片更可能的保存在同一个二级目录 中。某些人希望squid均匀的将cache文件放在每个二级目录中。然而,当cache初始写入时,你可以发现仅仅开头的少数目录包含了一些文件,例 如:
% cd /cache0; du -k
2164 ./00/00
2146 ./00/01
2689 ./00/02
1974 ./00/03
2201 ./00/04
2463 ./00/05
2724 ./00/06
3174 ./00/07
1144 ./00/08
1 ./00/09
1 ./00/0A
1 ./00/0B
这是完全正常的,不必担心。
7.1.5 参数:Options
Squid有2个依赖于不同存储机制的cache_dir选项:read-only标签和max-size值。
7.1.5.1 read-only
read-only选项指示Squid继续从cache_dir读取文件,但不往里面写新目标。它在squid.conf文件里看起来如下:
cache_dir ufs /cache0 7000 16 256 read-only
假如你想把cache文件从一个磁盘迁移到另一个磁盘,那么可使用该选项。如果你简单的增加一个cache_dir,并且删除另一个,squid的 命中率会显著下降。在旧目录是read-only时,你仍能从那里获取cache命中。在一段时间后,就可以从配置文件里删除read-only缓存目 录。
7.1.5.2 max-size
使用该选项,你可以指定存储在cache目录里的最大目标大小。例如:
cache_dir ufs /cache0 7000 16 256 max-size=1048576
注意值是以字节为单位的。在大多数情况下,你不必增加该选项。假如你做了,请尽力将所有cache_dir行以max-size大小顺序来存放(从小到大)。
7.2 磁盘空间基准
cache_swap_low和cache_swap_high指令控制了存储在磁盘上的对象的置换。它们的值是最大cache体积的百分比,这个最大cache体积来自于所有cache_dir大小的总和。例如:
cache_swap_low 90
cache_swap_high 95
如果总共磁盘使用低于cache_swap_low,squid不会删除cache目标。如果cache体积增加,squid会逐渐删除目标。在稳 定状态下,你发现磁盘使用总是相对接近cache_swap_low值。你可以通过请求cache管理器的storedir页面来查看当前磁盘使用状况 (见14.2.1.39章)。
请注意,改变cache_swap_high也许不会对squid的磁盘使用有太大效果。在squid的早期版本里,该参数有重要作用;然而现在,它不是这样了。
7.3 对象大小限制
你可以控制缓存对象的最大和最小体积。比maximum_object_size更大的响应不会被缓存在磁盘。然而,它们仍然是代理方式的。在该指令后的逻辑是,你不想某个非常大的响应来浪费空间,这些空间能被许多小响应更好的利用。该语法如下:
maximum_object_size size-specification
如下是一些示例:
maximum_object_size 100 KB
maximum_object_size 1 MB
maximum_object_size 12382 bytes
maximum_object_size 2 GB
Squid以两个不同的方法来检查响应大小。假如响应包含了Content-Length头部,squid将这个值与maximum_object_size值进行比较。假如前者大于后者,该对象立刻不可缓存,并且不会消耗任何磁盘空间。
不幸的是,并非每个响应都有Content-Length头部。在这样的情形下,squid将响应写往磁盘,把它当作来自原始服务器的数据。在响应 完成后,squid再检查对象大小。这样,假如对象的大小达到 maximum_object_size限制,它继续消耗磁盘空间。仅仅当squid在做读取响应的动作时,总共cache大小才会增大。
换句话说,活动的,或者传输中的目标,不会对squid内在的cache大小值有影响。这点有好处,因为它意味着squid不会删除cache里的 其他目标,除非目标不可缓存,并对总共cache大小有影响。然而,这点也有坏处,假如响应非常大,squid可能运行超出了磁盘自由空间。为了减少发生 这种情况的机会,你应该使用reply_body_max_size指令。某个达到reply_body_max_size限制的响应立即被删除。
Squid也有一个minimum_object_size指令。它允许你对缓存对象的大小设置最低限制。比这个值更小的响应不会被缓存在磁盘或内存里。注意这个大小是与响应的内容长度(例如,响应body大小)进行比较,后者包含在HTTP头部里。
7.4 分配对象到缓存目录
当squid想将某个可缓存的响应存储到磁盘时,它调用一个函数,用以选择cache目录。然后它在选择的目录里打开一个磁盘文件用于写。假如因为 某些理由,open()调用失败,响应不会被存储。在这样的情况下,squid不会试图在其他cache目录里打开另一个磁盘文件。
Squid有2个cache_dir选择算法。默认的算法叫做lease-load;替代的算法是round-robin。
least-load算法,就如其名字的意义一样,它选择当前工作负载最小的cache目录。负载概念依赖于存储机制。对aufs,coss和 diskd机制来说,负载与挂起操作的数量有关。对ufs来说,负载是不变的。在cache_dir负载相等的情况下,该算法使用自由空间和最大目标大小 作为附加选择条件。
该选择算法也取决于max-size和read-only选项。假如squid知道目标大小超出了限制,它会跳过这个cache目录。它也会跳过任何只读目录。
round-robin算法也使用负载作为衡量标准。它选择某个负载小于100%的cache目录,当然,该目录里的存储目标没有超出大小限制,并且不是只读的。
在某些情况下,squid可能选择cache目录失败。假如所有的cache_dir是满负载,或者所有目录的实际目标大小超出了max-size 限制,那么这种情况可能发生。这时,squid不会将目标写往磁盘。你可以使用cache管理器来跟踪squid选择cache目录失败的次数。请见 store_io页(14.2.1.41章),找到create.select_fail行。
7.5 置换策略
cache_replacement_policy指令控制了squid的磁盘cache的置换策略。Squid2.5版本提供了三种不同的置换策略:最少近来使用(LRU),贪婪对偶大小次数(GDSF),和动态衰老最少经常使用(LFUDA)。
LRU是默认的策略,并非对squid,对其他大部分cache产品都是这样。LRU是流行的选择,因为它容易执行,并提供了非常好的性能。在32位系统上,LRU相对于其他使用更少的内存(每目标12对16字节)。在64位系统上,所有的策略每目标使用24字节。
在过去,许多研究者已经提议选择LRU。其他策略典型的被设计来改善cache的其他特性,例如响应时间,命中率,或字节命中率。然而研究者的改进 结果也可能在误导人。某些研究使用并不现实的小cache目标;其他研究显示当cache大小增加时,置换策略的选择变得不那么重要。
假如你想使用GDSF或LFUDA策略,你必须在./configure时使用--enable-removal-policies选项(见 3.4.1章)。Martin Arlitt和HP实验室的John Dilley为squid写了GDSF和LFUDA算法。你可以在线阅读他们的文档:
http://www.hpl.hp.com/techreports/1999/HPL-1999-69.html
我在O'Reilly出版的书"Web Caching",也讨论了这些算法。
cache_replacement_policy指令的值是唯一的,这点很重要。不象squid.conf里的大部分其他指令,这个指令的位置很 重要。cache_replacement_policy指令的值在squid解析cache_dir指令时,被实际用到。通过预先设置替换策略,你可以 改变cache_dir的替换策略。例如:
cache_replacement_policy lru
cache_dir ufs /cache0 2000 16 32
cache_dir ufs /cache1 2000 16 32
cache_replacement_policy heap GDSF
cache_dir ufs /cache2 2000 16 32
cache_dir ufs /cache3 2000 16 32
在该情形中,头2个cache目录使用LRU置换策略,接下来2个cache目录使用GDSF。请记住,假如你已决定使用cache管理器的 config选项(见14.2.1.7章),这个置换策略指令的特性就非常重要。cache管理器仅仅输出最后一个置换策略的值,将它置于所有的 cache目录之前。例如,你可能在squid.conf里有如下行:
cache_replacement_policy heap GDSF
cache_dir ufs /tmp/cache1 10 4 4
cache_replacement_policy lru
cache_dir ufs /tmp/cache2 10 4 4
但当你从cache管理器选择config时,你得到:
cache_replacement_policy lru
cache_dir ufs /tmp/cache1 10 4 4
cache_dir ufs /tmp/cache2 10 4 4
就象你看到的一样,对头2个cache目录的heap GDSF设置被丢失了。
7.6 删除缓存对象
在某些情况下,你必须从squid的cache里手工删除一个或多个对象。这些情况可能包括:
+ 你的用户抱怨总接收到过时的数据;
+ 你的cache因为某个响应而“中毒”;
+ Squid的cache索引在经历磁盘I/O错误或频繁的crash和重启后,变得有问题;
+ 你想删除一些大目标来释放空间给新的数据;
+ Squid总从本地服务器中cache响应,现在你不想它这样做。
上述问题中的一些可以通过强迫web浏览器reload来解决。然而,这并非总是可靠。例如,一些浏览器通过载入另外的程序,从而显示某些类容类型;那个程序可能没有reload按钮,或甚至它了解cache的情况。
假如必要,你总可以使用squidclient程序来reload缓存目标。简单的在uri前面使用-r选项:
% squidclient -r http://www.lrrr.org/junk >;/tmp/foo
假如你碰巧在refresh_pattern指令里设置了ignore-reload选项,你和你的用户将不能强迫缓存响应更新。在这样的情形下,你最好清除这些有错误的缓存对象。
7.6.1 删除个别对象
Squid接受一种客户请求方式,用于删除cache对象。PURGE方式并非官方HTTP请求方式之一。它与DELETE不同,对后者, squid将其转发到原始服务器。PURGE请求要求squid删除在uri里提交的目标。squid返回200(OK)或404(Not Found)。
PURGE方式某种程度上有点危险,因为它删除了cache目标。除非你定义了相应的ACL,否则squid禁止PURGE方式。正常的,你仅仅允许来自本机和少数可信任主机的PURGE请求。配置看起来如下:
acl AdminBoxes src 127.0.0.1 172.16.0.1 192.168.0.1
acl Purge method PURGE
http_access allow AdminBoxes Purge
http_access deny Purge
squidclient程序提供了产生PURGE请求的容易方法,如下:
% squidclient -m PURGE http://www.lrrr.org/junk
代替的,你可以使用其他工具(例如perl脚本)来产生你自己的HTTP请求。它非常简单:
PURGE http://www.lrrr.org/junk HTTP/1.0
Accept: */*
注意某个单独的URI不唯一标明一个缓存响应。Squid也在cache关键字里使用原始请求方式。假如响应包含了不同的头部,它也可以使用其他请 求头。当你发布PURGE请求时,Squid使用GET和HEAD的原始请求方式来查找缓存目标。而且,Squid会删除响应里的所有variants, 除非你在PURGE请求的相应头部里指定了要删除的variants。Squid仅仅删除GET和HEAD请求的variants。
7.6.2 删除一组对象
不幸的是,Squid没有提供一个好的机制,用以立刻删除一组对象。这种要求通常出现在某人想删除所有属于同一台原始服务器的对象时。
因为很多理由,squid不提供这种功能。首先,squid必须遍历所有缓存对象,执行线性搜索,这很耗费CPU,并且耗时较长。当squid在搜 索时,用户会面临性能下降问题。第二,squid在内存里对URI保持MD5算法,MD5是单向哈希,这意味着,例如,你不能确认是否某个给定的MD5哈 希是由包含"www.example.com"字符串的URI产生而来。唯一的方法是从原始URI重新计算MD5值,并且看它们是否匹配。因为squid没有保持原始的URI,它不能执行这个重计算。
那么该怎么办呢?
你可以使用access.log里的数据来获取URI列表,它们可能位于cache里。然后,将它们用于squidclient或其他工具来产生PURGE请求,例如:
% awk '{print $7}' /usr/local/squid/var/logs/access.log /
| grep www.example.com /
| xargs -n 1 squidclient -m PURGE
7.6.3 删除所有对象
在极度情形下,你可能需要删除整个cache,或至少某个cache目录。首先,你必须确认squid没有在运行。
让squid忘记所有缓存对象的最容易的方法之一,是覆盖swap.state文件。注意你不能简单的删除swap.state文件,因为 squid接着要扫描cache目录和打开所有的目标文件。你也不能简单的截断swap.state为0大小。代替的,你该放置一个单字节在里面,例如:
# echo '' >; /usr/local/squid/var/cache/swap.state
当squid读取swap.state文件时,它获取到了错误,因为在这里的记录太短了。下一行读取就到了文件结尾,squid完成重建过程,没有装载任何目标元数据。
注意该技术不会从磁盘里删除cache文件。你仅仅使squid认为它的cache是空的。当squid运行时,它增加新文件到cache里,并且 可能覆盖旧文件。在某些情形下,这可能导致你的磁盘使用超出了自由空间。假如这样的事发生,你必须在再次重启squid前删除旧文件。
删除cache文件的方法之一是使用rm。然而,它通常花费很长的时间来删除所有被squid创建的文件。为了让squid快速启动,你可以重命名旧cache目录,创建一个新目录,启动squid,然后同时删除旧目录。例如:
# squid -k shutdown
# cd /usr/local/squid/var
# mv cache oldcache
# mkdir cache
# chown nobody:nobody cache
# squid -z
# squid -s
# rm -rf oldcache &
另一种技术是简单的在cache文件系统上运行newfs(或mkfs)。这点仅在你的cache_dir使用整个磁盘分区时才可以运行。
7.7 refresh_pattern
refresh_pattern指令间接的控制磁盘缓存。它帮助squid决定,是否某个给定请求是cache命中,或作为cache丢失对待。宽 松的设置增加了你的cache命中率,但也增加了用户接收过时响应的机会。另一方面,保守的设置,降低了cache命中率和过时响应。
refresh_pattern规则仅仅应用到没有明确过时期限的响应。原始服务器能使用Expires头部,或者Cache-Control:max-age指令来指定过时期限。
你可以在配置文件里放置任意数量的refresh_pattern行。squid按顺序查找它们以匹配正则表达式。当squid找到一个匹配时,它使用相应的值来决定,某个缓存响应是存活还是过期。refresh_pattern语法如下:
refresh_pattern [-i] regexp min percent max [options]
例如:
refresh_pattern -i /.jpg$ 30 50% 4320 reload-into-ims
refresh_pattern -i /.png$ 30 50% 4320 reload-into-ims
refresh_pattern -i /.htm$ 0 20% 1440
refresh_pattern -i /.html$ 0 20% 1440
refresh_pattern -i . 5 25% 2880
regexp参数是大小写敏感的正则表达式。你可以使用-i选项来使它们大小写不敏感。squid按顺序来检查refresh_pattern行;当正则表达式之一匹配URI时,它停止搜索。
min参数是分钟数量。它是过时响应的最低时间限制。如果某个响应驻留在cache里的时间没有超过这个最低限制,那么它不会过期。类似的,max参数是存活响应的最高时间限制。如果某个响应驻留在cache里的时间高于这个最高限制,那么它必须被刷新。
在最低和最高时间限制之间的响应,会面对squid的最后修改系数 (LM-factor)算法。对这样的响应,squid计算响应的年龄和最后修改系数,然后将它作为百分比值进行比较。响应年龄简单的就是从原始服务器产 生,或最后一次验证响应后,经历的时间数量。源年龄在Last-Modified和Date头部之间是不同的。LM-factor是响应年龄与源年龄的比 率。
图7-2论证了LM-factor算法。squid缓存了某个目标3个小时(基于Date和Last-Modified头部)。LM-factor 的值是50%,响应在接下来的1.5个小时里是存活的,在这之后,目标会过期并被当作过时处理。假如用户在存活期间请求cache目标,squid返回没 有确认的cache命中。若在过时期间发生请求,squid转发确认请求到原始服务器。
图7-2 基于LM-factor计算过期时间
理解squid检查不同值的顺序非常重要。如下是squid的refresh_pattern算法的简单描述:
+ 假如响应年龄超过refresh_pattern的max值,该响应过期;
+ 假如LM-factor少于refresh_pattern百分比值,该响应存活;
+ 假如响应年龄少于refresh_pattern的min值,该响应存活;
+ 其他情况下,响应过期。
refresh_pattern指令也有少数选项导致squid违背HTTP协议规范。它们如下:
override-expire
该选项导致squid在检查Expires头部之前,先检查min值。这样,一个非零的min时间让squid返回一个未确认的cache命中,即使该响应准备过期。
override-lastmod
改选项导致squid在检查LM-factor百分比之前先检查min值。
reload-into-ims
该选项让squid在确认请求里,以no-cache指令传送一个请求。换句话说,squid在转发请求之 前,对该请求增加一个If-Modified-Since头部。注意这点仅仅在目标有Last-Modified时间戳时才能工作。外面进来的请求保留 no-cache指令,以便它到达原始服务器。
ignore-reload
该选项导致squid忽略请求里的任何no-cache指令。