новости
статьи
.sysadmin

корпоративный поиск с помощью PHP и Apache Solr

установка Solr

Чтобы объединить Solr и PHP, нужно установить Solr, создать индекс, подготовить данные к индексированию Solr, загрузить индекс, написать PHP-код для выполнения запросов и представить результаты. Значительную часть работы по созданию поискового индекса можно выполнить из командной строки. Конечно, программный интерфейс PHP для Solr также может влиять на содержимое индекса.

Solr реализован с помощью технологии Java. Для запуска Solr и средств его администрирования необходимо установить комплект для разработки ПО на Java V1.5 (Java 5 SDK). Java V1.5 SDK поставляется несколькими производителями, например, Sun Microsystems, IBM и BEA Systems, и каждая из этих реализаций подходит для запуска Solr. Для выполнения установки просто выберите дистрибутив Java, подходящий для вашей операционной системы, и следуйте инструкциям по установке.

Во многих случаях установить Java V1.5 очень просто: нужно лишь запустить самораспаковывающийся архив и принять условия лицензионного соглашения. Расположенный в архиве скрипт выполняет все сложные операции за несколько секунд. Для других операционных систем, например Debian, Java 5 SDK поставляется через репозиторий APT. Например, если у вас Debian или Ubuntu, ПО Java V1.5 можно установить с помощью команды sudo apt- get install sun-java5-jdk.

Через APT также автоматически загружаются все зависимости, необходимые для использования Java 5 SDK, что весьма удобно.

Если ПО Java уже установлено и исполняемый файл Java прописан в переменной PATH, выполните команду java -version, чтобы определить версию имеющегося у вас Java-кода.

Здесь и далее для демонстрации будет использоваться операционная система Mac OS X V10.5 Leopard. Apple Leopard содержит Java V1.5. Если немного изменить конфигурацию Apache по умолчанию, Leopard сможет также запускать PHP-приложения. Выполнив команду java -version в терминальном окне Leopard, мы получим следующий результат:

$ which java
/usr/bin/java
$ java -version
java version "1.5.0_13"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_13-b05-237)
Java HotSpot(TM) Client VM (build 1.5.0_13-119, mixed mode, sharing)

Примечание: Leopard позволяет переключаться между Java V1.4 и V1.5 с помощью приложения Java Preferences, расположенного в
/Applications/Utilities/Java. Если вы получите сообщение, что в вашей системе используется V1.4, откройте Java Preferences и измените настройки как показано на рисунке 1.



Рис. 1. Приложение Java Preferences в Leopard.

Для установки Solr зайдите на сайт Apache.org, нажмите Resources > Download, выберите подходящее зеркало проекта и, перейдя по папкам, выберите архив (.tgz-файл) Solr V1.2. В процессе загрузки будет получен файл, имя которого будет сходно с apache-solr-1.2.0.tgz. Распакуйте архив, выполнив следующие команды:

$ tar xzf apache-solr-1.2.0.tgz
$ ls -F apache-solr-1.2.0
CHANGES.txt NOTICE.txt dist/ lib/
KEYS.txt README.txt docs/ src/
LICENSE.txt build.xml example/


Во вновь созданном каталоге подкаталог с именем dist содержит код Solr, упакованный как Java-архив (JAR). Подкаталог example/exampledocs содержит примеры сформатированных данных (обычно в виде XML-кода), готовые к индексированию Solr.

Каталог example содержит пример приложения Solr. Чтобы его выполнить, просто запустите механизм Java с помощью архива приложения: start.jar:
$ java -jar start.jar
2007-11-10 15:00:16.672::INFO: Logging to STDERR via org.mortbay.log.StdErrLog
2007-11-10 15:00:16.866::INFO: jetty-6.1.3
...
INFO: SolrUpdateServlet.init() done
2007-11-10 15:00:18.694::INFO: Started SocketConnector @ 0.0.0.0:8983

Теперь приложение доступно на порту 8983. Запустите браузер и в адресной строке введите http://localhost:8983/solr/admin/. Откроется интерфейс администрирования Solr (чтобы остановить сервер Solr, в командной строке нажмите Ctrl+C).

Однако в индексе Solr ещё нет данных, которыми можно управлять и которые можно запрашивать.

загрузка данных в Solr

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

Имея набор типов данных и правил, можно создать схему Solr, описывающую ваши данные, и управлять конструированием индексов. Затем нужно экспортировать данные, чтобы они соответствовали схеме, и загрузить их в Solr. Solr создаёт индексы "на лету", обновляя каждый индекс сразу после создания, изменения или удаления записей.

