Помощь очередь заданий для тяжелых функций

babahalki

Постоялец
Регистрация
6 Май 2016
Сообщения
247
Реакции
107
Работа по оптимизации работы сипла под нагрузкой продолжается. К сожалению сильно много выжать из оптимизации самых сложных запросов в БД не удалось, поэтому дальше мы развиваем следующую тему.

Логика работы.
При заходе пользователя на страницу, кел проверяет есть ли у него данные по работы отдельных функций. Если данные есть, пользователю грузим данные из кеша, а задание на обновление кеша, т.е. на полную отработку функции помещаем в очередь заданий. Cron 2 раза в час запускает выполнение очереди заданий и отрабатывает функции с обновлением кеша.

Преимущества:
1. Сайт может обрабатывать большее число пользователей без особого труда, скорость открытия самых тяжелых страниц (разделы с множеством товаров и включенных фильтров) возрастает в несколько раз. У нас без кеша страница может грузится секунд 15, а с кешем не более 4 секунд.
2. Кеш регулярно обновляется, те места куда ходят пользователи регулярно обновляются. Теоретически пользователь видит относительно свежий кеш, который обновился от получаса до нескольких часов назад.
3. Очередь заданий не нагружает систему, загрузка ЦП во время работы около 1%, но активно работает с mysql. Надо бы как-то оптимизировать.

Недостатки:
1. Кеш занимает приличный объем, сейчас у нас кеш занимает 8гб.
2. Кеш есть кеш, поэтому информация загружаемая пользователем хорошей свежести, но не первой.


Проблемы:
Есть особо сложные вызовы функций, когда после конвертации передаваемых им параметров из переменной массива в строку и обратно, что-то меняется и функция не отрабатывает как должна.


Помогите, если кто сталкивался.
Это класс очереди задач
Код:
<?php
require_once('Okay.php');

class simpleq
{
  public function addtask($key, $method, $task) {

     if (empty($task || $key)) return false;
     dtimer::log(__METHOD__.' key '.$key);
     $okay = new Okay();
     $task = serialize($task);
     $task = $okay->db->escape($task);
    
     $key = $okay->db->escape($key);
     $query = "
     INSERT t_queue
     (`key`,`method`, `task`)
     VALUES
     ('$key' , '$method' , '$task')
     ;";
     dtimer::log(__METHOD__.' query '.$query);
     $okay->db->query($query);
     return TRUE;
     }
    
  public function count_tasks() {
     //dtimer::log(__METHOD__.' start '.$key);
     $okay = new Okay();

     $query = "
     SELECT COUNT(*) as count
     from t_queue
     ;";
     $okay->db->query($query);
     $return = $okay->db->results();
     //print_r($return);
     return $return[0]->count;
     }
    
