Непонятное для меня поведение кода PHP


Dram
179

Есть скрипт парсера, структура примерно такая

$level1_url = $conn->query('SELECT * FROM `table`  WHERE `sid` = 0 limit 20');
$result = $level1_url->fetchAll(PDO::FETCH_ASSOC);


if (!empty($result)){

    foreach ($result as $value) {
        $sqlSID = "UPDATE `table` SET `sid` = '2'  where id = '{$value['id']}';";
        $conn->query($sqlSID);
    }


    $rez_count = count($result);
    for ($k=0; $k < $rez_count; $k++) {
            //логика парсинга 

   тут в самом низу запрос на UPDATE `table` и там же SET `sid` = '1'
  }
}

Лимит 20 подобран примерно чтобы успеть с гарантией отработать за минуту. Скрипт запускается по крону раз в минуту. Но чтобы еще себя обезопасить от дублей я всем выбранным записям ставлю SET `sid` = ‘2’ чтобы если вдруг скрипт все же будет выполняться более 1 минуты следующий заход по крону не запросил опять эти же данные.

В итоге я получаю более 20% данных в таблице в которых пустые поля и `sid` = ‘2’ и я не могу объяснить как такое может произойти.

Т.е. скрипт начал выполняться, запись получила  `sid` = ‘2’   но до конца до строки

тут в самом низу запрос на UPDATE `table` и там же SET `sid` = '1'

почему-то работа не дошла.

Причем если заново взять и тем записям у которых в базе остался  `sid` = ‘2’ поставить  `sid` = ‘0’ и заново по ним пройтись — то опять 80-90% из них обработает нормально. Т.е. проблема не в конкретных записях, косяк где-то в логике, но я не могу понять где?


Алеандр

В нормальных условиях ставят файл-локер, который проверяет, отработал ли предыдущий скрипт и, если отработал и файла нет, то запускается, а если он есть — то не запускается. А эти вот «гарантированно вложиться в минуту» — никакой гарантии не дают. Зачем себя так мучать то?


Dram

Алеандр #:
В нормальных условиях ставят файл-локер, который проверяет, отработал ли предыдущий скрипт и, если отработал и файла нет, то запускается, а если он есть — то не запускается. А эти вот «гарантированно вложиться в минуту» — никакой гарантии не дают. Зачем себя так мучать то?

А можно подробнее про локер, как это делается? А то я говнокодер новичок, что пришло первое в голову то и реализовал


Алеандр

Dram #:

А можно подробнее про локер, как это делается? А то я говнокодер новичок, что пришло первое в голову то и реализовал

Ну уж точно не новичок )

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

Скрипт — это маркер, что предыдущая копия этого скрипта еще не отработала, т.е. этот запуск требуется пропустить.

Есть файл? -> Нет -> Создать файл -> Работа скрипта -> Удалить файл
Есть файл? -> Да -> Выход


lutskboy

зачем пачками парсить?

парсим по одному разу.

скрипт отвалился. а где у вас set_time_limit, ignore_user_abort?

файл локер нужно от крвого крона кторый может запустится не раз как вы предположили а 2 или даже 3


lealhost

Локер подойдет да, правда иногда бывают неточности с одновременными блокировками, но это не про ваш случай, с такой частотой вызовов вероятность мала.

Помню писал скрипт, к которому 20-30 человек обращаются практически одновременно, так как скрипт не в режиме демона, то на каждый запрос по процессу.  Первые несколько запросов стабильно выполнялись одновременно. Решил проблему созданием локера-переменной в Memcached, отрабатывает в 100% случаев правильно (веду журналы). Будь это поточный демон, конечно бы использовал мьютексы.

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

Напишите простой класс логгера, который просто будет записывать время и описывать событие, записывать значение переменных, которые у вас теряются, выделите самые важные места в программе и добавьте туда журналирование. Пожалуй, это самое первое с чего нужно начинать любую программу. Иначе можно годы гадать на кофейной гуще и приделывать костыли.


Solmyr

Алеандр #:
При старте скрипта проверяется, есть ли в наличие файл-локер, любое имя, в любом удобном месте. Просто пустой файл, главное — это его наличие или отсутствие. Если он уже есть, то запуск скрипта не производится, банальный die().

Хороший способ положить сервис совсем 🙂

На самом деле надо в файл-локер писать pid запустившего его процесса.

При новом запуске считываем из локера pid   и проверяем существует ли этот процесс. Если да, то умираем. А если нет — то запускаемся и перетираем локер.


ArbNet

Алеандр #:
файл-локер

Вот это реально велосипед 😀 я когда в 2000 начинал сайты делать и то лучше способ был. А сейчас есть паттерн singleton один экземпляр класса и не надо никакого файла-локера 😁


LEOnidUKG

Solmyr #:
Хороший способ положить сервис совсем 🙂

На самом деле надо в файл-локер писать pid запустившего его процесса.

При новом запуске считываем из локера pid   и проверяем существует ли этот процесс. Если да, то умираем. А если нет — то запускаемся и перетираем локер.

Нельзя юзать PID, об этом написано в мануале:

https://www.php.net/manual/ru/function.getmypid.php

PHP: getmypid — Manual

  • www.php.net
Внимание Идентификаторы процессов в системе не уникальны, поэтому являются слабым источником энтропии. Мы не рекомендуем полагаться на pid в контекстах, влияющих на безопасность.


LEOnidUKG

ArbNet #:

Вот это реально велосипед 😀 я когда в 2000 начинал сайты делать и то лучше способ был. А сейчас есть паттерн singleton один экземпляр класса и не надо никакого файла-локера 😁

Вместо хвастовства, ТС-у бы ответили на его вопрос.


Dreammaker

Если проблема именно в блокировке и в том, что скрипт запускается в нескольких экземплярах из-за крона, то поставьте на сервере flock 

и запускайте скрипты примерно так:

*/1 * * * * /usr/bin/flock -n /tmp/ert.textphp.lockfile /usr/bin/php /path/to/file/test.php

Flock будет сам отпускать локфайл, когда скрипт завершится. Это несколько лучше, чем внутренний для скрипта обработчик локеров.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *