Введение в Elasticsearch в PHP
В этом уроке мы рассмотрим Elasticsearch и то, как мы можем использовать его в PHP. Elasticsearch — поисковый сервер с открытым исходным кодом, основанный на Apache Lucene. Мы можем использовать его для сверхбыстрого полнотекстового и другого сложного поиска. Он также включает REST API, который позволяет нам легко отправлять запросы на создание, удаление, обновление и получение данных.
Установка Elasticsearch
Чтобы установить Elasticsearch, нам сначала нужно установить Java. По умолчанию он недоступен в репозиториях, которые использует Ubuntu, поэтому нам нужно его добавить.
sudo add-apt-repository ppa:webupd8team/java sudo apt-get update
Как только это будет сделано, мы сможем установить Java.
sudo apt-get install oracle-java8-installer
Далее давайте загрузим Elasticsearch, используя wget
.
wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.5.2.tar.gz
На данный момент самой последней стабильной версией является 1.5.2, поэтому мы использовали ее выше. Если вы хотите убедиться, что получаете самую последнюю версию, загляните на страницу загрузок Elasticsearch.
Затем извлекаем и устанавливаем.
mkdir es tar -xf elasticsearch-1.5.2.tar.gz -C es cd es ./bin/elasticsearch
При доступе http://localhost:9200
в браузере мы получаем что-то похожее на следующее:
{ "status" : 200, "name" : "Rumiko Fujikawa", "cluster_name" : "elasticsearch", "version" : { "number" : "1.5.2", "build_hash" : "62ff9868b4c8a0c45860bebb259e21980778ab1c", "build_timestamp" : "2015-04-27T09:21:06Z", "build_snapshot" : false, "lucene_version" : "4.10.4" }, "tagline" : "You Know, for Search" }
Использование Elasticsearch
Теперь мы можем начать играть с Elasticsearch. Сначала давайте установим официальный клиент Elasticsearch для PHP.
composer require elasticsearch/elasticsearch
Далее давайте создадим новый php-файл, который мы будем использовать для тестирования, со следующим кодом, чтобы мы могли использовать клиент Elasticsearch.
<?php require 'vendor/autoload.php'; $client = new Elasticsearch\Client();
Индексирование документов
Индексирование новых документов можно выполнить, вызвав index
метод на клиенте. Этот метод принимает массив в качестве аргумента. Массив должен содержать body
, index
и type
в качестве своих ключей.
Это body
массив, содержащий данные, которые вы хотите проиндексировать. Это index
место, в котором вы хотите проиндексировать конкретный документ (соответствует базе данных в традиционной СУБД). Наконец, это type
тип, который вы хотите присвоить документу, и то, как вы хотите классифицировать документ. Это похоже на таблицу в мире СУБД. Вот пример:
$params = array(); $params['body'] = array( 'name' => 'Ash Ketchum', 'age' => 10, 'badges' => 8 ); $params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $result = $client->index($params);
Если распечатать, $result
вы получите что-то похожее на следующее:
Array ( [_index] => pokemon [_type] => pokemon_trainer [_id] => AU1Bn51W5l_vSaLQKPOy [_version] => 1 [created] => 1 )
В приведенном выше примере мы не указали идентификатор документа. Elasticsearch автоматически присваивает уникальный идентификатор, если ничего не указано. Попробуем присвоить идентификатор другому документу:
$params = array(); $params['body'] = array( 'name' => 'Brock', 'age' => 15, 'badges' => 0 ); $params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['id'] = '1A-000'; $result = $client->index($params);
Когда мы печатаем $result
:
Array ( [_index] => pokemon [_type] => pokemon_trainer [_id] => 1A-001 [_version] => 1 [created] => 1 )
Индексируя документы, мы не ограничиваемся одномерным массивом. Мы также можем индексировать многомерные:
$params = array(); $params['body'] = array( 'name' => 'Misty', 'age' => 13, 'badges' => 0, 'pokemon' => array( 'psyduck' => array( 'type' => 'water', 'moves' => array( 'Water Gun' => array( 'pp' => 25, 'power' => 40 ) ) ) ) ); $params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['id'] = '1A-002'; $result = $client->index($params);
Мы можем идти так глубоко, как захотим, но нам все равно необходимо соблюдать правильное хранение данных (не заходить слишком глубоко, сохранять их структурированными и логическими и т. д.), когда мы индексируем их с помощью Elasticsearch, как мы это делаем в настройке РСУБД.
Поиск документов
Мы можем искать существующие документы в определенном индексе, используя метод get
или search
. Основное различие между ними заключается в том, что этот get
метод обычно используется, когда вы уже знаете идентификатор документа. Он также используется для получения только одного документа. С другой стороны, этот search()
метод используется для поиска в нескольких документах, и вы можете использовать любое поле документа для своего запроса.
Get
Во-первых, давайте начнем с get
метода. Как и index
метод, этот принимает массив в качестве аргумента. Массив должен содержать index
, type и id
документа, который вы хотите найти.
$params = array(); $params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['id'] = '1A-001'; $result = $client->get($params);
Код выше вернет следующее:
Array ( [_index] => pokemon [_type] => pokemon_trainer [_id] => 1A-001 [_version] => 1 [found] => 1 [_source] => Array ( [name] => Brock [age] => 15 [badges] => 0 ) )
Поиск по определенным полям
Аргумент массива для search
метода должен иметь ключи index
, the type
и body
. Здесь body
мы указываем запрос. Для начала приведем пример того, как мы используем его для возврата всех документов, возраст которых равен 15.
$params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['body']['query']['match']['age'] = 15; $result = $client->search($params);
Это возвращает следующее:
Array ( [took] => 177 [timed_out] => [_shards] => Array ( [total] => 5 [successful] => 5 [failed] => 0 ) [hits] => Array ( [total] => 1 [max_score] => 1 [hits] => Array ( [0] => Array ( [_index] => pokemon [_type] => pokemon_trainer [_id] => 1A-001 [_score] => 1 [_source] => Array ( [name] => Brock [age] => 15 [badges] => 0 ) ) ) ) )
Давайте разложим результаты:
took
– количество миллисекунд, которое потребовалось для завершения запроса.timed_out
– возвращается,true
если истекло время запроса._shards
– по умолчанию Elasticsearch распределяет данные по 5 шардам. Если вы получили значение 5 дляtotal
иsuccessful
тогда каждый осколок в настоящее время исправен.hits
содержит результаты.
Однако метод, который мы использовали выше, позволяет нам искать только с глубиной первого уровня. Если мы хотим пойти дальше, нам придется использовать bool
запросы. Для этого мы указываем bool
в качестве элемента для query
. Затем мы можем перейти к нужному полю, .
начиная с поля первого уровня и заканчивая полем, которое мы хотим использовать в качестве запроса.
$params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['body']['query']['bool']['must'][]['match']['pokemon.psyduck.type'] = 'water'; $result = $client->search($params);
Поиск с помощью массивов
Мы можем выполнять поиск, используя массивы в качестве запроса (для сопоставления нескольких значений), указав элемент bool
, затем must
, terms
а затем поле, которое мы хотим использовать для запроса. Мы указываем массив, содержащий значения, которым мы хотим сопоставить. В приведенном ниже примере мы выбираем документы, у которых age
значение равно 10 и 15.
$params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['body']['query']['bool']['must']['terms']['age'] = array(10, 15);
Этот метод принимает только одномерные массивы.
Фильтрованный поиск
Далее давайте выполним поиск с фильтром. Чтобы использовать поиск с фильтром, нам нужно указать filtered
элемент и установить диапазон, который мы хотим вернуть для определенного поля. В приведенном ниже примере мы используем age
поле as. Мы выбираем документы, возраст которых больше или равен (gte) 11, но меньше или равен (lte) 20.
$params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['body']['query']['filtered']['filter']['range']['age']['gte'] = 11; $params['body']['query']['filtered']['filter']['range']['age']['lte'] = 20; $result = $client->search($params);
OR and AND
В сфере СУРБД мы привыкли использовать ключевые слова AND и OR для указания двух или более условий. Мы также можем сделать это с помощью Elasticsearch, используя фильтрованный поиск. В приведенном ниже примере мы используем and
фильтр для выбора документов с возрастом 10 и количеством значков 8. Возвращаются только документы, соответствующие этим критериям.
$params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['body']['query']['filtered']['filter']['and'][]['term']['age'] = 10; $params['body']['query']['filtered']['filter']['and'][]['term']['badges'] = 8; $result = $client->search($params);
Если вы хотите выбрать любой из них, вы можете использовать or
его.
$params['body']['query']['filtered']['filter']['or'][]['term']['age'] = 10; $params['body']['query']['filtered']['filter']['or'][]['term']['badges'] = 8;
Ограничение результатов
Результаты можно ограничить определенным числом, указав поле size
. Вот пример:
$params['body']['query']['filtered']['filter']['and'][]['term']['age'] = 10; $params['body']['query']['filtered']['filter']['and'][]['term']['badges'] = 8; $params['size'] = 1;
Это возвращает первый результат, поскольку мы ограничили результаты одним документом.
Пагинация
В сфере СУРБД у нас есть лимит и смещение. В Elasticsearch есть size
и from
. from
позволяет нам указать индекс первого результата в наборе результатов. Документы имеют нулевую индексацию. Таким образом, для 10 результатов на странице, если у нас есть размер 10, мы добавляем 10 к значению from
каждый раз, когда пользователь переходит на следующую страницу.
$params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['size'] = 10; $params['from'] = 10; // <-- will return second page
Обновление документа
Чтобы обновить документ, нам сначала нужно получить старые данные документа. Для этого мы указываем и то же index
, type и id
что мы делали ранее, а затем вызываем метод get
. Актуальные данные можно найти в пункте _source
. Все, что нам нужно сделать, это обновить текущие поля новыми значениями или добавить новые поля к этому элементу. Наконец, мы вызываем метод update
с теми же параметрами, что и метод get.
$params = array(); $params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['id'] = '1A-001'; $result = $client->get($params); $result['_source']['age'] = 21; //update existing field with new value //add new field $result['_source']['pokemon'] = array( 'Onix' => array( 'type' => 'rock', 'moves' => array( 'Rock Slide' => array( 'power' => 100, 'pp' => 40 ), 'Earthquake' => array( 'power' => 200, 'pp' => 100 ) ) ) ); $params['body']['doc'] = $result['_source']; $result = $client->update($params);
Это возвращает что-то похожее на следующее:
Array ( [_index] => pokemon [_type] => pokemon_trainer [_id] => 1A-001 [_version] => 2 )
Обратите внимание, что значение _version
увеличивается каждый раз, когда вы вызываете update
метод, независимо от того, были ли данные фактически обновлены.
Вам может быть интересно, почему у нас есть версия в документе, или даже у вас может возникнуть соблазн подумать, что в Elasticsearch есть функция, которая позволяет нам получить предыдущую версию документа. К сожалению, это не так. Версия просто служит счетчиком того, сколько раз документ обновлялся.
Удаление документа
Удаление документа можно выполнить, вызвав delete
метод. Этот метод принимает массив, содержащий index
и type
в id
качестве аргумента.
$params = array(); $params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['id'] = '1A-001'; $result = $client->delete($params);
Это возвращает следующее:
Array ( [found] => 1 [_index] => pokemon [_type] => pokemon_trainer [_id] => 1A-001 [_version] => 7 )
Обратите внимание, что вы получите сообщение об ошибке, если попытаетесь получить удаленный документ с помощью этого get
метода.
Заключение
В этой статье мы рассмотрели, как можно работать с Elasticsearch на PHP, используя официальный клиент Elasticsearch. В частности, мы рассмотрели, как индексировать новые документы, искать документы, разбивать результаты на страницы и удалять документы.