Apache缓存配置

Apache缓存配置

本文将介绍如何使用Apache HTTP Server的缓存功能来加速Web和代理服务,同时避免常见问题和错误配置。

Apache HTTP服务器提供了一系列缓存功能,旨在以各种方式提高服务器的性能。

三态RFC2616 HTTP缓存
mod_cache及其提供者模块mod_cache_disk提供智能的HTTP感知缓存。内容本身存储在缓存中,mod_cache旨在遵守控制内容可缓存性的所有各种HTTP头和选项。mod_cache针对简单和复杂的缓存配置,可以在其中处理代理内容,动态本地内容,或者需要加速对可能较慢的磁盘上的本地文件的访问。

双状态键/值共享对象缓存

共享对象缓存API(socache)及其提供程序模块提供基于服务器范围的键/值共享对象缓存。这些模块旨在缓存低级别数据,例如SSL会话和身份验证凭据。后端允许数据在服务器范围内存储在共享内存中,或者数据中心内存储在缓存中,例如memcachedistcache

专门的文件缓存

mod_file_cache提供了在服务器启动时将文件预加载到内存中的功能,并且可以改善访问时间并保存经常访问的文件上的文件句柄,因为不需要在每个请求上转到磁盘。

三态RFC2616 HTTP缓存

HTTP协议包含对RFC2616第13节描述的内联缓存机制的内置支持,mod_cache模块可用于利用此功能。

与简单的两个状态键/值缓存不同,其中内容在不再新鲜时完全消失,HTTP缓存包括保留陈旧内容的机制,并询问源服务器此陈旧内容是否已更改,如果不是则再次刷新。

HTTP缓存中的条目存在以下三种状态之一:

Fresh
如果内容足够新(比其新鲜寿命更年轻),则认为是Fresh。HTTP缓存可以免费提供新内容,而无需对源服务器进行任何调用。

Stale
如果内容太旧(早于其新鲜度生命周期),则认为是Stale。HTTP缓存应联系原始服务器,并在向客户端提供过时内容之前检查内容是否仍然是新的。如果原始服务器仍然无效,则原始服务器将使用替换内容进行响应,或者理想情况下,源服务器将使用代码进行响应以告知缓存内容仍然是新的,而无需再次生成或发送内容。内容再次变得新,循环继续。

HTTP协议允许缓存在某些情况下提供过时数据,例如当尝试使用源服务器刷新数据时出现5xx错误,或者另一个请求已经在刷新给定条目的过程中。在这些情况下,会在响应中添加警告标头。

Non Existent
如果缓存已满,则保留从缓存中删除内容以腾出空间的选项。内容可以随时删除,可以是旧或新。htcacheclean工具可以一次性运行,或者作为守护程序部署,以使缓存的大小保持在给定大小或给定数量的inode内。在尝试删除新内容之前,该工具会尝试删除旧内容。

与服务器的交互
mod_cache模块在两个可能的位置挂钩到服务器,具体取决于CacheQuickHandler指令的值:

快速处理阶段

这个阶段在请求处理期间很早就发生,就在解析请求之后。如果在缓存中找到内容,则立即提供该内容,并且几乎所有请求处理都被绕过。

在这种情况下,缓存的行为就像它已经“闩上”到服务器的前面一样。

此模式提供最佳性能,因为绕过了大多数服务器处理。但是,此模式也会绕过服务器处理的身份验证和授权阶段,因此在重要时应谨慎选择此模式。

mod_cache在此阶段运行时,具有“授权”标头(例如,HTTP基本身份验证)的请求既不可缓存也不从缓存提供。

正常处理阶段

在所有请求阶段完成之后,此阶段在请求处理的后期发生。在这种情况下,缓存的行为就像它已经“闩上”到服务器的后面一样。
此模式提供了最大的灵活性,因为缓存可能存在于过滤器链中的精确控制点,并且缓存的内容可以在发送到客户端之前进行过滤或个性化。

如果在缓存中找不到URL,mod_cache将向筛选器堆栈添加一个筛选器,以便记录对缓存的响应,然后停止,允许正常的请求处理继续。如果确定内容是可缓存的,则将内容保存到缓存中以供将来服务,否则将忽略该内容。

如果在缓存中找到的内容是旧的,则mod_cache模块将请求转换为条件请求。如果源服务器以正常响应响应,则缓存正常响应,替换已缓存的内容。如果源服务器响应304 Not Modified响应,则内容将再次标记为新的,缓存的内容由过滤器提供,而不是保存。

提高缓存命中率

