Nginx缓存:一切都是新的-被遗忘的很旧

在每个项目的生命周期中,服务器停止满足SLA要求并从字面上开始阻塞传入流量的时间到了。 之后,漫长的过程开始了,发现了瓶颈,繁重的查询,错误创建的索引,未缓存的数据,反之亦然,因为缓存中的更新数据和项目的其他方面经常被更新。

但是,当您的代码“完美”,所有繁重的请求都放在后台,所有可能的内容都已缓存并且服务器仍未达到我们所需的SLA指标时,该怎么办? 当然,如果可能的话,您当然可以购买新车,分配一些交通流量,并暂时忘记问题。

但是,如果您觉得服务器功能强大,或者有一个神奇的参数可以使站点加速100倍,则可以调用内置的nginx功能,该功能可以缓存来自后端的响应。 让我们看一下它是什么以及它如何帮助增加服务器处理的请求数量。

什么是Nginx缓存,它如何工作?


Nginx缓存可以显着减少后端的请求数量。 这可以通过保存HTTP响应一定时间来实现,并在再次访问资源时从缓存中返回它而无需代理后端请求。 即使是短时间的缓存,也将显着增加服务器处理的请求数量。

在继续配置nginx之前,您需要确保它是使用“ ngx_http_proxy_module”模块构建的,因为我们将使用此模块对其进行配置。

为了方便起见,可以将配置放在单独的文件中,例如“ /etc/nginx/conf.d/cache.conf”。 让我们看一下proxy_cache_path指令,它允许您配置缓存存储设置。

proxy_cache_path /var/lib/nginx/proxy_cache levels=1:2 keys_zone=proxy_cache:15m max_size=1G; 

“ / Var / lib / nginx / proxy_cache”指定服务器上的缓存存储路径。 Nginx将在此目录中保存带有后端响应的文件。 同时,nginx不会单独为缓存创建目录,您需要自己进行维护。

“ Levels = 1:2”-设置目录与缓存的嵌套级别。 嵌套级别通过“:”指示,在这种情况下,将创建2个目录,总共允许3个嵌套级别。 对于每个嵌套级别,从1到2的值都可用,指示如何创建目录名称。

重要的是目录名称不是随机选择的,而是基于文件名创建的。 反过来,文件名是来自缓存键的md5函数的结果;我们稍后将查看缓存键。

让我们在实践中看看如何构建缓存文件的路径:

 /var/lib/nginx/proxy_cache/2/49/07edcfe6974569ab4da6634ad4e5d492 

“ Keys_zone = proxy_cache:15m”参数设置共享内存中区域的名称,所有活动密钥及其信息都存储在该区域中。 通过“:”表示分配的内存大小(以MB为单位)。 根据nginx的数据,1 MB足以存储8000个密钥。

“ Max_size = 1G”定义了所有页面的最大缓存大小,超过此大小,nginx将负责删除需要的数据。

也可以控制缓存中数据的生存期,为此,定义“ proxy_cache_path”指令的“ inactive”参数就足够了,默认情况下为10分钟。 如果在“无效”参数指定的时间内没有调用缓存数据,则即使缓存不是“酸”的,也会删除该数据。

这个缓存是什么样的? 这实际上是服务器上的常规文件,其内容被写入:

•缓存键;
•缓存头;
•来自后端的内容响应。

如果标题和后端的响应都清楚了,那么“缓存键”就会有很多问题。 它是如何构建的以及如何进行管理?

为了描述在nginx中构建缓存键的模板,有一个proxy_cache_key指令,其中将字符串指定为参数。 字符串可以包含nginx中可用的任何变量。

例如:

 proxy_cache_key $request_method$host$orig_uri:$cookie_some_cookie:$arg_some_arg; 

cookie参数和get参数之间的“:”符号用于防止高速缓存键之间发生冲突,您可以选择任何其他符号。 默认情况下,nginx使用以下行来生成密钥:

 proxy_cache_key $scheme$proxy_host$request_uri; 

应注意以下指令,这些指令将帮助您更灵活地管理缓存:

proxy_cache_valid-指定响应缓存时间。 可以指示响应的特定状态,例如200、302、404等,或使用“ any”构造一次指定所有内容。 如果仅指定缓存时间,nginx将默认仅缓存200、301和302状态。

一个例子:

 proxy_cache_valid 15m; proxy_cache_valid 404 15s; 

在此示例中,我们将状态200、301、302的缓存生存期设置为15分钟(nginx默认使用它们,因为我们没有指定特定的状态)。 下一行将缓存时间设置为15秒,仅适用于状态为404的响应。

