Bloqueio de arquivo PHP file_put_contents


9

O Senario:

Você tem um arquivo com uma string (valor médio da sentença) em cada linha. Para argumentos, digamos que este arquivo tenha 1 MB de tamanho (milhares de linhas).

Você tem um script que lê o arquivo, altera algumas das cadeias de caracteres do documento (não apenas anexando, mas também removendo e modificando algumas linhas) e, em seguida, substitui todos os dados pelos novos.

As questões:

  1. O PHP, o SO ou o httpd etc. do 'servidor' já possui sistemas para interromper problemas como este (leitura / gravação na metade da gravação)?

  2. Se funcionar, explique como funciona e dê exemplos ou links para a documentação relevante.

  3. Caso contrário, existem coisas que eu posso habilitar ou configurar, como bloquear um arquivo até que uma gravação seja concluída e fazer com que todas as outras leituras e / ou gravações falhem até que o script anterior termine de gravar?

Minhas suposições e outras informações:

  1. O servidor em questão está executando PHP e Apache ou Lighttpd.

  2. Se o script for chamado por um usuário e estiver na metade da gravação no arquivo, e outro usuário ler o arquivo no momento exato. O usuário que o lê não receberá o documento completo, pois ainda não foi gravado. (Se esta suposição estiver errada, corrija-me)

  3. Estou preocupado apenas com a gravação e leitura de PHP em um arquivo de texto e, em particular, nas funções "fopen" / "fwrite" e principalmente em "file_put_contents". Examinei a documentação "file_put_contents", mas não encontrei o nível de detalhe ou uma boa explicação sobre o que o sinalizador "LOCK_EX" é ou faz.

  4. O cenário é um exemplo de um cenário de pior caso em que eu suponho que esses problemas têm maior probabilidade de ocorrer, devido ao tamanho grande do arquivo e à maneira como os dados são editados. Quero aprender mais sobre esses problemas e não quero ou preciso de respostas ou comentários como "use mysql" ou "por que você está fazendo isso" porque não estou fazendo isso, só quero aprender sobre leitura / gravação de arquivos com PHP e não parece estar procurando nos lugares certos / documentação e sim, eu entendo que PHP não é a linguagem perfeita para trabalhar com arquivos dessa maneira.


2
Por experiência, posso dizer que ler e gravar em arquivos grandes com PHP (1 MB não é tão grande assim, mas ainda assim) pode ser complicado (e lento). Você sempre pode bloquear o arquivo, mas provavelmente seria mais fácil e seguro apenas usar um banco de dados.
NullUserException 26/10/12

Eu sei que seria melhor usar um banco de dados. Por favor, leia a pergunta (último parágrafo número 4)
hozza 26/10/12

2
Eu li a pergunta; Estou dizendo que não é uma ótima idéia e existem alternativas melhores.
NullUserException 26/10/12

2
file_put_contents()é apenas um invólucro para a fopen()/fwrite()dança, LOCKEXfaz o mesmo como se você ligasse flock($handle, LOCKEX).
26512 yannis

2
@hozza É por isso que postei um comentário, não uma resposta.
NullUserException 26/10/12

Respostas:


4

1) Não 3) Não

Existem vários problemas com a abordagem sugerida original:

Primeiramente, alguns sistemas do tipo UNIX, como o Linux, podem não ter o suporte a bloqueio implementado. O sistema operacional não bloqueia arquivos por padrão. Vi os syscalls serem NOP (sem operação), mas isso foi há alguns anos, então você precisa verificar se um bloqueio definido por sua instância do aplicativo é respeitado por outra instância. (ou seja, 2 visitantes simultâneos). Se o bloqueio ainda não estiver implementado [muito provavelmente], o sistema operacional permitirá que você substitua esse arquivo.

A leitura de arquivos grandes linha por linha não é viável por motivos de desempenho. Sugiro usar file_get_contents () para carregar o arquivo inteiro na memória e depois explodi-lo () para obter as linhas. Como alternativa, use fread () para ler o arquivo em blocos. O objetivo é minimizar o número de chamadas de leitura.

Em relação ao bloqueio de arquivos:

LOCK_EX significa um bloqueio exclusivo (normalmente para gravação). Somente um processo pode conter um bloqueio exclusivo para um determinado arquivo em um determinado momento. LOCK_SH é um bloqueio compartilhado (normalmente para leitura). Mais de um processo pode conter um bloqueio compartilhado para um determinado arquivo em um determinado momento. LOCK_UN desbloqueia o arquivo. O desbloqueio é feito automaticamente, caso você use file_get_contents () http://en.wikipedia.org/wiki/File_locking#In_Unix-like_systems

Solução elegante

O PHP suporta filtros de fluxo de dados destinados ao processamento de dados em arquivos ou de outras entradas. Você pode criar um desses filtros corretamente usando a API padrão. http://php.net/manual/en/function.stream-filter-register.php http://php.net/manual/en/filters.php