当一个虚拟主机是由许多不同的服务器别名已知,确保将UseCanonicalName设置为On可以显着提高缓存命中率。这是因为服务于内容的虚拟主机的主机名在缓存密钥中使用。设置为On具有多个服务器名称或别名的虚拟主机将不会生成不同的缓存实体,而是根据规范主机名缓存内容。

新寿命

要缓存的格式良好的内容应使用Cache-Control标头的max-ages-maxage字段声明显式新生命周期,或者包含Expires标头。

同时,当客户端在请求中提供自己的Cache-Control标头时,客户端可以覆盖原始服务器定义的新生命周期。在这种情况下,请求和响应之间的最低新生命周期获胜。

当请求或响应中缺少此新生命周期时,将应用默认新生命周期。缓存实体的默认新生命周期为一小时,但使用CacheDefaultExpire指令可以轻松覆盖。

如果响应不包含Expires标头但包含Last-Modified标头,则mod_cache可以基于启发式推断新生命周期,可以通过使用CacheLastModifiedFactor指令来控制。

对于本地内容或未定义其自己的Expires标头的远程内容,可以使用mod_expires通过添加max-ageExpires来微调度生命周期。

还可以通过使用CacheMaxExpire来控制最大新寿命。

有条件请求简要说明

当内容从缓存过期并变得陈旧时,httpd将修改请求以使其成为条件。

当原始缓存响应中存在ETag标头时,mod_cache将向请求发送原始服务器的If-None-Match标头。当原始缓存响应中存在Last-Modified标头时,mod_cache将向原始服务器的请求添加If-Modified-Since标头。执行这些操作之一会使请求成为条件。

当源服务器接收到条件请求时,源服务器应根据请求检查ETagLast-Modified参数是否已更改。如果不更改,原点应该以简洁的“304 Not Modified”响应进行响应。这向缓存发出信号,表明旧内容仍然是新的,应该用于后续请求,直到再次达到内容的新的新生命周期。

如果内容已更改,则提供内容,就好像请求不是以条件开头一样。

有条件请求提供两个好处。首先,当向源服务器发出这样的请求时,如果来自源的内容与高速缓存中的内容匹配,则可以容易地确定,并且没有转移整个资源的开销。

其次,设计良好的源服务器将以这样的方式设计:条件请求的生成要比完整响应便宜得多。对于静态文件,通常所涉及的是对stat()或类似系统调用的调用,以查看文件的大小或修改时间是否已更改。因此,如果本地内容没有改变,甚至可以从高速缓存中更快地提供本地内容。

原始服务器应尽可能地支持条件请求,但是如果不支持条件请求,则源将响应,就好像请求不是有条件的,并且缓存将响应,就好像内容已更改并保存新内容到缓存。在这种情况下,缓存的行为类似于简单的两个状态缓存,其中内容实际上是新鲜的或已删除。

什么可以缓存?

通过HTTP缓存哪些响应总结如下:

  • 必须为此URL启用缓存。请参阅CacheEnableCacheDisable指令。
  • 如果响应的HTTP状态代码不是200,203,300,301或410,则它还必须指定“Expires”或“Cache-Control”标头。
  • 请求必须是HTTP GET请求。
  • 如果响应包含“Authorization:”标头,则它还必须在“Cache-Control:”标头中包含“s-maxage”,“must-revalidate”或“public”选项,否则它将不会被缓存。
  • 如果URL包含查询字符串(例如来自HTML表单GET方法),则除非响应通过包含“Expires:”标头或“Cache”的max-age或s-maxage指令指定显式过期,否则不会缓存它 - Control:“标题。
  • 如果响应的状态为200(OK),则响应还必须包括“Etag”,“Last-Modified”或“Expires”标题中的至少一个,或者max-ages-maxage指令。“Cache-Control:”标头,除非已使用CacheIgnoreNoLastMod指令另外要求。
  • 如果响应在“Cache-Control:”标头中包含“private”选项,则除非已使用CacheStorePrivate请求,否则不会存储该选项。
  • 同样,如果响应在“Cache-Control:”标头中包含“no-store”选项,则除非已使用CacheStoreNoStore,否则不会存储该选项。
  • 如果响应包含一个包含全部匹配*的“Vary:”标题,则不会存储响应。

什么不应该缓存?

应该由创建请求的客户端或构建响应的原始服务器通过正确设置Cache-Control头来决定内容是否应该是可缓存的,并且应该保留mod_cache以满足其意愿。适当的客户端或服务器。

不应缓存时间敏感的内容,或者根据HTTP协商未涵盖的请求的详细信息而变化的内容。此内容应使用Cache-Control标头声明自己不可缓存。

