<-
Apache > HTTP 服务器 > 文档 > 版本 2.4

缓存指南

可用语言:  en  |  fr  |  tr 

本文档是对 mod_cachemod_cache_diskmod_file_cachehtcacheclean 参考文档的补充。它描述了如何使用 Apache HTTP 服务器的缓存功能来加速 Web 和代理服务,同时避免常见问题和错误配置。

Support Apache!

另请参阅

top

简介

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

三态 RFC2616 HTTP 缓存
mod_cache 及其提供程序模块 mod_cache_disk 提供智能的 HTTP 感知缓存。内容本身存储在缓存中,mod_cache 旨在遵守所有控制内容可缓存性的各种 HTTP 标头和选项,如 RFC2616 第 13 节 中所述。 mod_cache 针对简单和复杂的缓存配置,您在处理代理内容、动态本地内容或需要加速访问可能速度较慢的磁盘上的本地文件时,都需要使用它。
二态键值共享对象缓存
共享对象缓存 API (socache) 及其提供程序模块提供服务器范围的基于键值的共享对象缓存。这些模块旨在缓存低级数据,例如 SSL 会话和身份验证凭据。后端允许数据存储在服务器范围内的共享内存中,或存储在数据中心范围内的缓存中,例如 memcache 或 distcache。
专用文件缓存
mod_file_cache 提供了在服务器启动时将文件预加载到内存中的功能,并且可以提高访问时间并节省经常访问的文件的文件句柄,因为不需要在每次请求时都访问磁盘。

要充分利用本文档,您应该熟悉 HTTP 的基础知识,并阅读有关 将 URL 映射到文件系统内容协商 的用户指南。

top

三态 RFC2616 HTTP 缓存

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

与简单的二态键值缓存不同,在简单的二态键值缓存中,内容在不再新鲜时会完全消失,HTTP 缓存包含一种机制来保留陈旧内容,并询问源服务器此陈旧内容是否已更改,如果未更改,则将其重新标记为新鲜。

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

新鲜
如果内容足够新(比其 **新鲜度生命周期** 更短),则认为它是 **新鲜的**。HTTP 缓存可以自由地提供新鲜内容,而无需向源服务器发出任何请求。
陈旧

如果内容太旧(比其 **新鲜度生命周期** 更长),则认为它是 **陈旧的**。HTTP 缓存应联系源服务器并检查内容是否仍然新鲜,然后再将陈旧内容提供给客户端。源服务器将以替换内容进行响应(如果不再有效),或者理想情况下,源服务器将以代码进行响应,以告知缓存内容仍然新鲜,而无需再次生成或发送内容。内容再次变为新鲜,循环继续。

HTTP 协议允许缓存某些情况下提供陈旧数据,例如,当尝试使用源服务器刷新数据时出现 5xx 错误,或者当另一个请求正在刷新给定条目时。在这些情况下,将向响应添加 Warning 标头。

不存在
如果缓存已满,它保留了从缓存中删除内容以腾出空间的选项。内容可以随时删除,并且可以是陈旧的或新鲜的。 htcacheclean 工具可以一次性运行,也可以作为守护进程部署,以将缓存的大小保持在给定大小或给定数量的 inode 内。该工具尝试先删除陈旧内容,然后再尝试删除新鲜内容。

有关 HTTP 缓存工作原理的完整详细信息,请参阅 RFC2616 第 13 节

与服务器交互

mod_cache 模块根据 CacheQuickHandler 指令的值,在服务器中的两个可能位置挂钩。

快速处理程序阶段

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

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

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