Схему Solr по умолчанию можно найти на Apache.org в хранилище исходного кода Solr. Для справки приведён фрагмент схемы по умолчанию.
<schema name="example" version="1.1">
...
<fields>
<field name="id" type="string" indexed="true" stored="true" required="true" />
<field name="name" type="text" indexed="true" stored="true"/>
<field name="nameSort" type="string" indexed="true" stored="false"/>
<field name="cat" type="text" indexed="true" stored="true" multiValued="true"/>
...
</fields>

<uniqueKey>id</uniqueKey>
...
<copyField source="name" dest="nameSort"/>
...
</schema>


Значительная часть схемы очевидна, однако кое-что требует пояснений:

- Как показано, поле id имеет строковый тип (type="string"), и его следует индексировать (indexed="true"). Это также обязательное поле (required="true"). При использовании этой схемы каждая запись, загруженная в Solr, должна иметь значение для этого поля. Далее, модификатор <uniqueKey>id</uniqueKey> декларирует, что поле id должно быть уникальным (самому Solr не требуется уникальное поле ID; это всего лишь правило, установленное в схеме индекса по умолчанию). Атрибут stored="true" указывает, что поле id должно быть извлекаемым (retrievable).

Зачем присваивать stored значение false? Можно применить неизвлекаемое (nonretrievable) поле для сортировки результатов в другом порядке, как в случае nameSort, которое является копией поля name (из-за команды copyField в последней строке), но ведёт себя по-другому. Обратите внимание, что nameSort имеет тип string, а name – (text). Схема индекса по умолчанию обрабатывает эти два типа несколько по-разному.

- Поле cat имеет тип multiValued. В записи может определяться множество значений для этого поля. Например, если ваше приложение управляет контентом, истории (story) можно присвоить несколько тем. Чтобы собрать данные обо всех темах, можно использовать поле cat (или самостоятельно определить аналогичное поле).

Ниже будет показан файл example/exampledocs/ipod_other.xml, представляющий две записи в каталоге аксессуаров для iPod.

Данные, сформатированные для схемы индекса Solr по умолчанию:

<add>
<doc>
<field name="id">F8V7067-APL-KIT</field>
<field name="name">Belkin Mobile Power Cord for iPod w/ Dock</field>
<field name="manu">Belkin</field>
<field name="cat">electronics</field>
<field name="cat">connector</field>
<field name="features">car power adapter, white</field>
<field name="weight">4</field>
<field name="price">19.95</field>
<field name="popularity">1</field>
<field name="inStock">false</field>
</doc>

<doc>
<field name="id">IW-02</field>
<field name="name">iPod & iPod Mini USB 2.0 Cable</field>
<field name="manu">Belkin</field>
<field name="cat">electronics</field>
<field name="cat">connector</field>
<field name="features">car power adapter for iPod, white</field>
<field name="weight">2</field>
<field name="price">11.50</field>
<field name="popularity">1</field>
<field name="inStock">false</field>
</doc>
</add>


Элемент add является командой Solr, добавляющей охваченные записи к индексу. Каждая запись захватывается в элемент doc, который использует последовательность поименованных элементов field для указания значений полей. Другие поля, определённые в схеме индекса Solr по умолчанию – weight, price, inStock, manu, features и popularity. Поле features имеет такие же атрибуты как и cat, но семантическое значение у него другое: оно перечисляет возможности продукта (которых может быть много).

поиск автозапчастей

В примере индексируется набор автозапчастей. Каждая запись об автозапчасти имеет несколько полей, примеры наиболее важных полей показаны в таблице 1. Название поля даётся в первом столбце, во втором приводится краткое описание, в третьем – логический тип. В четвёртом столбце показывается тип индекса, используемый для представления данных.

Таблица 1. Поля записи об автозапчасти.


ИмяОписаниеТипТип Solr
Part number (уникальное, обязательное)Идентификационный номерStringpartno
NameКраткое описаниеStringname
Model (обязательное, многозначное)Модель, например, "Camaro"Stringmodel
Model year (многозначное)Год выпуска, например, 2001Stringyear
PriceСтоимость за единицуFloatprice
In stockВ наличии или нетBooleaninStock
FeaturesПараметры деталиStringfeatures
TimestampОтметка времениStringtimestamp
WeightМасса бруттоFloatweight


Конкретные используемые поля – имена и атрибуты – просто заменили собой элемент fields из схемы по умолчанию.

Схема индекса автозапчастей:

<?xml version="1.0" encoding="utf-8" ?>
<schema name="autoparts" version="1.0">
...
<fields>
<field name="partno" type="string" indexed="true"
stored="true" required="true" />

<field name="name" type="text" indexed="true"
stored="true" required="true" />

<field name="model" type="text_ws" indexed="true" stored="true"
multiValued="true" required="true" />

<field name="year" type="text_ws" indexed="true" stored="true"
multiValued="true" omitNorms="true" />