Solução alternativa (em 3 etapas):

  1. Crie uma fila. Em vez de processar um nome de arquivo, use o banco de dados ou outro mecanismo para armazenar nomes de arquivos exclusivos em algum lugar pendente / e processado / processado. Dessa forma, nada é substituído. O banco de dados também será útil para armazenar informações adicionais, como metadados, registros de data e hora confiáveis, resultados de processamento e outros.

  2. Para arquivos de até alguns MB, leia o arquivo inteiro na memória e processe-o (file_get_contents () + explode () + foreach ())

  3. Para arquivos maiores, leia o arquivo em blocos (por exemplo, 1024 bytes) e processe + escreva em tempo real cada bloco como leitura (cuidado com a última linha que não termina com \ n. Ela precisa ser processada no próximo lote)


11
"Eu vi os syscalls sendo NOP (sem operação) ..." qual kernel?
Massimo

11
"A leitura de arquivos grandes linha por linha não é viável por motivos de desempenho. Sugiro usar file_get_contents () para carregar o arquivo inteiro na memória ..." Isso não faz sentido. Posso dizer: por razões de desempenho, não leia grandes arquivos na memória ... O que fazer depende de muitos outros fatores.
Massimo

4

Eu sei que isso tem muito tempo, mas no caso de alguém encontrar isso. IMHO o caminho a seguir é assim:

1) Abra o arquivo original (por exemplo, original.txt) usando file_get_contents ('original.txt').

2) Faça suas alterações / edições.

3) Use file_put_contents ('original.txt.tmp') e grave-o em um arquivo temporário original.txt.tmp.

4) Em seguida, mova o arquivo tmp para o arquivo original, substituindo o arquivo original. Para isso, você usa renomear ('original.txt.tmp', 'original.txt').

Vantagens: Enquanto o arquivo está sendo processado e gravado, o arquivo não está bloqueado e outras pessoas ainda podem ler o conteúdo antigo. Pelo menos nas caixas Linux / Unix, renomear é uma operação atômica. Qualquer interrupção durante a gravação do arquivo não toca no arquivo original. Somente quando o arquivo foi totalmente gravado no disco é movido. Leia mais interessante sobre isso nos comentários em http://php.net/manual/en/function.rename.php

Edite para endereçar comentários (também para comentar):

/programming/7054844/is-rename-atomic possui outras referências ao que você pode precisar fazer se estiver operando em sistemas de arquivos.

No bloqueio compartilhado da leitura, não sei por que isso seria necessário, pois nesta implementação não há gravação direta no arquivo. O bando do PHP (que é usado para obter o bloqueio) é um pouco, mas não confiável, e pode ser ignorado por outros processos. É por isso que estou sugerindo o uso da renomeação.

Idealmente, o arquivo de renomeação deve ser nomeado exclusivamente para o processo que está renomeando, para garantir que não dois processos façam a mesma coisa. Mas isso obviamente não impede a edição do mesmo arquivo por mais de uma pessoa ao mesmo tempo. Mas pelo menos o arquivo permanecerá intacto (a última edição vence).

Os passos 3) e 4) se tornariam o seguinte:

$tempfile = uniqid(microtime(true)); // make sure we have a unique name
file_put_contents($tempFile); // write temp file
rename($tempfile, 'original.txt'); // ideally on the same filesystem

Exatamente o que eu queria propor também. Mas eu também adquiria um bloqueio compartilhado durante a leitura para impedir o clobber de dados.
D3L

Renomear é uma operação atômica no mesmo disco, não em discos diferentes.
Xnoise 8/01

Para realmente garantir um nome único de arquivo temporário, você também pode usar astempnam funções, que criam um arquivo atomicamente e retornam o nome do arquivo.
Matthijs Kooijman

1

Na documentação do PHP para file_put_contents (), você pode encontrar no exemplo 2 o uso do LOCK_EX , colocando simplesmente:

file_put_contents('somefile.txt', 'some text', LOCK_EX);

O LOCK_EX é uma constante com um valor inteiro que pode ser usado em algumas funções em um bit a bit .

Também há uma função específica para controlar o bloqueio dos arquivos: maneira flock () .


Embora isso seja interessante e possa ser útil em algumas situações, ao ler, modificar e reescrever um arquivo, o bloqueio deve ser adquirido antes da leitura e mantido até que seja totalmente reescrito (caso contrário, outro processo pode ler uma cópia antiga e alterá-la de volta após o término do processo). Não acredito que isso possa ser alcançado file_get/put_contents.
Jules

0

Um problema que você não mencionou e que também precisa ser cuidadoso é nas condições de corrida em que duas instâncias do seu script estão sendo executadas quase ao mesmo tempo, por exemplo, esta ordem de ocorrências:

  1. Instância de script 1: lê o arquivo
  2. Instância de script 2: lê o arquivo
  3. Instância de script 1: grava alterações no arquivo
  4. Instância de script 2: sobrescreve as alterações da primeira instância de script no arquivo com suas próprias alterações (já que neste momento sua leitura ficou obsoleta).

Portanto, ao atualizar um arquivo grande, é necessário LOCK_EX esse arquivo antes de lê-lo e não liberar o bloqueio até que as gravações tenham sido feitas. Neste exemplo, acredito que isso fará com que a segunda instância do script seja interrompida um pouco enquanto aguarda sua vez de acessar o arquivo, mas isso é melhor do que a perda de dados.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.