При разработке высоконагруженных сайтов или корпоративных систем частенько возникает проблема с разработкой быстрого и удобного поискового движка. Ниже перечислены наиболее важные, на мой взгляд, требования к такому движку:
- Скорость
- Простота установки и настройки
- Цена (желательно бесплатно и с открытым кодом)
- Обмен информацией в формате JSON (по HTTP)
- Масштабируемость (возможность распределения на несколько серверов)
- Индексация в режиме реального времени
- Multi-tenancy (гибкость в настройках под индивидуального пользователя)
- Возможность переноса системы в облако
Хочу рассказать вам о новом поисковом движке Elasticsearch, который полностью удовлетворяет всем этим требованиям. В статье будет краткое описание, ссылка на авторитетную презентацию, а также описание установки и работы с ним.
На сегодняшний день существует множество различных реализаций таких систем, у каждой из них есть свои плюсы и минусы. Так как я не являюсь экспертом в этой области, то всю нижеперечисленную информацию прошу воспринимать только как моё субъективное мнение.
Так вот, недавно на глаза мне попалась презентация Андрея Змиевского (Andrei Zmievski), где он описывал возможности elasticsearch. Презентацию можно посмотреть тут (на английском).
Сайт проекта http://www.elasticsearch.org/
К сожалению, никакой информации на русском языке я найти не смог.
Что же это такое?
По сути — это новый фронт-енд к широко известному индексу Lucene. Главное отличие от конкурентов — это гибкость и простота в использовании. Добавление информации в индекс и поиск по индексу производятся с помощью простых HTTP запросов.
Установка и примеры работы с движком
Меня эта тема заинтересовала и я решил собственноручно испытать этот чудо-движок.
Итак, приступим
Установка
1. Скачиваем архив (http://www.elasticsearch.org/download/) и распаковываем его 2. Запускаем сервер Unix : bin/elasticsearch –f Windows : bin/elasticsearch.bat 3. Проверяем сервер curl -X GET http://localhost:9200/ Если все работает, сервер вернет вам JSON массив с какой-то информацией.
Индексация данных
Для примера создадим индекс пользователей хабра
Добавляем данные о первом пользователе
$ curl -XPUT 'http://localhost:9200/habrahabr/users/1' -d ' { "firstname" : "Piotr", "surname" : "Petrov", "birthDate" : "1981-01-01", "location" : "Moscow, Russian Federation", "skills" : ["PHP", "HTML", "C++", ".NET", "JavaScript"] }'
Добавляем данные о втором пользователе
$ curl -XPUT 'http://localhost:9200/habrahabr/users/2' -d ' { "firstname" : "Ivan", "surname" : "Sidorov", "birthDate" : "1978-12-13", "location" : "Briansk, Russian Federation", "skills" : ["HTML", "Ruby", "Python"] }'
Добавляем третьего пользователя
$ curl -XPUT 'http://localhost:9200/habrahabr/users/3' -d ' { "firstname" : "Stepan", "surname" : "Fomenko", "birthDate" : "1985-06-01", "location" : "Ukraine", "skills" : ["HTML", "XML", "Java", "JavaScript"] }'
Поиск: пробуем в деле
Для ознакомления я приведу несколько простых примеров поиска. На самом деле этот движок полностью соответствует своему названию “elastic” и можно создавать самые разнообразные запросы. Подробнее о запросах можно прочитать на сайте проекта www.elasticsearch.org/guide/reference/api
параметр pretty=true отображает ответ в более читабельном виде
пример 1: ищем всех пользователей с именем Ivan
$ curl -XGET 'http://localhost:9200/habrahabr/users/_search?q=firstname:Ivan&pretty=true'
пример 2: ищем всех пользователей из Украины со знанием PHP
$ curl -XGET 'http://localhost:9200/habrahabr/users/_search?pretty=true' -d ' { "query" : { "term" : { "location": "Ukraine", "skills": "PHP" } } }'
пример 3: ищем пользователей из России
$ curl -XGET 'http://localhost:9200/habrauser/_search?q=location:Russian%20Federation&pretty=true'
пример 4: подсчитываем количество пользователей из России
$ curl -XGET 'http://localhost:9200/habrauser/_count?q=location:Russian%20Federation&pretty=true'
P.S. UTF8 поддерживает нормально
Тестирование с большим объёмом данных
К сожалению у меня нет большого опыта работы с другими поисковыми движками, поэтому нет возможности и сравнить их с elasticsearch. Любопытства ради решил создать индекс из 5,000,000 пользователей.
Простенький скрипт для заполнения индекса (данные генерируются, но информация более-менее похожа на реальную)
<?php
ini_set('max_execution_time', 36000);
class userGenerator {
// вписал только несколько стран для примера
private $countries = array(
'Russian Federation',
'Ukraine',
'Germany',
'France',
'Lithuania',
'Latvia',
'Poland',
'Finland',
'Sweden'
);
public function run($cnt) {
for ($i = 0; $i < $cnt; $i++) {
$query = $this->generateQuery($i);
echo "generating user " . $i . " ... ";
exec($query);
echo "done" . PHP_EOL;
}
}
private function generateQuery($id) {
// Допустим самый старый программист родился в 1960-ом году
$date = new DateTime('1960-01-01');
return 'curl -XPUT \'http://localhost:9200/habrahabr/users/' . $id . '\' -d \'
{
"id" : "' . $id . '",
"firstname" : "' . ucfirst($this->generateWord(10)) . '",
"surname" : "' . ucfirst($this->generateWord(10)) . '",
"birthDate" : "' . $date->modify('+' . rand(0, 14600) . ' days')->format('Y-m-d') . '",
"location" : "' . $this->generateWord(10) . ', ' . $this->countries[array_rand($this->countries)] . '",
"skills" : ["' . strtoupper($this->generateWord(3)) . '", "' . strtoupper($this->generateWord(4)) . '", "' . strtoupper($this->generateWord(3)) . '"]
}\' ';
}
private function generateWord($length) {
$letters = array(
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z");
$word = '';
for ($i = 0; $i < $length; $i++) {
$word .= $letters[rand(0, 25)];
}
return $word;
}
}
$generator = new userGenerator();
$generator->run(5000000);
echo "complete";
?>
На создание индекса на моём домашнем (не особо мощном) ПК ушло где-то 5 часов. Учитывая то что я абсолютно ничего не настраивал и не оптимизировал, считаю что результат довольно неплохой. Тем более что время генерирования индекса для меня не особо критично. Думаю если покопаться в настройках, да ещё и оптимизировать мой скрипт так чтобы высылал не одиночные а групповые запросы (подробнее тут), то время сократилось бы в разы. Ну а если ещё и распараллелить этот процесс — тогда время можно сократить до часа.
Проверяем количество записей в индексе
curl -XGET 'http://localhost:9200/habrahabr/users/_count?q=*&pretty' { "count" : 5128888, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }
Проверяем скорость добавления новой записи
time curl -XPUT 'http://localhost:9200/habrahabr/users/5128889' -d ' { "firstname" : "Василий", "surname" : "Фёдоров", "birthDate" : "1975-07-11", "location" : "Riga, Latvia", "skills" : ["PERL", "PYTHON", "ActionScript"] }' {"ok":true,"_index":"habrahabr","_type":"users","_id":"5128891","_version":2} real 0m0.007s user 0m0.004s sys 0m0.000s
Проверяем скорость поиска информации
time curl -XGET 'http://localhost:9200/habrahabr/users/_search?q=location:Riga&pretty' { "took" : 5, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 8.854725, "hits" : [ { "_index" : "habrahabr", "_type" : "users", "_id" : "5128891", "_score" : 8.854725, "_source" : { "firstname" : "Василий", "surname" : "Фёдоров", "birthDate" : "1975-07-11", "location" : "Riga, Latvia", "skills" : ["PERL", "PYTHON", "ActionScript"] } } ] } } real 0m0.011s user 0m0.004s sys 0m0.000s
$ time curl -XGET 'http://localhost:9200/habrahabr/users/_count?q=location:Germany&pretty' { "count" : 570295, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 } } real 0m0.079s user 0m0.004s sys 0m0.000s
Выводы
На мой взгляд движок быстрый, качественный, простой в использовании. По ощущениям он гораздо быстрее того же Zend_Search_Lucene.
В этой статье я описал лишь небольшую часть его функционала — самые простые и примитивные функции. За пределами этой статьи остались транзакции, репликaции, фильтры и очень много других полезных функций. Также стоит упомянуть что к этому движку уже написаны библиотеки на Java и PHP (возможно и на других языках).
П.С. Прошу прощения за некоторое косноязычие текста и терминов.