如果内容经常更改,以分钟或秒的新鲜度生命周期表示,则内容仍然可以缓存,但是非常希望源服务器正确支持条件请求以确保不必定期生成完整响应。

可以通过智能使用Vary响应头来缓存基于客户端提供的请求头而变化的内容。

可变/协商的内容

当原始服务器设计为根据请求中的标头值响应不同的内容时,例如,为了在同一URL上提供多种语言,HTTP的缓存机制可以在同一URL上缓存同一页面的多个变体。

这是由原始服务器添加Vary标头来完成的,以指示在确定两个变体是否彼此不同时,高速缓存必须考虑哪些标头。

如果收到带有变化标题的响应,例如;

Vary: negotiate,accept-language,accept-charset

mod_cache仅向具有与原始请求匹配的accept-languageaccept-charset标头的请求者提供缓存内容。

内容的多个变体可以并行缓存,mod_cache使用Vary头和Vary列出的请求头的相应值来决定返回到客户端的许多变体中的哪一个。

缓存设置示例

缓存到磁盘

mod_cache模块依赖于特定的后端存储实现来管理缓存,并且为了缓存到磁盘,提供mod_cache_disk来支持这一点。

通常,模块这样配置 -

CacheRoot   "/var/cache/apache/"
CacheEnable disk /
CacheDirLevels 2
CacheDirLength 1

重要的是,由于缓存文件是本地存储的,因此操作系统内存缓存通常也会应用于它们的访问。因此,虽然文件存储在磁盘上,但如果频繁访问它们,操作系统很可能会确保它们实际上是从内存中提供的。

了解缓存存储

要将项目存储在缓存中,mod_cache_disk会创建所请求URL的22个字符的哈希值。此哈希包含主机名,协议,端口,路径和URL的任何CGI参数,以及由Vary头定义的元素,以确保多个URL不会相互冲突。

每个字符可以是64个不同字符中的任何一个,这意味着整体上有64 ^ 22个可能的哈希值。例如,URL可能会被散列到xyTGxSMO2b68mBCykqkp1w。此哈希用作在缓存中命名特定于该URL的文件的前缀,但首先根据CacheDirLevelsCacheDirLength指令将其拆分为目录。

CacheDirLevels指定应该有多少级别的子目录,CacheDirLength指定每个目录中应该有多少个字符。使用上面给出的示例设置,散列将变为文件名前缀为/var/cache/apache/x /y/TGxSMO2b68mBCykqkp1w

此技术的总体目标是减少可能在特定目录中的子目录或文件的数量,因为大多数文件系统随着此数量的增加而减慢。对于CacheDirLength设置为1,在任何特定级别最多可以有64个子目录。设置为2时,可以有64 * 64个子目录,依此类推。除非你有充分的理由不这样做,否则建议将CacheDirLength设置为1

设置CacheDirLevels取决于您预计要在缓存中存储的文件数。通过上例中使用设置值为2,最终可以创建总共4096个子目录。缓存了100万个文件,每个目录大约有245个缓存的URL。

每个URL在缓存存储中至少使用两个文件。通常存在.header文件,其包括关于URL的元信息,例如何时到期以及.data文件,其是要提供的内容的逐字副本。

在通过Vary标头协商的内容的情况下,将为所讨论的URL创建.vary目录。该目录将具有与不同协商内容相对应的多个.data文件。

维护磁盘缓存

mod_cache_disk模块不会尝试调节缓存使用的磁盘空间量,尽管它会优雅地停止任何磁盘错误,并且表现得好像缓存从未出现过一样。

相反,提供httpd的是htcacheclean工具,它用于定期清理缓存。确定运行htcacheclean的频率以及用于缓存的目标大小有点复杂,可能需要尝试并选择最佳值。

htcacheclean有两种操作模式。它可以作为持久守护进程运行,也可以定期从cron运行。htcacheclean可能需要一个小时或更长时间才能处理非常大(几十千兆字节)的缓存,如果从cron运行它,建议您确定典型运行需要多长时间,以避免一次运行多个实例。

还建议为htcacheclean选择适当的nice级别,以便在服务器运行时该工具不会导致过多的磁盘io。

因为mod_cache_disk本身并不注意使用多少空间,所以应该确保htcacheclean配置为在清理后留下足够的“增长空间”。

缓存到memcached
使用mod_cache_socache模块,mod_cache可以缓存来自各种实现的数据(aka:“providers”)。例如,使用mod_socache_memcache模块,可以指定将memcached用作为后端存储机制。

通常,模块的配置为:

