Elasticsearch je vyhledávač s json rest api, který používá Lucene a je napsaný v Javě. Popis všech výhod tohoto motoru je k dispozici na
Podobné nástroje se používají pro komplexní vyhledávání v databázi dokumentů. Například vyhledávání s přihlédnutím k morfologii jazyka nebo vyhledávání podle geo souřadnic.
V tomto článku budu hovořit o základech ES na příkladu indexování blogových příspěvků. Ukážu vám, jak dokumenty filtrovat, třídit a vyhledávat.
Abych nebyl závislý na operačním systému, budu všechny požadavky na ES provádět pomocí CURL. Existuje také plugin pro google chrome tzv
Text obsahuje odkazy na dokumentaci a další zdroje. Na konci jsou odkazy pro rychlý přístup k dokumentaci. Definice neznámých pojmů naleznete v
Instalace
K tomu potřebujeme nejprve Javu. Vývojáři
Distribuce ES je k dispozici na bin/elasticsearch
. Také dostupný
Po instalaci a spuštění zkontrolujeme funkčnost:
# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200
curl -X GET $ES_URL
Obdržíme něco takového:
{
"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"
}
Indexování
Přidáme příspěvek do 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"
}'
odpověď serveru:
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : false
}
ES automaticky vytvořeno
# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"
V odpovědi serveru jsem do komentářů přidal hodnoty polí indexovaného dokumentu:
{
"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"
}
}
}
}
}
}
Stojí za zmínku, že ES nerozlišuje mezi jednou hodnotou a polem hodnot. Například pole title jednoduše obsahuje titulek a pole tagů obsahuje pole řetězců, i když jsou v mapování reprezentovány stejným způsobem.
Více o mapování si povíme později.
žádosti
Načtení dokumentu podle jeho 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"
}
}
V odpovědi se objevily nové klíče: _version
и _source
. Obecně platí, že všechny klíče začínající na _
jsou klasifikovány jako oficiální.
Klíč _version
zobrazuje verzi dokumentu. Je potřeba, aby optimistický zamykací mechanismus fungoval. Například chceme změnit dokument, který má verzi 1. Odešleme změněný dokument a uvedeme, že se jedná o úpravu dokumentu s verzí 1. Pokud někdo jiný také upravoval dokument s verzí 1 a odeslal změny před námi, pak ES nepřijme naše změny, protože ukládá dokument s verzí 2.
Klíč _source
obsahuje dokument, který jsme indexovali. ES nepoužívá tuto hodnotu pro vyhledávací operace, protože K vyhledávání se používají indexy. Pro úsporu místa ES ukládá komprimovaný zdrojový dokument. Pokud potřebujeme pouze id a ne celý zdrojový dokument, můžeme zakázat zdrojové úložiště.
Pokud nepotřebujeme další informace, můžeme získat pouze obsah _source:
curl -XGET "$ES_URL/blog/post/1/_source?pretty"
{
"title" : "Веселые котята",
"content" : "<p>Смешная история про котят<p>",
"tags" : [ "котята", "смешная история" ],
"published_at" : "2014-09-12T20:44:42+00:00"
}
Můžete také vybrat pouze určitá pole:
# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Веселые котята"
}
}
Pojďme indexovat několik dalších příspěvků a spustit složitější dotazy.
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"
}'
Třídit
# найдем последний пост по дате публикации и извлечем поля 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 ]
} ]
}
}
Vybrali jsme poslední příspěvek. size
omezuje počet vydávaných dokladů. total
zobrazuje celkový počet dokumentů odpovídajících požadavku. sort
ve výstupu obsahuje pole celých čísel, podle kterých se provádí řazení. Tito. datum bylo převedeno na celé číslo. Více informací o třídění naleznete v
Filtry a dotazy
ES od verze 2 nerozlišuje mezi filtry a dotazy
Kontext dotazu se liší od kontextu filtru v tom, že dotaz generuje _score a neukládá se do mezipaměti. Později vám ukážu, co je _skóre.
Filtrovat podle data
Používáme žádost
# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"filter": {
"range": {
"published_at": { "gte": "2014-09-01" }
}
}
}'
Filtrování podle značek
Používáme
# найдем все документы, в поле 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" : [ "котята" ]
}
} ]
}
}
Fulltextové vyhledávání
Tři z našich dokumentů obsahují v poli obsahu následující:
<p>Смешная история про котят<p>
<p>Смешная история про щенков<p>
<p>Душераздирающая история про бедного котенка с улицы<p>
Používáme
# 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
} ]
}
}
Pokud však hledáme „příběhy“ v poli obsahu, nic nenajdeme, protože Rejstřík obsahuje pouze původní slova, nikoli jejich kmeny. Abyste mohli provádět vysoce kvalitní vyhledávání, musíte nakonfigurovat analyzátor.
Pole _score
ukazuje
Analyzátory
Analyzátory se skládají z jednoho
ES jich má několik
Pojďme použít
# используем анализатор 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
} ]
}
Standardní analyzátor rozdělil řetězec mezerami a vše převedl na malá písmena, ruský analyzátor odstranil nedůležitá slova, převedl je na malá písmena a ponechal kmen slov.
Podívejme se, který Tokenizer, TokenFilters, CharFilters používá ruský analyzátor:
{
"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 отсутствуют */
}
}
}
Pojďme si popsat náš analyzátor založený na ruštině, který vystřihne html tagy. Říkejme tomu výchozí, protože ve výchozím nastavení bude použit analyzátor s tímto názvem.
{
"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"
]
}
}
}
Nejprve budou ze zdrojového řetězce odstraněny všechny HTML tagy, poté jej standard tokenizer rozdělí na tokeny, výsledné tokeny se přesunou na malá písmena, nepodstatná slova budou odstraněna a zbývající tokeny zůstanou kmenem slova.
Vytvoření indexu
Výše jsme popsali výchozí analyzátor. Bude platit pro všechna pole řetězce. Náš příspěvek obsahuje pole tagů, takže tagy budou také zpracovány analyzátorem. Protože Hledáme příspěvky podle přesné shody se značkou, pak musíme zakázat analýzu pro pole značek.
Vytvořme index blog2 s analyzátorem a mapováním, ve kterém je analýza pole značek zakázána:
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"
}
}
}
}
}'
Pojďme přidat stejné 3 příspěvky do tohoto indexu (blog2). Tento proces vynechám, protože... je to podobné jako přidávání dokumentů do indexu blogu.
Fulltextové vyhledávání s podporou výrazů
Podívejme se na další typ žádosti:
# найдем документы, в которых встречается слово 'истории'
# 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"
]
}
}
}'
Protože Používáme analyzátor s ruským původem, pak tento požadavek vrátí všechny dokumenty, i když obsahují pouze slovo 'historie'.
Žádost může obsahovat speciální znaky, např.
""fried eggs" +(eggplant | potato) -frittata"
Syntaxe požadavku:
+ 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 поста про котиков
reference
PS
Pokud máte zájem o podobné články-lekce, nápady na nové články nebo návrhy na spolupráci, pak budu rád, když mi pošlete zprávu do osobní zprávy nebo emailu [chráněno e-mailem].
Zdroj: www.habr.com