Cache de páginas PHP - Melhorando a performance
Uma das grandes vedetes dos portais de conteúdo da Internet hoje em dia é a rapidez na atualização de conteúdos. Para isso, são criados os mais diversos sistemas de publicação e / ou gerenciamento de conteúdo (explicaremos a diferença entre esses termos em outra oportunidade), que armazenam todo o portal em banco de dados, dando a opção para o usuário que não conhece nada de marcações HTML ou de estilos, cadastrar seu conteúdo formatado de forma rápida, sem depender de “técnicos” para a tarefa.
O grande “porém” é que, em portais muito acessados, o tempo de resposta de um banco de dados a uma requisição pode não ser satisfatório, mesmo em casos de queries simples, como a montagem de um menu dinâmico, mas que são executadas simultaneamente por diversos (até milhares) de usuários. O efeito em cascata então é sentido, fazendo com que todas as outras requisições se tornem lentas.
Páginas dinâmicas, em um portal que utiliza um sistema de publicação / gerenciamento de conteúdo, podem conter vários elementos pequenos onde são realizadas diversas queries para a montagem do seu conteúdo. Essas queries, por menores e simples que sejam, custam tempo de processamento do servidor de banco de dados e tempo de espera pelo servidor de aplicação pela resposta do BD. Tudo isso somado a um grande número de usuários simultâneos, em uma arquitetura de rede que não esteja devidamente preparada, pode fazer seu portal vir abaixo.
Uma ação a ser realizada para tentar dizimar este problema é o cache das páginas geradas. No caso, busquei por uma solução (santo Google) e encontrei essa classe do companheiro Antônio Campos, de Portugal, onde partimos de um princípio simples, mas que eliminaria boa parte da concorrência nas requisições ao servidor de Banco de Dados, fazendo assim a performance do sistema aumentar consideravelmente.
A classe consiste em se pegar o buffer de saída do php antes da maneira que ele envia para o browser e armazená-lo em um arquivo html, em um diretório de cache específico. Na próxima requisição à esta página, antes começar o processamento da mesma, o sistema verifica se esta página já existe no cache ou se a mesma não é antiga (o tempo do cache é configurável). Caso atenda às condições, ele envia para o browser o arquivo html armazenado e não processa novamente a página, economizando assim um tempo absurdo de processamento e resposta de servidor.
A classe ainda possui uma função para limpar o cache, mas ainda meio “rudimentar”, uma vez que limpa todo o diretório de cache, o que não é interessante em grandes portais. Para a próxima versão (que pretendo postar aqui) vou implementar a funcionalidade somente limpar partes determinadas do cache, por idade ou por nome do arquivo.
Segue o código-fonte da classe, contendo o original escrito pelo Antônio e as modificações realizadas por mim.
<?php
/*
Criado por: Antonio Campos
Modificada por: Diogo Moreira
CHANGELOG
04-01-2008 -> Criação
20-02-2008 -> adicionado o parametro do caminho do arquivo de cache para o construtor, para prever arquivos em niveis diferentes
-> adicionado o tempo do cache em segundos
-> adicionada função para limpar o buffer de saída, para não guardar lixo no arquivo de cache
-> Segundo release!
*/
class Cache{
function Cache($diretorio_cache = "./cache/"){
//Diretório onde queremos guardar os arquivos de cache
$this->directorio_cache = $diretorio_cache; //Agora vamos começar a contruir uma string que irá conter o nome do script que vamos fazer cache
$this->pagina = $_SERVER['SCRIPT_NAME']."?";
//se existir uma query string vamos concatenar a mesma com o nome do arquivo
if (isset($_SERVER['QUERY_STRING'])){
$this->pagina=$this->pagina.$_SERVER['QUERY_STRING'];
}
}
function Inicio($tpo_cache = 600){ //equivalente a 10 minutos
//Gerar o nome do arquivo de cache correspondente à página que estamos
//O MD5 é só para evitar que o nome contenha careteres inválidos por exemplo ? e & comuns nas query strings
//coloquei a extensão html podem colocar o que quiserem
//limpa o buffer de saída antes de começar a execução
ob_clean();
$this->ficheiro_cache = $this->directorio_cache . md5($this->pagina) . '.html';
//Verificar se o ficheiro já existe na cache
if (file_exists($this->ficheiro_cache)){
if((time() - $tpo_cache) < filemtime($this->ficheiro_cache)){
//se já existir e não for antigo geramos o output deste arquivo
readfile($this->ficheiro_cache);
//como já fizemos output não queremos que mais nada seja executado!
exit();
}
unlink($this->ficheiro_cache);
}
//se chegar aqui é porque o arquivo não existia em cache e temos que o criar
//esta função cria um buffer onde é colocado o outup que é igualmente enviado ao cliente
ob_start();
}
function Fim(){
// Como não exise o arquivo no cache vamos cria-lo e abrir em modo w (w de write)
$fp = fopen($this->ficheiro_cache, 'w');
//Escrevemos o conteudo do buffer no novo ficheiro de cache
@fwrite($fp, ob_get_contents());
//escreve a data e hora do cache, apenas para controle
$hora = date('h:i:s, j-m-Y');
@fwrite($fp, "<!– Cache - $this->pagina - $hora –>");
//fechamos o file pointer
@fclose($fp);
//e fechamos tambem o buffer criado
ob_end_flush();
}
function Limpar(){
//ATENÇÃO: Este método apaga TODO o cache. Por isso, seu diretório onde ficam guardados os arquivos de
//cache não deve conter nenhum outro tipo de dado. Esta função será aprimorada para apagar somente o
//cache de determinada página
//criamos um apontador para o diretorio onde esta guardado o cache
if ($handle = $this->directorio_cache){
//um loop que percorre todos os ficheiros
while (false !== ($file = readdir($handle))){
//evitar um erro!!
if ($file != '.' and $file != '..'){
//somente uma informação
echo 'Apagado > '.$file . '<br>';
//apagar cada um dos arquivos
unlink($this->directorio_cache . '/' . $file);
}
}
//fechamos o apontador
closedir($handle);
}
}
}
?>
Segue um exemplo de utilização da classe:
<?php
//incluir o ficheiro onde temos a classe
include_once "cache.php";
$cache = new Cache();
//Iniciar o processo de cache
$cache->Inicio();
//Se o arquivo ja existir em cache nada mais sera executado abaixo desta linha
?>
<html>
<head>
<title>Exemplo de Cache em Php</title>
</head>
<body>
<h1>Conteudo da pagina</h1>
</body>
</html>
<?php
//Se chegou aqui é porque não existia em cache, por isso vamos chamar a função que escreve o arquivo no cache!
$cache->Fim();
?>
Links úteis: Blog do Antônio Campos (autor original da classe): http://antoniocampos.no-ip.com/