CacheEnable socache /
CacheSocache memcache:memcd.example.com:11211

可以通过将它们附加到由逗号分隔的多个CacheSocache memcache服务器,行的末尾来指定其他memcached服务器:

CacheEnable socache /
CacheSocache memcache:mem1.example.com:11211,mem2.example.com:11212

此格式还与其他各种mod_cache_socache提供程序一起使用。例如:

CacheEnable socache /
CacheSocache shmcb:/path/to/datafile(512000)
CacheEnable socache /
CacheSocache dbm:/path/to/datafile

专用文件缓存

在文件系统可能很慢或文件句柄很昂贵的平台上,可以选择在启动时将文件预加载到内存中。
在打开文件较慢的系统上,存在在启动时打开文件并缓存文件句柄的选项。这些选项可以帮助对静态文件的访问速度较慢的系统。

文件句柄缓存

打开文件的行为本身可能是延迟的来源,特别是在网络文件系统上。通过维护常用文件的打开文件描述符的缓存,httpd可以避免这种延迟。目前httpd提供了File-Handle Caching的一个实现。

CacheFile

httpd中最基本的缓存形式是mod_file_cache提供的文件句柄缓存。此缓存不是缓存文件内容,而是维护一个打开文件描述符的表。使用CacheFile指令在配置文件中指定以这种方式缓存的文件。

CacheFile指令指示httpd在启动时打开文件,并重新使用此文件句柄以便随后访问此文件。

CacheFile /usr/local/apache2/htdocs/index.html

如果打算以这种方式缓存大量文件,则必须确保正确设置操作系统对打开文件数的限制。

虽然使用CacheFile不会导致文件内容本身被缓存,但它确实意味着如果文件在httpd运行时发生更改,则这些更改将不会被选中。该文件将始终像httpd启动时那样提供服务。

如果在httpd运行时删除了该文件,它将继续维护一个打开的文件描述符,并像启动httpd时那样提供文件。这通常也意味着虽然该文件已被删除,并且未显示在文件系统上,但在httpd停止并且文件描述符关闭之前,将无法恢复额外的可用空间。

内存缓存

直接从系统内存提供服务是普遍提供内容的最快方法。从磁盘控制器读取文件,或者更糟糕的是,从远程网络读取文件的速度要慢几个数量级。磁盘控制器通常涉及物理过程,网络访问受可用带宽的限制。另一方面,内存访问只需几纳秒。

虽然系统内存并不便宜,但是字节数字是迄今为止最昂贵的存储类型,确保它有效使用非常重要。通过在内存中缓存文件,可以减少系统上可用的内存量。正如我们所看到的,在操作系统缓存的情况下,这不是一个问题,但是当使用httpd自己的内存中缓存时,确保不为缓存分配太多内存是很重要的。否则系统将被迫换掉内存,这可能会降低性能。

操作系统缓存
几乎所有现代操作系统都将文件数据缓存在内核直接管理的内存中。这是一个强大的功能,并且在大多数情况下操作系统都是正确的。例如,在Linux上,让我们看看第一次和第二次读取文件所花费的时间差别-

colm@coroebus:~$ time cat testfile > /dev/null
real    0m0.065s
user    0m0.000s
sys     0m0.001s
colm@coroebus:~$ time cat testfile > /dev/null
real    0m0.003s
user    0m0.003s
sys     0m0.000s

即使对于这个小文件,读取文件所需的时间也存在巨大差异。这是因为内核已将文件内容缓存在内存中。

通过确保系统上有“备用”内存,可以确保将越来越多的文件内容存储在此缓存中。这可以是内存缓存的一种非常有效的方法,并且根本不涉及httpd的额外配置。

此外,由于操作系统知道何时删除或修改文件,因此可以在必要时自动从缓存中删除文件内容。与httpd的内存中缓存相比,这是一个很大的优势,它无法知道文件何时发生了变化。

尽管自动操作系统缓存具有性能和优点,但在某些情况下,httpd可以更好地执行内存缓存。

MMapFile缓存
mod_file_cache提供了MMapFile指令,它允许在开始时让httpd将静态文件的内容映射到内存中(使用mmap系统调用)。httpd将使用内存中的内容来访问此文件的所有后续内容。

MMapFile /usr/local/apache2/htdocs/index.html

CacheFile指令一样,httpd启动后将不会获取这些文件中的任何更改。

MMapFile指令不跟踪它分配的内存量,因此您必须确保不要过度使用该指令。每个httpd子进程都将复制此内存,因此确保映射的文件不会太大而导致系统交换内存至关重要。