2015-03-27 5 views
3

У меня есть база данных продуктов, которая когда-либо синхронизируется с данными о продукте.Длинный скрипт PHP работает несколько раз

Процесс очень ясно:

  • Получить все продукты из базы данных по запросу
  • Loop через все продукты, а также получить и XML с другого сервера, product_id
  • данных Обновление от XML
  • Зарегистрировать изменения в файле.

Если я запрашиваю небольшое количество предметов, но ограничиваю их, например, 500 случайными продуктами, все идет хорошо. Но когда я запрашиваю все продукты, мой сценарий SOMETIMES переходит на fritz и начинает цикл несколько раз. Через несколько часов я все еще вижу, что мой файл журнала растет и добавляются продукты.

Я проверил все, что я мог придумать, например:

  • переменные не используются дважды, не переписывая друг друга
  • называют ли функция сама
  • Случается ли с небольшим количеством продуктов тоже: нет.
  • Сценарий вызывается с помощью cronjob, это настройки в порядке. (Да)

Причина, по которой это особенно странно, заключается в том, что она иногда идет вправо, а иногда и нет. Может ли это быть проблемой памяти?

EDIT wget -q -O /dev/null http://example.eu/xxxxx/cron.php?operation=sync его в Webmin называется на определенный час и минуту

код сотни строк длинных ...

Благодаря

+0

Возможно ли, что вы один и тот же продукт несколько раз? Это может произойти, если ваш запрос довольно большой с несколькими объединениями, которые могут привести к множественному набору результатов? – insanebits

+1

Не могли бы вы показать нам свой код? Это может быть условие в ваших методах. –

+0

Я думаю, нам нужно будет увидеть некоторые из ваших скриптов - «cronjob» и скрипт, который он запускает, чтобы получить большую часть дескриптора этого. –

ответ

3

Я сам решил проблему. Спасибо за все отклики!

Мой MySQL приурочен, в этом была проблема. Как только я добавил:

ini_set('mysql.connect_timeout', 14400); 
    ini_set('default_socket_timeout', 14400); 

к моему сценарию проблема остановлена. Я действительно надеюсь, что это поможет кому-то. Болся все блокирующие ответы, потому что они были очень полезны!

+0

Это не объясняет вашу проблему «множественного» цикла ... – ROunofF

+0

Я знаю, это странно, но я тестировал его 10 раз. Без этого он начинает зацикливаться, когда я кормлю его более чем 2500 продуктами, с этими правилами все идет гладко, как тюлень. –

+0

мне очень помог. : D –

6

У вас есть:

  • max_execution_time отключены , Ваш скрипт не закончится до тех пор, пока процесс не будет завершен до тех пор, пока это необходимо.
  • memory_limit отключен. Нет ограничений на количество данных, хранящихся в памяти.

500 записей были заполнены без проблем. Это указывает на то, что скрипты завершают свой процесс до следующей итерации cronjob. Например, если ваш cron запускается каждый час, 500 записей обрабатываются менее чем за час.

Если у вас есть cronjob, который будет обрабатывать большое количество записей, тогда рассмотрите возможность добавления механизма блокировки в процесс. Только разрешите запуск сценария один раз и начните снова, когда предыдущий процесс будет завершен.

Вы можете создать блокировку скрипта как часть скрипта оболочки перед выполнением вашего php-скрипта. Или, если у вас нет доступа к вашему серверу, вы можете использовать блокировку базы данных в скрипте php, что-то вроде этого.

class ProductCronJob 
{ 
    protected $lockValue; 

    public function run() 
    { 
     // Obtain a lock 
     if ($this->obtainLock()) { 
      // Run your script if you have valid lock 
      $this->syncProducts(); 

      // Release the lock on complete 
      $this->releaseLock(); 
     } 
    } 

    protected function syncProducts() 
    { 
     // your long running script 
    } 

    protected function obtainLock() 
    { 
     $time = new \DateTime; 
     $timestamp = $time->getTimestamp(); 
     $this->lockValue = $timestamp . '_syncProducts'; 

     $db = JFactory::getDbo(); 

     $lock = [ 
      'lock'   => $this->lockValue, 
      'timemodified' => $timestamp 
     ]; 
     // lock = '0' indicate that the cronjob is not active. 
     // Update #__cronlock set lock = '', timemodified = '' where name = 'syncProducts' and lock = '0' 
//  $result = $db->updateObject('#__cronlock', $lock, 'id'); 

//  $lock = SELECT * FROM #__cronlock where name = 'syncProducts'; 

     if ($lock !== false && (string)$lock !== (string)$this->lockValue) { 
      // Currently there is an active process - can't start a new one 

      return false; 

      // You can return false as above or add extra logic as below 

      // Check the current lock age - how long its been running for 
//   $diff = $timestamp - $lock['timemodified']; 
//   if ($diff >= 25200) { 
//    // The current script is active for 7 hours. 
//    // You can change 25200 to any number of seconds you want. 
//    // Here you can send notification email to site administrator. 
//    // ... 
//   } 
     } 

     return true; 
    } 

    protected function releaseLock() 
    { 
     // Update #__cronlock set lock = '0' where name = 'syncProducts' 
    } 
} 
+0

Эй, Сатрун, спасибо за ваш ответ. Я попробую механизм блокировки, но дело в том, что в журнале можно увидеть, что все продукты обновляются в течение 45 минут, но cron работает только один раз в день. –

+0

@Hans - добавьте свой скрипт 'cron' к вопросу. Может быть, в этом что-то не так. –

+0

@HansWassink механизм блокировки должен устранить одну возможную причину вашей проблемы. Если после этого проблема все еще существует, то проблема с логикой сценария наверняка. Также убедитесь, что вы удалите die() с конца вашего скрипта перед использованием блокировки. – satrun77

4

Я вижу две возможности: - кроновая называет сценарий гораздо чаще - скрипт занимает слишком много времени каким-то образом.

вы можете попробовать оценить время, которое занимает одна итерация цикла. это можно сделать со временем(). возможно, результат удивляет, возможно, нет. вы также можете получить количество результатов. умножьте два, таким образом, у вас будет оценка того, сколько времени должен пройти процесс.

$productsToSync = $db->loadObjectList(); 

и

foreach ($productsToSync AS $product) { 

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

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

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

5

Ваш скрипт работает довольно долго (~ 45 м), и wget считает, что это «тайм-аут», поскольку вы не возвращаете никаких данных. По умолчанию wget будет иметь значение тайм-аута 900 и количество повторных попыток 20. Итак, сначала вы должны изменить команду wget, чтобы предотвратить это:

wget -tries = 0 --timeout = 0 -q -O/dev/нуль http://example.eu/xxxxx/cron.php?operation=sync

Теперь удаление таймаута может привести к другой проблеме, поэтому вместо того, чтобы вы могли отправить (и flush, чтобы заставить веб-сервер, чтобы отправить его) данные из вашего сценария, чтобы убедиться, что Wget не думаете, что сценарий «тайм-аут» , что-то каждые 1000 циклов или что-то в этом роде. Подумайте об этом как о баре прогресса ...

Просто имейте в виду, что вы столкнетесь с проблемой, когда время работы приблизится к вашему периоду, так как два крона будут работать параллельно. Вы должны оптимизировать свой процесс и/или иметь механизм блокировки, возможно?

Смежные вопросы