«1. Обзор

В этой статье мы рассмотрим фундаментальную концепцию поисковой системы Apache Solr — полнотекстовый поиск.

Apache Solr — это среда с открытым исходным кодом, предназначенная для работы с миллионами документов. Мы рассмотрим его основные возможности на примерах с использованием библиотеки Java — SolrJ.

2. Конфигурация Maven

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

Для связи с сервером мы определим зависимость Maven для клиента SolrJ:

<dependency>
    <groupId>org.apache.solr</groupId>
    <artifactId>solr-solrj</artifactId>
    <version>6.4.2</version>
</dependency>

Вы можете найти последнюю зависимость здесь.

3. Индексирование данных

Для индексации и поиска данных нам нужно создать ядро; мы создадим один именованный элемент для индексации наших данных.

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

Есть много разных способов индексировать данные. Мы можем использовать обработчики импорта данных для импорта данных непосредственно из реляционных баз данных, загрузки данных с помощью Solr Cell с помощью Apache Tika или загрузки данных XML/XSLT, JSON и CSV с помощью обработчиков индексов.

3.1. Индексирование документа Solr

Мы можем индексировать данные в ядро, создав SolrInputDocument. Во-первых, нам нужно заполнить документ нашими данными, а затем вызвать API SolrJ только для индексации документа:

SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", id);
doc.addField("description", description);
doc.addField("category", category);
doc.addField("price", price);
solrClient.add(doc);
solrClient.commit();

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

3.2. Индексирование Bean-компонентов

SolrJ предоставляет API для индексирования Java-бинов. Чтобы проиндексировать bean-компонент, нам нужно аннотировать его аннотациями @Field:

public class Item {

    @Field
    private String id;

    @Field
    private String description;

    @Field
    private String category;

    @Field
    private float price;
}

Как только у нас есть bean-компонент, индексирование становится простым:

solrClient.addBean(item); 
solrClient.commit();

4. Solr Queries

Поиск — это самый мощные возможности Solr. После того, как мы проиндексировали документы в нашем репозитории, мы можем искать ключевые слова, фразы, диапазоны дат и т. д. Результаты сортируются по релевантности (оценке).

4.1. Базовые запросы

Сервер предоставляет API для операций поиска. Мы можем вызвать обработчики запросов /select или /query.

Давайте выполним простой поиск:

SolrQuery query = new SolrQuery();
query.setQuery("brand1");
query.setStart(0);
query.setRows(10);

QueryResponse response = solrClient.query(query);
List<Item> items = response.getBeans(Item.class);

SolrJ будет внутренне использовать основной параметр запроса q в своем запросе к серверу. Количество возвращаемых записей будет равно 10, проиндексировано с нуля, если начало и строки не указаны.

Приведенный выше поисковый запрос будет искать любые документы, содержащие полное слово «brand1» в любом из проиндексированных полей. Обратите внимание, что простой поиск не чувствителен к регистру.

Давайте посмотрим на другой пример. Мы хотим найти любое слово, содержащее «rand», которое начинается с любого количества символов и заканчивается только одним символом. Мы можем использовать подстановочные знаки * и ? в нашем запросе:

query.setQuery("*rand?");

Запросы Solr также поддерживают логические операторы, как и в SQL:

query.setQuery("brand1 AND (Washing OR Refrigerator)");

Все логические операторы должны быть написаны заглавными буквами; те, которые поддерживаются синтаксическим анализатором запросов, — это И, ИЛИ, НЕ, + и — .

Более того, если мы хотим искать в определенных полях, а не во всех проиндексированных полях, мы можем указать их в запросе:

query.setQuery("description:Brand* AND category:*Washing*");

4.2. Фразовые запросы

До этого момента наш код искал ключевые слова в проиндексированных полях. Мы также можем выполнять поиск по фразе в проиндексированных полях:

query.setQuery("Washing Machine");

Когда у нас есть такая фраза, как «Стиральная машина», стандартный анализатор запросов Solr анализирует ее как «Стиральная машина ИЛИ машина». Чтобы найти целую фразу, мы можем только добавить выражение в двойных кавычках:

query.setQuery("\"Washing Machine\"");

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

query.setQuery("\"Washing equipment\"~2");

4.3. Запросы диапазона

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

Допустим, мы хотим найти товары, цена которых находится в диапазоне от 100 до 300:

query.setQuery("price:[100 TO 300]");

