Сегодня я демонстрирую использование
ElasticSearch — открытого, распределенного и масштабируемого полнотекстового поискового движка и инструмента анализа данных в веб-приложении Java.
Прежде всего, я хочу поделиться с вами видео записью. Я сделал это видео с целью визуального подтверждения проблем, с которыми я столкнулся в серии статей о ElasticSearch.
Наши данные будут храниться в системе управления базами данных в корпоративном приложении, где есть большой трафик и где одновременно входящие запросы могут быть выражены сотнями или даже тысячами. Однако маршрутизация всех этих данных в базу данных может создать риск узких мест. Основная цель этого руководства — минимизация операций ввода-вывода в базе данных, которые, скорее всего, создают риск узких мест (
особенно при работе с большими данными ) в веб-приложении с помощью инструмента, подобного ElasticSearch.
Приложение (
введение происходит в видео ) с Java API, разработанным для этой цели, выполняет некоторые операции, такие как отправка почтовых данных в ElasticSearch, получение, поиск (
полный текст ), а также операции обновления и удаления. ElasticSearch также работает как хранилище данных в этом приложении. Гипотетический пример, которому мы будем следовать, — это запись опубликованных статей на kodcu.com, которая была в предыдущих статьях.
Инструменты и технологии, которые я использовал в примере приложения:
- ElasticSearch версия 0.90.3
- JSF версия 2.2
- PrimeFaces версия 3.5
- Jetty 7.x Maven Плагин
- Maven версия 3.0.4
- JDK версия 1.7
<dependency> <groupId>org.glassfish</groupId> <artifactId>javax.faces</artifactId> <version>${jsf.version}</version> </dependency> <dependency> <groupId>org.primefaces</groupId> <artifactId>primefaces</artifactId> <version>${primeFaces.version}</version> </dependency> <dependency> <groupId>org.primefaces.themes</groupId> <artifactId>bootstrap</artifactId> <version>${primeFacesTheme.version}</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>${commonsFileUpload.version}</version> </dependency> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>${elastic.version}</version> </dependency> <dependency> <groupId>jboss</groupId> <artifactId>jboss-j2ee</artifactId> <version>${jbossJ2ee.version}</version> </dependency>3. ClientProvider.java
public class ClientProvider { private static ClientProvider instance = null; private static Object lock = new Object(); private Client client; private Node node; public static ClientProvider instance(){ if(instance == null) { synchronized (lock) { if(null == instance){ instance = new ClientProvider(); } } } return instance; } public void prepareClient(){ node = nodeBuilder().node(); client = node.client(); } public void closeNode(){ if(!node.isClosed()) node.close(); } public Client getClient(){ return client; } public void printThis() { System.out.println(this); } }Мы получим объект client еще раз, и мы будем выполнять все операции над ElasticSearch из верификатора клиента, который был закодирован с помощью
одноэлементного подхода, а не получать снова и снова перед каждой операцией.
4. ElasticSearchSystemEventListener.java
@Override public void processEvent(SystemEvent event) throws AbortProcessingException { if(event instanceof PostConstructApplicationEvent){ /* Preparing the ElasticSearch Client */ System.out.println("*********************************************"); System.out.println("Preparing the ElasticSearch Client"); ClientProvider.instance().prepareClient(); System.out.println("The ElasticSearch Client was prepared"); System.out.println("*********************************************"); } if(event instanceof PreDestroyApplicationEvent){ /* ElasticSearch node is closing */ System.out.println("*********************************************"); System.out.println("ElasticSearch Node is closing"); ClientProvider.instance().closeNode(); System.out.println("ElasticSearch Node was closed"); System.out.println("*********************************************"); } }Поскольку приложение было построено с использованием
JSF , мы использовали
реализацию SystemEventListener , которая является оптимальным вариантом для создания встроенного узла при возникновении приложения, запроса клиента с этого узла, а затем закрытия его по завершении приложения.
5. Перечисление данных в DataTable Чтобы в полной мере использовать функциональные возможности, предоставляемые
компонентом DataTable в PrimeFaces, документы, полученные с помощью API поиска, необходимо преобразовать в POJO. Эта операция выполняется вручную. Для этого можно использовать
библиотеку jackson-databind .
public void prepareDocumentList(){ wildCardQuery = ""; ClientProvider.instance().getClient() .admin().indices().prepareRefresh().execute().actionGet(); try { SearchResponse response = ClientProvider.instance().getClient() .prepareSearch(INDEX_NAME) .setTypes(TYPE_NAME) .setQuery(matchAllQuery()) .execute() .actionGet(); articleList.clear(); Article temporary = null; String[] tags = null; if (response != null) { for (SearchHit hit : response.getHits()) { try { tags = hit.getSource().get("tags").toString().split(","); temporary = new Article(Long.parseLong(hit.getSource().get("id").toString()), hit.getSource().get("title").toString(), hit.getSource().get("content").toString(), new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()).parse(hit.getSource().get("postDate").toString()), hit.getSource().get("author").toString(), tags); } catch (ParseException e) { e.printStackTrace(); } articleList.add(temporary); } } collectionSort(); } catch (IndexMissingException ex){ System.out.println("IndexMissingException: " + ex.toString()); } }6. Реестр и обновление
public void saveArticle() { Long ID = 1l; try { CountResponse countResponse= ClientProvider.instance().getClient() .prepareCount(INDEX_NAME) .setQuery(termQuery("_type", TYPE_NAME)) .execute().actionGet(); ID += countResponse.getCount(); } catch (IndexMissingException ex){ System.out.println("IndexMissingException: " + ex.toString()); } String[] postTags = tag.split(","); try { if(null == selectArticle) ClientProvider.instance().getClient().prepareIndex(INDEX_NAME, TYPE_NAME, ID.toString()) .setSource(putJsonDocument(ID, article.getTitle(), article.getContent(), article.getPostDate(), postTags, article.getAuthor())).execute().actionGet(); else { Map<String, Object> updateObject = new HashMap<String, Object>(); updateObject.put("title", selectArticle.getTitle()); updateObject.put("content", selectArticle.getContent()); updateObject.put("postDate", selectArticle.getPostDate()); updateObject.put("author", selectArticle.getAuthor()); updateObject.put("tags", postTags); ClientProvider.instance().getClient().prepareUpdate(INDEX_NAME, TYPE_NAME, selectArticle.getId().toString()) .setScript("ctx._source.title=title; ctx._source.content=content; " + "ctx._source.postDate=postDate; ctx._source.author=author; " + "ctx._source.tags=tags") .setScriptParams(updateObject).execute().actionGet(); } } catch (Exception ex){ FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Sorry, an error has occurred", ex.toString())); } prepareDocumentList(); initArticle(); }7. Удалить
public void removeArticle(){ try { ClientProvider.instance().getClient().prepareDelete(INDEX_NAME, TYPE_NAME, selectArticle.getId().toString()).execute().actionGet(); } catch (Exception ex){ FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Sorry, an error has occurred", ex.toString())); } prepareDocumentList(); initArticle(); }8. Полный текстовый источник и подстановочный знак
public void fullTextSearch(){ articleList.clear(); Article temporary = null; String[] tags = null; try { QueryBuilder queryBuilder = QueryBuilders.queryString("*"+wildCardQuery+"*").field("_all"); SearchRequestBuilder searchRequestBuilder = ClientProvider.instance().getClient().prepareSearch(INDEX_NAME); searchRequestBuilder.setTypes(TYPE_NAME); searchRequestBuilder.setSearchType(SearchType.DEFAULT); searchRequestBuilder.setQuery(queryBuilder); searchRequestBuilder.setFrom(0).setSize(60).setExplain(true); SearchResponse response = searchRequestBuilder.execute().actionGet(); if (response != null) { for (SearchHit hit : response.getHits()) { try { tags = hit.getSource().get("tags").toString().split(","); temporary = new Article(Long.parseLong(hit.getSource().get("id").toString()), hit.getSource().get("title").toString(), hit.getSource().get("content").toString(), new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()).parse(hit.getSource().get("postDate").toString()), hit.getSource().get("author").toString(), tags); } catch (ParseException e) { e.printStackTrace(); } articleList.add(temporary); } } collectionSort(); } catch (IndexMissingException ex){ System.out.println("IndexMissingException: " + ex.toString()); } }queryString , который использует анализатор запросов для анализа содержимого, позволяет вам указать поле, в котором
будет работать queryString , а также использовать
подстановочный знак . Для QueryString поле имеет значение
index.query.default_field, и по умолчанию задано «
_all », а строка запроса как таковая обрабатывается во всех полях. Подстановочный знак «
* » относится к любой строке символов, включая пустые.
9. Демо-приложение Применение:
ElasticSearch