Autor Zpráva
Numero1990
Profil
Zdravím,
mám následující problém. Na serveru mám nárazově stovky návštěv za minutu a potřebuji naprogramovat následující věc:

Kvůli tomu, že nejsem na moc výkonném serveru a nemůžu si dovolit platit za VPS, se musím obejít bez databáze (při takových návštěvách mi MySQL server po chvíli vrací - "MySQL server has gone away." - avšak všechny dotazy do DB mám optimalizované, už jsem to tu řešil). Potřebuji, aby se po zavolání skriptu otevřel soubor, přečetl se, lehce rozparsoval, pozměnil nějaké hodnoty a opět uložil. V nejlepším případě, aby se žádná data neztratila a ostatní instance skriptu čekaly, než doběhne ten předchozí.

Příklad:
V souboru budu mít posloupnost čísel. Úplně na začátku to bude "0 0 0 0 0". Když se zavolá skript, otevře se tento soubor, rozparsuje se a provede se jistý výpočet. Uloží se třeba "0 3 9 2 5". Potřeboval bych (pokud to je možné), aby pokud se spustí skript vícekrát, ostatní čekaly, než doběhne předchozí atp. Každé spuštění skriptu mi vygeneruje novou posloupnost z té předchozí a pokud se jednou zapíše něco špatně (výpočet posloupnosti je ovlivňován vnějšími vlivy (předané parametry, aj)), chyba se bude šířit lavinově a ovlivní mi všechny další výpočty. Je tohle možné v PHP nějak udělat?

Díky.
Alphard
Profil
Koukal jste třeba na http://doc.nette.org/cs/atomicity nebo jiné hotové třídy, které řeší atomicitu?
Numero1990
Profil
Našel jsem si tohle, jen si nejsem jist, jestli to jde použít bez Nette.

Zajímá mě použítí pro můj případ, pokud chci otevřít soubor a on je právě čten, vrátí mi fopen FALSE?
$file = fopen('safe://data.txt', 'r+');
Pokud ano, jak nejlépe vyřešit, aby skript čekal, než doběhne předchozí? Prostě v do.. while($file) to zkoušet?

Díky.
Majkl578
Profil
Numero1990:
jestli to jde použít bez Nette
Lze. Zrovna tato třída nemá žádné závislosti na dalších částech Nette.

Zajímá mě použítí pro můj případ, pokud chci otevřít soubor a on je právě čten, vrátí mi fopen FALSE?
FALSE by vracet neměl, měl by vracet normálně resource. Registroval jsi předtím protokol safe:// pomocí Nette\Utils\SafeStream::register()?
Numero1990
Profil
Proč mi tento kód funguje (pro zjednodušení jsem nezahrnul složitý výpočet)?
Zkoušel jsem fread() i fgets(), ani jedna mi nic nenačte.

index.php
<?php
$clock = microtime(TRUE);

require_once './SafeStream.php';
Nette\Utils\SafeStream::register();  

echo '<pre>';
$handler = fopen('safe://data.txt', 'r+');

//sleep(5);
$data = fgets($handler);
var_dump($data);
$numbers = explode(' ', $data);
$numbers[0] = ((int) $numbers[0]) + 1;
var_dump($numbers);
fwrite($handler, implode(' ', $numbers));
fclose($handler);

echo '</pre>';
echo '<div style="position: fixed; right: 10px; bottom: 10px;">'.((microtime(TRUE) - $clock) * 1000).' ms</div>';
?>

Výstup:
bool(false)
array(1) {
  [0]=>
  int(1)
}

data.txt
0 0 0 0 0



