Nginx学习:FastCGI模块(二)缓存配置
通过上篇文章的学习,普通的 PHP 与 Nginx 的连接就已经没啥大问题了。一般的网站直接那套配置就够了,这也是 Nginx 非常友好的一面。很多在默认的配置文件中注释掉的内容,只要打开就是可以直接使用的。不过,FastCGI 可不是一个小模块,还有很多的配置指令,要想深入,要想调优,这些指令还是多少要了解一下的。
今天学习的内容基本上也都是可以设置在 http、server、location 中的,有特殊情况的我会单独说。
FastCGI缓存配置
是的,你没看错,FastCGI 也有缓存系统,但它走的是文件缓存。通过之前的学习,我们知道 Nginx 对静态文件的优化还是相当强悍的,因此,文件缓存的效率并不低。
对于动态网站来说,如果走 UnixSocket ,其实网络开销不大,而且即使是走端口请求的 FastCGI ,通常也是本地部署,对于动态语言来说不会有太大的性能影响。当然,如果你使用的是 Laravel 这种重型框架,即使啥都不做,也是有一些性能损耗的。如果再加上数据库读写或者其它 IO 操作的话,性能还会进一步下降。通常来说我们会怎么做呢?上 Redis 做缓存。是的,这没问题,不过 Nginx 的 FastCGI 缓存也值得一试,起码不用再装第三方组件了。
我们先来看一套配置。
首先,需要在 http 模块下定义缓存路径。
代码语言:javascript复制// http 下面,不能放在 server 或者 location 下
fastcgi_cache_path cache levels=1:2 keys_zone=fcgi:10m;
先不用管参数是啥意思,现在你只要知道第一个参数是指定缓存文件存放的路径就好了。这个路径可以是相对也可以是绝对路径,现在我们配的是相对路径,默认就会是程序运行目录下新建一个 cache 目录。我这里就是 /usr/local/nginx/cache 。
然后进行缓存相关的配置。
代码语言:javascript复制location ^~ /fastcgi2/ {
alias html/fastcgi1/;
fastcgi_pass unix:/var/sock/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $request_filename;
include fastcgi_params;
fastcgi_cache fcgi;
fastcgi_cache_valid 200 302 301 5m;
fastcgi_cache_valid any 1m;
fastcgi_cache_key fgi$request_method://$host$request_uri;
}
上半部分没什么新鲜的,不过有一点,我们使用了 alias ,并且将 SCRIPT_FILENAME 设置为 $request_filename
了,其实就是想使用文章中的 fastcgi1 这个目录。
然后加了三个 fastcgi_cache 相关的配置,暂时也不用管它,我们后面会一个一个地介绍,先看效果。
现在重载配置,并且访问 /fastcgi2/1.php?c=1 。结果正常,不急,先看下目录情况。
代码语言:javascript复制ll /usr/local/nginx/cache/7/31/
total 4
-rw------- 1 www www 1598 Aug 24 10:36 72116ad9a0fa863d35002f652d3b2317
目录生成了,还有一个缓存文件也出现了。接着,查看一下文件内容。
代码语言:javascript复制^E^@^@^@^@^@^@^@??^Ec^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@????????N^Ec^@^@^@^@?y7?^@^@?^A?^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
KEY: fcgiGET://192.168.56.88/fastcgi2/1.php?c=1
^A^F^@^A^D?^C^@X-Powered-By: PHP/7.2.24^M
Content-type: text/html; charset=UTF-8^M
^M
Array
(
[USER] => www
[HOME] => /home/www
[HTTP_COOKIE] => a=123; b=321
[HTTP_CONNECTION] => keep-alive
[HTTP_ACCEPT_ENCODING] => gzip, deflate, br
[HTTP_HOST] => 192.168.56.88
[HTTP_POSTMAN_TOKEN] => 09d5caca-7903-458e-b181-9f8c5b3e2520
[HTTP_ACCEPT] => */*
[HTTP_USER_AGENT] => PostmanRuntime/7.29.2
[REDIRECT_STATUS] => 200
[SERVER_NAME] => core.nginx.test
[SERVER_PORT] => 80
……………………
……………………
上面一堆乱码,下面有个 Key 信息,然后是响应头信息,最后是正常返回的响应体内容。没错,就是我们所请求的页面的内容。
接下来,尝试修改 /fastcgi1/1.php 文件,然后再次请求,你会发现返回的结果不会有什么变动了,这就是缓存生效了。啥时候失效呢?等 5 分钟,也就是 fastcgi_cache_valid 配置项定义的时间,或者添加一个条件可以不走缓存,比如:
代码语言:javascript复制fastcgi_cache_bypass $arg_nocache;
$arg_[name]
变量还记得是啥吧,这样我们再次访问页面,添加一个 GET 参数 nocache ,随便给一个非 0 值,就不会走缓存了。想要清除缓存怎么办?抱歉,这是商业版的功能,也就是 fastcgi_cache_purge
指令提供的功能,我们没法用,要想像 Redis 一样可以删除,要么手动去删除缓存目录,要么使用第三方模块比如 ngx_cache_purge
好了,一套简单的配置就完成了。不过 FastCGI 的缓存模块所拥有的配置指令远不止这些,我们接下来就一个一个详细地学习一下。
fastcgi_cache_path
设置缓存的路径和其他参数,只能配置在 http 模块中。
代码语言:javascript复制fastcgi_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=size] [min_free=size] [manager_files=number] [manager_sleep=time] [manager_threshold=time] [loader_files=number] [loader_sleep=time] [loader_threshold=time] [purger=on|off] [purger_files=number] [purger_sleep=time] [purger_threshold=time];
缓存数据会存储在这个配置所指定的目录的文件中。缓存中的键名和文件名都是将 MD5 函数应用于代理 URL 的结果。 levels 参数定义缓存的层次级别:从 1 到 3,每个级别接受值 1 或 2。它和后面我们会学到的 log_format
比较像,都必须在 http 中定义,而且可以通过不同的名字定义多个,后面要用的时候指定名称就行了。在这个配置中就是 keys_zone 这个参数,表示共享内存区域。所有活动密钥和有关数据的信息都存储在共享内存区域中,其名称和大小由 keys_zone 参数配置。一兆字节的区域可以存储大约 8000 个密钥。如果是商业版本,共享内存区域还存储扩展缓存信息,因此,需要为相同数量的键指定更大的区域大小。例如,一兆字节的区域可以存储大约 4000 个密钥。
第一个参数 path 就是路径,这个是需要我们指定的,这个配置项没有默认值,不配置 fastcgi_cache_path ,后面的 fastcgi_cache 就没法用,整个缓存功能也就无法使用。缓存中最终存储的完整文件名就像是我们上面看到的:
代码语言:javascript复制/usr/local/nginx/cache/7/31/72116ad9a0fa863d35002f652d3b2317
缓存的响应会首先写入临时文件,然后重命名该文件到指定的缓存目录。从 0.8.9 版本开始,临时文件和缓存可以放在不同的文件系统上。但是,请注意,在这种情况下,文件是跨两个文件系统复制的,而不是廉价的重命名操作。因此,建议对于任何给定位置,缓存和保存临时文件的目录都放在同一个文件系统上。根据 use_temp_path 参数 (1.7.10) 设置临时文件的目录。如果此参数被省略或设置为值 on,将使用由 fastcgi_temp_path 指令为给定位置设置的目录。如果该值设置为 off,则临时文件将直接放在缓存目录中。 这个指令我们将在下篇文章中学习到。
在 inactive 参数指定的时间内未访问的缓存数据将从缓存中删除,无论其新鲜度如何。默认情况下,非活动设置为 10 分钟。
特殊的“缓存管理器”进程监控由 max_size 参数设置的最大缓存大小,以及由 min_free (1.19.1) 参数设置的带缓存文件系统上的最小可用空间量来决定。当超出大小或没有足够的可用空间时,它会删除最近最少使用的数据。数据在 manager_files、manager_threshold 和 manager_sleep 参数 (1.11.5) 配置的迭代中被删除。在一次迭代中,最多删除 manager_files 个项目(默认为 100)。一次迭代的持续时间受 manager_threshold 参数的限制(默认为 200 毫秒)。在迭代之间,由 manager_sleep 参数配置的暂停(默认为 50 毫秒)。
启动后一分钟,特殊的“缓存加载器”进程被激活。它将有关存储在文件系统上的先前缓存数据的信息加载到缓存区域中。加载也是在迭代中完成的。在一次迭代中,最多加载 loader_files 个项目(默认情况下,100 个)。此外,一次迭代的持续时间受 loader_threshold 参数的限制(默认为 200 毫秒)。在迭代之间,由 loader_sleep 参数配置的暂停(默认为 50 毫秒)。
最后,其它的一些参数都是商业版的,没法用,也没法测,不讨论了。我们直接来看看多配置一个,然后将前面的例子换成新的路径配置。
代码语言:javascript复制// http下
fastcgi_cache_path cache1 use_temp_path=on levels=1 keys_zone=fff:1m;
// fastcgi2 的 location 下
fastcgi_cache fff;
fastcgi_cache_key fff$request_method://$host$request_uri;
重载配置后,看看新的缓存文件是不是生成到了 cache1 目录下了。这里有两个小问题需要注意下:
- 原来那个 fastcgi_cache_path 不用删,和 log_format 一样,同时配置多个,只要名称不同就行了
- fastcgi_cache_key 需要稍做修改,它是决定缓存 key 的,如果不改,还会走原来的缓存
fastcgi_cache
定义用于缓存的共享内存区域。
代码语言:javascript复制fastcgi_cache zone | off;
默认值是 off ,就是上面那个配置中 keys_zone 所定义的共享区域名称。同一个区域可以在多个地方使用。参数值可以包含变量 (1.7.9)。 off 参数禁用从先前配置级别继承的缓存。
最主要的作用就是表明当前的 server 或 location 使用哪个缓存路径配置,或者在 http 下定义一个全局的也可以。
fastcgi_cache_background_update
允许启动后台子请求以更新过期的缓存项,同时将过时的缓存响应返回给客户端。
代码语言:javascript复制fastcgi_cache_background_update on | off;
默认是关闭的,请注意,有必要在更新时允许使用陈旧的缓存响应。
fastcgi_cache_bypass
定义不从缓存中获取响应的条件。
代码语言:javascript复制fastcgi_cache_bypass string ...;
没有默认值,上面我们已经用过啦,其实它的意思是如果字符串参数中至少有一个值不为空且不等于“0”,则不会从缓存中获取响应:
代码语言:javascript复制fastcgi_cache_bypass $cookie_nocache $arg_nocache$arg_comment;
fastcgi_cache_bypass $http_pragma $http_authorization;
这两行有点复杂了吧,其实是一样的意思,cookie 或请求参数中有 nocache 字段,或者请求头有 Pragma 或 Authoriaztion ,并且这些字段都不是空或0的时候,就不走缓存,前面我们演示过效果啦。它可以与 fastcgi_no_cache 指令一起使用。
fastcgi_cache_key
定义一个用于缓存的键。
代码语言:javascript复制fastcgi_cache_key string;
上面也已经用过了,并且还提到注意的地方了,一般来说,请求地址加 URI 就够了,不过最好再加上请求方法以及你可能需要的字段拼接成一个字符串就好了。
fastcgi_cache_lock
启用后,一次只允许一个请求通过将请求传递给 FastCGI 服务器来填充根据 fastcgi_cache_key 指令标识的新缓存元素。
代码语言:javascript复制fastcgi_cache_lock on | off;
默认值 off ,其实就是缓存过期后,如果有多个相同缓存元素的请求同时到达,要怎么处理,就像更新 Redis 时如果多个请求到达会产生击穿问题一样,这里也是加锁来解决。相同缓存元素的其他请求要么等待响应出现在缓存中,要么等待释放该元素的缓存锁,直到由 fastcgi_cache_lock_timeout 指令设置的时间。
fastcgi_cache_lock_age
如果传递给 FastCGI 服务器以填充新缓存元素的最后一个请求在指定时间内未完成,则可以将另一个请求传递给 FastCGI 服务器。
代码语言:javascript复制fastcgi_cache_lock_age time;
默认值 5s 。
fastcgi_cache_lock_timeout
为 fastcgi_cache_lock 设置超时。
代码语言:javascript复制fastcgi_cache_lock_timeout time;
默认值 5s ,当时间到期时,请求将被传递到 FastCGI 服务器,但是,响应不会被缓存。在 1.7.8 之前,可以缓存响应。
fastcgi_cache_max_range_offset
为字节范围请求设置字节偏移量。
代码语言:javascript复制fastcgi_cache_max_range_offset number;
如果范围超出偏移量,范围请求将被传递到 FastCGI 服务器,并且响应不会被缓存。
fastcgi_cache_methods
如果此指令中列出了客户端请求方法,则响应将被缓存。
代码语言:javascript复制fastcgi_cache_methods GET | HEAD | POST ...;
“GET”和“HEAD”方法总是添加到列表中,但建议明确指定它们。另请参见 fastcgi_no_cache 指令。
这个好测,可以先删了缓存文件,然后使用 POST 请求我们之前的测试文件,就会发现没有缓存文件产生。这时配置一条这个指令加上 POST ,缓存就正常生效了。
fastcgi_cache_min_uses
设置将缓存响应的请求数。
代码语言:javascript复制fastcgi_cache_min_uses number;
默认值 1 ,意思就是,只要有一条请求来了,就缓存,一般不用改它。
fastcgi_cache_purge
定义将请求视为缓存清除请求的条件。
代码语言:javascript复制fastcgi_cache_purge string ...;
商业版本提供的,咱们没有。
fastcgi_cache_revalidate
使用带有“If-Modified-Since”和“If-None-Match”标头字段的条件请求启用过期缓存项的重新验证。
代码语言:javascript复制fastcgi_cache_revalidate on | off;
默认值 off ,就是通过请求头中的 HTTP 缓存相关字段来做为缓存的更新依据,需要我们 PHP 代码中添加响应头及处理,我没测试也没写了,如果哪天有需要再试试。
fastcgi_cache_use_stale
确定在与 FastCGI 服务器通信期间发生错误时可以使用陈旧缓存响应的情况。
代码语言:javascript复制fastcgi_cache_use_stale error | timeout | invalid_header | updating | http_500 | http_503 | http_403 | http_404 | http_429 | off ...;
默认值是 off ,这个指令的参数与 fastcgi_next_upstream 指令的参数相匹配。如果无法选择用于处理请求的 FastCGI 服务器,则错误参数还允许使用过时的缓存响应。此外,如果当前正在更新,更新参数允许使用陈旧的缓存响应。这允许在更新缓存数据时最大限度地减少对 FastCGI 服务器的访问次数。
在响应过时 (1.11.10) 后的指定秒数内,也可以直接在响应标头中启用使用过时的缓存响应。这比使用指令参数的优先级低。
- 如果当前正在更新,则“Cache-Control”标头字段的“stale-while-revalidate”扩展允许使用过时的缓存响应。
- “Cache-Control”标头字段的“stale-if-error”扩展允许在发生错误时使用过时的缓存响应。
为了在填充新缓存元素时尽量减少对 FastCGI 服务器的访问次数,可以使用 fastcgi_cache_lock 指令。
看明白了吗?是不是一脸懵B?其实我们只需要设置一个 fastcgi_cache_use_stale http_500
来进行测试,首先正常访问页面生成缓存后,修改测试用的 PHP 文件,throw 一个异常,这样就会返回 500 了,然后等缓存时间过了,会发现页面依然还可以正常打开。这就是它的作用,当然,并不是说不报错了,错误信息会显示在日志中,但前台依然会返回缓存中的正常内容。
2022/08/25 08:42:13 [error] 1419#0: *7 FastCGI sent in stderr: "PHP message: PHP Fatal error: Uncaught Exception
fastcgi_cache_valid
为不同的响应代码设置缓存时间。
代码语言:javascript复制fastcgi_cache_valid [code ...] time;
这个命令我们已经使用过了,例如,以下指令
代码语言:javascript复制fastcgi_cache_valid 200 302 10m;
fastcgi_cache_valid 404 1m;
表示的就是为代码为 200 和 302 的响应设置 10 分钟的缓存时间,为代码为 404 的响应设置 1 分钟的缓存时间。
如果只指定缓存时间
代码语言:javascript复制fastcgi_cache_valid 5m;
那么只有 200、301 和 302 响应被缓存。
此外,可以指定 any 参数来缓存任何响应:
代码语言:javascript复制fastcgi_cache_valid 200 302 10m;
fastcgi_cache_valid 301 1h;
fastcgi_cache_valid any 1m;
缓存的参数也可以直接在响应头中设置。这比使用指令设置缓存时间具有更高的优先级。
- “X-Accel-Expires”标头字段设置响应的缓存时间(以秒为单位)。零值禁用响应缓存。如果该值以 @ 前缀开头,则它设置自 Epoch 以来的绝对时间(以秒为单位),直到可以缓存响应。
- 如果头部不包含“X-Accel-Expires”字段,可以在头部字段“Expires”或“Cache-Control”中设置缓存参数。
- 如果标头包含“Set-Cookie”字段,则不会缓存此类响应。
- 如果标头包含具有特殊值“*”的“Vary”字段,则不会缓存此类响应(1.7.7)。如果标头包含具有另一个值的“Vary”字段,则将考虑相应的请求标头字段(1.7.7)缓存此类响应。
可以使用 fastcgi_ignore_headers 指令禁用对这些响应头字段中的一个或多个的处理。
最后这个响应头的设置我们可以单独测试一下,在 PHP 文件中,设置一个头 header("X-Accel-Expires: 60");
,设置之前缓存键的 fastcgi_cache_valid 对于 200 是使用 any ,并且缓存 15s ,在添加了响应头后,可以测试 15秒后不会更新缓存,而是要等到 1分钟 后才更新的。响应头的优先级确实更高。
fastcgi_no_cache
定义不将响应保存到缓存的条件。
代码语言:javascript复制fastcgi_no_cache string ...;
如果字符串参数中至少有一个值不为空且不等于“0”,则不会保存响应,和 fastcgi_cache_bypass 配置方式类似的,那个是即使有缓存文件也不走缓存,这个是完全不生成缓存文件。
总结
又是涨姿势的一天吧,原来我还真不知道有这个功能,很多东西在系统学习之前确实都不会了解得太深入。特别是现在各种方便的工具帮我们配置网站之后,更是很少人会去仔细查看文档中的内容。
不过话说回来,真正在使用动态语言进行缓存时,其实让动态语言自身去处理还是更方便一些,比如我们在这里就看到更新缓存或者删除缓存还是比较费劲的,不像 Redis 之类的非常简单,而且 Redis 走内存,速度比硬盘缓存可能还更占优势。使用 Nginx 缓存最大的好处还是减少一次内部的 CGI 调用。因此,如何使用,什么场景,还是要看具体的业务情况了。
参考文档:
http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html