Об авторе: Патрик Триест — инженер полного стека, энтузиаст данных, постоянно обучающийся и программист. Адрес автора на гитхабеgithub.com/triestpa, смотрите адрес этой статьиblog.patricktriest.com ... arch/】
Исходный код этой статьи можно найти в репозитории GitHub —GitHub.com/tries TPA/Так….
Добавление быстрого и гибкого полнотекстового поиска в приложение — непростая задача ни для кого. Многие основные базы данных, такие как PostgreSQL и MongoDB, ограничены структурами запросов и индексов и предоставляют только базовые возможности текстового поиска. Для обеспечения эффективного полнотекстового поиска обычно требуется отдельная база данных. Elasticsearch — это такая база данных с открытым исходным кодом, которая обеспечивает гибкость и возможности быстрого полнотекстового поиска.
В этой статье Docker используется для настройки среды зависимостей. Docker — самый распространенный механизм контейнеризации в настоящее время. Эту технологию используют Uber, Spotify, ADP и Paypal. Его преимущество в том, что он не имеет ничего общего с операционной системой и может работать в Windows, macOS и Linux. Это просто. Если вы никогда раньше не использовали Docker, ничего страшного, в этой статье подробно описан файл конфигурации.
В этой статье также используются Node.js (с использованием платформы Koa) и Vue.js для создания поисковых API и интерфейсных веб-приложений.
Приложение поиска, представленное в этой статье, может предоставить:
1. Быстро: результаты запроса должны возвращаться в режиме реального времени, чтобы улучшить взаимодействие с пользователем.
2. Гибкость: процесс поиска можно настроить в соответствии с различными данными и сценариями использования.
3. Лучший совет: для ошибок ввода возвращайте наиболее вероятный результат.
4. Полный текст: в дополнение к поиску по ключевым словам и тегам, мы надеемся, что сможем искать весь соответствующий текст
Чтобы поисковое приложение соответствовало вышеуказанным требованиям, лучше всего использовать базу данных, оптимизированную для полнотекстового поиска, поэтому в этой статье используется Elasticsearch. Elasticsearch — это размещаемая в памяти база данных с открытым исходным кодом, разработанная на Java и изначально включенная в библиотеку Apache Lucene. Ниже приведены некоторые официально предоставленные сценарии использования Elasticsearch:
1. Википедия использует Elasticsearch для обеспечения полнотекстового поиска, предоставляя такие функции, как выделение текста, поиск по мере ввода и подсказки «вы имели в виду».
2. Guardian использует Elasticsearch для интеграции социальных данных посетителей с авторами.
3. Stack Overflow объединяет информацию о местоположении и другие подобные функции с полнотекстовым поиском, чтобы предоставить соответствующие вопросы и ответы.
4. Github использует Elasticsearch для поиска по 130 миллиардам строк кода.
«Индекс» — это структура данных, которая обеспечивает быстрый запрос и возврат в базу данных. База данных обычно генерирует индексную информацию для поля данных и соответствующего местоположения таблицы. Сохраняя информацию об индексе в доступной для поиска структуре данных (обычно B-Tree), база данных может получать ответы линейного поиска (например, «Найти строку с ID=5») для оптимизированных запросов данных.
Индекс базы данных можно рассматривать как систему сортировки карт школьной библиотеки.Если вы знаете название и автора книги, вы можете точно определить вход в поисковый контент. Таблицы базы данных обычно имеют несколько таблиц индексов, которые могут ускорить запросы (например, индекс столбца имени может значительно ускорить запросы по определенному имени).
Инвертированные индексы работают совершенно иначе. Содержимое каждой строки (или каждого документа) разбивается, и каждая запись (в данном случае каждое слово) указывает на документ, который ее содержит.
Структура данных инвертированного индекса очень быстра для запроса, в котором находится документ «футбол». Elasticsearch использует инвертированные индексы, оптимизированные для памяти, чтобы выполнять мощные и настраиваемые задачи полнотекстового поиска.
Для авторов Docker может предоставить совместимую с платформой среду установки (которая может работать в системах Windows, macOS и Linux). Как правило, Node.js, Elasticsearch и Nginx требуют разных шагов установки.Если они работают в среде Docker, им нужно только определить разные файлы конфигурации, и они могут работать в любой среде Docker. Кроме того, поскольку приложения запускаются в изолированных контейнерах и мало связаны с локальным хостом, редко возникают проблемы с устранением неполадок типа «но я могу это запустить».
Установить Докер -docs.docker.com/engine/Inst…
Установите Docker Compose —docs.docker.com/compose/INS…
/public — хранит данные для внешних веб-приложений Vue.js.
/server — исходные файлы Node.js на стороне сервера.
gs-api — логика серверного приложения контейнера Node.js.
gs-frontend — контейнер Nginx для обслуживания интерфейсных веб-приложений.
gs-search — контейнер Elasticsearch для хранения данных поиска
Этот файл определяет стек приложения без установки Elasticsearch, Node.js или Nginx на локальном хосте. Каждый контейнер открывает соответствующие порты для хоста для доступа и отладки Node API, экземпляров Elasticsearch и интерфейсных приложений с хоста.
# 2.4 Добавить Dockerfile
В этой статье используются официальные образы Nginx и Elasticsearch, но требуется воссоздание собственных образов для Node.js. существует
Корневой каталог приложения определяет простой файл конфигурации Dockerfile.
В этот файл конфигурации Docker копируется исходный код приложения, а пакет зависимостей NPM устанавливается для формирования собственного образа. Также необходимо добавить файл .dockerignore, чтобы избежать копирования ненужных файлов.
Затем добавьте файл приложения Node.js в server/app.js.
Наконец, добавьте файл конфигурации узла package.json:
Этот файл определяет команду запуска приложения и зависимости Node.js.
Запустите docker-compose up, чтобы запустить приложение:
Посетите localhost:3000, чтобы убедиться, что сервер возвращает информацию «hello world».
Наконец, посетите localhost: 9200, чтобы убедиться, что Elasticsearch работает.Если он работает, он должен вернуть следующий вывод:
Если все выходные URL в порядке, поздравляем, вся структура приложения работает правильно, и теперь начинается настоящая забавная часть.
Затем используйте docker-compose для пересборки измененного приложения. После этого запустите docker-compose up -d, чтобы перезапустить фоновый процесс.
После запуска приложения запустите docker exec gs-api "node" "server/connection.js" из командной строки, запустите скрипт в контейнере, и вы должны увидеть следующий вывод:
Если все пойдет хорошо, вы можете удалить вызов checkConnection() в последней строке, так как финальное приложение будет вызывать его из-за пределов модуля подключения.
#3.1 Добавить вспомогательную функцию для сброса индекса
Добавьте следующее в файл server/connection.js checkConnection, чтобы упростить сброс индекса.
Здесь определяется отображение для библиографического указателя. Индексы Elasticsearch аналогичны таблицам SQL или соединениям MongoDB. С помощью сопоставления мы можем определить каждое поле и тип данных документа. Elasticsearch не использует схему, поэтому технически нет необходимости добавлять сопоставление, но сопоставление позволяет лучше контролировать, как обрабатываются данные.
Например, есть два поля ключевых слов: «название» и «автор», а текст обозначается как поле «текст». Определение поисковой системы таким образом имеет совершенно другое действие: при поиске система ищет возможные совпадения в текстовом поле и точные совпадения в поле ключевого слова. Может показаться, что разница невелика, но она оказывает большое влияние на поисковое поведение и скорость поиска.
Функции и свойства экспортируются в конец файла, и к ним могут обращаться другие модули.
CDN. Патрик использует T.com/data/books. …, а затем разархивируйте его в подкаталог books/ корневого каталога проекта.
Вы также можете использовать командную строку для выполнения вышеуказанного:
За этим следует заявление: *** НАЧАЛО ЭТОГО ПРОЕКТА GUTENBERG EBOOK HEART OF DARKNESS ***, за которым следует фактическое содержание книги.
В конце книги вы найдете заявление об окончании книги: *** КОНЕЦ ЭТОГО ПРОЕКТА GUTENBERG EBOOK HEART OF DARKNESS ***, за которым следует более подробная лицензия на книгу.
Следующим шагом является программное извлечение метаданных из книги и извлечение содержимого книги из базы данных.
Сначала получите список всех файлов в каталоге books.
Запустите docker-compose -d --build, чтобы перестроить образ и обновить приложение.
Запустите docker exec gs-api «node» «server/load_data.js», чтобы вызвать приложение, содержащее скрипт load_data, и вы должны увидеть следующий вывод Elasticsearch. Затем сценарий завершается с ошибкой, вызывая вспомогательную функцию (parseBookFile), которая еще не существует.
Эта функция выполняет следующие функции:
1. Прочитайте файл из файловой системы
2. Используйте регулярные выражения для извлечения названий книг и авторов
3. Извлеките содержимое книги, найдя ***
4. Разберите абзац
5. Очистить данные, удалить пустые строки
Наконец, возвращает объект, содержащий список названий книг, авторов и абзацев.
Запустив docker-compose up -d --build и docker exec gs-api "node" "server/load_data.js", вы получите следующий результат:
На этом этапе скрипт успешно разобрался с названием и автором книги, и скрипт тоже не сработает из-за той же проблемы (вызов функции, которая еще не определена)
Эта функция индексирует абзацы книги, включая информацию об авторе, названии и метаданных абзаца. Вставка абзацев с помощью массовой операции намного эффективнее, чем индексация абзацев по отдельности.
HTTP-интерфейс Elasticsearch удобен для тестирования вставки данных, но опасен при прямом доступе к веб-приложениям. Операционные функции API (такие как добавление и удаление документов напрямую) не должны предоставляться непосредственно приложению, но должен быть написан простой API Node.js для получения клиентских запросов и их пересылки (через частную сеть) в Elasticsearch для запроса.
Этот модуль определяет простую функцию поиска, которая использует входную информацию для сопоставления запросов. Подробные поля объясняются следующим образом:
1. from: Отметьте номер страницы для результата. Каждый запрос по умолчанию возвращает 10 результатов, поэтому, указав from как 10, можно напрямую отобразить результаты запроса от 10 до 20.
2. запрос: конкретные ключевые слова запроса
3. оператор: конкретная операция запроса, в этом примере используется оператор «и», и предпочтительно отображаются результаты, содержащие все ключевые слова запроса.
4. нечеткость: уровень исправления орфографических ошибок (или уровень нечеткого запроса), по умолчанию 2. Чем выше значение, тем выше допустимая неоднозначность; например, значение 1 вернет результат Патрика на запрос Патрика.
5. основные моменты: возвращает дополнительную информацию, которая содержит отображаемую текстовую информацию в формате HTML.
Вы можете настроить эти параметры, чтобы увидеть конкретную информацию об отображении, вы можете проверитьElastic Full-Text Query DSLЧтобы получить больше информации:
Этот код импортирует среду зависимости службы дляKoa.jsNode API Server устанавливает простые механизмы регистрации и обработки ошибок.
Вставьте следующий код после //ДОБАВИТЬ КОНЕЧНЫЕ ТОЧКИ ЗДЕСЬ в server/app.js:
Перезапустите сервер с помощью docker-compose up -d --build. В браузере вызовите эту службу. Например:http://localhost:3000/search?term=java
Возвращаемый результат должен выглядеть следующим образом:
Мы используем библиотеки Joi и Koa-Joi-Validate для этого типа проверки:
Теперь, если вы перезапустите сервер и сделаете запрос с отсутствующими параметрами (http://localhost:3000/search) будет возвращена ошибка HTTP 400, например: Неверный URL-запрос — дочерний «термин» завершается ошибкой, поскольку [«термин» требуется].
Журналы можно просмотреть с помощью docker-compose logs -f api.
Приложение очень простое, просто определите некоторые общие свойства данных, добавьте метод получения и функцию подкачки результатов; интервал поиска установлен на 100 мс, чтобы предотвратить частые вызовы API.
Объяснение того, как работает Vue.js, выходит за рамки этой статьи, если вы хотите узнать об этом, вы можете проверить это.Официальная документация Vue.js.
Эта функция вернет отсортированные абзацы данной книги.
Вышеуказанные пять функциональных блоков обеспечивают логические операции по загрузке и листанию (отображение 10 абзацев на странице) в книге.
Добавьте код пользовательского интерфейса для отображения страницы книги под разделителем в /public/index.html следующим образом:
Перезапустите сервер приложений (docker-compose up -d --build) и откройте localhost:8080. На этом этапе, если вы нажмете на результат поиска, вы можете запросить контекст абзаца. Если вы заинтересованы в поиске результатов, вы можете даже прочитать запрос.
Поздравляем! ! На данный момент основная рама была построена. Ко всему вышеприведенному коду можно получить доступ изздесьполучить.
Я запускаю приведенный выше пример (search.patriktriest.com) на облачном устройстве объемом 1,7 ГБ (15 долларов в месяц), чего достаточно для запуска узлов Elasticsearch. Иногда весь узел зависает при начальной загрузке данных. По моему опыту, Elasticsearch потребляет больше ресурсов, чем традиционные PostgreSQL и MongoDB, и если вам нужно обеспечить идеальные результаты обслуживания, стоимость может быть очень высокой.
Например, предположим, что нам нужно хранить пользователей в таблице PostgreSQL, но использовать ES для обработки функции запроса пользователя. Если пользователь «Альберт» решит изменить имя на «Аль», это изменение необходимо будет внести как в основной репозиторий PostgreSQL, так и в кластер ES.
Эта операция несколько сложна и зависит от существующего программного стека. Существует множество доступных ресурсов с открытым исходным кодом, начиная от процесса мониторинга журналов операций MongoDB и автоматической синхронизации данных удаления с ES и заканчивая созданием пользовательских подключаемых модулей PostgreSQL, которые автоматически взаимодействуют с ES на основе индексов PSQL.
Если ни один из ранее упомянутых вариантов не работает, вы можете вручную обновить индекс Elasticsearch на основе изменений базы данных в коде на стороне сервера. Но я не думаю, что этот вариант лучший, потому что использование пользовательской бизнес-логики для синхронизации ES сложно и может привести к множеству ошибок.
Необходимость синхронизации Elasticsearch с основной базой данных является не столько слабостью ES, сколько вызвана сложной архитектурой; стоит подумать о добавлении в приложение выделенной поисковой системы, но есть и компромиссы, которые следует учитывать.
Функциональность режима «панель поиска» — далеко не единственный вариант использования Elasticsearch. ES также является распространенным инструментом для хранения и анализа журналов, обычно используемым в архитектуре ELK (Elasticsearch, Logstash, Kibana). Гибкий полнотекстовый поиск, реализованный в ES, также полезен для задач специалистов по данным, таких как изменение, нормализация правописания наборов данных или поиск наборов данных.
Ниже приведены соображения по этому проекту:
1. Добавляйте в приложение больше любимых книг, создавайте личную библиотеку, ищите трудолюбие
2. Создайте механизм защиты от плагиата, индексируя научные статьи Google.
3. Создайте приложение для проверки орфографии, индексируя слова в словаре в ES.
4. Создайте свою собственную поисковую систему в Интернете, которая конкурирует с Google, загрузив Common Crawl Corpus в ES (обратите внимание, что существует 5 миллиардов страниц контента, что является очень большим набором данных).
5. Используйте Elasticsearch в журналистике: ищите названия функций и термины, например, в Panama papers и Paradise papers.
Весь код в этой статье является открытым исходным кодом и может быть найден в репозитории Github.ссылка для скачивания. Надеюсь, эта статья будет полезна для всех вас.
Исходный код этой статьи можно найти в репозитории GitHub —GitHub.com/tries TPA/Так….
Добавление быстрого и гибкого полнотекстового поиска в приложение — непростая задача ни для кого. Многие основные базы данных, такие как PostgreSQL и MongoDB, ограничены структурами запросов и индексов и предоставляют только базовые возможности текстового поиска. Для обеспечения эффективного полнотекстового поиска обычно требуется отдельная база данных. Elasticsearch — это такая база данных с открытым исходным кодом, которая обеспечивает гибкость и возможности быстрого полнотекстового поиска.
В этой статье Docker используется для настройки среды зависимостей. Docker — самый распространенный механизм контейнеризации в настоящее время. Эту технологию используют Uber, Spotify, ADP и Paypal. Его преимущество в том, что он не имеет ничего общего с операционной системой и может работать в Windows, macOS и Linux. Это просто. Если вы никогда раньше не использовали Docker, ничего страшного, в этой статье подробно описан файл конфигурации.
В этой статье также используются Node.js (с использованием платформы Koa) и Vue.js для создания поисковых API и интерфейсных веб-приложений.
1 Что такое эластичный поиск
Полнотекстовый поиск в современных приложениях требует высокой нагрузки. Функцию поиска также сложно выполнить (многие популярные веб-сайты имеют некачественные функции, но возврат либо медленный, либо неточный), в основном из-за лежащей в основе базы данных: многие стандартные реляционные базы данных могут предоставлять только базовые функции сопоставления строк и только ограниченные поддержка запросов CONTAINS или LIKE SQL.Приложение поиска, представленное в этой статье, может предоставить:
1. Быстро: результаты запроса должны возвращаться в режиме реального времени, чтобы улучшить взаимодействие с пользователем.
2. Гибкость: процесс поиска можно настроить в соответствии с различными данными и сценариями использования.
3. Лучший совет: для ошибок ввода возвращайте наиболее вероятный результат.
4. Полный текст: в дополнение к поиску по ключевым словам и тегам, мы надеемся, что сможем искать весь соответствующий текст
Чтобы поисковое приложение соответствовало вышеуказанным требованиям, лучше всего использовать базу данных, оптимизированную для полнотекстового поиска, поэтому в этой статье используется Elasticsearch. Elasticsearch — это размещаемая в памяти база данных с открытым исходным кодом, разработанная на Java и изначально включенная в библиотеку Apache Lucene. Ниже приведены некоторые официально предоставленные сценарии использования Elasticsearch:
1. Википедия использует Elasticsearch для обеспечения полнотекстового поиска, предоставляя такие функции, как выделение текста, поиск по мере ввода и подсказки «вы имели в виду».
2. Guardian использует Elasticsearch для интеграции социальных данных посетителей с авторами.
3. Stack Overflow объединяет информацию о местоположении и другие подобные функции с полнотекстовым поиском, чтобы предоставить соответствующие вопросы и ответы.
4. Github использует Elasticsearch для поиска по 130 миллиардам строк кода.
1.1 Уникальность Elasticsearch
По сути, Elasticsearch обеспечивает быстрый и гибкий полнотекстовый поиск с использованием инвертированного индекса.«Индекс» — это структура данных, которая обеспечивает быстрый запрос и возврат в базу данных. База данных обычно генерирует индексную информацию для поля данных и соответствующего местоположения таблицы. Сохраняя информацию об индексе в доступной для поиска структуре данных (обычно B-Tree), база данных может получать ответы линейного поиска (например, «Найти строку с ID=5») для оптимизированных запросов данных.
Индекс базы данных можно рассматривать как систему сортировки карт школьной библиотеки.Если вы знаете название и автора книги, вы можете точно определить вход в поисковый контент. Таблицы базы данных обычно имеют несколько таблиц индексов, которые могут ускорить запросы (например, индекс столбца имени может значительно ускорить запросы по определенному имени).
Инвертированные индексы работают совершенно иначе. Содержимое каждой строки (или каждого документа) разбивается, и каждая запись (в данном случае каждое слово) указывает на документ, который ее содержит.
Структура данных инвертированного индекса очень быстра для запроса, в котором находится документ «футбол». Elasticsearch использует инвертированные индексы, оптимизированные для памяти, чтобы выполнять мощные и настраиваемые задачи полнотекстового поиска.
2 Установка проекта
2.0 Docker
В этой статье в качестве среды разработки проекта используется Docker. Docker — это механизм контейнеризации, и приложения могут работать в изолированной среде, независимой от локальной массовой системы и среды разработки. Многие приложения интернет-компаний уже работают в контейнерах из-за их большой гибкости и возможности настройки.Для авторов Docker может предоставить совместимую с платформой среду установки (которая может работать в системах Windows, macOS и Linux). Как правило, Node.js, Elasticsearch и Nginx требуют разных шагов установки.Если они работают в среде Docker, им нужно только определить разные файлы конфигурации, и они могут работать в любой среде Docker. Кроме того, поскольку приложения запускаются в изолированных контейнерах и мало связаны с локальным хостом, редко возникают проблемы с устранением неполадок типа «но я могу это запустить».
2.1 Установите Docker и Docker-compose
Для этого проекта требуется только среда Docker и Docker-compose. Последний является официальным инструментом Docker, который организует и определяет несколько конфигураций контейнеров в одном стеке приложений.Установить Докер -docs.docker.com/engine/Inst…
Установите Docker Compose —docs.docker.com/compose/INS…
2.2 Установите каталог установки проекта
Создайте корневой каталог проекта (например, guttenberg_search) и определите в нем два подкаталога:/public — хранит данные для внешних веб-приложений Vue.js.
/server — исходные файлы Node.js на стороне сервера.
2.3 Добавьте файл конфигурации docker-compose
Затем создайте файл docker-compose.yml, определяющий конфигурацию для каждого контейнера в стеке:gs-api — логика серверного приложения контейнера Node.js.
gs-frontend — контейнер Nginx для обслуживания интерфейсных веб-приложений.
gs-search — контейнер Elasticsearch для хранения данных поиска
version: '3'
services:
api: # Node.js App
container_name: gs-api
build: .
ports:
- "3000:3000" # Expose API port
- "9229:9229" # Expose Node process debug port (disable in production)
environment: # Set ENV vars
- NODE_ENV=local
- ES_HOST=elasticsearch
- PORT=3000
volumes: # Attach local book data directory
- ./books:/usr/src/app/books
frontend: # Nginx Server For Frontend App
container_name: gs-frontend
image: nginx
volumes: # Serve local "public" dir
- ./public:/usr/share/nginx/html
ports:
- "8080:80" # Forward site to localhost:8080
elasticsearch: # Elasticsearch Instance
container_name: gs-search
image: docker.elastic.co/elasticsearch/elasticsearch:6.1.1
volumes: # Persist ES data in seperate "esdata" volume
- esdata:/usr/share/elasticsearch/data
environment:
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.type=single-node
ports: # Expose Elasticsearch ports
- "9300:9300"
- "9200:9200"
volumes: # Define seperate volume for Elasticsearch data
esdata:
Этот файл определяет стек приложения без установки Elasticsearch, Node.js или Nginx на локальном хосте. Каждый контейнер открывает соответствующие порты для хоста для доступа и отладки Node API, экземпляров Elasticsearch и интерфейсных приложений с хоста.
# 2.4 Добавить Dockerfile
В этой статье используются официальные образы Nginx и Elasticsearch, но требуется воссоздание собственных образов для Node.js. существует
Корневой каталог приложения определяет простой файл конфигурации Dockerfile.
# Use Node v8.9.0 LTS
FROM node:carbon
Setup app working directory
WORKDIR /usr/src/app
Copy package.json and package-lock.json
COPY package*.json ./
Install app dependencies
RUN npm install
Copy sourcecode
COPY . .
Start app
CMD [ "npm", "start" ]
В этот файл конфигурации Docker копируется исходный код приложения, а пакет зависимостей NPM устанавливается для формирования собственного образа. Также необходимо добавить файл .dockerignore, чтобы избежать копирования ненужных файлов.
node_modules/
npm-debug.log
books/
public/
Примечание. Нет необходимости копировать node_modules, так как мы будем использовать npm install для установки этих процессов позже. Если вы скопируете node_modules в контейнер, это может вызвать проблемы совместимости. Например, если вы установите пакет bcrypt на macOS, если вы скопируете этот модуль в контейнер Ubuntu, это вызовет проблему несоответствия ОС.
2.5 Добавить базовые файлы
Перед тестированием файла конфигурации вам также необходимо скопировать файл-заполнитель в каталог приложения. Добавьте следующую базовую информацию о конфигурации в файл public/index.html:<html><body>Hello World From The Frontend Container</body></html>
Затем добавьте файл приложения Node.js в server/app.js.
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => {
ctx.body = 'Hello World From the Backend Container'
})
const port = process.env.PORT || 3000
app.listen(port, err => {
if (err) console.error(err)
console.log(`App Listening on Port ${port}`
})
Наконец, добавьте файл конфигурации узла package.json:
{
"name": "guttenberg-search",
"version": "0.0.1",
"description": "Source code for Elasticsearch tutorial using 100 classic open source books.",
"scripts": {
"start": "node --inspect=0.0.0.0:9229 server/app.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/triestpa/guttenberg-search.git"
},
"author": "patrick.triest@gmail.com",
"license": "MIT",
"bugs": {
"url": "https://github.com/triestpa/guttenberg-search/issues"
},
"homepage": "https://github.com/triestpa/guttenberg-search#readme",
"dependencies": {
"elasticsearch": "13.3.1",
"joi": "13.0.1",
"koa": "2.4.1",
"koa-joi-validate": "0.5.1",
"koa-router": "7.2.1"
}
}
Этот файл определяет команду запуска приложения и зависимости Node.js.
Примечание. Вам не нужно специально запускать npm install, пакет зависимостей будет автоматически установлен при создании контейнера.
2.6 Начать тест
Все готово к открытию, и пришло время протестировать его. Начиная с корневого каталога проекта, запуск docker-compose автоматически создаст приложение-контейнер Node.js.Запустите docker-compose up, чтобы запустить приложение:
Примечание. Этот шаг может занять много времени, так как Docker может потребоваться загрузить базовый образ. В будущем выполнение будет выполняться быстрее, поскольку базовый образ уже доступен локально.Посетите localhost:8080, вы должны увидеть вывод «hello world», как показано ниже.
Посетите localhost:3000, чтобы убедиться, что сервер возвращает информацию «hello world».
Наконец, посетите localhost: 9200, чтобы убедиться, что Elasticsearch работает.Если он работает, он должен вернуть следующий вывод:
{
"name" : "SLTcfpI",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "iId8e0ZeS_mgh9ALlWQ7-w",
"version" : {
"number" : "6.1.1",
"build_hash" : "bd92e7f",
"build_date" : "2017-12-17T20:23:25.338Z",
"build_snapshot" : false,
"lucene_version" : "7.1.0",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
Если все выходные URL в порядке, поздравляем, вся структура приложения работает правильно, и теперь начинается настоящая забавная часть.
3 Доступ к эластичному поиску
Первый шаг — подключиться к локальному экземпляру Elasticsearch.3.0 Добавить модуль связи ES
Добавьте следующий код инициализации в server/connection.js:const elasticsearch = require('elasticsearch')
// Core ES variables for this project
const index = 'library'
const type = 'novel'
const port = 9200
const host = process.env.ES_HOST || 'localhost'
const client = new elasticsearch.Client({ host: { host, port } })
/** Check the ES connection status */
async function checkConnection () {
let isConnected = false
while (!isConnected) {
console.log('Connecting to ES')
try {
const health = await client.cluster.health({})
console.log(health)
isConnected = true
} catch (err) {
console.log('Connection Failed, Retrying...', err)
}
}
}
checkConnection()
Затем используйте docker-compose для пересборки измененного приложения. После этого запустите docker-compose up -d, чтобы перезапустить фоновый процесс.
После запуска приложения запустите docker exec gs-api "node" "server/connection.js" из командной строки, запустите скрипт в контейнере, и вы должны увидеть следующий вывод:
{ cluster_name: 'docker-cluster',
status: 'yellow',
timed_out: false,
number_of_nodes: 1,
number_of_data_nodes: 1,
active_primary_shards: 1,
active_shards: 1,
relocating_shards: 0,
initializing_shards: 0,
unassigned_shards: 1,
delayed_unassigned_shards: 0,
number_of_pending_tasks: 0,
number_of_in_flight_fetch: 0,
task_max_waiting_in_queue_millis: 0,
active_shards_percent_as_number: 50 }
Если все пойдет хорошо, вы можете удалить вызов checkConnection() в последней строке, так как финальное приложение будет вызывать его из-за пределов модуля подключения.
#3.1 Добавить вспомогательную функцию для сброса индекса
Добавьте следующее в файл server/connection.js checkConnection, чтобы упростить сброс индекса.
/** Clear the index, recreate it, and add mappings */
async function resetIndex () {
if (await client.indices.exists({ index })) {
await client.indices.delete({ index })
}
await client.indices.create({ index })
await putBookMapping()
}
3.2 Добавить схему книги
Сразу после resetIndex добавьте следующую функцию:/** Add book section schema mapping to ES */
async function putBookMapping () {
const schema = {
title: { type: 'keyword' },
author: { type: 'keyword' },
location: { type: 'integer' },
text: { type: 'text' }
}
return client.indices.putMapping({ index, type, body: { properties: schema } })
}
Здесь определяется отображение для библиографического указателя. Индексы Elasticsearch аналогичны таблицам SQL или соединениям MongoDB. С помощью сопоставления мы можем определить каждое поле и тип данных документа. Elasticsearch не использует схему, поэтому технически нет необходимости добавлять сопоставление, но сопоставление позволяет лучше контролировать, как обрабатываются данные.
Например, есть два поля ключевых слов: «название» и «автор», а текст обозначается как поле «текст». Определение поисковой системы таким образом имеет совершенно другое действие: при поиске система ищет возможные совпадения в текстовом поле и точные совпадения в поле ключевого слова. Может показаться, что разница невелика, но она оказывает большое влияние на поисковое поведение и скорость поиска.
Функции и свойства экспортируются в конец файла, и к ним могут обращаться другие модули.
module.exports = {
client, index, type, checkConnection, resetIndex
}
4 Загрузить исходные данные
В этой статье используются данные, предоставленные Project Gutenberg, приложением, предоставляющим бесплатные электронные книги онлайн. Включает в себя 100 классических произведений, таких как «Вокруг Земли за 80 дней», «Ромео и Джульетта» и «Одиссея».4.1 Загрузка данных книги
Данные для этой статьи можно загрузить со следующих веб-сайтов:CDN. Патрик использует T.com/data/books. …, а затем разархивируйте его в подкаталог books/ корневого каталога проекта.
Вы также можете использовать командную строку для выполнения вышеуказанного:
wget https://cdn.patricktriest.com/data/books.zip
unar books.zip
4.2 Предварительный просмотр книг
Откройте книгу, например 219-0.txt. Книги начинаются с лицензии общего доступа, за которой следует название, автор, дата выпуска, язык и кодировка символов.Title: Heart of Darkness
Author: Joseph Conrad
Release Date: February 1995 [EBook #219]
Last Updated: September 7, 2016
Language: English
Character set encoding: UTF-8
За этим следует заявление: *** НАЧАЛО ЭТОГО ПРОЕКТА GUTENBERG EBOOK HEART OF DARKNESS ***, за которым следует фактическое содержание книги.
В конце книги вы найдете заявление об окончании книги: *** КОНЕЦ ЭТОГО ПРОЕКТА GUTENBERG EBOOK HEART OF DARKNESS ***, за которым следует более подробная лицензия на книгу.
Следующим шагом является программное извлечение метаданных из книги и извлечение содержимого книги из базы данных.
4.3 Чтение каталога данных
В этом разделе напишите скрипт для чтения содержимого книги и добавьте его в Elasticsearch.Скрипт хранится в server/load_data.js.Сначала получите список всех файлов в каталоге books.
const fs = require('fs')
const path = require('path')
const esConnection = require('./connection')
/** Clear ES index, parse and index all files from the books directory */
async function readAndInsertBooks () {
try {
// Clear previous ES index
await esConnection.resetIndex()
// Read books directory
let files = fs.readdirSync('./books').filter(file => file.slice(-4) === '.txt')
console.log(`Found ${files.length} Files`)
// Read each book file, and index each paragraph in elasticsearch
for (let file of files) {
console.log(`Reading File - ${file}`)
const filePath = path.join('./books', file)
const { title, author, paragraphs } = parseBookFile(filePath)
await insertBookData(title, author, paragraphs)
}
} catch (err) {
console.error(err)
}
}
readAndInsertBooks()
Запустите docker-compose -d --build, чтобы перестроить образ и обновить приложение.
Запустите docker exec gs-api «node» «server/load_data.js», чтобы вызвать приложение, содержащее скрипт load_data, и вы должны увидеть следующий вывод Elasticsearch. Затем сценарий завершается с ошибкой, вызывая вспомогательную функцию (parseBookFile), которая еще не существует.
4.4 Чтение файла данных
Создайте файл server/load_data.js для чтения метаданных и содержимого каждой книги:/** Read an individual book text file, and extract the title, author, and paragraphs */
function parseBookFile (filePath) {
// Read text file
const book = fs.readFileSync(filePath, 'utf8')
// Find book title and author
const title = book.match(/^Title:\s(.+)$/m)[1]
const authorMatch = book.match(/^Author:\s(.+)$/m)
const author = (!authorMatch || authorMatch[1].trim() === '') ? 'Unknown Author' : authorMatch[1]
console.log(`Reading Book - ${title} By ${author}`)
// Find Guttenberg metadata header and footer
const startOfBookMatch = book.match(/^\*{3}\s*START OF (THIS|THE) PROJECT GUTENBERG EBOOK.+\*{3}$/m)
const startOfBookIndex = startOfBookMatch.index + startOfBookMatch[0].length
const endOfBookIndex = book.match(/^\*{3}\s*END OF (THIS|THE) PROJECT GUTENBERG EBOOK.+\*{3}$/m).index
// Clean book text and split into array of paragraphs
const paragraphs = book
.slice(startOfBookIndex, endOfBookIndex) // Remove Guttenberg header and footer
.split(/\n\s+\n/g) // Split each paragraph into it's own array entry
.map(line => line.replace(/\r\n/g, ' ').trim()) // Remove paragraph line breaks and whitespace
.map(line => line.replace(/_/g, '')) // Guttenberg uses "_" to signify italics. We'll remove it, since it makes the raw text look messy.
.filter((line) => (line && line.length !== '')) // Remove empty lines
console.log(`Parsed ${paragraphs.length} Paragraphs\n`)
return { title, author, paragraphs }
}
Эта функция выполняет следующие функции:
1. Прочитайте файл из файловой системы
2. Используйте регулярные выражения для извлечения названий книг и авторов
3. Извлеките содержимое книги, найдя ***
4. Разберите абзац
5. Очистить данные, удалить пустые строки
Наконец, возвращает объект, содержащий список названий книг, авторов и абзацев.
Запустив docker-compose up -d --build и docker exec gs-api "node" "server/load_data.js", вы получите следующий результат:
На этом этапе скрипт успешно разобрался с названием и автором книги, и скрипт тоже не сработает из-за той же проблемы (вызов функции, которая еще не определена)
4.5 Индексирование файлов данных в ES
Последний шаг — добавить функцию insertBookData в load_data.js, чтобы вставить извлеченные данные из предыдущего раздела в индекс Elasticsearch./** Bulk index the book data in Elasticsearch */
async function insertBookData (title, author, paragraphs) {
let bulkOps = [] // Array to store bulk operations
// Add an index operation for each section in the book
for (let i = 0; i < paragraphs.length; i++) {
// Describe action
bulkOps.push({ index: { _index: esConnection.index, _type: esConnection.type } })
// Add document
bulkOps.push({
author,
title,
location: i,
text: paragraphs[i]
})
if (i > 0 && i % 500 === 0) { // Do bulk insert in 500 paragraph batches
await esConnection.client.bulk({ body: bulkOps })
bulkOps = []
console.log(`Indexed Paragraphs ${i - 499} - ${i}`)
}
}
// Insert remainder of bulk ops array
await esConnection.client.bulk({ body: bulkOps })
console.log(`Indexed Paragraphs ${paragraphs.length - (bulkOps.length / 2)} - ${paragraphs.length}\n\n\n`)
}
Эта функция индексирует абзацы книги, включая информацию об авторе, названии и метаданных абзаца. Вставка абзацев с помощью массовой операции намного эффективнее, чем индексация абзацев по отдельности.
Массовое индексирование этих абзацев может привести к тому, что это приложение будет работать на слабом компьютере (у меня только 1,7 ГБ памяти), если у вас высокопроизводительный компьютер (более 4 ГБ контента), вам может не понадобиться рассматривать пакетные операции.Запуск docker-compose up -d --build и docker exec gs-api "node" "server/load_data.js" выводит следующее:
5 Поиск
Elasticsearch заполнен данными из 100 книг (около 230 000 абзацев), и в этом разделе будут выполняться некоторые поисковые операции.5.0 Простой http-запрос
Во-первых, используйтеhttp://localhost:9200/library/ ... retty, здесь используется ключевое слово полнотекстового запроса "Java", ввод должен быть следующим:{
"took" : 11,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 13,
"max_score" : 14.259304,
"hits" : [
{
"_index" : "library",
"_type" : "novel",
"_id" : "p_GwFWEBaZvLlaAUdQgV",
"_score" : 14.259304,
"_source" : {
"author" : "Charles Darwin",
"title" : "On the Origin of Species",
"location" : 1080,
"text" : "Java, plants of, 375."
}
},
{
"_index" : "library",
"_type" : "novel",
"_id" : "wfKwFWEBaZvLlaAUkjfk",
"_score" : 10.186235,
"_source" : {
"author" : "Edgar Allan Poe",
"title" : "The Works of Edgar Allan Poe",
"location" : 827,
"text" : "After many years spent in foreign travel, I sailed in the year 18-- , from the port of Batavia, in the rich and populous island of Java, on a voyage to the Archipelago of the Sunda islands. I went as passenger--having no other inducement than a kind of nervous restlessness which haunted me as a fiend."
}
},
...
]
}
}
HTTP-интерфейс Elasticsearch удобен для тестирования вставки данных, но опасен при прямом доступе к веб-приложениям. Операционные функции API (такие как добавление и удаление документов напрямую) не должны предоставляться непосредственно приложению, но должен быть написан простой API Node.js для получения клиентских запросов и их пересылки (через частную сеть) в Elasticsearch для запроса.
5.1 Сценарий запроса
В этом разделе описывается, как отправлять запросы в Elasticsearch из приложения Node.js. Сначала создайте новый файл: server/search.jsconst { client, index, type } = require('./connection')
module.exports = {
/** Query ES index for the provided term */
queryTerm (term, offset = 0) {
const body = {
from: offset,
query: { match: {
text: {
query: term,
operator: 'and',
fuzziness: 'auto'
} } },
highlight: { fields: { text: {} } }
}
return client.search({ index, type, body })
}
}
Этот модуль определяет простую функцию поиска, которая использует входную информацию для сопоставления запросов. Подробные поля объясняются следующим образом:
1. from: Отметьте номер страницы для результата. Каждый запрос по умолчанию возвращает 10 результатов, поэтому, указав from как 10, можно напрямую отобразить результаты запроса от 10 до 20.
2. запрос: конкретные ключевые слова запроса
3. оператор: конкретная операция запроса, в этом примере используется оператор «и», и предпочтительно отображаются результаты, содержащие все ключевые слова запроса.
4. нечеткость: уровень исправления орфографических ошибок (или уровень нечеткого запроса), по умолчанию 2. Чем выше значение, тем выше допустимая неоднозначность; например, значение 1 вернет результат Патрика на запрос Патрика.
5. основные моменты: возвращает дополнительную информацию, которая содержит отображаемую текстовую информацию в формате HTML.
Вы можете настроить эти параметры, чтобы увидеть конкретную информацию об отображении, вы можете проверитьElastic Full-Text Query DSLЧтобы получить больше информации:
6 API
В этом разделе представлен HTTP API, доступ к которому осуществляется с помощью внешнего кода.6.0 API Server
Измените содержимое server/app.js следующим образом:const Koa = require('koa')
const Router = require('koa-router')
const joi = require('joi')
const validate = require('koa-joi-validate')
const search = require('./search')
const app = new Koa()
const router = new Router()
// Log each request to the console
app.use(async (ctx, next) => {
const start = Date.now()
await next()
const ms = Date.now() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}`)
})
// Log percolated errors to the console
app.on('error', err => {
console.error('Server Error', err)
})
// Set permissive CORS header
app.use(async (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', '*')
return next()
})
// ADD ENDPOINTS HERE
const port = process.env.PORT || 3000
app
.use(router.routes())
.use(router.allowedMethods())
.listen(port, err => {
if (err) throw err
console.log(`App Listening on Port ${port}`)
})
Этот код импортирует среду зависимости службы дляKoa.jsNode API Server устанавливает простые механизмы регистрации и обработки ошибок.
6.1 Связывание конечных точек службы с запросами
В этом разделе добавляется конечная точка службы, которую серверная сторона предоставляет службе запросов Elasticsearch.Вставьте следующий код после //ДОБАВИТЬ КОНЕЧНЫЕ ТОЧКИ ЗДЕСЬ в server/app.js:
/**
* GET /search
* Search for a term in the library
*/
router.get('/search', async (ctx, next) => {
const { term, offset } = ctx.request.query
ctx.body = await search.queryTerm(term, offset)
}
)
Перезапустите сервер с помощью docker-compose up -d --build. В браузере вызовите эту службу. Например:http://localhost:3000/search?term=java
Возвращаемый результат должен выглядеть следующим образом:
{
"took": 242,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 93,
"max_score": 13.356944,
"hits": [{
"_index": "library",
"_type": "novel",
"_id": "eHYHJmEBpQg9B4622421",
"_score": 13.356944,
"_source": {
"author": "Charles Darwin",
"title": "On the Origin of Species",
"location": 1080,
"text": "Java, plants of, 375."
},
"highlight": {
"text": ["<em>Java</em>, plants of, 375."]
}
}, {
"_index": "library",
"_type": "novel",
"_id": "2HUHJmEBpQg9B462xdNg",
"_score": 9.030668,
"_source": {
"author": "Unknown Author",
"title": "The King James Bible",
"location": 186,
"text": "10:4 And the sons of Javan; Elishah, and Tarshish, Kittim, and Dodanim."
},
"highlight": {
"text": ["10:4 And the sons of <em>Javan</em>; Elishah, and Tarshish, Kittim, and Dodanim."]
}
}
...
]
}
}
6.2 Проверка ввода
В это время сервер еще очень хрупок.Далее проверяются входные параметры, отсеивается неверный или отсутствующий ввод и возвращается ошибка.Мы используем библиотеки Joi и Koa-Joi-Validate для этого типа проверки:
/**
* GET /search
* Search for a term in the library
* Query Params -
* term: string under 60 characters
* offset: positive integer
*/
router.get('/search',
validate({
query: {
term: joi.string().max(60).required(),
offset: joi.number().integer().min(0).default(0)
}
}),
async (ctx, next) => {
const { term, offset } = ctx.request.query
ctx.body = await search.queryTerm(term, offset)
}
)
Теперь, если вы перезапустите сервер и сделаете запрос с отсутствующими параметрами (http://localhost:3000/search) будет возвращена ошибка HTTP 400, например: Неверный URL-запрос — дочерний «термин» завершается ошибкой, поскольку [«термин» требуется].
Журналы можно просмотреть с помощью docker-compose logs -f api.
7 интерфейсных приложений
Аппаратное обеспечение сервера /search в порядке, в этом разделе написан простой интерфейсный API тестирования веб-приложений.7.1 Vue.js
В этом разделе для разработки внешнего интерфейса используется Vue.js. Создайте новый файл /public/app.js:const vm = new Vue ({
el: '#vue-instance',
data () {
return {
baseUrl: 'http://localhost:3000', // API url
searchTerm: 'Hello World', // Default search term
searchDebounce: null, // Timeout for search bar debounce
searchResults: [], // Displayed search results
numHits: null, // Total search results found
searchOffset: 0, // Search result pagination offset
selectedParagraph: null, // Selected paragraph object
bookOffset: 0, // Offset for book paragraphs being displayed
paragraphs: [] // Paragraphs being displayed in book preview window
}
},
async created () {
this.searchResults = await this.search() // Search for default term
},
methods: {
/** Debounce search input by 100 ms */
onSearchInput () {
clearTimeout(this.searchDebounce)
this.searchDebounce = setTimeout(async () => {
this.searchOffset = 0
this.searchResults = await this.search()
}, 100)
},
/** Call API to search for inputted term */
async search () {
const response = await axios.get(`${this.baseUrl}/search`, { params: { term: this.searchTerm, offset: this.searchOffset } })
this.numHits = response.data.hits.total
return response.data.hits.hits
},
/** Get next page of search results */
async nextResultsPage () {
if (this.numHits > 10) {
this.searchOffset += 10
if (this.searchOffset + 10 > this.numHits) { this.searchOffset = this.numHits - 10}
this.searchResults = await this.search()
document.documentElement.scrollTop = 0
}
},
/** Get previous page of search results */
async prevResultsPage () {
this.searchOffset -= 10
if (this.searchOffset < 0) { this.searchOffset = 0 }
this.searchResults = await this.search()
document.documentElement.scrollTop = 0
}
}
})
Приложение очень простое, просто определите некоторые общие свойства данных, добавьте метод получения и функцию подкачки результатов; интервал поиска установлен на 100 мс, чтобы предотвратить частые вызовы API.
Объяснение того, как работает Vue.js, выходит за рамки этой статьи, если вы хотите узнать об этом, вы можете проверить это.Официальная документация Vue.js.
7.2 HTML
Замените /public/index.html следующим:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Elastic Library</title>
<meta name="description" content="Literary Classic Search Engine.">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link href="https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.min.css" rel="stylesheet" type="text/css" />
<link href="https://cdn.muicss.com/mui-0.9.20/css/mui.min.css" rel="stylesheet" type="text/css" />
<link href="https://fonts.googleapis.com/css?family=EB+Garamond:400,700|Open+Sans" rel="stylesheet">
<link href="styles.css" rel="stylesheet" />
</head>
<body>
<div class="app-container" id="vue-instance">
<!-- Search Bar Header -->
<div class="mui-panel">
<div class="mui-textfield">
<input v-model="searchTerm" type="text" v-on:keyup="onSearchInput()">
<label>Search</label>
</div>
</div>
<!-- Search Metadata Card -->
<div class="mui-panel">
<div class="mui--text-headline">{{ numHits }} Hits</div>
<div class="mui--text-subhead">Displaying Results {{ searchOffset }} - {{ searchOffset + 9 }}</div>
</div>
<!-- Top Pagination Card -->
<div class="mui-panel pagination-panel">
<button class="mui-btn mui-btn--flat" v-on:click="prevResultsPage()">Prev Page</button>
<button class="mui-btn mui-btn--flat" v-on:click="nextResultsPage()">Next Page</button>
</div>
<!-- Search Results Card List -->
<div class="search-results" ref="searchResults">
<div class="mui-panel" v-for="hit in searchResults" v-on:click="showBookModal(hit)">
<div class="mui--text-title" v-html="hit.highlight.text[0]"></div>
<div class="mui-divider"></div>
<div class="mui--text-subhead">{{ hit._source.title }} - {{ hit._source.author }}</div>
<div class="mui--text-body2">Location {{ hit._source.location }}</div>
</div>
</div>
<!-- Bottom Pagination Card -->
<div class="mui-panel pagination-panel">
<button class="mui-btn mui-btn--flat" v-on:click="prevResultsPage()">Prev Page</button>
<button class="mui-btn mui-btn--flat" v-on:click="nextResultsPage()">Next Page</button>
</div>
<!-- INSERT BOOK MODAL HERE -->
</div>
<script src="https://cdn.muicss.com/mui-0.9.28/js/mui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.17.0/axios.min.js"></script>
<script src="app.js"></script>
</body>
</html>
7.3 CSS
Добавьте новый файл: /public/styles.css:body { font-family: 'EB Garamond', serif; }
.mui-textfield > input, .mui-btn, .mui--text-subhead, .mui-panel > .mui--text-headline {
font-family: 'Open Sans', sans-serif;
}
.all-caps { text-transform: uppercase; }
.app-container { padding: 16px; }
.search-results em { font-weight: bold; }
.book-modal > button { width: 100%; }
.search-results .mui-divider { margin: 14px 0; }
.search-results {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
}
.search-results > div {
flex-basis: 45%;
box-sizing: border-box;
cursor: pointer;
}
@media (max-width: 600px) {
.search-results > div { flex-basis: 100%; }
}
.paragraphs-container {
max-width: 800px;
margin: 0 auto;
margin-bottom: 48px;
}
.paragraphs-container .mui--text-body1, .paragraphs-container .mui--text-body2 {
font-size: 1.8rem;
line-height: 35px;
}
.book-modal {
width: 100%;
height: 100%;
padding: 40px 10%;
box-sizing: border-box;
margin: 0 auto;
background-color: white;
overflow-y: scroll;
position: fixed;
top: 0;
left: 0;
}
.pagination-panel {
display: flex;
justify-content: space-between;
}
.title-row {
display: flex;
justify-content: space-between;
align-items: flex-end;
}
@media (max-width: 600px) {
.title-row{
flex-direction: column;
text-align: center;
align-items: center
}
}
.locations-label {
text-align: center;
margin: 8px;
}
.modal-footer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
display: flex;
justify-content: space-around;
background: white;
}
7.4 Тестирование
Откройте localhost:8080, и вы увидите простую разбивку на страницы, возвращающую результаты. На этом этапе вы можете ввести несколько ключевых слов для тестирования запроса.Этот шаг не требует повторного запуска команды docker-compose up, чтобы изменения вступили в силу. Локальный общедоступный каталог монтируется непосредственно в контейнере сервера Ngnix, поэтому изменения данных локальной системы переднего плана напрямую отражаются в контейнеризованном приложении.
Если щелкнуть любой из выходов, эффекта не будет, а значит, есть еще какие-то функции, которые нужно добавить в приложение.
8-страничная проверка
Лучше всего щелкнуть любой из выходных данных, чтобы узнать, из какой книги взят контекст.8.0 Добавление запросов Elasticsearch
Во-первых, нам нужно определить простой запрос, который получает отрывки из данной книги. Добавьте следующее в module.exports в server/search.js:/** Get the specified range of paragraphs from a book */
getParagraphs (bookTitle, startLocation, endLocation) {
const filter = [
{ term: { title: bookTitle } },
{ range: { location: { gte: startLocation, lte: endLocation } } }
]
const body = {
size: endLocation - startLocation,
sort: { location: 'asc' },
query: { bool: { filter } }
}
return client.search({ index, type, body })
}
Эта функция вернет отсортированные абзацы данной книги.
8.1 Добавить сервисный порт API
Этот раздел свяжет функции предыдущего раздела с сервисным портом API. Добавьте следующее под исходным портом службы /search в server/app.js:/**
* GET /paragraphs
* Get a range of paragraphs from the specified book
* Query Params -
* bookTitle: string under 256 characters
* start: positive integer
* end: positive integer greater than start
*/
router.get('/paragraphs',
validate({
query: {
bookTitle: joi.string().max(256).required(),
start: joi.number().integer().min(0).default(0),
end: joi.number().integer().greater(joi.ref('start')).default(10)
}
}),
async (ctx, next) => {
const { bookTitle, start, end } = ctx.request.query
ctx.body = await search.getParagraphs(bookTitle, start, end)
}
)
8.2 Добавить пользовательский интерфейс
В этом разделе добавлена функция внешнего запроса и отображается вся страница информации в книге, содержащей содержимое запроса. Добавьте следующее в блок методов /public/app.js:/** Call the API to get current page of paragraphs */
async getParagraphs (bookTitle, offset) {
try {
this.bookOffset = offset
const start = this.bookOffset
const end = this.bookOffset + 10
const response = await axios.get(`${this.baseUrl}/paragraphs`, { params: { bookTitle, start, end } })
return response.data.hits.hits
} catch (err) {
console.error(err)
}
},
/** Get next page (next 10 paragraphs) of selected book */
async nextBookPage () {
this.$refs.bookModal.scrollTop = 0
this.paragraphs = await this.getParagraphs(this.selectedParagraph._source.title, this.bookOffset + 10)
},
/** Get previous page (previous 10 paragraphs) of selected book */
async prevBookPage () {
this.$refs.bookModal.scrollTop = 0
this.paragraphs = await this.getParagraphs(this.selectedParagraph._source.title, this.bookOffset - 10)
},
/** Display paragraphs from selected book in modal window */
async showBookModal (searchHit) {
try {
document.body.style.overflow = 'hidden'
this.selectedParagraph = searchHit
this.paragraphs = await this.getParagraphs(searchHit._source.title, searchHit._source.location - 5)
} catch (err) {
console.error(err)
}
},
/** Close the book detail modal */
closeBookModal () {
document.body.style.overflow = 'auto'
this.selectedParagraph = null
}
Вышеуказанные пять функциональных блоков обеспечивают логические операции по загрузке и листанию (отображение 10 абзацев на странице) в книге.
Добавьте код пользовательского интерфейса для отображения страницы книги под разделителем в /public/index.html следующим образом:
<!-- Book Paragraphs Modal Window -->
<div v-if="selectedParagraph" ref="bookModal" class="book-modal">
<div class="paragraphs-container">
<!-- Book Section Metadata -->
<div class="title-row">
<div class="mui--text-display2 all-caps">{{ selectedParagraph._source.title }}</div>
<div class="mui--text-display1">{{ selectedParagraph._source.author }}</div>
</div>
<br>
<div class="mui-divider"></div>
<div class="mui--text-subhead locations-label">Locations {{ bookOffset - 5 }} to {{ bookOffset + 5 }}</div>
<div class="mui-divider"></div>
<br>
<!-- Book Paragraphs -->
<div v-for="paragraph in paragraphs">
<div v-if="paragraph._source.location === selectedParagraph._source.location" class="mui--text-body2">
<strong>{{ paragraph._source.text }}</strong>
</div>
<div v-else class="mui--text-body1">
{{ paragraph._source.text }}
</div>
<br>
</div>
</div>
<!-- Book Pagination Footer -->
<div class="modal-footer">
<button class="mui-btn mui-btn--flat" v-on:click="prevBookPage()">Prev Page</button>
<button class="mui-btn mui-btn--flat" v-on:click="closeBookModal()">Close</button>
<button class="mui-btn mui-btn--flat" v-on:click="nextBookPage()">Next Page</button>
</div>
</div>
Перезапустите сервер приложений (docker-compose up -d --build) и откройте localhost:8080. На этом этапе, если вы нажмете на результат поиска, вы можете запросить контекст абзаца. Если вы заинтересованы в поиске результатов, вы можете даже прочитать запрос.
Поздравляем! ! На данный момент основная рама была построена. Ко всему вышеприведенному коду можно получить доступ изздесьполучить.
9 недостатков Elasticsearch
9.0 Потребление ресурсов
Elasticsearch — это приложение, потребляющее вычислительные ресурсы. Официальная рекомендация — запускать как минимум на устройствах с памятью более 64 ГБ, но не менее 8 ГБ. Elasticsearch — это база данных в памяти, поэтому запросы будут выполняться быстро, но при этом потреблять много памяти. В рабочей среде настоятельно рекомендуется запускать кластер Elasticsearch для обеспечения высокой доступности, автоматического сегментирования и избыточности данных.Я запускаю приведенный выше пример (search.patriktriest.com) на облачном устройстве объемом 1,7 ГБ (15 долларов в месяц), чего достаточно для запуска узлов Elasticsearch. Иногда весь узел зависает при начальной загрузке данных. По моему опыту, Elasticsearch потребляет больше ресурсов, чем традиционные PostgreSQL и MongoDB, и если вам нужно обеспечить идеальные результаты обслуживания, стоимость может быть очень высокой.
9.1 Синхронизация между базами данных
Для многих приложений хранение данных в Elasticsearch не идеально. Рекомендуется использовать ES в качестве транзакционной базы данных, но поскольку ES несовместима со стандартом ACID (при расширении системы для импорта данных это может вызвать проблему потерянных операций записи), поэтому не рекомендуется. Во многих сценариях ES играет совершенно особую роль, например полнотекстовый запрос, в котором необходимо скопировать определенные данные из основной базы данных в базу данных Elasticsearch.Например, предположим, что нам нужно хранить пользователей в таблице PostgreSQL, но использовать ES для обработки функции запроса пользователя. Если пользователь «Альберт» решит изменить имя на «Аль», это изменение необходимо будет внести как в основной репозиторий PostgreSQL, так и в кластер ES.
Эта операция несколько сложна и зависит от существующего программного стека. Существует множество доступных ресурсов с открытым исходным кодом, начиная от процесса мониторинга журналов операций MongoDB и автоматической синхронизации данных удаления с ES и заканчивая созданием пользовательских подключаемых модулей PostgreSQL, которые автоматически взаимодействуют с ES на основе индексов PSQL.
Если ни один из ранее упомянутых вариантов не работает, вы можете вручную обновить индекс Elasticsearch на основе изменений базы данных в коде на стороне сервера. Но я не думаю, что этот вариант лучший, потому что использование пользовательской бизнес-логики для синхронизации ES сложно и может привести к множеству ошибок.
Необходимость синхронизации Elasticsearch с основной базой данных является не столько слабостью ES, сколько вызвана сложной архитектурой; стоит подумать о добавлении в приложение выделенной поисковой системы, но есть и компромиссы, которые следует учитывать.
в заключении
Полнотекстовый поиск — важная функция современных приложений, но ее также трудно реализовать. Elasticsearch предоставляет реализацию для быстрого и индивидуального поиска, но есть и другие альтернативы. Apache Solr — еще одна аналогичная реализация с открытым исходным кодом, основанная на Apache Lucene (той же библиотеке, которая используется в ядре Elasticsearch). Algolia — это недавно активная веб-платформа модели «поиск как услуга», которую проще использовать новичкам (недостаток заключается в том, что настройка несильна, а более поздние инвестиции могут быть большими).Функциональность режима «панель поиска» — далеко не единственный вариант использования Elasticsearch. ES также является распространенным инструментом для хранения и анализа журналов, обычно используемым в архитектуре ELK (Elasticsearch, Logstash, Kibana). Гибкий полнотекстовый поиск, реализованный в ES, также полезен для задач специалистов по данным, таких как изменение, нормализация правописания наборов данных или поиск наборов данных.
Ниже приведены соображения по этому проекту:
1. Добавляйте в приложение больше любимых книг, создавайте личную библиотеку, ищите трудолюбие
2. Создайте механизм защиты от плагиата, индексируя научные статьи Google.
3. Создайте приложение для проверки орфографии, индексируя слова в словаре в ES.
4. Создайте свою собственную поисковую систему в Интернете, которая конкурирует с Google, загрузив Common Crawl Corpus в ES (обратите внимание, что существует 5 миллиардов страниц контента, что является очень большим набором данных).
5. Используйте Elasticsearch в журналистике: ищите названия функций и термины, например, в Panama papers и Paradise papers.
Весь код в этой статье является открытым исходным кодом и может быть найден в репозитории Github.ссылка для скачивания. Надеюсь, эта статья будет полезна для всех вас.