Elasticsearch es un motor de búsqueda con json rest api, que utiliza Lucene y está escrito en Java. Una descripción de todas las ventajas de este motor está disponible en . En lo que sigue nos referiremos a Elasticsearch como ES.
Se utilizan motores similares para búsquedas complejas en una base de datos de documentos. Por ejemplo, buscar teniendo en cuenta la morfología del idioma o buscar por coordenadas geográficas.
En este artículo hablaré sobre los conceptos básicos de ES usando el ejemplo de indexación de publicaciones de blog. Le mostraré cómo filtrar, ordenar y buscar documentos.
Para no depender del sistema operativo, realizaré todas las solicitudes a ES mediante CURL. También hay un complemento para Google Chrome llamado .
El texto contiene enlaces a documentación y otras fuentes. Al final hay enlaces para un acceso rápido a la documentación. Las definiciones de términos desconocidos se pueden encontrar en .
Instalación de ES
Para hacer esto, primero necesitamos Java. Desarrolladores Instale versiones de Java más recientes que Java 8 actualización 20 o Java 7 actualización 55.
La distribución ES está disponible en . Después de descomprimir el archivo, debe ejecutar bin/elasticsearch. También disponible . Hay . .
Después de la instalación y el lanzamiento, verifiquemos la funcionalidad:
# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200
curl -X GET $ES_URLRecibiremos algo como esto:
{
"name" : "Heimdall",
"cluster_name" : "elasticsearch",
"version" : {
"number" : "2.2.1",
"build_hash" : "d045fc29d1932bce18b2e65ab8b297fbf6cd41a1",
"build_timestamp" : "2016-03-09T09:38:54Z",
"build_snapshot" : false,
"lucene_version" : "5.4.1"
},
"tagline" : "You Know, for Search"
}Indexación
Agreguemos una publicación a ES:
# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.
curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
"title": "Веселые котята",
"content": "<p>Смешная история про котят<p>",
"tags": [
"котята",
"смешная история"
],
"published_at": "2014-09-12T20:44:42+00:00"
}'
respuesta del servidor:
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : false
}
ES creado automáticamente blog y correo. Podemos hacer una analogía condicional: un índice es una base de datos y un tipo es una tabla en esta base de datos. Cada tipo tiene su propio esquema: , como una tabla relacional. El mapeo se genera automáticamente cuando se indexa el documento:
# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"En la respuesta del servidor, agregué los valores de los campos del documento indexado en los comentarios:
{
"blog" : {
"mappings" : {
"post" : {
"properties" : {
/* "content": "<p>Смешная история про котят<p>", */
"content" : {
"type" : "string"
},
/* "published_at": "2014-09-12T20:44:42+00:00" */
"published_at" : {
"type" : "date",
"format" : "strict_date_optional_time||epoch_millis"
},
/* "tags": ["котята", "смешная история"] */
"tags" : {
"type" : "string"
},
/* "title": "Веселые котята" */
"title" : {
"type" : "string"
}
}
}
}
}
}Vale la pena señalar que ES no diferencia entre un valor único y una matriz de valores. Por ejemplo, el campo de título simplemente contiene un título y el campo de etiquetas contiene una matriz de cadenas, aunque se representan de la misma manera en el mapeo.
Hablaremos más sobre el mapeo más adelante.
solicitudes
Recuperar un documento por su id:
# извлечем документ с id 1 типа post из индекса blog
curl -XGET "$ES_URL/blog/post/1?pretty"{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Веселые котята",
"content" : "<p>Смешная история про котят<p>",
"tags" : [ "котята", "смешная история" ],
"published_at" : "2014-09-12T20:44:42+00:00"
}
}Nuevas claves aparecieron en la respuesta: _version и _source. En general, todas las claves que comienzan con _ están clasificados como oficiales.
Llave _version muestra la versión del documento. Es necesario para que funcione el mecanismo de bloqueo optimista. Por ejemplo, queremos cambiar un documento que tiene la versión 1. Enviamos el documento modificado e indicamos que se trata de una edición de un documento con la versión 1. Si alguien más también editó un documento con la versión 1 y envió los cambios antes que nosotros, entonces ES no aceptará nuestros cambios, porque almacena el documento con la versión 2.
Llave _source contiene el documento que indexamos. ES no utiliza este valor para operaciones de búsqueda porque Los índices se utilizan para realizar búsquedas. Para ahorrar espacio, ES almacena un documento fuente comprimido. Si solo necesitamos la identificación y no el documento fuente completo, entonces podemos desactivar el almacenamiento de origen.
Si no necesitamos información adicional, solo podemos obtener el contenido de _source:
curl -XGET "$ES_URL/blog/post/1/_source?pretty"{
"title" : "Веселые котята",
"content" : "<p>Смешная история про котят<p>",
"tags" : [ "котята", "смешная история" ],
"published_at" : "2014-09-12T20:44:42+00:00"
}
También puede seleccionar solo ciertos campos:
# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Веселые котята"
}
}Indexemos algunas publicaciones más y ejecutemos consultas más complejas.
curl -XPUT "$ES_URL/blog/post/2" -d'
{
"title": "Веселые щенки",
"content": "<p>Смешная история про щенков<p>",
"tags": [
"щенки",
"смешная история"
],
"published_at": "2014-08-12T20:44:42+00:00"
}'curl -XPUT "$ES_URL/blog/post/3" -d'
{
"title": "Как у меня появился котенок",
"content": "<p>Душераздирающая история про бедного котенка с улицы<p>",
"tags": [
"котята"
],
"published_at": "2014-07-21T20:44:42+00:00"
}'Сортировка
# найдем последний пост по дате публикации и извлечем поля title и published_at
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"size": 1,
"_source": ["title", "published_at"],
"sort": [{"published_at": "desc"}]
}'{
"took" : 8,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : null,
"hits" : [ {
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_score" : null,
"_source" : {
"title" : "Веселые котята",
"published_at" : "2014-09-12T20:44:42+00:00"
},
"sort" : [ 1410554682000 ]
} ]
}
}Elegimos el último post. size limita el número de documentos a emitir. total muestra el número total de documentos que coinciden con la solicitud. sort en la salida contiene una matriz de números enteros mediante los cuales se realiza la clasificación. Aquellos. la fecha se convirtió a un número entero. Puede encontrar más información sobre la clasificación en .
Filtros y consultas
ES desde la versión 2 no distingue entre filtros y consultas, en cambio .
Un contexto de consulta se diferencia de un contexto de filtro en que la consulta genera una puntuación _score y no se almacena en caché. Te mostraré qué es _score más adelante.
Filtrar por fecha
Usamos la solicitud en el contexto del filtro:
# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"filter": {
"range": {
"published_at": { "gte": "2014-09-01" }
}
}
}'Filtrar por etiquetas
Uso para buscar identificadores de documentos que contengan una palabra determinada:
# найдем все документы, в поле tags которых есть элемент 'котята'
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"_source": [
"title",
"tags"
],
"filter": {
"term": {
"tags": "котята"
}
}
}'{
"took" : 9,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 2,
"max_score" : 1.0,
"hits" : [ {
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"title" : "Веселые котята",
"tags" : [ "котята", "смешная история" ]
}
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"title" : "Как у меня появился котенок",
"tags" : [ "котята" ]
}
} ]
}
}Búsqueda de texto completo
Tres de nuestros documentos contienen lo siguiente en el campo de contenido:
<p>Смешная история про котят<p><p>Смешная история про щенков<p><p>Душераздирающая история про бедного котенка с улицы<p>
Uso para buscar identificadores de documentos que contengan una palabra determinada:
# source: false означает, что не нужно извлекать _source найденных документов
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"_source": false,
"query": {
"match": {
"content": "история"
}
}
}'{
"took" : 13,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : 0.11506981,
"hits" : [ {
"_index" : "blog",
"_type" : "post",
"_id" : "2",
"_score" : 0.11506981
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_score" : 0.11506981
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "3",
"_score" : 0.095891505
} ]
}
}Sin embargo, si buscamos “historias” en el campo de contenido, no encontraremos nada, porque El índice contiene sólo las palabras originales, no sus raíces. Para realizar una búsqueda de alta calidad, debe configurar el analizador.
Campo _score espectáculos . Si la solicitud se ejecuta en un contexto de filtro, entonces el valor _score siempre será igual a 1, lo que significa una coincidencia completa con el filtro.
Analizadores
son necesarios para convertir el texto fuente en un conjunto de tokens.
Los analizadores constan de uno y varios opcionales . El tokenizador puede ir precedido de varios . Los tokenizadores dividen la cadena de origen en tokens, como espacios y caracteres de puntuación. TokenFilter puede cambiar tokens, eliminar o agregar otros nuevos, por ejemplo, dejar solo la raíz de la palabra, eliminar preposiciones, agregar sinónimos. CharFilter: cambia toda la cadena fuente, por ejemplo, recorta etiquetas html.
ES tiene varios . Por ejemplo, un analizador .
usemos y veamos cómo los analizadores estándar y ruso transforman la cadena “Historias divertidas sobre gatitos”:
# используем анализатор standard
# обязательно нужно перекодировать не ASCII символы
curl -XGET "$ES_URL/_analyze?pretty&analyzer=standard&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"{
"tokens" : [ {
"token" : "веселые",
"start_offset" : 0,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 0
}, {
"token" : "истории",
"start_offset" : 8,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 1
}, {
"token" : "про",
"start_offset" : 16,
"end_offset" : 19,
"type" : "<ALPHANUM>",
"position" : 2
}, {
"token" : "котят",
"start_offset" : 20,
"end_offset" : 25,
"type" : "<ALPHANUM>",
"position" : 3
} ]
}# используем анализатор russian
curl -XGET "$ES_URL/_analyze?pretty&analyzer=russian&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"{
"tokens" : [ {
"token" : "весел",
"start_offset" : 0,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 0
}, {
"token" : "истор",
"start_offset" : 8,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 1
}, {
"token" : "кот",
"start_offset" : 20,
"end_offset" : 25,
"type" : "<ALPHANUM>",
"position" : 3
} ]
}El analizador estándar dividió la cadena por espacios y convirtió todo a minúsculas, el analizador ruso eliminó las palabras sin importancia, las convirtió a minúsculas y dejó la raíz de las palabras.
Veamos qué Tokenizer, TokenFilters, CharFilters utiliza el analizador ruso:
{
"filter": {
"russian_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"russian_keywords": {
"type": "keyword_marker",
"keywords": []
},
"russian_stemmer": {
"type": "stemmer",
"language": "russian"
}
},
"analyzer": {
"russian": {
"tokenizer": "standard",
/* TokenFilters */
"filter": [
"lowercase",
"russian_stop",
"russian_keywords",
"russian_stemmer"
]
/* CharFilters отсутствуют */
}
}
}Describamos nuestro analizador basado en ruso, que recortará etiquetas html. Llamémoslo predeterminado, porque De forma predeterminada se utilizará un analizador con este nombre.
{
"filter": {
"ru_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"ru_stemmer": {
"type": "stemmer",
"language": "russian"
}
},
"analyzer": {
"default": {
/* добавляем удаление html тегов */
"char_filter": ["html_strip"],
"tokenizer": "standard",
"filter": [
"lowercase",
"ru_stop",
"ru_stemmer"
]
}
}
}Primero, todas las etiquetas HTML se eliminarán de la cadena de origen, luego el estándar del tokenizador la dividirá en tokens, los tokens resultantes se moverán a minúsculas, las palabras insignificantes se eliminarán y los tokens restantes seguirán siendo la raíz de la palabra.
Creando un índice
Arriba describimos el analizador predeterminado. Se aplicará a todos los campos de cadena. Nuestra publicación contiene una serie de etiquetas, por lo que el analizador también procesará las etiquetas. Porque Buscamos publicaciones por coincidencia exacta con una etiqueta, luego debemos desactivar el análisis para el campo de etiquetas.
Creemos un blog2 de índice con analizador y mapeo, en el que el análisis del campo etiquetas esté deshabilitado:
curl -XPOST "$ES_URL/blog2" -d'
{
"settings": {
"analysis": {
"filter": {
"ru_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"ru_stemmer": {
"type": "stemmer",
"language": "russian"
}
},
"analyzer": {
"default": {
"char_filter": [
"html_strip"
],
"tokenizer": "standard",
"filter": [
"lowercase",
"ru_stop",
"ru_stemmer"
]
}
}
}
},
"mappings": {
"post": {
"properties": {
"content": {
"type": "string"
},
"published_at": {
"type": "date"
},
"tags": {
"type": "string",
"index": "not_analyzed"
},
"title": {
"type": "string"
}
}
}
}
}'Agreguemos las mismas 3 publicaciones a este índice (blog2). Omitiré este proceso porque... es similar a agregar documentos al índice del blog.
Búsqueda de texto completo con soporte de expresiones.
Veamos otro tipo de solicitud:
# найдем документы, в которых встречается слово 'истории'
# query -> simple_query_string -> query содержит поисковый запрос
# поле title имеет приоритет 3
# поле tags имеет приоритет 2
# поле content имеет приоритет 1
# приоритет используется при ранжировании результатов
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
"query": {
"simple_query_string": {
"query": "истории",
"fields": [
"title^3",
"tags^2",
"content"
]
}
}
}'Porque Estamos utilizando un analizador con derivación rusa, entonces esta solicitud devolverá todos los documentos, aunque solo contengan la palabra "historia".
La solicitud puede contener caracteres especiales, por ejemplo:
""fried eggs" +(eggplant | potato) -frittata"Sintaxis de solicitud:
+ signifies AND operation
| signifies OR operation
- negates a single token
" wraps a number of tokens to signify a phrase for searching
* at the end of a term signifies a prefix query
( and ) signify precedence
~N after a word signifies edit distance (fuzziness)
~N after a phrase signifies slop amount# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
"query": {
"simple_query_string": {
"query": "-щенки",
"fields": [
"title^3",
"tags^2",
"content"
]
}
}
}'
# получим 2 поста про котиковreferencias
PS
Si está interesado en este tipo de artículos-lecciones, tiene ideas para nuevos artículos o tiene propuestas de cooperación, estaré encantado de recibir un mensaje en un mensaje personal o por correo electrónico a m.kuzmin+habr@darkleaf.ru.
Fuente: habr.com
