Статьи

Установить и интегрировать Elasticsearch с Drupal

В этом уроке я расскажу о возможности использования Drupal 7 в качестве системы управления контентом, которая обеспечивает работу другого высокопроизводительного приложения. Чтобы проиллюстрировать последнее, я буду использовать микрофрейм Silex PHP и Elasticsearch в качестве источника данных. Цель состоит в том, чтобы создать доказательство концепции, демонстрируя совместное использование этих трех технологий.

esdrupalsilex

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

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

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

Во второй части мы создадим небольшое приложение Silex, которое извлекает и отображает данные узлов непосредственно из Elasticsearch, полностью обходя установку Drupal.

Elasticsearch

Первым шагом является установка Elasticsearch на сервере. Предполагая, что вы используете Linux, вы можете следовать этому руководству и настроить его на запуск при запуске сервера. Есть несколько опций конфигурации, которые вы можете установить здесь .

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

network.bind_host: localhost

И добавьте следующий:

 script.disable_dynamic: true

Эти параметры гарантируют, что Elasticsearch недоступен извне, и динамические сценарии не допускаются. Это рекомендуемые меры безопасности, которые вам необходимо предпринять.

Drupal

Следующим шагом является настройка сайта Drupal на том же сервере. Используя модуль Elasticsearch Connector Drupal , вы можете получить некоторую интеграцию с экземпляром Elasticsearch: он поставляется с PHP SDK для Elasticsearch, некоторой статистикой об экземпляре Elasticsearch и некоторыми другими полезными подмодулями. Я оставлю это на ваше усмотрение, чтобы исследовать тех, кто на досуге.

После того, как модуль соединителя включен, в вашем пользовательском модуле вы можете получить оболочку объекта клиента Elasticsearch для доступа к данным:

 $client = elastic_connector_get_client_by_id('my_cluster_id');

Здесь my_cluster_idadmin/config/elasticsearch-connector/clusters Объект $client

Вставка данных

Первое, что нам нужно сделать, это убедиться, что мы вставляем некоторые данные Drupal в Elasticsearch. Придерживаясь узлов сейчас, мы можем написать реализацию hook_node_insert (), которая будет сохранять каждый новый узел в Elasticsearch. Вот пример, внутри пользовательского модуля с именем elastic

 /**
 * Implements hook_node_insert().
 */
function elastic_node_insert($node) {
  $client = elasticsearch_connector_get_client_by_id('my_cluster_id');
  $params = _elastic_prepare_node($node);

  if ( ! $params) {
    drupal_set_message(t('There was a problem saving this node to Elasticsearch.'));
    return;
  }

  $result = $client->index($params);
  if ($result && $result['created'] === false) {
    drupal_set_message(t('There was a problem saving this node to Elasticsearch.'));
    return;
  }

  drupal_set_message(t('The node has been saved to Elasticsearch.'));
}

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

 /**
 * Prepares a node to be added to Elasticsearch
 *
 * @param $node
 * @return array
 */
function _elastic_prepare_node($node) {

  if ( ! is_object($node)) {
    return;
  }

  $params = array(
    'index' => 'node',
    'type' => $node->type,
    'body' => array(),
  );

  // Add the simple properties
  $wanted = array('vid', 'uid', 'title', 'log', 'status', 'comment', 'promote', 'sticky', 'nid', 'type', 'language', 'created', 'changed', 'revision_timestamp', 'revision_uid');
  $exist = array_filter($wanted, function($property) use($node) {
    return property_exists($node, $property);
  });
  foreach ($exist as $field) {
    $params['body'][$field] = $node->{$field};
  }

  // Add the body field if exists
  $body_field = isset($node->body) ? field_get_items('node', $node, 'body') : false;
  if ($body_field) {
    $params['body']['body'] = $body_field;
  }

  // Add the image field if exists
  $image_field = isset($node->field_image) ? field_get_items('node', $node, 'field_image') : false;
  if ($image_field) {
    $params['body']['field_image'] = array_map(function($img) {
      $img = file_load($img['fid']);
      $img->url = file_create_url($img->uri);
      return $img;
    }, $image_field);
  }

  return $params;
}

Я написал только вспомогательную функцию, которая отвечает за «сериализацию» данных узла и подготовку их для вставки в Elasticsearch. Это всего лишь пример, и он определенно не является полным или полностью масштабируемым . Также предполагается, что соответствующим именем поля изображения является field_image Важно отметить, что мы вставляем узлы в индекс node$node->type

