Краткая предыстория. Задача в общем виде ставилась так: есть каталог продукции на неком головном сайте и ряд шаблонных статей, которые (и те и другие) являются материалом для наполнения сайтов-сателлитов. На этапе развертывания сайта-сателлита (спутника) необходимо импортировать какую то ветку товарного каталога с головного сайта, а также импортировать ряд шаблонных статей как основу разделов сайта-спутника. После развертывания сайта-спутника желательно, чтобы изменения в товарном каталоге на головном сайте транслировались и на сайт-спутник, где есть соответствующие импортированные статьи, т.е. нужна своеобразная синхронизация публикаций.
Частный случай. Конкретная реализация этой связки (головной сайт и сайт-спутник) уже практически завершена (остались штрихи по дизайну и наполнению, но это уже не моя забота :) ), это пара сайтов suet-co.ru и suet-motor.ru. Suet-motor.ru содержит ветку товарного каталога по моторам с головного сайта. Цель suet-motor — в привлечении покупателей по тематике «моторы». Он должен содержать несколько статей, посвященных данной тематике и каталог, ссылки из которого ведут на конечные позиции товаров на головном сайте. Там мы можем подробно изучить характеристики найденных на suet-motor товаров и заказать их.
Техника синхронизации.
Обмен данными. Первый вопрос в том как же обмениваться данными между сайтами, оставаясь в рамках движка drupal. Мне пришла идея воспользоваться техникой, которая применяется в drupal в случае ajax запросов. Для каждой стороны был написан модуль, в которых в объявляется hook_menu — зацепка для меню с элементами типа MENU_CALLBACK :
1 2 3 4 5 |
$items['export-server-query'] = array( 'page callback' => '_exportsource_server', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK ); |
Т.е. создаётся специальная страница, которой можно передавать параметры, а в ответ получать какой то массив данных, обработанный, к примеру, serialize(). Параметры можно передавать, как часть url. Вот как было это сделано у меня. Специальная функция генерировала url из имени команды и массива параметров —
1 2 3 4 5 6 7 8 9 10 11 |
function _export_request($command, $vars = array()) { //собираем ссылку для запроса $connect_url = 'http://' . variable_get('expd_server', '') . '/export-server-query/' . urlencode($command); if (!empty($vars)) foreach ($vars as $v) $connect_url .= '/' . urlencode($v); //rand - мешает отвечать на запросы кешам разных уровней $data = file_get_contents($connect_url . '/?rand=' . rand(1, 1000000)); //ответ сериализуем return unserialize($data); } |
А принимающая сторона, реализующая страницу типа MENU_CALLBACK, обрабатывала запрос следующим образом :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/* обработка запросов к серверу */ function _exportsource_server() { header('Cache-Control: store, no-cache, must-revalidate, post-check=0, pre-check=0'); $flag = 0; //проверка прав удаленного сервера - может ли он посылать нам команды? //самое простое - есть ли его ip в списке разрешнных, если да - $flag = 1 ... if (!$flag) { //нет прав - прекращаем работу echo serialize("Error: you are not allowed to access to this resource !"); exit; } //обработка запроса - загрузка вх. переменных $command = arg(1); $param1 = arg(2); ... //какой то массив данных - его вернем как результат, но пока он пуст $result = array(); switch ($command) { case 'command 1': ... break; case 'command N': ... break; } echo serialize($result); } |
Отложенное выполнение. Давайте посмотрим в какой момент лучше всего производить синхронизацию данных. Так как событие изменения статьи можно перехватить (в hook_form_alter добавляем обработчик $form[#submit] для формы нужного типа публикации ), то это наиболее удобный момент для синхронизации. Но это только на первый взгляд.
Когда мы меняем статью в каталоге на головном сайте, надо изменить её и на сайтах-спутниках. Для этого спутники надо каким то образом оповестить (выполнить запрос ко всем спутникам из какого-то известного списка). При этом можно либо помнить, кто из спутников запрашивал определенные статьи, а можно оповещать все спутники, и они сами должны решать — нужно ли запрашивать изменившуюся статью или нет.
Процесс редактирования часто связан с многократным сохранением статьи. Если каждый раз немедленно оповещать всех спутников, а они в свою очередь начнут синхронизироваться, то выйдет не очень хорошая ситуация с использованием ресурсов веб-сервера.
Эти рассуждения привели меня к следующему решению. При сохранении статей на головном сайте, я добавляю запись в специальную очередь-таблицу обновлений с помощью sql-команды replace. Записи имеют код статьи (node-id или nid) в качестве ключа, и потому новая команда для каждой ноды не добавляет строк. А сама команда может быть или delete или insert соответственно для удаления статьи и обновления/добавления.
После того как команда попадает в очередь, она там хранится до вызова cron. Обработчик (hook_cron) в моём модуле грузит весь текущий список команд и оповещает сайты-спутники. Те в свою очередь не бросаются сразу выполнять запросы на обновление статей, а тоже складывают команды, прошедшие проверку в уже собственные очереди команд. И, как вы уже поняли, очереди команд на спутниках выполняются также во время уже собственной зацепки hook_cron. Лучше запуски cron для спутников и головного сайта разнести по времени, чтобы они не накладывались.