Написание Плагина Поиска Для Elasticsearch

Elaticsearch — популярная поисковая система и база данных NoSQL. Одной из его интересных особенностей является поддержка плагинов, которые могут расширить встроенный функционал и добавить некоторую бизнес-логику на уровень поиска.

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



Написание плагина поиска для Elasticsearch

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

Но я бы хотел сосредоточиться на самом плагине, поэтому пример очень простой.

Также следует отметить, что я не являюсь коммиттером Elasticsearch, представленная информация в основном получена методом проб и ошибок и может быть в чем-то неверной.

Итак, предположим, у нас есть документ Event со свойствами start и stop, в котором время хранится в виде строки в формате «ЧЧ:ММ:СС».

Задача — отсортировать события за заданное время так, чтобы активные события (начало <= time <= stop) are at the beginning of the output. An example of such a document:

  
  
  
  
  
  
  
   

{ "start": "09:00:00", "stop": "18:30:00" }



Плагин

я взял за основу пример от одного из разработчиков Elasticsearch .

Плагин состоит из одного или нескольких скриптов, которые необходимо зарегистрировать:

public class ExamplePlugin extends AbstractPlugin { public void onModule(ScriptModule module) { module.registerScript(EventInProgressScript.SCRIPT_NAME, EventInProgressScript.Factory.class); } }

Полный исходный код Скрипт состоит из двух частей: NativeScriptFactory и самого скрипта, который наследуется от AbstractSearchScript. Фабрика отвечает за создание сценария (и в то же время за проверку параметров).

Стоит отметить, что скрипт для поиска создается только один раз (на каждом шарде), поэтому инициализацию/обработку параметров следует производить на этом этапе.

Клиентское приложение должно передать в скрипт следующие параметры:

  • time - строка в формате «ЧЧ:ММ:СС», интересующий нас момент времени
  • use_doc — определяет, какой метод использовать для доступа к данным документа (подробнее об этом позже)


public static class Factory implements NativeScriptFactory { @Override public ExecutableScript newScript(@Nullable Map<String, Object> params) { LocalTime time = params.containsKey(PARAM_TIME) ? new LocalTime(params.get(PARAM_TIME)) : null; Boolean useDoc = params.containsKey(PARAM_USE_DOC) ? (Boolean) params.get(PARAM_USE_DOC) : null; if (time == null || useDoc == null) { throw new ScriptException("Parameters \"time\" and \"use_doc\" are required"); } return new EventInProgressScript(time, useDoc); } }

Полный исходный код Итак, скрипт создан и готов к работе.

Самое важное в скрипте — метод run():

@Override public Integer run() { Event event = useDoc ? parser.getEvent(doc()) : parser.getEvent(source()); return event.isInProgress(time) ? 1 : 0; }

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

Это напрямую влияет на производительность плагина.

В целом алгоритм здесь такой:

  1. Читаем нужные нам данные документа
  2. Рассчитать результат
  3. Возвращение его в Elasticsearch
Чтобы получить доступ к данным документа, вам необходимо использовать один из методов source(), полей() или doc().

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

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



public class Event { public static final String START = "start"; public static final String STOP = "stop"; private final LocalTime start; private final LocalTime stop; public Event(LocalTime start, LocalTime stop) { this.start = start; this.stop = stop; } public boolean isInProgress(LocalTime time) { return (time.isEqual(start) || time.isAfter(start)) && (time.isBefore(stop) || time.isEqual(stop)); } }

(в тривиальных случаях, конечно, можно просто использовать данные из документа и сразу вернуть результат, и так будет быстрее) Результат в нашем случае равен «1» для событий, происходящих сейчас (начало <= time <= stop), and “0” for all others. The result type is Integer, because Elasticsearch cannot sort by Boolean. После обработки скрипта для каждого документа будет определено значение, по которому Elasticsearch будет их сортировать.

Миссия выполнена!

Интеграционные тесты

Помимо того, что тесты хороши сами по себе, они еще и являются отличной отправной точкой для отладки.

Очень удобно поставить точку останова и запустить отладку нужного теста.

Без этого отладка плагина была бы очень сложной.

Схема тестирования интеграции плагинов примерно такая:

  1. Запустить тестовый кластер
  2. Создать индекс и сопоставление
  3. Добавить документ
  4. Попросите сервер вычислить значение скрипта для заданных параметров и документа.

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

Более подробная информация - на GitHub .

Вероятно, существует два способа создания тестовых документов.

Первый — построить документ прямо в тесте — пример можно посмотреть.

Здесь .

Этот вариант довольно хорош, и я использовал его первым.

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

Поэтому второй способ — хранить картографирование И данные отдельно в качестве ресурсов.

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

В общем, любой способ хорош, выбор за вами.

Для запроса результата расчета скрипта воспользуемся стандартным Java-клиентом:

SearchResponse searchResponse = client() .

prepareSearch(TEST_INDEX).

setTypes(TEST_TYPE) .

addScriptField(scriptName, "native", scriptName, scriptParams) .

execute() .

actionGet();

Полный исходный код

Интеграция с Travis-CI
Дополнительной частью программы является интеграция с Непрерывная интеграция с помощью системы Travis .

Давайте добавим файл .

Трэвис :

language: java jdk: - openjdk7 - oraclejdk7 script: - mvn test

и CI-сервер будет проверять ваш код после каждого изменения, это выглядит так .

Это мелочь, но приятно.



Приложение

Итак, плагин готов и протестирован.

Пришло время попробовать это в действии.



Монтаж
Вы можете прочитать об установке плагинов в официальной документации .

Собранный плагин находится в .

/target. Чтобы упростить локальную установку, я написал небольшой скрипт, который собирает плагин и устанавливает его:

mvn clean package if [ $? -eq 0 ]; then

Теги: #elasticsearch #java #Технологии поиска #программирование #java #NoSQL

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.