  public function getlasttask() {
     dtimer::log(__METHOD__);
     $okay = new Okay();
     $okay->db->query("
     SELECT *
     FROM t_queue
     ORDER BY id
     LIMIT 1
     ;");
     $return = $okay->db->results()[0];
     dtimer::log(__METHOD__.' results:'.print_r($return,true));
     return $return;
     }
    
  public function gettask($id) {
     dtimer::log(__METHOD__.' id '.$id);
     if (empty($id)) return FALSE;
     $okay = new Okay();
     $okay->db->query("
     SELECT *
     FROM t_queue
     WHERE id = $id
     ;");
     $return = $okay->db->results()[0];
     dtimer::log(__METHOD__.' id '.$return->key);
     return $return;
     }
    
  public function exectask($id) {
     dtimer::log(__METHOD__.' id '.$id);
     if (empty($id)) return FALSE;
     $okay = new Okay();
     $okay->db->query("
     SELECT *
     FROM t_queue
     WHERE id = $id
     ;");
     $task = unserialize($okay->db->results()[0]->task);
     return eval($task);
     }
    
  public function execlasttask() {
     dtimer::log(__METHOD__.' start ');
     $okay = new Okay();

     $query_select = "
     SELECT *
     FROM t_queue
     ORDER BY id
     LIMIT 1
     ;";
    
     $okay->db->query($query_select);
     $results = $okay->db->results();
     //print($results);
     if(!$results) return false;
    
     $id = $results[0]->id;
     $task = unserialize($results[0]->task);
    
     $query_delete = "
     DELETE t
     FROM t_queue t
     WHERE id = '$id'
     ;";
    
     $delete_t = $okay->db->query($query_delete);
     eval($task);
     return true;
     }  
}

Это функция с добавлением в очередь задач.
Код:
  public function get_images($filter = array()) {
    
     //Проверяем нужен ли нам кеш
     if($filter['nocache']) {
       // делаем $filter_key, без параметра nocache, чтобы кеш правильно искал
       $filter_key = $filter;
       unset($filter_key['nocache']);
       $filter_key = var_export($filter_key,true);
     } else {
       //делаем $filter_export если nocache не задан
       $filter_key = var_export($filter,true);
     }
    
     //формируем $key для кеша и планировщика заданий
     $key = "get_images"."_".hash(MD4, $filter_key);
     dtimer::log(__METHOD__ . ' key after calculate '. $key . ' ' . __LINE__);
    
     // если кеширование включено и параметр nocache не задан берем задание из кеша
     // и добавляем task на обновление кеша
     if(!$filter['nocache']) {
     global $cache, $queue;
     if($cache) $get_images = $cache->get("$key");
     $filter['nocache'] = 1;
     $filter_export = var_export($filter, true);
     //создаем переменную для задачи, соединяя между собой нужную функцию и массив параметров
     $task = '$okay->products->get_images(';
     $task .= $filter_export;
     $task .= ');';
     $method = $filter['method'];
    
     //пишем в базу сформированное задание
     dtimer::log(__METHOD__ . ' add task key ' .$key.' '. __LINE__);    
     if($queue) $queue->addtask($key, $method, $task);
    
     // если есть берем данные из кеша и заканчиваем выполнение функции
     if($get_images != null) return $get_images;
     }



  $product_id_filter = '';
  if(!empty($filter['product_id'])) {
  $product_id_filter = $this->db->placehold('AND i.product_id in(?@)', (array)$filter['product_id']);
  }
  
  // images
  $query = $this->db->placehold("SELECT
  i.id,
  i.product_id,
  i.name,
  i.filename,
  i.position
  FROM __images AS i
  WHERE
  1
  $product_id_filter
  ORDER BY i.product_id, i.position
  ");
  $this->db->query($query);

     //phpfastcache
     $get_images = $this->db->results();
     dtimer::log(__METHOD__.' results db:'.print_r($get_images,true));
     // Write products to Cache in 30 days with same keyword 30 * 24 * 60 * 60
     global $cache;
     if($cache) {
       dtimer::log(__METHOD__ . ' cache set ' .$key. ' ' . __LINE__);
       $cache->set("$key", $get_images, 2592000);
     }
     return $get_images;
  }

Как видно входные параметры $filter для функции в виде массива конвертируются в строку через var_export($filter, true), потом формируется задание, которые потом сможет принять eval().

//создаем переменную для задачи, соединяя между собой нужную функцию и массив параметров
$task = '$okay->products->get_images('; /*берем функцию*/
$task .= $filter_export; /*добавляем параметры массива в виде array(...........)*/
$task .= ');';/*закрываем скобки*/


В итоге в переменной $task у нас строка, которую мы через serialize($task) добавляем в бд.
Когда задание выполняется, просходит обратная конвертация unserialize($results->task), а потом eval() полученного.
Все работает, но спотыкается на некоторых параметрах.
Вот пример выхлопа из очереди заданий.
Код:
stdClass Object
(
    [id] => 91683
    [key] => get_categories_options_15ec77effec3ef909b8c0eff9ee56473
    [method] => Features::get_categories_options_354
    [task] => s:627:"$okay->features->get_categories_options(array (
  'visible' => 1,
  'feature_id' =>
  array (
    0 => 4,
    1 => 20,
    2 => 21,
    3 => 22,
    4 => 24,
    5 => 17,
    6 => 18,
    7 => 19,
    8 => 9,
    9 => 8,
    10 => 14,
    11 => 13,
    12 => 10,
    13 => 2,
    14 => 25,
    15 => 15,
    16 => 16,
    17 => 26,
    18 => 27,
    19 => 12,
    20 => 34,
    21 => 35,
    22 => 36,
    23 => 3,
    24 => 31,
    25 => 23,
  ),
  'category_id' =>
  array (
    0 => 47,
  ),
  'features' =>
  array (
    2 =>
    array (
      0 => 'sovremennyj',
    ),
  ),
  'brand_id' => '40',
  'nocache' => 1,
));";
)

В чем может быть проблема?
 
Отдебажьте, посмотрите что делается после вывода из базы и десериализаци, совпадет ли исходная функция с тем что вышло в итоге, на каждом этапе сверяйте.
 
Отдебажьте, посмотрите что делается после вывода из базы и десериализаци, совпадет ли исходная функция с тем что вышло в итоге, на каждом этапе сверяйте.
Спасибо уже разобрался. Косяк был в функции чпу okay cms. Старые ссылки из ПС вида sevenlight.ru/catalog/lyustry/?1=2&2=3 обрабатывались системой криво. В итоге к методу get_products делался запрос с кривыми параметрами, что и приводило к ошибке, когда метод выполнялся с сайта, ошибка пролетала без предупреждений. Когда тоже самое запускалось через очередь заданий eval($okay->products->get_products..... там вылетали ошибки, которые и попадали в лог cron, подправил функцию chpu_parse_url(), в view/productsview.php и кривые ссылки обрубались еще до обращения к api CMS, сейчас все ок. по сравнению со стандартной скоростью работы - небо и земля. разрабам надо бы занятся оптимизацией скорости работы, а они все какую-то х... лепят.
 
Спасибо уже разобрался. Косяк был в функции чпу okay cms. Старые ссылки из ПС вида sevenlight.ru/catalog/lyustry/?1=2&2=3 обрабатывались системой криво. В итоге к методу get_products делался запрос с кривыми параметрами, что и приводило к ошибке, когда метод выполнялся с сайта, ошибка пролетала без предупреждений. Когда тоже самое запускалось через очередь заданий eval($okay->products->get_products..... там вылетали ошибки, которые и попадали в лог cron, подправил функцию chpu_parse_url(), в view/productsview.php и кривые ссылки обрубались еще до обращения к api CMS, сейчас все ок. по сравнению со стандартной скоростью работы - небо и земля. разрабам надо бы занятся оптимизацией скорости работы, а они все какую-то х... лепят.
Ну тут я думаю дело в аудитории, мало кто поднимает на симпле каталоги в десятки и сотни тысяч товаров, для этого есть другие движки, либо писать на фреймворках все что угодно с блек-джеком и шлю..ми)) А для маленького магазинчика сойдет

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

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

Мы сейчас кучу разных CMS перепробовали. Добиться на нашем каталоге более быстрой работы чем через симплу не удается. Хотя у нас крутится все на простом шаред хостинге, а сравнивали с довольно мощными конфигурациями на VPS. Симпла - это как жигули, можно ремонтировать и модернизировать в своем гараже. А учитывая, что двигатель у всех как на формуле 1 стандартный. PHP и есть PHP, то из жигулей можно выжать более высокий КПД.
 
Кстати. Вот сама доработка. Выкладываю кусок нужного кода

файл view/productsView.php
ОРИГИНАЛ
Код:
  // Свойства товаров
  if(!empty($category)) {
  $features = array();
  foreach($this->features->get_features(array('category_id'=>$category->id, 'in_filter'=>1)) as $feature) {
  $features[$feature->id] = $feature;
  $this->features_urls[] = $feature->url;
  if($val = $this->request->get($feature->id)) {
  $filter['features'][$feature->id] = $val;
  }
  }

ДОРАБОТКА
Код:
  // Свойства товаров
  if(!empty($category)) {
  $features = array();
  foreach($this->features->get_features(array('category_id'=>$category->id, 'in_filter'=>1)) as $feature) {
  $features[$feature->id] = $feature;
         $this->features_urls[] = $feature->url;
  $val = $this->request->get($feature->id);
  if(is_array($val)) {
           $filter['features'][$feature->id] = $val;
}
}

Как вы можете видеть в оригинале система формирует массив для фильтра, который потом пойдет в работу, не проверяя, а являются ли получаемые оттуда значения корректными.
Вот система получает значения фильтров.
if($val = $this->request->get($feature->id)) {....
А вот она их уже запиливает в массив
$filter['features'][$feature->id] = $val;

Дело в том, что там может быть не массив, а строка. Вот по такой к примеру ссылке, которая у нас осталась со времен симплы /catalog/svetilniki-podvesnye?42=68&17=20&page=59

там формируется строка, в результате массив $filter['features'] наполняется неверными данными, потом все идет в обращение к методам get_products get_options count_products и везде ошибка. Получаем холостую нагрузку на систему.

Доработка делает следущее, перед формированием массива $filter['features'][$feature->id] = $val; проводится проверка является ли $val массивом. Все очень просто.
 
100 000 у меня держит без особых проблем, админка подтормаживает а так норм
 
Назад
Сверху