Domanda
Parte della mia ultima webapp deve scrivere per archiviare un discreto importo come parte della sua registrazione. Un problema che ho notato è che se ci sono alcuni utenti simultanei, le scritture possono si sovrascrivono a vicenda (invece di aggiungere un file). Presumo che ciò sia dovuto al fatto che il file di destinazione può essere aperto in più punti contemporaneamente.
flock (...)
è in genere eccezionale ma non sembra funzionare su NFS ... Il che è un grosso problema per me poiché il server di produzione utilizza un array NFS.
La cosa più vicina che ho visto a una soluzione reale riguarda il tentativo di creare una directory di blocco e attendere fino a quando non può essere creata. Dire che manca di eleganza è un eufemismo dell'anno, forse del decennio.
Qualche idea migliore?
Modifica: dovrei aggiungere che non ho root sul server e fare l'archiviazione in un altro modo non è davvero possibile in qualsiasi momento presto, non ultimo entro la mia scadenza.
Soluzione
Un altro trucco sporco sarebbe flock ()
a " local " e apri / scrivi sul file NFS solo se tieni il blocco sul file locale.
Modifica: dalla pagina flock ()
:
flock () non funzionerà su NFS e molti altri file system collegati in rete. Dai un'occhiata la documentazione del sistema operativo per maggiori dettagli.
Modifica 2:
Ovviamente si usa sempre il database per sincronizzare l'accesso (suppongo che la tua app usi un db). Questo sarebbe un grande successo se stai registrando molto.
Se è solo per la registrazione, hai davvero bisogno di un file di registro centralizzato? Potresti accedere localmente (e anche combinare i log quando ruotano alla fine della giornata, se necessario)?
Altri suggerimenti
Le operazioni di directory sono NON atomiche in NFSv2 e NFSv3 (fare riferimento al libro "NFS Illustrated" di Brent Callaghan, ISBN 0-201-32570-5; Brent è un veterano di NFS a Sun).
NFSv2 ha due operazioni atomiche:
- link simbolico
- rinomina
Con NFSv3 anche la chiamata di creazione è atomica.
Sapendo questo, è possibile implementare spin-lock per file e directory (in shell, non PHP):
blocca dir corrente:
while ! ln -s . lock; do :; done
blocca un file:
while ! ln -s ${f} ${f}.lock; do :; done
sblocco (presupposto, il processo in esecuzione ha davvero acquisito il blocco):
sblocca dir corrente:
mv lock deleteme && rm deleteme
sblocca un file:
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Anche Rimuovi non è atomico, quindi prima la rinomina (che è atomico) e quindi rimuovi.
Per le chiamate symlink e rename, entrambi i nomi dei file devono risiedere sul file stesso filesystem. La mia proposta: usa solo nomi di file semplici e inserisci file e blocco nella stessa directory.
Un approccio potrebbe essere quello di impostare un'istanza Memcache , condivisa tra ciascuno dei server virtuali . Puoi ape flock ()
inserendo una voce del nome file nella cache quando avvii le operazioni del file locale e cancellandolo al termine.
Ogni server può accedere a questo pool prima di un'operazione di file e vedere se questo "blocca" è presente, ad esempio
// Check for lock, using $filename as key
$lock = $memcache->get($filename);
if(!$lock) {
// Set lock in memcache for $filename
$memcache->set($filename, 1);
// Do file operations...
// Blow away "lock"
$memcache->delete($filename);
}
Non è la soluzione più elegante, ma dovrebbe consentirti di controllare i blocchi da tutti i server nella tua configurazione con relativa facilità.
Puoi anche usare dio_fcntl () per bloccare i file su volumi NFS. Richiede il pacchetto dio, che per impostazione predefinita potrebbe non far parte dell'installazione php.
Anche se non è possibile floccare () i file su NFS e I / O possono essere asincroni, le operazioni di directory su NFS sono atomiche. Ciò significa che in qualsiasi momento, una directory esiste o non esiste.
Per implementare la tua funzione di blocco NFS, controlla o crea una directory quando vuoi che sia bloccata e rimuovila quando hai finito.
Sfortunatamente, probabilmente non è compatibile con qualsiasi altra applicazione che non hai scritto tu.
Dovresti semplicemente usare memcache add ed evitare una race condition.
if ($memcache->add($filename, 1, 1))
{
$memcache->delete($filename);
}