PHP file_put_contents Blocco file


9

Il Senario:

Hai un file con una stringa (valore medio della frase) su ogni riga. Per amor di se, diciamo che questo file ha una dimensione di 1 Mb (migliaia di righe).

Hai uno script che legge il file, cambia alcune delle stringhe all'interno del documento (non solo aggiungendo ma anche rimuovendo e modificando alcune righe) e quindi sovrascrive tutti i dati con i nuovi dati.

Le domande:

  1. PHP, OS o httpd ecc. Del server dispongono già di sistemi per bloccare problemi come questo (leggere / scrivere a metà di una scrittura)?

  2. In tal caso, spiegare come funziona e fornire esempi o collegamenti alla documentazione pertinente.

  3. In caso contrario, ci sono cose che posso abilitare o configurare, come bloccare un file fino al completamento di una scrittura e far fallire tutte le altre letture e / o scritture fino al termine della scrittura dello script precedente?

I miei presupposti e altre informazioni:

  1. Il server in questione esegue PHP e Apache o Lighttpd.

  2. Se lo script viene chiamato da un utente ed è a metà della scrittura sul file e un altro utente legge il file in quel preciso momento. L'utente che lo legge non otterrà l'intero documento, poiché non è stato ancora scritto. (Se questo presupposto è sbagliato, per favore correggimi)

  3. Mi occupo solo della scrittura e della lettura di PHP in un file di testo, e in particolare delle funzioni "fopen" / "fwrite" e principalmente "file_put_contents". Ho esaminato la documentazione "file_put_contents" ma non ho trovato il livello di dettaglio o una buona spiegazione di cosa sia o faccia il flag "LOCK_EX".

  4. Lo scenario è un esempio di uno scenario peggiore in cui presumo che questi problemi abbiano maggiori probabilità di verificarsi, a causa delle grandi dimensioni del file e del modo in cui i dati vengono modificati. Voglio saperne di più su questi problemi e non voglio né ho bisogno di risposte o commenti come "usa mysql" o "perché lo stai facendo" perché non lo sto facendo, voglio solo imparare a leggere / scrivere i file con PHP e non sembra che stia cercando nei posti / documentazione giusti e sì, capisco che PHP non è il linguaggio perfetto per lavorare con i file in questo modo.


2
Posso dirti per esperienza che leggere e scrivere su file di grandi dimensioni con PHP (1 MB non è poi così grande, ma comunque) può essere complicato (e lento). Puoi sempre bloccare il file, ma probabilmente sarebbe più semplice e sicuro usare un database.
NullUserException,

So che sarebbe meglio usare un DB. Si prega di leggere la domanda (ultimo paragrafo numero 4)
hozza,

2
Ho letto la domanda; Sto dicendo che non è una grande idea e ci sono alternative migliori.
NullUserException,

2
file_put_contents()è solo un involucro per la fopen()/fwrite()danza, LOCKEXfa lo stesso come se chiamassi flock($handle, LOCKEX).
yannis,

2
@hozza Ecco perché ho pubblicato un commento, non una risposta.
NullUserException,

Risposte:


4

1) No 3) No

Esistono diversi problemi con l'approccio originale suggerito:

Innanzitutto, per alcuni sistemi simili a UNIX come Linux potrebbe non essere implementato il supporto del blocco. Il sistema operativo non blocca i file per impostazione predefinita. Ho visto che i syscall sono NOP (nessuna operazione), ma è qualche anno fa, quindi è necessario verificare se un blocco impostato dalla propria istanza dell'applicazione è rispettato da un'altra istanza. (ovvero 2 visitatori simultanei). Se il blocco non è ancora implementato [molto probabilmente lo è], il sistema operativo consente di sovrascrivere quel file.

La lettura di file di grandi dimensioni riga per riga non è possibile per motivi di prestazioni. Suggerisco di usare file_get_contents () per caricare l'intero file in memoria e quindi esploderlo () per ottenere le righe. In alternativa, utilizzare fread () per leggere il file in blocchi. L'obiettivo è ridurre al minimo il numero di chiamate in lettura.

Per quanto riguarda il blocco dei file:

LOCK_EX indica un blocco esclusivo (in genere per la scrittura). Solo un processo può contenere un blocco esclusivo per un determinato file in un determinato momento. LOCK_SH è un blocco condiviso (in genere per la lettura), più di un processo può contenere un blocco condiviso per un determinato file in un determinato momento. LOCK_UN sblocca il file. Lo sblocco viene eseguito automaticamente nel caso in cui si utilizzi file_get_contents () http://it.wikipedia.org/wiki/File_locking#In_Unix-like_systems

Soluzione elegante

PHP supporta i filtri del flusso di dati che sono destinati all'elaborazione di dati in file o da altri input. Potresti voler creare uno di questi filtri correttamente usando l'API standard. http://php.net/manual/en/function.stream-filter-register.php http://php.net/manual/en/filters.php

