Comments 77
А чем обусловлен выбор именно такого метода.
Чем плох мемкэш? Он рассматривался как вариант?
Чем плох мемкэш? Он рассматривался как вариант?
Да, рассматривался, мемкеш — отличный выбор, если понимать его плюсы и минусы по сравнению с данными способами: если мемкеш на отдельном сервере, а все скрипты крутятся на другом, то это лишние сетевые издержки.
Здесь я просто хотел показать альтернативные мемкешу варианты, т.к. на хабре не нашёл упоминаний таких решений.
Здесь я просто хотел показать альтернативные мемкешу варианты, т.к. на хабре не нашёл упоминаний таких решений.
А если нужный сервис крутится на нескольких машинах?
Ведь для отказоустойчивости нужно минимум на двух-трёх машинах запускаться.
Хотя конечно зависит от того, какие задачи решаются, и нужна ли глобальная блокировка.
Можно намекнуть, для чего примерно используется это решение?
Ведь для отказоустойчивости нужно минимум на двух-трёх машинах запускаться.
Хотя конечно зависит от того, какие задачи решаются, и нужна ли глобальная блокировка.
Можно намекнуть, для чего примерно используется это решение?
Конкретно это решение изменено и завуалировано, чтобы никто не заподозрил меня в плохих делах =)
Если на нескольких машинах, то либо использовать тяжеловесный ZooKeeper (недавно была статья про него тут), либо же — синхронизироваться через мастер-БД (в постгресе есть pg_try_advisory_lock, который подойдет для большинства случаев). Можно и через мемкэшевый cas, да, но только мемкэш может прилечь или очиститься внезапно (мало ли, какие бывают форс-мажоры), а если приляжет мастер-база, то уж будет точно не до блокировок. :)
Как известно, APC сейчас стоит почти на всех продакшн-машинах с пхп (будь шаред хостинг, или свой сервер) просто для кеширования опкода, так что нет смысла ставить мемкешд (тем более что вы проиграете в производительности) до тех пор, пока кеш-сервера не надо шардить. Да и там встанет вопрос — почему бы не редись, который по мне так на голову лучше
Как мне кажется, сложность с мемкешом в том, что если у нас есть несколько бекендов, то:
— придется вводить префиксы для серверов (каждому бекенду придется иметь отдельный конфиг)
— появятся затраты на сетевое взаимодействие
— придется вводить префиксы для серверов (каждому бекенду придется иметь отдельный конфиг)
— появятся затраты на сетевое взаимодействие
А чем он тут хорош? Расточительно по памяти — мемкешд использует корзины, да и при нехватке памяти корзины буду молча вываливаться из кеша.
В данном случае, я бы просто проверил на is_writeable, подавил бы ошибку и не заморачивался.
В данном случае, я бы просто проверил на is_writeable, подавил бы ошибку и не заморачивался.
на мемкеше ситуация когда куча потоков пытаются одновременно просчитать и сохранить тяжелые данные в хайлоаде тоже более чем актуальна.
Обходится при помощи php_memcached и cas, но читаемость и предсказуемость кода резко падает.
Обходится при помощи php_memcached и cas, но читаемость и предсказуемость кода резко падает.
ПС: Мне кажется, на хабре пора вводить разделение статей на уровень подготовки читателя)
Не у Вас одного в голове такие мысли появляются.
Причем уровни:
-Чайник
-Джуниор
-Интермедиэйт
-Сениор
как минимум, ибо статьи 1-3 категори вообще не интересно читать :)
Причем уровни:
-Чайник
-Джуниор
-Интермедиэйт
-Сениор
как минимум, ибо статьи 1-3 категори вообще не интересно читать :)
Согласен. Для комфортного чтения статьи нужны знания в следующих обсластях.
Пока можно просто ставить теги, чтоб не заходить в статьи, которые не интересны, но это на совести авторов.
*использовать
Не все удосуживаются почитать документацию и поэтому не знают об этой прекрасной функции.
Кстати возможности блокировки можно использовать в file_put_contents указав флаг.
Например:
file_put_contents($filename, $data, FILE_APPEND | LOCK_EX);
Кстати возможности блокировки можно использовать в file_put_contents указав флаг.
Например:
file_put_contents($filename, $data, FILE_APPEND | LOCK_EX);
Жаль, что у file_get_contents такого флага нет.
Перед использованием flock() необходимо получить дескриптор файла, например с помощью fopen() — это обращение к диску. Зачем? У абстрактного race condition =) (пример из статьи не очень подходит) в критической части операций с файлами может не быть вообще, и добавлять сюда файловую систему (да, операционка много что закеширует, но тем не менее) — лишняя трата ресурсов.
Когда-то в мануале было написано, что FILE_APPEND и LOCK_EX взаимоисключают друг-друга, т.к FILE_APPEND сама по себе атомарная операция. Теперь этот текст почему то убрали: ru2.php.net/manual/en/function.file-put-contents.php
Тоже тестил движок, на 1000 одновременных запросов (с помощью ab) и c кэшем начались твориться чудеса, пришлось выкручиваться.
Кусочек кода из метода записи данных в кеш:
return false потому, как кеш — необязательный, и чтобы долго не ждать — можно и пропустить запись.
Работает стабильно, с помощью штатных функций, не нужно дополнительных классов.
Кусочек кода из метода записи данных в кеш:
$cache_size_file = fopen(CACHE.'/size', 'c+b');
$time = microtime(true);
while (!flock($cache_size_file, LOCK_EX)) {
if ($time < microtime() - .5) {
fclose($cache_size_file);
return false;
}
time_nanosleep(0, 1000000);
}
unset($time);
/**
Some work here
*/
flock($cache_size_file, LOCK_UN);
fclose($cache_size_file);
return false потому, как кеш — необязательный, и чтобы долго не ждать — можно и пропустить запись.
Работает стабильно, с помощью штатных функций, не нужно дополнительных классов.
У меня к Вам вопрос: habrahabr.ru/post/148527/#comment_5014784
Есть два потока 1 и 2.
1: file_exists($filename) — true
2: file_exists($filename) — true
1: !$this->validate() -true
2: !$this->validate() -true
1: $this->canUseApc() -true
2: $this->canUseApc() -true
2: apc_add('some_key', 1)
2: unlink($filename);
2: apc_delete('some_key');
1: apc_add('some_key', 1)
1: unlink($filename); — NO SUCH FILE!
1: file_exists($filename) — true
2: file_exists($filename) — true
1: !$this->validate() — true
2: !$this->validate() — true
1: $sem = sem_get(1);
1: sem_acquire($sem) && file_exists($filename) — true
1: unlink($filename);
1: sem_remove($sem);
2: $sem = sem_get(1);
2: sem_acquire($sem)
2: file_exists($filename) — false!
2: return false;
3: зависнет на веки т.к. семафор так и не был сброшен
php.net/manual/ru/function.sem-get.php
resource sem_get ( int $key [, int $max_acquire = 1 [, int $perm = 0666 [, int $auto_release = 1 ]]] )
sem_get() returns an id that can be used to access the System V semaphore with the given key.
A second call to sem_get() for the same key will return a different semaphore identifier, but both identifiers access the same underlying semaphore.
OK :)
Не знаю насколько вероятно что подобные сценарии когда-нибудь возникнут на реально работающей системе — но, насколько я знаю в случае с многопоточным программированием если что-то может произойти — оно обязательно случиться на продакшне :)
function canUseApc() {
return extension_loaded('apc') && ini_get('apc.enabled') && php_sapi_name() !== 'cli';
}
function getFlagFromFile($filename) {
if (file_exists($filename)) {
if (!$this->validate()) {
if ($this->canUseApc() && apc_add('some_key', 1)) { //***
unlink($filename);
apc_delete('some_key'); //***
}
return false;
}
else {
return file_get_contents($filename);
}
}
return false;
}
1: file_exists($filename) — true
2: file_exists($filename) — true
1: !$this->validate() -true
2: !$this->validate() -true
1: $this->canUseApc() -true
2: $this->canUseApc() -true
2: apc_add('some_key', 1)
2: unlink($filename);
2: apc_delete('some_key');
1: apc_add('some_key', 1)
1: unlink($filename); — NO SUCH FILE!
function getFlagFromFile($filename) {
if (file_exists($filename)) {
if (!$this->validate()) {
$sem = sem_get(1); //***
if (sem_acquire($sem) && file_exists($filename)) { //***
unlink($filename);
sem_remove($sem); //***
}
return false;
}
else {
return file_get_contents($filename);
}
}
return false;
}
1: file_exists($filename) — true
2: file_exists($filename) — true
1: !$this->validate() — true
2: !$this->validate() — true
1: $sem = sem_get(1);
1: sem_acquire($sem) && file_exists($filename) — true
1: unlink($filename);
1: sem_remove($sem);
2: $sem = sem_get(1);
2: sem_acquire($sem)
2: file_exists($filename) — false!
2: return false;
3: зависнет на веки т.к. семафор так и не был сброшен
php.net/manual/ru/function.sem-get.php
resource sem_get ( int $key [, int $max_acquire = 1 [, int $perm = 0666 [, int $auto_release = 1 ]]] )
sem_get() returns an id that can be used to access the System V semaphore with the given key.
A second call to sem_get() for the same key will return a different semaphore identifier, but both identifiers access the same underlying semaphore.
function getFlagFromFile($filename) {
if (file_exists($filename)) {
if (!$this->validate()) {
if ($race = RaceCondition::prevent('FLAG_'.$filename)) { //***
unlink($filename);
$race->release(); //***
}
return false;
}
else {
return file_get_contents($filename);
}
}
return false;
}
OK :)
Не знаю насколько вероятно что подобные сценарии когда-нибудь возникнут на реально работающей системе — но, насколько я знаю в случае с многопоточным программированием если что-то может произойти — оно обязательно случиться на продакшне :)
Кстати говоря, если в случае устаревания файла мы его всёравно удаляем, то разве @unlink($filename) не лучшее решение?) в том и другом случае возвращаем false. Нам впринципе не важно был ли файл удален нами или кто-то до нас уже заметил что он устарел и удалил его…
Лучшее, но, подозреваю, смысл статьи показать как поступать в случаях, когда атомарности не добиться.
Нет, только не собака, пожалуйста, не надо!!!
@unlink($filename) вернёт false например ещё тогда, когда файл не удалён, а просто прав на удаление не хватило ну и ещё кучу других забавных ситуаций можно придумать с собакой.
@unlink($filename) вернёт false например ещё тогда, когда файл не удалён, а просто прав на удаление не хватило ну и ещё кучу других забавных ситуаций можно придумать с собакой.
Первый пример странен в этом месте:
1: $this->canUseApc() -true
2: $this->canUseApc() -true
2: apc_add('some_key', 1)
2: unlink($filename);
2: apc_delete('some_key');
1: apc_add('some_key', 1)
скорее вот так:
1: $this->canUseApc() -true
2: $this->canUseApc() -true
2: apc_add('some_key', 1) -true
1: apc_add('some_key', 1) -false
2: unlink($filename);
2: apc_delete('some_key');
1: return;
а второй — замечание верное, sem_remove($sem); надо вынести из условия
1: $this->canUseApc() -true
2: $this->canUseApc() -true
2: apc_add('some_key', 1)
2: unlink($filename);
2: apc_delete('some_key');
1: apc_add('some_key', 1)
скорее вот так:
1: $this->canUseApc() -true
2: $this->canUseApc() -true
2: apc_add('some_key', 1) -true
1: apc_add('some_key', 1) -false
2: unlink($filename);
2: apc_delete('some_key');
1: return;
а второй — замечание верное, sem_remove($sem); надо вынести из условия
Поправил в статье.
А что мешает второму потоку получить лок, удалить файл, освободить лок до того как первый поток получит шанс взять лок? :) Я согласен что это маловероятно — более того, если бы мы обсуждали код на С то в некоторых случаях (архитектура, количество ядер… ) это было бы даже невозможно — но мы говорим о php :) Какие гарантии дает php о последовательности выполнений инструкций в нескольких потоках? Тут можно сказать что это невероятно, но в таком случае тем труднее будет потом отловить возникшую багу, если она таки случиться, например, 2 раза… за два месяца :) и оба раза под носом у заказчика…
«Зависнет на веки»? OMG :)
Тело Вашего цикла никогда не будет выполнено, разве нет?
while (!flock($cache_size_file, LOCK_EX)) {
if ($time < microtime() - .5) {
fclose($cache_size_file);
return false;
}
time_nanosleep(0, 1000000);
}
Почему бы не отдать чистку кеша третьей стороне? Допустим пусть крон стартует регулярно и чистит устарелые данные. Сами же скрипты будут заниматься своим делом и брать всегда данные из кеша, если он есть и не заниматься проверками и удалением.
Лучше не крон — очередь.
Я бы вообще вопрос ребром поставил: зачем чистить кеш? Не лучше ли использовать кеш с необходимым автовытеснением.
Может, я чего-то не понимаю, но почему не добавить проверку на существование файла сразу перед удалением? А для надежности, можна после удаления еще файловый кеш почистить, как тут в первом примере
А зачем вам кешировать данные в файл? Может лучше сразу в APC?
Боже, пошли заказчика, который после таких писателей в отчаении обратится и заплатит и так не дешовый рейт * 3, лишь бы исправить проблемы и вернуть проект к жизни.
Я даже не хочу пояснять в чем тут проблема, ибо ответы очевидны — дам лишь намёк: у вас поток в 2-3 тысячи запросов в секунду и тут кеш протухает…
Я даже не хочу пояснять в чем тут проблема, ибо ответы очевидны — дам лишь намёк: у вас поток в 2-3 тысячи запросов в секунду и тут кеш протухает…
Вы можете меня бить палками, но на более-менее нагруженных проектах кешем должен заниматься отдельный скрипт по крону и только. Или подобные варианты как отдельно висящий кеш демон.
Уточняю: валидацией кеша занимается только демон. Если кеш-страница отсутствует, то лочится кеш-страница до момента окончания её генерации (все скрипты ожидают её генерации или таймаут). А вот генерацией может заниматься первый поток, который наткнулся на отсутсвие.
Хотя, вариант валидировать в скрипте после отдачи контента вполне годный. Но тоже не удаление, а лишь установка флага невалидности.
Да, @ зло, но не проще ли было использовать @unlink(); в данном конкретном случае?
Объективно — зачем городить здесь решение «проблемы» с race condition в данном случае?
Объективно — зачем городить здесь решение «проблемы» с race condition в данном случае?
а почему просто не обернуть удаление файла try catch? И в catch записывать, например в лог-файл или БД ошибку и потом кроном проверять.
Ведь если ошибка возникает из-за того, что файл был удален в «соседнем потоке», то такая ошибка подавится.
А если ошибка была вызвана из-за другой причины, то такая ошибка так же вылетит и в коде из топика.
Ведь если ошибка возникает из-за того, что файл был удален в «соседнем потоке», то такая ошибка подавится.
А если ошибка была вызвана из-за другой причины, то такая ошибка так же вылетит и в коде из топика.
А еще файл можно помечать как «удаленный», эту информацию хранить в БД и кроном потом чистить.
В качестве первого аргумента sem_get можно передавать любое число? Это не вызовет коллизий с другими процессами в системе?
Sign up to leave a comment.
Боремся с race condition в PHP