Сегодня я демонстрирую использование
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
2.Dependencies
<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