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:
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)?
Se funcionar, explique como funciona e dê exemplos ou links para a documentação relevante.
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:
O servidor em questão está executando PHP e Apache ou Lighttpd.
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)
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.
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.
fonte
file_put_contents()
é apenas um invólucro para afopen()/fwrite()
dança,LOCKEX
faz o mesmo como se você ligasseflock($handle, LOCKEX)
.Respostas:
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):
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.
Para arquivos de até alguns MB, leia o arquivo inteiro na memória e processe-o (file_get_contents () + explode () + foreach ())
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)
fonte
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:
fonte
tempnam
funções, que criam um arquivo atomicamente e retornam o nome do arquivo.Na documentação do PHP para file_put_contents (), você pode encontrar no exemplo 2 o uso do LOCK_EX , colocando simplesmente:
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 () .
fonte
file_get/put_contents
.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:
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.
fonte