mod_cache 在此阶段运行时,具有“Authorization”标头(例如,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 标头。执行这两个操作之一会使请求成为 **条件请求**。

当源服务器收到条件请求时,源服务器应检查 ETag 或 Last-Modified 参数是否已更改,具体取决于请求。如果没有,源服务器应以简洁的“304 Not Modified”响应进行响应。这向缓存发出信号,表明陈旧内容仍然新鲜,应在内容的新鲜度生命周期再次达到之前用于后续请求。

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

条件请求提供两个好处。首先,当向源服务器发出此类请求时,如果来自源服务器的内容与缓存中的内容匹配,则可以轻松地确定这一点,而无需传输整个资源。

其次,精心设计的源服务器将以一种方式设计,即条件请求比完整响应便宜得多。对于静态文件,通常只涉及调用 stat() 或类似的系统调用,以查看文件的大小或修改时间是否已更改。因此,即使是本地内容也可能仍然从缓存中更快地提供,前提是它没有更改。

源服务器应尽一切努力支持条件请求,但如果条件请求不受支持,源服务器将像请求不是条件请求一样进行响应,并且缓存将像内容已更改一样进行响应,并将新内容保存到缓存中。在这种情况下,缓存的行为将类似于简单的二态缓存,其中内容实际上是新鲜的或已删除。

可以缓存什么?

有关 HTTP 缓存可以缓存哪些响应的完整定义,请参见 RFC2616 第 13.4 节 响应可缓存性,总结如下

  1. 必须为该 URL 启用缓存。请参见 CacheEnableCacheDisable 指令。
  2. 如果响应的 HTTP 状态代码不是 200、203、300、301 或 410,则它还必须指定“Expires”或“Cache-Control”标头。
  3. 请求必须是 HTTP GET 请求。
  4. 如果响应包含“Authorization:”标头,则它还必须在“Cache-Control:”标头中包含“s-maxage”、“must-revalidate”或“public”选项,否则不会被缓存。
  5. 如果 URL 包含查询字符串(例如,来自 HTML 表单 GET 方法),则除非响应通过包含“Expires:”标头或“Cache-Control:”标头的 max-age 或 s-maxage 指令来指定明确的过期时间,否则不会被缓存,如 RFC2616 第 13.9 和 13.2.1 节所述。
  6. 如果响应的状态为 200(OK),则响应还必须至少包含一个“Etag”、“Last-Modified”或“Expires”标头,或“Cache-Control:”标头的 max-age 或 s-maxage 指令,除非已使用 CacheIgnoreNoLastMod 指令来要求否则。
  7. 如果响应在“Cache-Control:”标头中包含“private”选项,则除非已使用 CacheStorePrivate 来要求否则,否则不会被存储。
  8. 同样,如果响应在“Cache-Control:”标头中包含“no-store”选项,则除非已使用 CacheStoreNoStore,否则不会被存储。
  9. 如果响应包含包含匹配所有“*”的“Vary:”标头,则不会被存储。

不应该缓存什么?

应该由创建请求的客户端或构造响应的源服务器来决定内容是否应该可缓存,方法是正确设置 Cache-Control 标头,并且 mod_cache 应该被单独保留以根据需要满足客户端或服务器的意愿。

对时间敏感的内容,或根据请求的特定细节而变化的内容(HTTP 协商未涵盖),不应该被缓存。此类内容应使用 Cache-Control 标头声明自己不可缓存。

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

根据客户端提供的请求标头而变化的内容可以通过明智地使用 Vary 响应标头来缓存。

可变/协商内容

当源服务器被设计为根据请求中标头的值返回不同的内容时,例如在同一 URL 上提供多种语言,HTTP 的缓存机制使得在同一 URL 上缓存同一页面的多个变体成为可能。

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

例如,如果收到包含以下 vary 标头的响应;

Vary: negotiate,accept-language,accept-charset

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

内容的多个变体可以并排缓存,mod_cache 使用 Vary 标头以及 Vary 列出的请求标头的对应值来决定向客户端返回哪种变体。

top

缓存设置示例

缓存到磁盘

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 处理非常大的(数十 GB)缓存可能需要一个小时或更长时间,如果你从 cron 运行它,建议你确定典型运行需要多长时间,以避免一次运行多个实例。

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


图 1:典型的缓存增长/清理序列。

由于 mod_cache_disk 本身不会注意使用了多少空间,因此你应该确保 htcacheclean 被配置为在清理后留出足够的“增长空间”。

缓存到 memcached

使用 mod_cache_socache 模块,mod_cache 可以缓存来自各种实现(又称:“提供者”)的数据。例如,使用 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
top

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

Apache HTTP 服务器提供了一个低级共享对象缓存,用于在 socache 接口中缓存信息,例如 SSL 会话或身份验证凭据。

为每个实现提供了额外的模块,提供以下后端

mod_socache_dbm
基于 DBM 的共享对象缓存。
mod_socache_dc
基于 Distcache 的共享对象缓存。
mod_socache_memcache
基于 Memcache 的共享对象缓存。
mod_socache_shmcb
基于共享内存的共享对象缓存。

缓存身份验证凭据

mod_authn_socache 模块允许缓存身份验证的结果,从而减轻身份验证后端的负载。

缓存 SSL 会话

mod_ssl 模块使用 socache 接口来提供会话缓存和钉扎缓存。

top

专门的文件缓存

在文件系统可能很慢或文件句柄很昂贵的平台上,可以选择在启动时将文件预加载到内存中。

在打开文件很慢的系统上,可以选择在启动时打开文件并缓存文件句柄。这些选项可以帮助在访问静态文件很慢的系统上。

文件句柄缓存

打开文件的行为本身可能是一个延迟的来源,尤其是在网络文件系统上。通过维护对常用服务文件的打开文件描述符的缓存,httpd 可以避免这种延迟。目前,httpd 提供了文件句柄缓存的一种实现。

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 的内存缓存无法知道文件何时发生更改。

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

MMapFile 缓存

mod_file_cache 提供了 MMapFile 指令,允许你在启动时将静态文件的内容映射到内存中(使用 mmap 系统调用)。httpd 将使用内存中的内容来访问该文件的所有后续请求。

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

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

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

top

安全注意事项

授权和访问控制

mod_cache 的默认状态下使用 CacheQuickHandler 设置为 On,非常类似于在服务器前面安装一个缓存反向代理。请求将由缓存模块提供服务,除非它确定应查询源服务器,就像外部缓存一样,这会极大地改变 httpd 的安全模型。

由于遍历文件系统层次结构以检查潜在的 .htaccess 文件将是一个非常昂贵的操作,这会部分地抵消缓存的意义(为了加快请求速度),mod_cache 不会对缓存的实体是否被授权提供服务做出任何决定。换句话说;如果 mod_cache 已缓存了一些内容,只要该内容未过期,它就会从缓存中提供服务。

例如,如果你的配置允许通过 IP 地址访问资源,则应确保此内容未被缓存。可以使用 CacheDisable 指令或 mod_expires 来实现这一点。如果不加控制,mod_cache - 非常类似于反向代理 - 会在提供服务时缓存内容,然后将其提供给任何客户端,无论其 IP 地址如何。

CacheQuickHandler 指令设置为 Off 时,将执行完整的请求处理阶段集,并且安全模型保持不变。

本地漏洞

由于可以从缓存中向最终用户提供请求,因此缓存本身可能成为那些希望篡改或干扰内容的人的目标。必须牢记,缓存必须始终可由运行 httpd 的用户写入。这与通常推荐的将所有内容保持为 Apache 用户不可写入的情况形成鲜明对比。

如果 Apache 用户被入侵,例如通过 CGI 进程中的漏洞,则缓存可能会成为目标。当使用 mod_cache_disk 时,插入或修改缓存的实体相对容易。

与 Apache 用户可能进行的其他类型的攻击相比,这带来了更高的风险。如果使用的是 mod_cache_disk,则应牢记这一点 - 确保在发布安全升级时升级 httpd,并尽可能使用 suEXEC 以非 Apache 用户身份运行 CGI 进程。

缓存中毒

当将 httpd 作为缓存代理服务器运行时,还存在所谓的缓存中毒的可能性。缓存中毒是一个广泛的术语,用于描述攻击者导致代理服务器从源服务器检索不正确(通常是不可取的)内容的攻击。

例如,如果运行 httpd 的系统使用的 DNS 服务器容易受到 DNS 缓存中毒的攻击,攻击者可能能够控制 httpd 在从源服务器请求内容时连接到的位置。另一个例子是所谓的 HTTP 请求走私攻击。

本文档不是深入讨论 HTTP 请求走私的正确位置(相反,请尝试使用你最喜欢的搜索引擎),但必须意识到,可以进行一系列请求,并利用源 Web 服务器上的漏洞,使攻击者能够完全控制代理检索到的内容。

拒绝服务/缓存清除

Vary 机制允许将同一 URL 的多个变体并排缓存。根据客户端提供的标头值,缓存将选择正确的变体以返回给客户端。当尝试对已知在正常使用情况下包含各种可能值的标头进行变体时,此机制可能会成为问题,例如 User-Agent 标头。根据特定网站的流行程度,可能会为同一 URL 创建数千或数百万个重复的缓存条目,挤占缓存中的其他条目。

在其他情况下,可能需要在每次请求时更改特定资源的 URL,通常是在 URL 中添加一个“缓存清除”字符串。如果服务器为该内容声明了重要的新鲜度生命周期,则这些条目可能会挤占缓存中的合法条目。虽然 mod_cache 提供了 CacheIgnoreURLSessionIdentifiers 指令,但应谨慎使用此指令,以确保下游代理或浏览器缓存不会遇到相同的拒绝服务问题。

可用语言:  en  |  fr  |  tr 

top

评论

注意
这不是一个问答部分。此处放置的评论应指向有关改进文档或服务器的建议,如果这些评论已实施或被认为无效/主题无关,则我们的版主可能会将其删除。有关如何管理 Apache HTTP Server 的问题应直接发送到我们的 IRC 频道 #httpd(在 Libera.chat 上)或发送到我们的 邮件列表