Приведенный выше запрос найдет все товары, цена которых находится в диапазоне от 100 до 300 включительно. Мы можем использовать «}» и «{» для исключения конечных точек:

query.setQuery("price:{100 TO 300]");

4.4. Запросы фильтра

Запросы фильтра можно использовать для ограничения расширенного набора результатов, которые могут быть возвращены. Запрос фильтра не влияет на оценку:

SolrQuery query = new SolrQuery();
query.setQuery("price:[100 TO 300]");
query.addFilterQuery("description:Brand1","category:Home Appliances");

«

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

5. Фасетный поиск

Фасетирование помогает упорядочить результаты поиска по группам. Мы можем фасетировать поля, запросы или диапазоны.

5.1. Границы поля

query.addFacetField("category");

QueryResponse response = solrClient.query(query);
List<Count> facetResults = response.getFacetField("category").getValues();

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

FacetResults будет содержать количество каждой категории в результатах.

5.2. Фасетирование запроса

query.addFacetQuery("Washing OR Refrigerator");
query.addFacetQuery("Brand2");

QueryResponse response = solrClient.query(query);
Map<String,Integer> facetQueryMap = response.getFacetQuery();

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

В результате в карте facetQueryMap будет количество запросов фасетов.

5.3. Огранка диапазона

query.addNumericRangeFacet("price", 100, 275, 25);

QueryResponse response = solrClient.query(query);
List<RangeFacet> rangeFacets =  response.getFacetRanges().get(0).getCounts();

Огранка диапазона используется для получения количества диапазонов в результатах поиска. Следующий запрос вернет количество ценовых диапазонов от 100 до 251 с промежутком 25:

Помимо числовых диапазонов, Solr также поддерживает диапазоны дат, интервальные фасеты и сводные фасеты.

6. Выделение хитов

itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 100f);
itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 300f);
itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "Home Appliances", 200f);
itemSearchService.index("hm0004", "Brand2 Dishwasher", "Washing equipments", 250f);

SolrQuery query = new SolrQuery();
query.setQuery("Appliances");
query.setHighlight(true);
query.addHighlightField("category");
QueryResponse response = solrClient.query(query);

Map<String, Map<String, List<String>>> hitHighlightedMap = response.getHighlighting();
Map<String, List<String>> highlightedFieldMap = hitHighlightedMap.get("hm0001");
List<String> highlightedList = highlightedFieldMap.get("category");
String highLightedText = highlightedList.get(0);

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

query.setHighlightSimplePre("<strong>");
query.setHighlightSimplePost("</strong>");

Мы получим highLightedText как «Домашняя \u003cem\u003eТехника\u003c/em\u003e». Обратите внимание, что ключевое слово для поиска «Бытовая техника» помечено тегом \u003cem\u003e. Тег выделения по умолчанию, используемый Solr, — \u003cem\u003e, но мы можем изменить его, установив теги pre и post:

7. Подсказки при поиске

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

7.1. Проверка орфографии

Стандартный обработчик поиска не включает компонент проверки орфографии; его нужно настраивать вручную. Есть три способа сделать это. Вы можете найти детали конфигурации на официальной вики-странице. В нашем примере мы будем использовать IndexBasedSpellChecker, который использует проиндексированные данные для проверки орфографии ключевых слов.

query.setQuery("hme");
query.set("spellcheck", "on");
QueryResponse response = solrClient.query(query);

SpellCheckResponse spellCheckResponse = response.getSpellCheckResponse();
Suggestion suggestion = spellCheckResponse.getSuggestions().get(0);
List<String> alternatives = suggestion.getAlternatives();
String alternative = alternatives.get(0);

Давайте найдем ключевое слово с орфографической ошибкой:

Ожидаемой альтернативой для нашего ключевого слова «hme» должно быть «home», так как наш индекс содержит термин «home». Обратите внимание, что перед выполнением поиска необходимо активировать проверку орфографии.

7.2. Автоматическое предложение терминов

Мы можем захотеть получить предложения по неполным ключевым словам, чтобы помочь в поиске. Компонент предложения Solr должен быть настроен вручную. Вы можете найти детали конфигурации на его официальной вики-странице.

SolrQuery query = new SolrQuery();
query.setRequestHandler("/suggest");
query.set("suggest", "true");
query.set("suggest.build", "true");
query.set("suggest.dictionary", "mySuggester");
query.set("suggest.q", "Hom");
QueryResponse response = solrClient.query(query);
        
SuggesterResponse suggesterResponse = response.getSuggesterResponse();
Map<String,List<String>> suggestedTerms = suggesterResponse.getSuggestedTerms();
List<String> suggestions = suggestedTerms.get("mySuggester");

Мы настроили обработчик запросов с именем /suggest для обработки предложений. Давайте получим предложения по ключевому слову «Hom»:

Список предложений должен содержать все слова и фразы. Обратите внимание, что в нашей конфигурации мы настроили подсказчик с именем mySuggester.

8. Заключение

Эта статья является кратким введением в возможности и особенности поисковой системы Solr.

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