<field name="price" type="sfloat" indexed="true"
stored="true" required="true" />

<field name="inStock" type="boolean" indexed="true"
stored="true" default="false" />

<field name="features" type="text" indexed="true"
stored="true" multiValued="true" />

<field name="timestamp" type="date" indexed="true"
stored="true" default="NOW" multiValued="false" />

<field name="weight" type="sfloat" indexed="true" stored="true" />
</fields>

<uniqueKey>partno</uniqueKey>

<defaultSearchField>name</defaultSearchField>
</schema>


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

<add>
<doc>
<field name="partno">1</field>
<field name="name">Spark plug</field>
<field name="model">Boxster</field>
<field name="model">924</field>
<field name="year">1999</field>
<field name="year">2000</field>
<field name="price">25.00</field>
<field name="inStock">true</field>
</doc>
<doc>
<field name="partno">2</field>
<field name="name">Windshield</field>
<field name="model">911</field>
<field name="year">1991</field>
<field name="year">1999</field>
<field name="price">15.00</field>
<field name="inStock">false</field>
</doc>
</add>


Установим новую схему индекса и загрузим данные в Solr. Сначала остановите демон Solr (если он всё ещё работает), нажав Ctrl+C. Заархивируйте существующую схему Solr, расположенную в example/solr/conf/schema.xml. Далее, скопируйте листинг 6 в текстовый файл, сохраните его в /tmp/schema.xml и скопируйте этот файл в example/solr/conf/schema.xml. Создайте ещё один файл для данных. Теперь можно снова запустить Solr и задействовать утилиту для отправки файлов, поставляющуюся вместе с примером.

$ cd apache-solr-1.2/example
$ cp solr/conf/schema.xml solr/conf/default_schema.xml
$ chmod a-w solr/conf/default_schema.xml

$ vi /tmp/schema.xml
...
$ cp /tmp/schema.xml solr/conf/schema.xml

$ vi /tmp/parts.xml
...

$ java -jar start.jar
...
2007-11-11 16:56:48.279::INFO: Started SocketConnector @ 0.0.0.0:8983

$ java -jar exampledocs/post.jar /tmp/parts.xml
SimplePostTool: version 1.2
SimplePostTool: WARNING: Make sure your XML documents are encoded in UTF-8,
other encodings are not currently supported
SimplePostTool: POSTing files to http://localhost:8983/solr/update...
SimplePostTool: POSTing file parts.xml
SimplePostTool: COMMITting Solr index changes...


Всё получилось! Если вы хотите убедиться, что индекс существует и в нём содержатся два документа, снова перейдите в браузере по адресу http://localhost:8983/solr/admin/. В верхней части страницы вы должны увидеть "(autoparts)". Если это так, поместите курсор в поле запроса в середине страницы и введите partno: 1 or partno: 2.

Результат будет примерно таким:

3 on 10 0 partno: 1 OR partno: 2 2.2
true Boxster 924 Spark plug 1 25.0 2007-11-11T21:58:45.899Z 1999 2000
false 911 Windshield 2 15.0 2007-11-11T21:58:45.953Z 1991 1999


Попробуйте и другие запросы. Синтаксис запросов Lucene (поисковый механизм, использующийся в Solr) описывается на wiki-ресурсе Lucene. Попробуйте также снова отредактировать и загрузить данные. Так как поле partno объявлено уникальным, многократные операции загрузки одного и того же номера детали просто заменяют старую запись в индексе новой. Кроме команды add можно также использовать commit, optimize и delete. Последняя команда может удалить конкретную запись по её ID или множество записей с помощью запроса.

а теперь – PHP

Наконец, мы добавляем в пример PHP.

Существуют, как минимум, два PHP Solr API. Наиболее надёжная реализация – PHP Solr Client Донована Хименеса (см. раздел Ресурсы). Код распространяется под той же лицензией что и Solr, отлично документирован и совместим с Solr V1.2. Самая последняя на момент написания статьи версия вышла 2 октября 2007 г.

Solr Client предоставляет четыре PHP-класса:

- Apache_Solr_Service представляет сервер Solr. С помощью его методов можно выполнять эхо-тестирование сервера (ping), добавлять и удалять документы, вносить изменения, оптимизировать индекс и выполнять запросы.

- Apache_Solr_Document воплощает документ Solr. Методы этого класса управляют парами (ключ, значение) и многозначными полями. К значениям полей можно получить доступ путём прямого разыменования, например, $document->title = 'Something'; ... echo $document->title;.

- Apache_Solr_Response формирует ответ Solr. Этот код зависит от функции json_decode(), идущей в комплекте с PHP V5.2.0 или более поздними версиями; её также можно установить вместе с PHP Extension Community Library.