proxy_cache_lock-此指令将有助于避免在设置一组缓存后立即多次传递给后端,只需将值设置在“ on”位置即可。 所有其他请求将等待缓存中的响应,或者等待超时以阻止对该页面的请求。 因此,可以配置所有超时。

proxy_cache_lock_age-允许您为服务器的响应设置超时限制,之后将在设置缓存后将下一个请求发送给它。 默认值为5秒。

proxy_cache_lock_timeout-设置等待锁定的时间,之后将请求发送到后端,但不会缓存响应。 默认值为5秒。

proxy_cache_use_stale-另一个有用的指令,允许您配置何时可以使用过时的缓存。

一个例子:

 proxy_cache_use_stale error timeout updating; 

在这种情况下,如果出现连接错误,发送请求,从服务器读取响应,超过发送请求的等待限制,从服务器读取响应或在请求时更新了缓存中的数据,它将使用过时的缓存。

proxy_cache_bypass-指定在什么条件下nginx不会从缓存中获取响应,而是立即将请求重定向到后端。 如果至少一个参数不为空且不等于“ 0”。 一个例子:

 proxy_cache_bypass $cookie_nocache $arg_nocache; 

proxy_no_cache-设置nginx将不将响应从后端保存到缓存的条件。 操作原理与proxy_cache_bypass指令的原理相同。

页面缓存的可能问题


如上所述,除了缓存HTTP响应外,nginx还保存从后端接收的标头。 如果您的站点使用会话,则会话cookie也将被缓存。 访问您幸运缓存的页面的所有用户将收到您存储在会话中的个人数据。

您将面临的下一个挑战是缓存管理。 当然,您可以将微不足道的缓存时间设置为2-5分钟,这在大多数情况下就足够了。 但这并不是在所有情况下都适用,因此我们将重新发明自行车。 现在,首先是第一件事。

Cookie保存管理

Nginx端的缓存施加了一些设计限制。 例如,我们不能在缓存的页面上使用会话,因为用户没有到达后端,另一个限制是后端传递cookie。 由于nginx缓存所有标头,为了避免在缓存中存储其他人的会话,我们需要禁止为缓存的页面传递cookie。 proxy_ignore_headers指令将帮助我们解决这一问题。 该参数列出了应从后端忽略的标头。

一个例子:

 proxy_ignore_headers "Set-Cookie"; 

通过这一行,我们将忽略从代理服务器安装Cookie的情况,也就是说,用户将收到不带“ Set-Cookies”标头的响应。 因此,后端尝试写入Cookie的所有内容都将在客户端被忽略,因为它甚至不知道有什么用途。 开发应用程序时应考虑此cookie限制。 例如,要请求授权,您可以关闭标题点火,以便用户接收会话cookie。

您还应该考虑会话生存期,可以在php.ini配置的“ session.gc_maxlifetime ”参数中进行查看。 假设用户登录该站点并开始查看新闻源,则所有数据已经​​在nginx缓存中。 一段时间后,用户注意到他的授权已消失,并且他再次需要执行授权过程,尽管这段时间他一直在网站上观看新闻。 发生这种情况是因为nginx在所有请求上都从缓存返回了结果,而没有向后端发送请求。 因此,后端确定用户处于非活动状态,并且在“ session.gc_maxlifetime ”中指定的时间后删除了会话文件。

为了防止这种情况的发生,我们可以模拟后端请求。 例如,通过ajax发送一个可以保证传递到后端的请求。 要将nginx缓存传递到后端,只需发送POST请求,您还可以使用“ proxy_cache_bypass”指令中的规则,或直接禁用此页面的缓存。 该请求不必回馈,它可以是一个只有一行开始会话的文件。 这种请求的目的是延长用户在站点上时会话的生命周期,并且nginx认真地将缓存的数据提供给所有请求。

缓存刷新管理

首先,您需要确定需求,我们正在努力实现什么目标。 假设我们的网站上有一个部分,其中包含流行体育赛事的文字广播。 当从缓存中提供页面时,所有新消息都会在套接字上出现。 为了使用户能够在第一次启动时(而不是15分钟前)在当前时间看到当前消息,我们需要能够在任何时间独立清除nginx缓存。 同时,nginx可能与应用程序不在同一台机器上。 同样,重置的要求之一是能够一次跨多个页面删除缓存。

