Статьи,обзоры,справочники по PHP

       

Проверка рабоспособности flock на практике.


Для чего это написано.

Представьте, у вас есть нагруженная система (много посетителей), которая хранит какую-то информацию в файлах. Очевидно, любые скрипты по записи и чтении файлов могут работать параллельно и мешать друг-другу. Еще очевидно, что в таких операциях нужно использовать блокировку файлов, но даже в этом может быть проблема. Маленькая и незаметная, она проявляется только при большой нагрузке на сервер.

Сделаем 2 скрипта. Первый - будет писать в файл случайное число (берется из rand) на первой строке файла и md5 проверку этого числа строкой ниже. Прежде, чем записать файл скрипт его обрезает для записи - режим w. Второй - читает обе строки из файла и следит, что md5 соответствует числу из первой строки, т.к. как бы проверяет целостность файла.

В начале простой пример. Перезапись одного файла работает без проблем:

> /tmp/11qq")); // заблокировали flock($f,2);

// считали число $r=intval(fgets($f,100));

// проверка глюков - число не должно быть меньше 1 000 000 ! if ($r> /tmp/11qq");

// указатель на начало + запись обратно увеличенного числа fseek($f,0); fputs($f,($r+1)." ");

fclose($f);

?>

А вот чтение без блокировки с параллельной записью нарушает целостность данных. Делаем 2 скрипта - первый будет постоянно писать в файл, второй постоянно читать. Запись идет с блокировкой, чтение - без. Для теста запустите 2 утилиты, чтобы параллельно слать запросы на оба скрипта.

запись:

> /tmp/11qq")); flock($f,2);



function make_seed() { list($usec,$sec) = explode(" ", microtime()); return ((float)$sec+(float)$usec) * 100000; } mt_srand(make_seed());

$s = mt_rand(); $s=$s." ".microtime(); $md=md5($s);

fputs($f,"$s\n$md\n");

fclose($f);

?>

чтение

> /tmp/11qq")); $s=trim(fgets($f,100)); $md=trim(fgets($f,100)); if ($md!=md5($s)) system("echo error_md5 >> /tmp/11qq"); fclose($f); ?>

Каково же было мое удивление, когда от добавления flock в скрипт чтения ничего не изменилось...


> /tmp/11qq")); flock($f,2); $s=trim(fgets($f,100)); $md=trim(fgets($f,100));

if ($md!=md5($s)) { system("echo error_md5 >> /tmp/11qq"); echo "md5 error"; $ff=fopen ("/tmp/11log.txt","a+"); flock($ff,2); fputs($ff,time()." ".filesize("flock2_test.txt")." [$s|$md]\n"); fclose($ff); }

fclose($f); ?>

Пример 0.1% вызовов скрипта чтения заканчивался провалом. Во всех случаях мне скрипт читал пустой файл с 0-вой длиной.

Потом я проделал 2 хитрых фичи:

заменил
$f=fopen("flock2_test.txt","r+") or die(system("echo fopen_write >> /tmp/11qq")); flock($f,2);
на
flock($f=fopen("flock2_test.txt","r+"),2);
с надеждой, что теперь операция открытия + блокирования не будет разрываться. Это не помогло. Еще я придумал, делать usleep(10) после открытия и до блокировки в скрипте чтения... Типа, открыли файл и подвесили немного процесс с надеждой, что другие пишущие скрипты сумеют за это время заблокировать файл. Это тоже не помогло. Да, еще писал по 2 flock'f:
flock($f,1); flock($f,2);
на всякий случай :) И это не спасло.

Третья попытка удалась и проблема с активным чтением и записью решилась. Просто надо было в скрипте записи открывать файл не на обнуление (w), а только чтение+запись (r+). Одним словом проблема решилась заменой скрипта записи:

// открыли файл на чтение+запись $f=fopen("flock2_test.txt","r+") or die(system("echo fopen_write >> /tmp/11qq")); // заблокировали flock($f,2); // обнулили до 0 байт (как бы стерли) ftruncate($f,0);

Содержание раздела