Soluzione alternativa (in 3 passaggi):

  1. Crea una coda. Invece di elaborare un nome file, utilizzare il database o un altro meccanismo per archiviare nomi file univoci da qualche parte in sospeso / ed elaborati / elaborati. In questo modo nulla viene sovrascritto. Il database sarà anche utile per archiviare informazioni aggiuntive, come metadati, timestamp affidabili, risultati di elaborazione e altro.

  2. Per file fino a pochi MB, leggi l'intero file in memoria e quindi elaboralo (file_get_contents () + explode () + foreach ())

  3. Per file più grandi leggere il file in blocchi (ovvero 1024 byte) ed elaborare + scrivere in tempo reale ogni blocco come lettura (attenzione all'ultima riga che non termina con \ n. Deve essere elaborata nel batch successivo)


1
"Ho visto i syscalls essere NOP (nessuna operazione) ..." quale kernel?
Massimo,

1
"La lettura di file di grandi dimensioni riga per riga non è fattibile per motivi di prestazioni. Suggerisco di utilizzare file_get_contents () per caricare l'intero file in memoria ..." Questo non ha senso. Posso dire: per motivi di prestazioni non leggere i file di grandi dimensioni in memoria ... Cosa fare dipende da molti altri fattori.
Massimo,

4

So che è vecchio, ma nel caso qualcuno lo incontri. IMHO il modo di procedere è così:

1) Apri il file originale (es. Original.txt) usando file_get_contents ('original.txt').

2) Apporta le tue modifiche / modifiche.

3) Utilizzare file_put_contents ('original.txt.tmp') e scriverlo in un file temporaneo original.txt.tmp.

4) Quindi spostare il file tmp nel file originale, sostituendo il file originale. Per questo usi rename ('original.txt.tmp', 'original.txt').

Vantaggi: mentre il file viene elaborato e scritto nel file non è bloccato e altri possono ancora leggere il vecchio contenuto. Almeno su scatole Linux / Unix la ridenominazione è un'operazione atomica. Eventuali interruzioni durante la scrittura del file non toccano il file originale. Solo una volta che il file è stato completamente scritto su disco viene spostato. Più interessante leggere su questo nei commenti a http://php.net/manual/en/function.rename.php

Modifica per indirizzare i commenti (anche per un commento):

/programming/7054844/is-rename-atomic contiene ulteriori riferimenti a ciò che potrebbe essere necessario fare se si opera su filesystem.

Sul blocco condiviso per la lettura non sono sicuro del motivo per cui sarebbe necessario in quanto in questa implementazione non è possibile scrivere direttamente sul file. Il gregge di PHP (che viene utilizzato per ottenere il blocco) è un po 'ma inaffidabile e può essere ignorato da altri processi. Ecco perché sto suggerendo di usare la ridenominazione.

Idealmente, il file di rinomina dovrebbe essere nominato in modo univoco per il processo che sta eseguendo la ridenominazione, in modo da assicurarsi che non 2 processi facciano la stessa cosa. Ma questo ovviamente non impedisce la modifica dello stesso file da parte di più di una persona contemporaneamente. Ma almeno il file verrà lasciato intatto (l'ultima modifica vince).

Passaggio 3) e 4) diventerebbero quindi:

$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

Esattamente quello che volevo proporre anche io. Ma vorrei anche acquisire un blocco condiviso durante la lettura per impedire il blocco dei dati.
d3L

Rinomina è un'operazione atomica sullo stesso disco, non su dischi diversi.
Xnoise

Per garantire davvero un nome univoco per il tempfile, puoi anche usare letempnam funzioni, che creano atomicamente un file e restituiscono il nome del file.
Matthijs Kooijman,

1

Nella documentazione di PHP per file_put_contents () puoi trovare nell'esempio # 2 l'uso di LOCK_EX , semplicemente:

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

Il LOCK_EX è una costante mediante un numero intero valore che può essere utilizzato su alcune funzioni di un bit .

Esiste anche una funzione specifica per controllare il blocco dei file: modo flock () .


Mentre questo è interessante e potrebbe essere utile in alcune situazioni, durante la lettura, la modifica e la riscrittura di un file, il blocco dovrebbe essere acquisito prima di leggerlo e mantenuto fino a quando non è stato completamente riscritto (altrimenti un altro processo potrebbe leggere una vecchia copia e cambiarlo al termine del processo). Non credo che questo possa essere realizzato con file_get/put_contents.
Jules,

0

Un problema che non hai menzionato è che devi anche stare attento alle condizioni di gara in cui due istanze del tuo script sono in esecuzione quasi nello stesso momento, ad esempio questo ordine di occorrenze:

  1. Istanza di script 1: legge il file
  2. Istanza di script 2: legge il file
  3. Istanza di script 1: scrive le modifiche al file
  4. Istanza di script 2: sovrascrive le modifiche della prima istanza di script in file con le proprie modifiche (poiché a questo punto la lettura è diventata obsoleta).

Pertanto, quando si aggiorna un file di grandi dimensioni, è necessario LOCK_EX quel file prima di leggerlo e non rilasciare il blocco fino a quando non sono state eseguite le scritture. In questo esempio credo che causerà un po 'di blocco della seconda istanza dello script mentre attende il suo turno per accedere al file, ma questo è meglio dei dati persi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.