Обновление данных

Вставки недостаточно, нам нужно убедиться, что изменения узлов также отражаются в Elasticsearch. Мы можем сделать это с помощью реализации hook_node_update () :

 /**
 * Implements hook_node_update().
 */
function elastic_node_update($node) {
  if ($node->is_new !== false) {
    return;
  }

  $client = elasticsearch_connector_get_client_by_id('my_cluster_id');
  $params = _elastic_prepare_node($node);

  if ( ! $params) {
    drupal_set_message(t('There was a problem updating this node in Elasticsearch.'));
    return;
  }

  $result = _elastic_perform_node_search_by_id($client, $node);
  if ($result && $result['hits']['total'] !== 1) {
    drupal_set_message(t('There was a problem updating this node in Elasticsearch.'));
    return;
  }

  $params['id'] = $result['hits']['hits'][0]['_id'];
  $version = $result['hits']['hits'][0]['_version'];
  $index = $client->index($params);

  if ($index['_version'] !== $version + 1) {
    drupal_set_message(t('There was a problem updating this node in Elasticsearch.'));
    return;
  }
  
  drupal_set_message(t('The node has been updated in Elasticsearch.'));
}

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

 /**
 * Helper function that returns a node from Elasticsearch by its nid.
 *
 * @param $client
 * @param $node
 * @return mixed
 */
function _elastic_perform_node_search_by_id($client, $node) {
  $search = array(
    'index' => 'node',
    'type' => $node->type,
    'version' => true,
    'body' => array(
      'query' => array(
        'match' => array(
          'nid' => $node->nid,
        ),
      ),
    ),
  );

  return $client->search($search);
}

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

Удаление данных

Последняя (на данный момент) функция, которая нам нужна, — это возможность удалять данные из Elasticsearch при удалении узла. hook_node_delete () может помочь нам в этом:

 /**
 * Implements hook_node_delete().
 */
function elastic_node_delete($node) {
  $client = elasticsearch_connector_get_client_by_id('my_cluster_id');

  // If the node is in Elasticsearch, remove it
  $result = _elastic_perform_node_search_by_id($client, $node);
  if ($result && $result['hits']['total'] !== 1) {
    drupal_set_message(t('There was a problem deleting this node in Elasticsearch.'));
    return;
  }

  $params = array(
    'index' => 'node',
    'type' => $node->type,
    'id' => $result['hits']['hits'][0]['_id'],
  );

  $result = $client->delete($params);
  if ($result && $result['found'] !== true) {
    drupal_set_message(t('There was a problem deleting this node in Elasticsearch.'));
    return;
  }

  drupal_set_message(t('The node has been deleted in Elasticsearch.'));
}

Опять же, мы ищем узел в Elasticsearch и используем возвращенный идентификатор в качестве маркера для удаления документа.

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

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

Один из способов проверить, заполняется ли Elasticsearch, — отключить ограничение удаленного доступа, о котором я упоминал выше, и которое нужно включить. Убедитесь, что вы делаете это только в вашей локальной среде, среде разработки. Таким образом, вы можете выполнять HTTP-запросы прямо из браузера и получать данные JSON от Elasticsearch.

Вы можете выполнить быстрый поиск всех узлов в Elasticsearch, перейдя по этому URL:

 http://localhost:9200/node/_search

… Где localhost указывает на ваш локальный сервер, а 9200 является портом Elasticsearch по умолчанию.

Только для узлов статьи:

 http://localhost:9200/node/article/_search

А для отдельных статей автоматически сгенерированные идентификаторы Elasticsearch:

 http://localhost:9200/node/article/AUnJgdPGGE7A1g9FtqdV

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

Вывод

В этой статье мы увидели, как мы можем начать работать над интеграцией Elasticsearch с Drupal. Очевидно, что мы можем сделать гораздо больше даже на основе небольших достижений. При необходимости мы можем расширить интеграцию с другими объектами и даже с конфигурацией Drupal. В любом случае, теперь у нас есть некоторые данные Drupal в Elasticsearch, готовые для использования из внешнего приложения.

Это внешнее приложение будет задачей второй части этого урока. Мы настроим небольшое приложение Silex, которое, используя Elasticsearch PHP SDK, будет считывать данные Drupal непосредственно из Elasticsearch. Как и в части 1, описанной выше, мы не будем проходить пошаговое руководство по выполнению заданной задачи, а вместо этого рассмотрим один из способов, с помощью которого вы можете начать строить эту интеграцию. Увидимся там.