在开始编写解决方案之前,让我们先看看nginx提供了什么。 为了重置缓存,nginx有一个特殊的指令称为“ proxy_cache_purge”,该指令记录了重置缓存的条件。 条件实际上是一条正常的行,如果不为空且不为“ 0”,则将通过传递的键删除缓存。 考虑一个小例子。

 proxy_cache_path /data/nginx/cache keys_zone=cache_zone:10m; map $request_method $purge_method { PURGE 1; default 0; } server { ... location / { proxy_pass http://backend; proxy_cache cache_zone; proxy_cache_key $uri; proxy_cache_purge $purge_method; } } 

一个例子来自nginx官方网站。

$ purge_method变量负责刷新缓存,这是proxy_cache_purge指令的条件,默认情况下设置为0。 这意味着nginx在“正常”模式下工作(它保存来自后端的响应)。 但是,如果将请求方法更改为“ PURGE”,则将使用相应的缓存键来删除缓存条目,而不是通过保存响应来代理后端请求。 也可以通过在缓存键的末尾指定“ *”来指定删除掩码。 因此,我们不需要知道缓存在磁盘上的位置以及密钥形成的原理,nginx承担了这些责任。 但是这种方法也有缺点。

  • proxy_cache_purge指令可作为商业订阅的一部分使用。
  • 只能逐点删除缓存,也可以使用{cache key}“ *”形式的掩码删除缓存

由于缓存页面的地址可以完全不同,没有共同的部分,因此带掩码“ *”和指令“ proxy_cache_purge”的方法不适合我们。 回顾一点理论并发现自己喜欢的想法还有待时日。

我们知道nginx缓存是服务器上的常规文件。 我们在“ proxy_cache_path”指令中独立指定了用于存储缓存文件的目录,甚至还指定了使用“级别”从该目录形成文件路径的逻辑。 我们唯一缺少的是缓存密钥的正确格式。 但是我们也可以在“ proxy_cache_key”指令中看到它。 现在我们要做的就是:

  • 完全按照proxy_cache_key指令中的指定形成页面的完整路径;
  • 将结果字符串编码为md5;
  • 使用“ levels”参数中的规则创建嵌套目录。
  • 现在,我们已经拥有服务器上缓存文件的完整路径。 现在剩下要做的就是删除这个文件。 从入门部分开始,我们知道nginx可能不在应用程序计算机上,因此您需要使一次删除多个地址成为可能。 再次,我们描述算法:
  • 高速缓存文件的生成路径我们将写入该文件;
  • 让我们编写一个简单的bash脚本,将其与应用程序一起放入计算机。 他的任务是通过ssh连接到我们已缓存nginx的服务器,并删除步骤1中生成的文件中指定的所有缓存文件;

从理论到实践,我们将写一个小例子来说明我们的工作算法。

步骤1.生成包含缓存路径的文件。

 $urls = [ 'httpGETdomain.ru/news/111/1:2', 'httpGETdomain.ru/news/112/3:4', ]; function to_nginx_cache_path(url) { $nginxHash = md5($url); $firstDir = substr($nginxHash, -1, 1); $secondDir = substr($nginxHash, -3, 2); return "/var/lib/nginx/proxy_cache/$firstDir/$secondDir/$nginxHash"; } //        tmp $filePath = tempnam('tmp', 'nginx_cache_'); //      $fileStream = fopen($filePath, 'a'); foreach ($urls as $url) { //      $cachePath = to_nginx_cache_path($url); //       fwrite($fileStream, $cachePath . PHP_EOL); } //     fclose($fileStream); //  bash       exec("/usr/local/bin/cache_remover $filePath"); 

请注意,变量$ urls包含已缓存页面的url,已采用nginx配置中指定的proxy_cache_key格式。 网址充当页面上显示的实体的标签。 例如,您可以在数据库中创建一个常规表,其中每个实体都将映射到显示该实体的特定页面。 然后,当更改任何数据时,我们可以在表上进行选择并删除我们需要的所有页面的缓存。

步骤2.连接到缓存服务器并删除缓存文件。

 #      ,      FILE_LIST=`cat $1 | tr "\n" " "` #   ssh  SSH=`which ssh` USER="root" #         nginx HOST="10.10.1.0" #   KEY="/var/keys/id_rsa" # SSH ,          $SSH -i ${KEY} ${USER}@${HOST} "rm -f ${FILE_LIST}" #       rm -rf rm -f $1 #   

以上示例仅供参考,请勿在生产中使用。 在示例中,省略了对输入参数和命令限制的检查。 您可能会遇到的问题之一是将参数的长度限制为rm命令。 在开发环境中以小批量进行测试时,很容易错过这一点,在生产中会出现“ rm:参数列表过长”错误。

自定义块缓存


让我们总结一下我们要做的事情:

  • 减少了后端的负载;
  • 了解如何管理缓存
  • 学会了在任何给定时间刷新缓存。

但是,并非一切看起来都像乍看起来那样好。 现在,如果不是每个站点,那么可能恰好每个站点都具有注册/授权功能,通过后,我们将要在标题中的某个位置显示用户名。 具有名称的块是唯一的,并应显示授权我们使用的用户名。 由于nginx保存了来自后端的响应,并且在页面的情况下,它是页面的html内容,带有个人数据的块也将被缓存。 该网站的所有访问者都将看到第一个传递给后端以获取一组缓存的用户的名称。
因此,后端不应提供个人信息所在的块,以使该信息不属于nginx缓存。

有必要考虑页面的这些部分的替代加载。 与往常一样,这可以通过多种方式来完成,例如,在加载页面,发送ajax请求并显示加载器代替个人内容之后。 我们今天将考虑的另一种方法是使用ssi标签。 首先让我们了解SSI是什么,然后如何将其与Nginx缓存结合使用。

什么是SSI及其运作方式


SSI(服务器端包含项,服务器端包含项)是嵌入html页面的一组命令,该命令告诉服务器该做什么。

以下是此类命令(指令)的列表:

•if / elif / else / endif-分支运算符;
•echo-显示变量的值;
•包含-允许您将另一个文件的内容插入文档中。
仅讨论最后一条指令。 include指令具有两个参数:
•file-指定服务器上文件的路径。 关于当前目录;
•virtual-指示服务器上文档的虚拟路径。

我们对“虚拟”参数很感兴趣,因为在服务器上指定文件的完整路径并不总是很方便,或者在分布式体系结构的情况下,服务器上的文件根本不存在。 指令示例:

 <!--#include virtual="/user/personal_news/"--> 

为了使nginx开始处理ssi插入,您需要按如下所示修改位置:

 location / { ssi on; ... } 

现在,由位置“ /”处理的所有请求都将能够执行ssi插入。

我们的请求将如何处理整个方案?

  • 客户请求页面;
  • Nginx代理后端请求;
  • 后端为页面提供ssi插入;
  • 结果存储在缓存中;
  • Nginx“查询”缺失的区块;
  • 结果页面将发送到客户端。

从步骤中可以看到,ssi构造将进入nginx缓存,这将不允许缓存个人块,并且带有所有插入内容的现成html页面将被发送到客户端。 在我们的加载工作中,nginx独立地请求缺少的页面块。 但是,与任何其他解决方案一样,此方法也有其优点和缺点。 想象一下,页面上有多个块,根据用户的不同应显示不同的块,然后将每个这样的块替换为ssi插入。 如预期的那样,Nginx将从后端请求每个这样的块,也就是说,来自用户的一个请求将立即为后端生成多个请求,而我根本不需要。

通过ssi摆脱持久的后端请求


为了解决这个问题,nginx模块“ ngx_http_memcached_module”将为我们提供帮助。 该模块允许从memcached服务器接收值。 通过模块写将无法正常工作,应用服务器应注意这一点。 考虑一个与模块一起配置nginx的小例子:

 server { location /page { set $memcached_key "$uri"; memcached_pass 127.0.0.1:11211; error_page 404 502 504 = @fallback; } location @fallback { proxy_pass http://backend; } } 

在变量$ memcache_key中,我们指定了Nginx尝试从memcache获取数据的键。 在memcached_pa​​ss指令中设置用于连接到内存缓存服务器的参数。 可以通过几种方式指定连接:

•域名;

 memcached_pass cache.domain.ru; 

•IP地址和端口;

 memcached_pass localhost:11211; 

•Unix套接字;

 memcached_pass unix:/tmp/memcached.socket; 

•上游指令。

 upstream cachestream { hash $request_uri consistent; server 10.10.1.1:11211; server 10.10.1.2:11211; } location / { ... memcached_pass cachestream; ... } 

如果nginx设法从缓存服务器获得响应,那么它将响应提供给客户端。 如果缓存中没有数据,则请求将通过“ @fallback”发送到后端。 在nginx下对memcached模块进行的这种小设置将帮助我们减少ssi插入对后端传递请求的数量。

我们希望本文是有用的,并且我们能够展示一种优化服务器负载的方法,考虑设置nginx缓存的基本原理,并解决使用它时出现的问题。

Source: https://habr.com/ru/post/zh-CN428127/


All Articles