- Apache_Solr_Service_Balancer улучшает Apache_Solr_Service, позволяя подключаться ко многим сервисам Solr в окружении. Этот класс здесь не рассматривается.

Загрузите PHP Solr Client и извлеките его в рабочий каталог. Перейдите в каталог SolrPhpClient. Далее, проверьте файл Apache/Solr/Service.php. На момент написания статьи в строке 335 отсутствовала замыкающая точка с запятой. Отредактируйте файл и, если нужно, добавьте точку с запятой. Также проверьте файл Apache/Solr/Document.php. Строки 112–117 должны быть следующими.

if (!is_array($this->_fields[$key]))
{
$this->_fields[$key] = array($this->_fields[$key]);
}

$this->_fields[$key][] = $value;


После исправления файлов можно устанавливать каталог Apache и другие PHP-библиотеки.

В приведённом ниже коде показано PHP-приложение, подключающееся к сервису Solr, добавляющее два документа к индексу и выполняющее
использовавшийся ранее запрос номера детали.

require_once( 'Apache/Solr/Service.php' );

//
//
// Пытаемся подключиться к указанному серверу, порту и url
//
$solr = new Apache_Solr_Service( 'localhost', '8983', '/solr' );

if ( ! $solr->ping() ) {
echo 'Solr service not responding.';
exit;
}

//
//
// Создаём два документа, представляющих две автозапчасти.
// На практике документы скорее всего будут собираться
// из запроса к базе данных.
//
$parts = array(
'spark_plug' => array(
'partno' => 1,
'name' => 'Spark plug',
'model' => array( 'Boxster', '924' ),
'year' => array( 1999, 2000 ),
'price' => 25.00,
'inStock' => true,
),
'windshield' => array(
'partno' => 2,
'name' => 'Windshield',
'model' => '911',
'year' => array( 1999, 2000 ),
'price' => 15.00,
'inStock' => false,
)
);

$documents = array();

foreach ( $parts as $item => $fields ) {
$part = new Apache_Solr_Document();

foreach ( $fields as $key => $value ) {
if ( is_array( $value ) ) {
foreach ( $value as $datum ) {
$part->setMultiValue( $key, $datum );
}
}
else {
$part->$key = $value;
}
}

$documents[] = $part;
}

//
//
// Загружаем документы в индекс
//
try {
$solr->addDocuments( $documents );
$solr->commit();
$solr->optimize();
}
catch ( Exception $e ) {
echo $e->getMessage();
}

//
//
// Выполняем несколько запросов. Задаём непосредственный путь, начальное смещение
// для документов результатов, а также максимальное количество возвращаемых
// документов результатов. Можно также использовать четвёртый параметр
// для управления сортировкой и выделением результатов,
// а также и другие опции.
//
$offset = 0;
$limit = 10;

$queries = array(
'partno: 1 OR partno: 2',
'model: Boxster',
'name: plug'
);

foreach ( $queries as $query ) {
$response = $solr->search( $query, $offset, $limit );

if ( $response->getHttpStatus() == 200 ) {
// print_r( $response->getRawResponse() );

if ( $response->response->numFound > 0 ) {
echo "$query <br />";

foreach ( $response->response->docs as $doc ) {
echo "$doc->partno $doc->name <br />";
}

echo '<br />';
}
}
else {
echo $response->getHttpStatusMessage();
}
}
?>

Сначала код подключается к указанному серверу Solr по указанному адресу и порту и с помощью метода ping() проверяет работоспособность сервера. Далее, код транслирует записи, представленные как PHP-массивы, в документы Solr. Если у поля одно значение, простой аксессор добавляет пару (ключ, значение) к документу. Если у поля множество значений, список значений присваивается ключу со специальной функцией setMultiValue(). Можно видеть, что этот процесс сильно напоминает XML-представление документа Solr.

Для оптимизации функция addDocuments() вставляет несколько документов в индекс. Последующие функции commit() и optimize() завершают добавления. Ниже несколько запросов извлекают данные из индекса. Просмотреть результаты можно через две "линзы": функция getRawResponse() выдаёт целый, непроанализированный результат, а функция docs() возвращает массив документов с указанными аксессорами.

Если запрос не будет одобрен Solr, код выводит сообщение об ошибке. Пустой набор результатов не генерирует выходных данных.

заключение

Solr – невероятно мощный инструмент, а PHP API ускоряет интеграцию на любой платформе. Что ещё лучше, Solr прост в установке и работе, а при необходимости можно задействовать и его продвинутые возможности. Лучше всего то, что Solr бесплатен. Не платите за поисковый механизм. Сэкономьте ваши деньги и переходите на Solr.

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



Мартин Страйчер, главный редактор, Linux Magazine. Впервые опубликовано на IBM developerWorks
обсудить статью
© сетевые решения
.
.