O que descobri quando se fala sobre o bloqueio de sessão no PHP é que metade das minhas conversas gira em torno de “oh, eu me lembro da sessão de bloqueio do PHP, que custou alguns dias da minha vida, mas melhorou seriamente o desempenho das minhas aplicações quando eu as evito”, ou a outra ponta “o que diabos é bloqueio de sessão PHP?”.
Especificamente, os mecanismos de bloqueio envolvidos em sessões do PHP não são claros para todos e podem causar lentidão nos aplicativos se você não levá-los em conta.
Isso não tem que ser um problema. Se você está ciente do que se passa nos bastidores, você pode antecipar esse comportamento em suas aplicações PHP e evitá-lo completamente.
O que acontece quando você chama session_start()
Vamos pegar uma configuração básica PHP como exemplo: sempre que você iniciar uma sessão PHP, o PHP irá criar um arquivo simples no caminho session.save_path, que é padrão para /var/lib/php/session. Todos os dados da sessão são armazenados lá.
Se o usuário ainda não tiver um cookie de sessão, uma nova ID será gerada e o usuário receberá um cookie configurado em sua máquina. Se é um usuário que está retornando, ele vai enviar sua ID do cookie para o servidor, o PHP irá analisá-lo e carregar os dados correspondentes de session.save_path.
Simplificando, isso é session_start().
Bloqueios de sessão e concorrência
Em um exemplo um pouco mais completo, aqui está o que acontece nos bastidores quando uma sessão é iniciada no PHP.
Cronometragem | Código PHP | Linux/Servidor |
0ms | session_start(); | Bloqueio de arquivo criado em /var/lib/php/session/sess_ $identifier |
15ms | Consultas SQL, para loops, chamadas de API de terceiros | Bloqueio de arquivo no resto da sessão |
350ms | Término do script PHP | Bloqueio de arquivo é removido do arquivo de sessão em /var/lib/php/sess_$identifier |
Sempre que iniciar session_start() (ou quando session.auto_start do PHP é definido como verdadeiro, ele irá fazer isso automaticamente em cada script PHP), o sistema operacional irá bloquear o arquivo da sessão. A maioria das implementações usa flock, que também é usado para evitar o overlap de cronjobs ou outros bloqueios de arquivos no Linux.
De uma máquina Linux, o bloqueio na sessão se parece com isto:
$ fuser /var/lib/php/session/sess_cdmtgg3noi8fb6j2vqkaai9ff5 /var/lib/php/session/sess_cdmtgg3noi8fb6j2vqkaai9ff5: 2768 2769 2770
fuser relata os 3 PIDs de processos que tem esse arquivo bloqueado ou está aguardando liberação.
$ lsof /var/lib/php/session/sess_cdmtgg3noi8fb6j2vqkaai9ff5 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME php-fpm 2769 http_demo 5uW REG 253,1 0 655415 sess_cdmtgg3noi8fb6j2vqkaai9ff5
lsof relata o PID e o comando que mantém o bloqueio atual.
Esse bloqueio é mantido no arquivo até que o script termine ou que o bloqueio seja propositadamente removido (mais sobre isso abaixo). Isso age como um bloqueio de leitura e escrita: cada tentativa de ler a sessão vai ter que esperar até que o bloqueio seja liberado.
O bloqueio em si não é um problema. É uma salvaguarda para evitar gravações no arquivo da sessão de vários locais diferentes, possivelmente corrompendo dados ou removendo dados anteriores.
Isso se torna um problema quando um segundo script PHP, concorrente, tenta acessar a mesma sessão PHP.
Cronometragem | script 1 | Linux/Servidor | script 2 |
0ms | session_start(); | Bloqueio de arquivo (flock) em /var/lib/php/session/sess_$identifier definido pelo script 1 | session_start(); é chamado, mas está bloqueando o bloqueio de arquivo existente. Este é o lugar onde o PHP apenas “espera” que o bloqueio seja removido |
15ms | Consultas SQL, para loops, chamadas de API de terceiros | Bloqueio de arquivo no resto da sessão | Este script ainda está esperando, sem fazer nada |
350ms | Término do script 1 | Bloqueio de arquivo do script 1 é removido | Script 2 ainda está esperando… |
360ms | Script 2 recebe o novo bloqueio de arquivo | Script 2 agora pode fazer as suas consultas SQL, loops | |
700ms | Bloqueio de arquivo removido do script 2 | Término do script 2 |
No caso de a tabela acima não ser muito clara:
- Quando dois arquivos PHP tentam iniciar uma sessão ao mesmo tempo, apenas um “ganha” e obtém o bloqueio. O outro tem de esperar.
- Enquanto espera, ele não está fazendo nada: session_start() está bloqueando todas as execuções futuras.
- Assim que o bloqueio do primeiro script é removido, o segundo script pode continuar, recebendo o bloqueio.
Na maioria dos cenários, isso faz o PHP se comportar como um conjunto de scripts síncronos para o mesmo usuário: um é executado após o outro, não há solicitações paralelas, mesmo se você tentar chamar esses arquivos PHP pelo AJAX.
Então, em vez de ter ambos os scripts terminando em torno de 350ms, o primeiro script termina em 350ms, mas o segundo leva o dobro do tempo (700ms) porque teve que esperar o primeiro ser concluído.
Manipuladores de sessão alternativos: Redis, memcached, mysql
Se você está procurando uma solução rápida e pensar “Eu vou guardar minhas sessões em memcached”, vai ficar desapontado. A configuração padrão do memcached usa a mesma lógica de segurança descrita acima: sessões serão bloqueadas assim que uma chamada PHP usá-las.
Se você estiver usando a extensão Memcached do PHP, pode definir o memcached.sess_locking para ‘off’ para evitar bloqueios de sessão. O valor padrão é ‘on’, o que o faz agir como o manipulador de sessão normal.
Se você estiver usando Redis, você está com “sorte” (*), pois o manipulador de sessão do Redis ainda não suporta bloqueio. Com Redis como um backend de armazenamento de sessão, não haverá bloqueios.
Se você estiver usando o MySQL como backend de sessão (como o Drupal faz), você tem uma implementação personalizada: não há nenhuma extensão padrão do PHP que permita usar o MySQL como armazenamento de sessão. Em algum lugar do seu código PHP, há uma chamada de função para session_set_save_handler() que descreve qual classe ou método é responsável por ler e escrever seus dados de sessão. Isso significa que a sua aplicação decide se as sessões serão bloqueadas ou não.
(*)ver abaixo
Bloqueio de sessão PHP: o problema que está tentando corrigir
Pode parecer que estou sendo excessivamente negativo sobre esse comportamento, mas eu não estou. Você deve apenas estar ciente do comportamento. Também é uma coisa boa ele existir.
Imaginem o seguinte cenário. Isso mostra onde isso pode dar errado. Se não existisse tal coisa como um “bloqueio de sessão”, isto que aconteceria se dois scripts fossem executados ao mesmo tempo nos mesmos dados da sessão:
Cronometragem | script 1 | script 2 |
0ms | session_start(); Os dados da sessão estão agora em $_SESSION |
session_start(); Os dados da sessão estão agora em $_SESSION |
15ms | Script 1 grava dados na sessão: $_SESSION [ ‘Payment_id’] = 1; | Script 2 também grava dados na sessão: $_SESSION [ ‘Payment_id’] = 5; |
350ms | sleep(1); | Script termina, salvando os dados em $_SESSION. |
450ms | Script termina, salvando seus dados em $_SESSION. | |
Qual valor está na sessão? O valor do script 1. Os dados armazenados pelo script 2 são substituídos pela última gravação, realizada pelo script 1. |
Esse é um bug de concorrência muito estranho e difícil de solucionar. A sessão de bloqueio impede isso.
Mas você pode não querer isso. É principalmente um problema quando se grava dados na sessão. Se você tem um script PHP que só lê os dados da sessão (como um monte de chamadas de AJAX faria), você pode ler os dados com segurança várias vezes.
Por outro lado, se você tiver um script de longa duração que lê os dados da sessão e altera os valores, mas um segundo script se inicia e lê os dados velhos e obsoletos, isso também pode causar problemas na sua aplicação.
Fechando o bloqueio de sessão: PHP 5.x e PHP 7
No PHP, há uma função chamada session_write_close(). Ela faz o que seu nome sugere: grava os dados da sessão e fecha o arquivo, removendo assim o bloqueio. Em seu código PHP, você pode usar assim.
<?php ... // Isso funciona no PHP 5.x e PHP 7 session_start(); $_SESSION['something'] = 'foo'; $_SESSION['yolo'] = 'swag'; session_write_close(); // Faça o resto da sua execução PHP abaixo
O exemplo acima abre a sessão (o que lê os dados da sessão em $_SESSION), escreve os dados nela e fecha o bloqueio. De agora em diante, ele não pode gravar mais no mesmo arquivo de sessão. Se mais manipulações em $_SESSION acontecerem na sequência do script, essas alterações não serão salvas.
A partir do PHP 7, contudo, você pode definir opções adicionais quando chamar session_start();.
<?php session_start([ 'read_and_close' => true, ]); ?>
Isso é o equivalente aproximado de:
<?php session_start(); session_write_close(); ?>
Ele lê os dados da sessão e imediatamente libera o bloqueio para que outros scripts não fiquem bloqueados de usá-lo.
Demonstração: quão lento é lento?
Nada bate uma boa demonstração para mostrar esse comportamento. Eu configurei um repositório GitHub bem simples que mostra esse comportamento.
É simples e feio, mas mostra o que se passa: demo.ma.ttias.be/demo-php-blocking-sessions.
Se você tiver sido pego por esses bloqueios de sessão PHP, deixa-me saber. Se você encontrou uma forma original ou solução alternativa para resolver o seu problema em particular, eu ficaria ainda mais interessado!
***
Mattias Geniar faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://ma.ttias.be/php-session-locking-prevent-sessions-blocking-in-requests/
Mensagem do anunciante:
Conheça a Umbler, startup de Cloud Hosting por demanda feita para agências e desenvolvedores. Experimente grátis!
Powered by WPeMatico