Caching Guide

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

Introduction

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

为了从本文档中获得最大收益,您应该熟悉 HTTP 的基础知识,并已阅读将 URL Map 到文件系统Content negotiation的用户指南。

三态 RFC2616 HTTP 缓存

Related Modules Related Directives
mod_cache
mod_cache_disk
CacheEnable
CacheDisable
UseCanonicalName
CacheNegotiatedDocs

HTTP 协议包含对嵌入式缓存机制由 RFC2616 第 13 节描述的内置支持,可以使用mod_cache模块来利用这一点。

与简单的两种状态键/值缓存不同,该内容在不再新鲜时会完全消失,HTTP 缓存包含一种机制,用于保留陈旧的内容,并询问原始服务器该陈旧的内容是否已更改,如果没有,请使其再次新鲜。

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

HTTP 协议确实允许高速缓存在某些情况下为过时的数据提供服务,例如,尝试用源服务器刷新数据失败并出现 5xx 错误时,或者当另一个请求已经在刷新给定条目的过程中时。在这些情况下,会将WarningHeaders 添加到响应中。

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

与服务器的交互

mod_cache模块根据CacheQuickHandler指令的值在两个可能的位置挂接到服务器:

在这种情况下,缓存的行为就像是已“固定”在服务器的前端。

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

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

在这种情况下,缓存的行为就像是已“固定”在服务器的背面。

此模式提供了最大的灵 Active,因为存在潜在的缓存发生在过滤器链中精确控制的位置的可能性,并且可以在将缓存的内容发送给 Client 端之前对其进行过滤或个性化。

如果在缓存中找不到 URL,则mod_cachefilter添加到过滤器堆栈中,以记录对缓存的响应,然后站下来,从而 continue 进行正常的请求处理。如果确定内容可缓存,则将内容保存到缓存中以备将来使用,否则将忽略该内容。

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

改善缓存命中

当虚拟主机被许多不同的服务器别名之一识别时,确保将UseCanonicalName设置为On可以大大提高缓存命中率。这是因为在缓存键中使用了服务内容的虚拟主机的主机名。将设置设置为On时,具有多个服务器名称或别名的虚拟主机将不会生成不同的缓存实体,而是根据规范的主机名缓存内容。

Freshness Lifetime

打算缓存的格式正确的内容应使用Cache-ControlHeaders 的max-ages-maxage字段或通过包含ExpiresHeaders 声明显式的新鲜度生存期。

同时,当 Client 端在请求中提出自己的Cache-ControlHeaders 时,Client 端可以覆盖原始服务器定义的新鲜度生存期。在这种情况下,将获得请求和响应之间的最低新鲜度生存期。

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

如果响应中不包含ExpiresHeaders,但包含Last-ModifiedHeaders,则mod_cache可以根据启发式推断出新鲜度,可以通过使用CacheLastModifiedFactor指令进行控制。

对于本地内容或未定义自己的ExpiresHeaders 的远程内容,可以使用mod_expires通过添加max-ageExpires来微调新鲜度寿命。

也可以使用CacheMaxExpire来控制最大新鲜度寿命。

条件请求简要指南

当内容从缓存中过期并变得陈旧,而不是传递原始请求时,httpd 将修改该请求以使其成为有条件的。

当原始缓存的响应中存在ETag头时,mod_cache将向源服务器的请求中添加If-None-Match头。当原始缓存的响应中存在Last-ModifiedHeaders 时,mod_cache会将If-Modified-SinceHeaders 添加到原始服务器的请求中。执行以上任一操作都会使请求成为“有条件的”请求。

当源服务器收到条件请求时,源服务器应检查 ETag 或 Last-Modified 参数是否已更改,以适合该请求。如果不是,则原点应以简洁的“ 304 未修改”响应进行响应。这向缓存发出 signal,表明过时的内容仍然是新鲜的,应将其用于后续请求,直到再次达到该内容的新新鲜度生存期为止。

如果内容已更改,则将内容作为请求的开始条件。

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

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

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

可以缓存什么?

RFC2616 第 13.4 节响应的可缓存性定义了 HTTP 缓存可以缓存的响应的完整定义,可以总结如下:

什么不应该被缓存?

应当由 Client 端创建请求,或者由原始服务器构造响应,以通过正确设置Cache-ControlHeaders 来决定内容是否可缓存,并且应单独保留mod_cache以实现 Client 端的意愿或服务器(视情况而定)。