Tak problém je v módu 'r+', při 'r' vše funguje. Soubor data.txt se uzamkne a nedovolí mi ho číst (??? WTF). Jak to vyřešit?
Majkl578
Profil
[#5] Numero1990:
U r+ mi pomohlo přidání fseek($handler, 0) na 9. řádek (hned za fopen). Zdá se, že jde o bug (pozice se neresetuje ZDE na začátek souboru, což se u r očekává).
Numero1990
Profil
$clock = microtime(TRUE);

require_once './SafeStream.php';
Nette\Utils\SafeStream::register();    

echo '<pre>';
$handler = fopen('safe://data.txt', 'r+');
fseek($handler, 0);
sleep(5);
$data = fread($handler, 8192);

var_dump($data);
$numbers = explode(' ', $data);
$numbers[0] = ((int) $numbers[0]) + 1;
var_dump(implode(' ', $numbers));
fseek($handler, 0);
fwrite($handler, implode(' ', $numbers));
fclose($handler);

echo '</pre>';
echo '<div style="position: fixed; right: 10px; bottom: 10px;">'.((microtime(TRUE) - $clock) * 1000).' ms</div>';

Takhle bych si představoval ten zápis, ale nefunguje to tak, jak bych potřeboval.

Spustím-li 2 procesy, tak se stane toto:
1) 1. proces si otevře soubor a načte "0 0 0 0 0" a soubor uzamkne.
2) 2. proces si otevře soubor a načte "0 0 0 0 0" a soubor uzamkne. (!)
3) 1. proces chce uložit "1 0 0 0 0", ale selže:
Warning: rename(E:\development\www\sandbox\fread-fwrite-atomicity\data.txt~~0.53670409946458.tmp,E:\development\www\sandbox\fread-fwrite-atomicity\data.txt) [function.rename]: Přístup byl odepřen. (code: 5) in E:\development\www\sandbox\fread-fwrite-atomicity\SafeStream.php on line 194
4) 2. proces chce uložit "1 0 0 0 0" a jemu se to povede. (!)

Já bych to potřeboval právě že opačně, aby další procesy čekaly, dokud neskončí ten předchozí.
Numero1990
Profil
Mezi voláním file_exist() a následném vytváření může jiný proces soubor vytvořit, proto mě napadlo ho vytvořit a zkontrolovat, jestli funkce nevrátila error.

Tak mě napadlo toto:
1) Jakmile se spustí skript, zavolá se mkdir('lock') (podle testu je vytvoření a mazání složky rychlejší než vytvořit soubor, zavřít ho a smazat).
2) Zkontroluji poslední error přes error_get_last() - pokud složka už existovala, uspím se přes usleep na náhodně vygenerovaný čas (v rozmezí 10 - 50 ms) - tím zajistím, že nebude tolik docházet ke kolizím. Tohle bude v cyklu.
3) Pokud k erroru nedošlo, znamená to, že můj proces může soubor otevřít. Otevřu ho, přečtu, zparsuju, provedu, co potřebuji, uložím a složku smažu.

Myslíte, že by to mohlo fungovat?

Edit:// Tak nakonec jsem musel použít soubory, se složkami byl problém, že se někdy nesmazaly, a tak tam lock zůstal na furt. Výsledek je takový a funguje to docela dobře.

<?php
$clock = microtime(TRUE);

require_once './SafeStream.php';
Nette\Utils\SafeStream::register();

while(TRUE)
{    
    $handler2 = fopen('safe://lock', 'x');
    
    if($handler2 !== FALSE)
    {
        sleep(2);
        
        echo '<pre>';
        
        $handler = fopen('safe://data.txt', 'r+');
        fseek($handler, 0);
        $data = fread($handler, 8192);
        
        var_dump($data);
        $numbers = explode(' ', $data);
        $index = mt_rand(0, sizeof($numbers) - 1);
        $numbers[$index] = ((int) $numbers[$index]) + 1;
        var_dump(implode(' ', $numbers));
        fseek($handler, 0);
        fwrite($handler, implode(' ', $numbers));
        fclose($handler);
        
        echo '</pre>';
        
        fclose($handler2);
        unlink('lock');
        break;
    }
    else
        usleep(mt_rand(10000, 50000));
}

echo '<div style="position: fixed; right: 10px; bottom: 10px;">'.((microtime(TRUE) - $clock) * 1000).' ms</div>';
?>

Vaše odpověď

Mohlo by se hodit


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm: