Na vida de cada projeto, chega o momento em que o servidor deixa de atender aos requisitos de SLA e literalmente começa a engasgar com a quantidade de tráfego recebido. Depois disso, começa o longo processo de encontrar gargalos, consultas pesadas, índices criados incorretamente, dados não armazenados em cache ou vice-versa, dados atualizados com freqüência no cache e em outros lados obscuros do projeto.
Mas o que fazer quando seu código é “perfeito”, todas as solicitações pesadas são colocadas em segundo plano, tudo o que era possível foi armazenado em cache e o servidor ainda não atinge os indicadores de SLA de que precisamos? Se possível, é claro que você pode comprar carros novos, distribuir parte do tráfego e esquecer o problema por um tempo.
Mas se você acha que seu servidor é capaz de mais ou se existe um parâmetro mágico que acelera o site em 100 vezes, lembre-se do recurso nginx interno que permite armazenar em cache as respostas do back-end. Vamos dar uma olhada no que é e como ele pode ajudar a aumentar o número de solicitações processadas pelo servidor.
O que é o cache do Nginx e como ele funciona?
O cache do Nginx pode reduzir significativamente o número de solicitações para o back-end. Isso é possível salvando a resposta HTTP por um certo tempo e, ao acessar o recurso novamente, retornando-o do cache sem fazer proxy da solicitação do back-end. O armazenamento em cache, mesmo por um curto período, aumentará significativamente o número de solicitações processadas pelo servidor.
Antes de prosseguir com a configuração do nginx, você precisa ter certeza de que ele foi criado com o módulo “ngx_http_proxy_module”, já que iremos configurá-lo usando este módulo.
Por conveniência, você pode colocar a configuração em um arquivo separado, por exemplo, “/etc/nginx/conf.d/cache.conf”. Vamos dar uma olhada na diretiva proxy_cache_path, que permite definir as configurações de armazenamento em cache.
proxy_cache_path /var/lib/nginx/proxy_cache levels=1:2 keys_zone=proxy_cache:15m max_size=1G;
“/ Var / lib / nginx / proxy_cache” especifica o caminho de armazenamento em cache no servidor. É nesse diretório que o nginx salva os arquivos com a resposta do back-end. Ao mesmo tempo, o nginx não criará um diretório para o cache de forma independente. Você precisa cuidar disso sozinho.
"Níveis = 1: 2" - define o nível de aninhamento de diretórios com um cache. Os níveis de aninhamento são indicados por ":"; nesse caso, serão criados 2 diretórios, no total são permitidos 3 níveis de aninhamento. Para cada nível de aninhamento, valores de 1 a 2 estão disponíveis, indicando como criar o nome do diretório.
O ponto importante é que o nome do diretório não é escolhido aleatoriamente, mas é criado com base no nome do arquivo. O nome do arquivo, por sua vez, é o resultado da função md5 da chave do cache; veremos a chave do cache um pouco mais tarde.
Vamos ver na prática como o caminho para o arquivo de cache é construído:
/var/lib/nginx/proxy_cache/2/49/07edcfe6974569ab4da6634ad4e5d492
O parâmetro "Keys_zone = proxy_cache: 15m" define o nome da zona na memória compartilhada, onde todas as chaves e informações ativas são armazenadas. Através de:: indica o tamanho da memória alocada em MB. De acordo com o nginx, 1 MB é suficiente para armazenar 8 mil chaves.
"Max_size = 1G" define o tamanho máximo do cache para todas as páginas acima das quais o nginx cuidará da exclusão de dados menos necessários.
Também é possível controlar o tempo de vida dos dados no cache, para isso basta definir o parâmetro "inativo" da diretiva "proxy_cache_path", que é de 10 minutos por padrão. Se durante o tempo especificado no parâmetro "inativo" não houver chamadas para os dados do cache, esses dados serão excluídos, mesmo que o cache ainda não esteja "azedo".
Como é esse cache? Na verdade, este é um arquivo comum no servidor, cujo conteúdo está escrito:
Chave de cache;
• cabeçalhos de cache;
• resposta de conteúdo do back-end.
Se tudo estiver claro com os cabeçalhos e a resposta do back-end, haverá várias perguntas na "chave de cache". Como é construído e como pode ser gerenciado?
Para descrever o modelo para criar uma chave de cache no nginx, existe uma diretiva proxy_cache_key, na qual uma cadeia de caracteres é especificada como parâmetro. Uma string pode consistir em quaisquer variáveis disponíveis no nginx.
Por exemplo:
proxy_cache_key $request_method$host$orig_uri:$cookie_some_cookie:$arg_some_arg;
O símbolo “:” entre o parâmetro cookie e o parâmetro get é usado para evitar colisões entre chaves de cache, você pode escolher qualquer outro símbolo de sua escolha. Por padrão, o nginx usa a seguinte linha para gerar a chave:
proxy_cache_key $scheme$proxy_host$request_uri;
As seguintes diretivas devem ser observadas que ajudarão você a gerenciar seu cache com mais flexibilidade:
proxy_cache_valid - especifica o tempo de armazenamento em cache da resposta. É possível indicar o status específico da resposta, por exemplo, 200, 302, 404, etc., ou especificar tudo de uma vez usando a construção "any". Se apenas o tempo de armazenamento em cache for especificado, o nginx assumirá o padrão de armazenar em cache apenas os status 200, 301 e 302.
Um exemplo:
proxy_cache_valid 15m; proxy_cache_valid 404 15s;
Neste exemplo, definimos a vida útil do cache como 15 minutos para os status 200, 301, 302 (o nginx os usa por padrão, pois não especificamos um status específico). A próxima linha configurou o tempo de armazenamento em cache para 15 segundos, apenas para respostas com um status 404.
proxy_cache_lock - Esta diretiva ajudará a evitar várias passagens para o back-end imediatamente após um conjunto de cache, apenas defina o valor na posição "ligado". Todas as outras solicitações aguardam uma resposta no cache ou um tempo limite para bloquear a solicitação na página. Assim, todos os tempos limite podem ser configurados.
proxy_cache_lock_age - Permite definir um limite de tempo limite para uma resposta do servidor, após o qual a próxima solicitação será enviada a ele depois que o cache for definido. O padrão é 5 segundos.
proxy_cache_lock_timeout - Define o tempo de espera pelo bloqueio, após o qual a solicitação será enviada ao back-end, mas a resposta não será armazenada em cache. O padrão é 5 segundos.
proxy_cache_use_stale - Outra diretiva útil que permite configurar quando é possível usar um cache obsoleto.
Um exemplo:
proxy_cache_use_stale error timeout updating;
Nesse caso, ele usará um cache desatualizado em caso de erro de conexão, enviando uma solicitação, lendo uma resposta do servidor, excedendo o limite de espera para enviar uma solicitação, lendo uma resposta do servidor ou se os dados no cache forem atualizados no momento da solicitação.
proxy_cache_bypass - Especifica as condições sob as quais o nginx não receberá uma resposta do cache, mas redirecionará imediatamente a solicitação para o back-end. Se pelo menos um dos parâmetros não estiver vazio e não for igual a "0". Um exemplo:
proxy_cache_bypass $cookie_nocache $arg_nocache;
proxy_no_cache - Define a condição sob a qual o nginx não salvará a resposta do back-end no cache. O princípio de operação é o mesmo da diretiva proxy_cache_bypass.
Possíveis problemas com o cache da página
Como mencionado acima, juntamente com o cache de uma resposta HTTP, o nginx salva os cabeçalhos recebidos do back-end. Se o seu site usa uma sessão, o cookie da sessão também será armazenado em cache. Todos os usuários que visitarem a página que você teve sorte em armazenar em cache receberão seus dados pessoais armazenados na sessão.
O próximo desafio que você enfrentará é o gerenciamento de cache. Obviamente, você pode definir um tempo de cache insignificante de 2 a 5 minutos e isso será suficiente na maioria dos casos. Mas como isso não é aplicável em todas as situações, reinventaremos nossa bicicleta. Agora, as primeiras coisas primeiro.
Gerenciamento de preservação de cookiesO armazenamento em cache no lado nginx impõe algumas restrições de design. Por exemplo, não podemos usar sessões em páginas em cache, pois o usuário não acessa o back-end, outra limitação é a entrega de cookies pelo back-end. Como o nginx armazena em cache todos os cabeçalhos, para evitar armazenar a sessão de outra pessoa no cache, precisamos proibir a entrega de cookies para páginas em cache. A diretiva proxy_ignore_headers nos ajudará com isso. O argumento lista os cabeçalhos que devem ser ignorados no back-end.
Um exemplo:
proxy_ignore_headers "Set-Cookie";
Com essa linha, ignoramos a instalação de cookies do servidor proxy, ou seja, o usuário receberá uma resposta sem o cabeçalho "Set-Cookies". Consequentemente, tudo o que o back-end tentou gravar no cookie será ignorado no lado do cliente, pois ele nem saberá que algo foi planejado para ele. Essa restrição de cookies deve ser considerada ao desenvolver um aplicativo. Por exemplo, para solicitar autorização, você pode desativar a ignição do cabeçalho para que o usuário receba um cookie de sessão.
Você também deve levar em consideração o tempo de vida da sessão, que pode ser visualizado no parâmetro “
session.gc_maxlifetime ” da configuração do php.ini. Imagine que o usuário fez login no site e começou a visualizar o feed de notícias, todos os dados já estão no cache nginx. Depois de algum tempo, o usuário percebe que sua autorização desapareceu e ele novamente precisa passar pelo processo de autorização, embora durante todo esse tempo estivesse no site, assistindo às notícias. Isso aconteceu porque em todas as solicitações o nginx retornou o resultado do cache sem enviar uma solicitação ao back-end. Portanto, o back-end decidiu que o usuário estava inativo e após um tempo especificado em "
session.gc_maxlifetime " excluiu o arquivo da sessão.
Para impedir que isso aconteça, podemos emular solicitações de back-end. Por exemplo, através do ajax, envie uma solicitação que será garantida para passar para o back-end. Para passar o cache nginx para o back-end, basta enviar uma solicitação POST, você também pode usar a regra da diretiva “proxy_cache_bypass” ou simplesmente desativar o cache desta página. A solicitação não precisa retribuir, pode ser um arquivo com uma única linha iniciando a sessão. O objetivo dessa solicitação é prolongar a vida útil da sessão enquanto o usuário estiver no site, e o nginx conscientemente fornece os dados em cache para todas as suas solicitações.
Gerenciamento de liberação de cachePrimeiro, você precisa determinar os requisitos, que objetivo estamos tentando alcançar. Digamos que nosso site tenha uma seção com uma transmissão de texto de eventos esportivos populares. Ao carregar a página é fornecida a partir do cache, todas as novas mensagens aparecem nos soquetes. Para que o usuário veja as mensagens atuais no horário atual na primeira inicialização, em vez de 15 minutos atrás, precisamos ser capazes de limpar independentemente o cache do nginx a qualquer momento. Ao mesmo tempo, o nginx pode não estar localizado na mesma máquina que o aplicativo. Além disso, um dos requisitos para uma redefinição será a capacidade de excluir o cache, em várias páginas ao mesmo tempo.
Antes de começar a escrever sua solução, vamos ver o que o nginx oferece imediatamente. Para redefinir o cache, o nginx possui uma diretiva especial chamada “proxy_cache_purge”, que registra a condição para redefinir o cache. A condição é na verdade uma linha normal que, se não estiver vazia e não "0", excluirá o cache pela chave passada. Considere um pequeno exemplo.
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; } }
Um exemplo é retirado do site oficial da nginx.A variável $ purge_method é responsável por liberar o cache, que é uma condição para a diretiva proxy_cache_purge e é definida como 0 por padrão. Isso significa que o nginx funciona no modo "normal" (salva as respostas do back-end). Mas se você alterar o método de solicitação para "PURGE", em vez de fazer proxy da solicitação de back-end ao salvar a resposta, a entrada do cache será excluída usando a chave de cache correspondente. Também é possível especificar uma máscara de exclusão especificando um "*" no final da chave de cache. Portanto, não precisamos saber a localização do cache no disco e o princípio de formação de chave, o nginx assume essas responsabilidades. Mas também há desvantagens nessa abordagem.
- A diretiva proxy_cache_purge está disponível como parte de uma assinatura comercial.
- Só é possível excluir o cache no sentido dos ponteiros do mouse ou usando a máscara do formulário {cache key} “*”
Como os endereços das páginas em cache podem ser completamente diferentes, sem partes comuns, a abordagem com a máscara "*" e a diretiva "proxy_cache_purge" não é adequada para nós. Resta lembrar um pouco de teoria e descobrir seu ide favorito.
Sabemos que o cache nginx é um arquivo regular no servidor. Especificamos de maneira independente o diretório para armazenar arquivos de cache na diretiva “proxy_cache_path”, até especificamos a lógica de formar o caminho para o arquivo deste diretório usando “levels”. A única coisa que está faltando é a formação correta da chave de armazenamento em cache. Mas também podemos vê-lo na diretiva “proxy_cache_key”. Agora tudo o que precisamos fazer é:
- forme o caminho completo para a página, exatamente como especificado na diretiva proxy_cache_key;
- codifique a string resultante em md5;
- crie diretórios aninhados usando a regra do parâmetro "levels".
- E agora já temos o caminho completo para o arquivo de cache no servidor. Agora, tudo o que resta para nós é excluir esse mesmo arquivo. Desde a parte introdutória, sabemos que o nginx pode não estar localizado na máquina do aplicativo, portanto, é necessário excluir vários endereços ao mesmo tempo. Novamente, descrevemos o algoritmo:
- Os caminhos gerados para os arquivos de cache que escreveremos no arquivo;
- Vamos escrever um script bash simples que colocamos na máquina com o aplicativo. Sua tarefa será conectar via ssh ao servidor em que temos o cache do nginx e excluir todos os arquivos de cache especificados no arquivo gerado a partir da etapa 1;
Passamos da teoria à prática, escreveremos um pequeno exemplo que ilustra nosso algoritmo de trabalho.
Etapa 1. Gerando um arquivo com caminhos para o cache.
$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");
Observe que a variável $ urls contém a URL das páginas em cache, já no formato proxy_cache_key especificado na configuração do nginx. O URL atua como uma tag para as entidades exibidas na página. Por exemplo, você pode criar uma tabela regular no banco de dados, onde cada entidade será mapeada para uma página específica na qual é exibida. Em seguida, ao alterar qualquer dado, podemos fazer uma seleção na tabela e excluir o cache de todas as páginas que precisamos.
Etapa 2. Conecte-se ao servidor de cache e exclua os arquivos de cache.
# , FILE_LIST=`cat $1 | tr ` # ssh SSH=`which ssh` USER= # nginx HOST= # KEY= # SSH , $SSH -i ${KEY} ${USER}@${HOST} # rm -rf rm -f $1 #
Os exemplos acima são apenas para orientação, não os use na produção. Nos exemplos, as verificações dos parâmetros de entrada e das restrições de comando são omitidas. Um dos problemas que você pode encontrar é limitar o tamanho do argumento ao comando rm. Ao testar no ambiente de desenvolvimento em pequenos volumes, isso pode ser facilmente esquecido e, na produção, você recebe o erro "rm: lista de argumentos muito longa".
Cache de bloco personalizado
Vamos resumir o que conseguimos fazer:
- reduziu a carga no back-end;
- Aprenda a gerenciar o cache
- aprendeu a liberar o cache a qualquer momento.
Mas nem tudo é tão bom quanto parece à primeira vista. Agora, provavelmente, se não todo primeiro, então precisamente todo segundo site possui uma funcionalidade de registro / autorização, após a passagem pela qual queremos exibir o nome de usuário em algum lugar do cabeçalho. O bloco com o nome é único e deve exibir o nome de usuário sob o qual estamos autorizados. Como o nginx salva a resposta do back-end e, no caso da página, é o conteúdo html da página, o bloco com dados pessoais também será armazenado em cache. Todos os visitantes do site verão o nome do primeiro usuário que passou para o back-end para um conjunto de cache.
Portanto, o back-end não deve fornecer blocos nos quais as informações pessoais estão localizadas para que essas informações não caiam no cache do nginx.
É necessário considerar o carregamento alternativo dessas partes da página. Como sempre, isso pode ser feito de várias maneiras, por exemplo, depois de carregar a página, enviar uma solicitação ajax e exibir o carregador no lugar do conteúdo pessoal. Outra maneira que consideraremos hoje é usar tags ssi. Vamos primeiro entender o que é SSI e, em seguida, como podemos usá-lo em conjunto com o cache nginx.
O que é SSI e como funciona
SSI (inclusões do lado do servidor, inclusões do lado do servidor) é um conjunto de comandos incorporados em uma página html que informa ao servidor o que fazer.
Aqui está uma lista desses comandos (diretivas):
If / elif / else / endif - O operador de ramificação;
• eco - exibe os valores das variáveis;
• incluir - Permite inserir o conteúdo de outro arquivo no documento.
Apenas a última diretiva será discutida. A diretiva include possui dois parâmetros:
• arquivo - especifica o caminho para o arquivo no servidor. Em relação ao diretório atual;
• virtual - indica o caminho virtual para o documento no servidor.
Estamos interessados no parâmetro "virtual", já que nem sempre é conveniente especificar o caminho completo para o arquivo no servidor ou, no caso de uma arquitetura distribuída, o arquivo no servidor simplesmente não existe. Diretiva de exemplo:
Para que o nginx comece a processar inserções ssi, é necessário modificar o local da seguinte maneira:
location / { ssi on; ... }
Agora todas as solicitações processadas pelo local “/” poderão executar inserções ssi.
Como o nosso pedido passará por todo esse esquema?
- o cliente solicita a página;
- Nginx proxies a solicitação para o back-end;
- o back-end fornece a página com inserções ssi;
- o resultado é armazenado no cache;
- O Nginx "pergunta" os blocos ausentes;
- A página resultante é enviada ao cliente.
Como você pode ver nas etapas, as construções ssi entrarão no cache nginx, o que permitirá não armazenar em cache os blocos pessoais, e uma página html pronta com todas as inserções será enviada ao cliente. Aqui nosso carregamento funciona, o nginx solicita independentemente os blocos de páginas ausentes. Mas, como qualquer outra solução, essa abordagem tem seus prós e contras. Imagine que existem vários blocos na página que devem ser exibidos de forma diferente, dependendo do usuário, e cada um desses blocos será substituído por uma inserção ssi. O Nginx, conforme o esperado, solicitará cada bloco desse tipo do back-end, ou seja, um pedido do usuário gerará imediatamente vários pedidos para o back-end, o que eu não gostaria.
Livrar-se de solicitações persistentes de back-end via ssi
Para resolver esse problema, o módulo nginx “ngx_http_memcached_module” nos ajudará. O módulo permite receber valores do servidor em cache do memcached. A escrita no módulo não funcionará; o servidor de aplicativos deve cuidar disso. Considere um pequeno exemplo de configuração do nginx em conjunto com um módulo:
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; } }
Na variável $ memcache_key, especificamos a chave pela qual o nginx tentará obter dados do memcache. Os parâmetros para conectar-se ao servidor memcache são definidos na diretiva memcached_pass. A conexão pode ser especificada de várias maneiras:
Nome de domínio;
memcached_pass cache.domain.ru;
• endereço IP e porta;
memcached_pass localhost:11211;
Soquete unix;
memcached_pass unix:/tmp/memcached.socket;
• diretiva a montante.
upstream cachestream { hash $request_uri consistent; server 10.10.1.1:11211; server 10.10.1.2:11211; } location / { ... memcached_pass cachestream; ... }
Se o nginx conseguiu obter uma resposta do servidor de cache, ele a fornece ao cliente. Se não houver dados no cache, a solicitação será enviada ao back-end via "@fallback". Essa pequena configuração do módulo memcached no nginx nos ajudará a reduzir o número de solicitações de passagem para o back-end das inserções ssi.
Esperamos que este artigo seja útil e que possamos mostrar uma das maneiras de otimizar a carga no servidor, considerar os princípios básicos da configuração do cache do nginx e fechar os problemas que surgem ao usá-lo.