时间敏感的内容,或随 HTTP 协商未涵盖的请求的具体情况而变化的内容,不应被缓存。此内容应使用Cache-ControlHeaders 声明自己不可缓存。

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

可以通过智能使用Vary响应 Headers 来缓存根据 Client 端提供的请求 Headers 而异的内容。

Variable/Negotiated Content

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

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

例如,如果接收到带有可变 Headers 的响应,例如;

Vary: negotiate,accept-language,accept-charset

mod_cache仅将缓存的内容提供给请求者,这些请求者的 accept-language 和 accept-charset Headers 与原始请求的 Headers 匹配。

内容的多个变体可以并排缓存,mod_cache使用VaryHeaders 和Vary列出的请求 Headers 的相应值来确定要返回给 Client 端的许多变体中的哪一个。

缓存设置示例

Related Modules Related Directives
mod_cache
mod_cache_disk
mod_cache_socache
mod_socache_memcache
CacheEnable
CacheRoot
CacheDirLevels
CacheDirLength
CacheSocache

缓存到磁盘

mod_cache模块依赖于特定的后端存储实现来 Management 缓存,并提供了对磁盘mod_cache_disk的缓存来支持该缓存。

通常,模块将被配置为:

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

重要的是,由于缓存文件是本地存储的,因此 os 内存中的缓存通常也将应用于其访问。因此,尽管文件存储在磁盘上,但如果经常访问它们,则 os 很可能会确保从内存中实际获取文件。

了解缓存存储

要将项目存储在缓存中,mod_cache_disk将创建所请求 URL 的 22 个字符的哈希。该哈希值包含 URL 的主机名,协议,端口,路径和任何 CGI 参数,以及 Vary Headers 定义的元素,以确保多个 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”Headers 协商内容,则将为所讨论的 URL 创建“ .vary”目录。该目录将具有多个“ .data”文件,它们对应于不同协商的内容。

维护磁盘缓存

mod_cache_disk模块不会尝试调节高速缓存所使用的磁盘空间量,尽管它会在出现任何磁盘错误时正常运行,并且表现为永远不会存在高速缓存。

相反,httpd 提供了htcacheclean工具,该工具可让您定期清理缓存。确定运行htcacheclean的频率以及用于缓存的目标大小多少有些复杂,并且可能需要反复试验才能选择最佳值。

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

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

Figure 1:典型的高速缓存增长/清除 Sequences。

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

缓存到 memcached

使用mod_cache_socache模块,mod_cache可以缓存来自各种实现方式(也称为“提供者”)的数据。例如,使用mod_socache_memcache模块,可以指定memcached用作后端存储机制。

通常,模块将配置为:

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

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

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

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

Related Modules Related Directives
mod_authn_socache
mod_socache_dbm
mod_socache_dc
mod_socache_memcache
mod_socache_shmcb
mod_ssl
AuthnCacheSOCache
SSLSessionCache
SSLStaplingCache

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

为每个实现提供了附加模块,提供了以下后端:

缓存身份验证凭据

Related Modules Related Directives
mod_authn_socache AuthnCacheSOCache

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

缓存 SSL 会话

Related Modules Related Directives
mod_ssl SSLSessionCache
SSLStaplingCache

mod_ssl模块使用socache接口提供会话缓存和装订缓存。

专用文件缓存

Related Modules Related Directives
mod_file_cache CacheFile
MMapFile

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

在打开文件的速度较慢的系统上,存在用于在启动时打开文件并缓存文件句柄的选项。这些选项可在访问静态文件速度较慢的系统上提供帮助。

File-Handle Caching

打开文件的行为本身可能会导致延迟,特别是在网络文件系统上。通过维护常用文件的打开文件 Descriptors 的缓存,httpd 可以避免这种延迟。当前,httpd 提供了一种文件句柄缓存的实现。

CacheFile

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

CacheFile指令指示 httpd 在启动时打开文件,并重新使用此文件句柄进行所有后续对该文件的访问。

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

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

尽管使用CacheFile不会使文件内容每秒被高速缓存,但这确实意味着如果在 httpd 运行时文件发生更改,这些更改将不会被接收。该文件将与启动 httpd 时的文件一致。

如果在 httpd 运行时删除了文件,它将 continue 维护打开的文件 Descriptors,并像启动 httpd 时一样提供文件。这通常也意味着,尽管该文件将被删除并且不会显示在文件系统上,但是直到 httpd 停止并且文件 Descriptors 关闭后,额外的可用空间才能恢复。

In-Memory Caching

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

虽然系统内存并不便宜,但逐字节是迄今为止最昂贵的存储类型,确保有效地使用它很重要。通过将文件缓存在内存中,可以减少系统上可用的内存量。就像我们将看到的那样,就 os 缓存而言,这并不是什么大问题,但是在使用 httpd 自己的内存缓存时,重要的是要确保您没有为缓存分配过多的内存。否则,系统将被迫换出内存,这可能会降低性能。

os 缓存

几乎所有现代 os 都在内核直接 Management 的内存中缓存文件数据。这是一项强大的功能,并且在大多数情况下,os 会使其正确运行。例如,在 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 配置。

此外,由于 os 知道何时删除或修改文件,因此它可以在必要时自动从缓存中删除文件内容。与 httpd 的内存中缓存相比,这是一个很大的优势,后者无法知道文件何时更改。

尽管自动 os 缓存具有性能和优点,但在某些情况下,httpd 可能会更好地执行内存中缓存。

MMapFile Caching

mod_file_cache提供了MMapFile指令,该指令使您可以在开始时使用 mmap 系统调用将 httpd 将静态文件的内容 Map 到内存中。 httpd 将使用内存中的内容来进行对该文件的所有后续访问。

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

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

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

Security Considerations

授权和访问控制

CacheQuickHandler设置为On的默认状态下使用mod_cache非常类似于将反向代理缓存到服务器的前端。缓存模块将为请求提供服务,除非它确定应该像外部缓存一样查询原始服务器,这将大大改变 httpd 的安全模型。

由于遍历文件系统层次结构以检查潜在的.htaccess文件将是一项非常昂贵的操作,从而部分地破坏了缓存点(以加快请求的速度),因此mod_cache不会决定是否授权缓存实体进行服务。换一种说法;如果mod_cache已缓存了某些内容,则只要该内容尚未过期,就会从缓存中提供该内容。

例如,如果您的配置允许通过 IP 地址访问资源,则应确保不缓存此内容。您可以使用CacheDisable指令或mod_expires进行此操作。不用检查,mod_cache(非常类似于反向代理)会在提供内容时缓存内容,然后将其提供给任何 IP 地址上的任何 Client 端。

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

Local exploits

由于可以从缓存中满足对最终用户的请求,因此缓存本身可以成为希望破坏或干扰内容的用户的目标。重要的是要记住,缓存必须始终由运行 httpd 的用户可写。这与通常建议的情况下保持所有 Apache 用户不可写的内容形成鲜明对比。

如果 Apache 用户受到损害(例如,由于 CGI 流程中的缺陷),则可能会将缓存作为目标。使用mod_cache_disk时,插入或修改缓存的实体相对容易。

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

Cache Poisoning

当将 httpd 作为缓存代理服务器运行时,也有可能发生所谓的缓存中毒。缓存中毒是攻击的广义术语,其中,攻击者使代理服务器从原始服务器检索不正确的(通常是不受欢迎的)内容。

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

本文档不是深入讨论 HTTP 请求走私的正确位置(相反,请尝试您喜欢的搜索引擎),但是,请务必注意,可以发出一系列请求并利用以下漏洞来进行攻击,这一点很重要。原始 Web Service 器,以便攻击者可以完全控制代理检索的内容。

拒绝服务/ Cachebusting

Vary 机制允许并排缓存同一 URL 的多个变体。根据 Client 端提供的 Headers 值,缓存将选择正确的变体返回 Client 端。当尝试更改已知在正常使用下包含多种可能值的 Headers(例如User-AgentHeaders)时,此机制可能会成为问题。根据特定网站的流行程度,可以为同一 URL 创建成千上万个重复的缓存条目,从而排挤缓存中的其他条目。

在其他情况下,可能需要在每个请求上更改特定资源的 URL,通常是通过在 URL 上添加“ cachebuster”字符串来实现。如果此内容被服务器声明为可缓存很长时间,则这些条目可能会挤出缓存中的合法条目。尽管mod_cache提供了CacheIgnoreURLSessionIdentifiers指令,但应谨慎使用此指令以确保下游代理或浏览器缓存不会遭受相同的拒绝服务问题。

首页