Elasticsearch — vyhľadávací nástroj s json rest api, ktorý používa Lucene a je napísaný v Jave. Popis všetkých výhod tohto nástroja je dostupný na . V texte budeme nazývať Elasticsearch jednoducho ES.
Podobné nástroje sa používajú pri zložitom vyhľadávaní v databáze dokumentov. Napríklad vyhľadávanie s ohľadom na morfológiu jazyka alebo vyhľadávanie podľa geo súradníc.
V tomto článku vám predstavím základy ES na príklade indexovania príspevkov blogu. Ukážem, ako filtrovať, triediť a vyhľadávať dokumenty.
Aby som nebol závislý od operačného systému, všetky požiadavky na ES budem vykonávať pomocou CURL. Existuje aj plugin pre Google Chrome s názvom .
V texte sú umiestnené odkazy na dokumentáciu a ďalšie zdroje. Na konci sú umiestnené odkazy na rýchly prístup k dokumentácii. Definície neznámych termínov si môžete prečítať v .
Inštalácia ES
Na to budeme najprv potrebovať Javu. Vývojári musí nainštalovať verzie Javy novšie ako Java 8 update 20 alebo Java 7 update 55.
Distribúcia ES je dostupná na . Po rozbalení archívu je potrebné spustiť bin/elasticsearch. Tiež sú k dispozícii . Existuje . .
Po inštalácii a spustení overíme funkčnosť:
# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200
curl -X GET $ES_URLObdržíme približne takúto odpoveď:
{
"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"
}Indexovanie
Pridáme príspevok do ES:
# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.
curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
"title": "Веселые котята",
"content": "<p>Vtipný príbeh o mačiatkach<p>",
"tags": [
"mačiatka",
"vtipný príbeh"
],
"published_at": "2014-09-12T20:44:42+00:00"
}'
odpoveď servera:
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : false
}
ES automaticky vytvoril blog a post. Môžeme vykonať analógiu: index — to je databáza, a typ — tabuľka v tejto databáze. Každý typ má svoj vlastný schému — , rovnako ako v relačnej tabuľke. Mapping sa automaticky generuje pri indexovaní dokumentu:
# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"V odpovedi servera som pridal v komentároch hodnoty polí oindexovaného dokumentu:
{
"blog" : {
"mappings" : {
"post" : {
"properties" : {
/* "content": "<p>Vtipný príbeh o mačiatkach<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": ["mačky", "smiešna história"] */
"tags" : {
"type" : "string"
},
/* "title": "Zábavné mačky" */
"title" : {
"type" : "string"
}
}
}
}
}
}Je potrebné poznamenať, že ES nerobí rozdiely medzi jednou hodnotou a poľom hodnôt. Napríklad pole title obsahuje jednoducho nadpis, zatiaľ čo pole tags — pole reťazcov, aj keď sú v mappingu zobrazené rovnako.
Neskôr budeme hovoriť o mappingu podrobnejšie.
Dotazy
Získanie dokumentu podľa 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": "\u0412\u0435\u0441\u0435\u043b\u044b\u0435 \u043a\u043e\u0442\u044f\u0442\u0430",
"content": "<p>Vtipn\u00fd pr\u00edbeh o ma\u010diatkach<p>",
"tags": [
"\u043a\u043e\u0442\u044f\u0442\u0430",
"\u0441\u043c\u0435\u0448\u043d\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f"
],
"published_at": "2014-09-12T20:44:42+00:00"
}
}Vo výpise sa objavili nové kľúče: _version a _source. Všeobecne, všetky kľúče, ktoré začínajú na _ sú služobné.
Parameter _version zobrazuje verziu dokumentu. Je potrebná na fungovanie mechanizmu optimistických zámkov. Napríklad, chceme zmeniť dokument s verziou 1. Odosielame zmenený dokument a uvádzame, že ide o úpravu dokumentu s verziou 1. Ak niekto iný tiež upravoval dokument s verziou 1 a odoslal zmeny pred nami, ES neprijme naše zmeny, pretože uchováva dokument s verziou 2.
Parameter _source obsahuje dokument, ktorý sme indexovali. ES túto hodnotu nepoužíva na vyhľadávacie operácie, pretože na vyhľadávanie sa používajú indexy. Na úsporu miesta ES uchováva komprimovaný pôvodný dokument. Ak potrebujeme len id a nie celý pôvodný dokument, je možné vypnúť uchovávanie pôvodného zdroja.
Ak nepotrebujeme ďalšie informácie, môžeme získať len obsah _source:
curl -XGET "$ES_URL/blog/post/1/_source?pretty"{
"title": "\u0412\u0435\u0441\u0435\u043b\u044b\u0435 \u043a\u043e\u0442\u044f\u0442\u0430",
"content": "<p>Vtipn\u00fd pr\u00edbeh o ma\u010diatkach<p>",
"tags": [
"\u043a\u043e\u0442\u044f\u0442\u0430",
"\u0441\u043c\u0435\u0448\u043d\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f"
],
"published_at": "2014-09-12T20:44:42+00:00"
}
Môžeme si tiež vybrať len určité polia:
# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Veselé mačky"
}
}Poďme indexovať niekoľko ďalších príspevkov a vykonať zložitejšie dopyty.
curl -XPUT "$ES_URL/blog/post/2" -d'
{
"title": "Zábavné šteniatka",
"content": "<p>Vtipný príbeh o šteniatkach<p>",
"tags": [
"šteniatka",
"vtipný príbeh"
],
"published_at": "2014-08-12T20:44:42+00:00"
}'curl -XPUT "$ES_URL/blog/post/3" -d'
{
"title": "Ako sa mi objavil mačiatko",
"content": "<p>Srdcervúci príbeh o chudobnom mačiatku z ulice<p>",
"tags": [
"mačiatka"
],
"published_at": "2014-07-21T20:44:42+00:00"
}'Zoradenie
# найдем последний пост по дате публикации и извлечем поля 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" : "Veselé mačky",
"published_at" : "2014-09-12T20:44:42+00:00"
},
"sort" : [ 1410554682000 ]
} ]
}
}Vybrali sme posledný príspevok. size obmedzuje počet dokumentov vo výstupoch. total ukazuje celkový počet dokumentov, ktoré vyhovujú dopytu. sort vo výstupu obsahuje pole celých čísel, podľa ktorých sa vykonáva triedenie. T.j. dátum sa premenil na celé číslo. Viac o triedení si môžete prečítať v .
Filtre a dopyty
ES od verzie 2 nerozlišuje filtre a dopyty, namiesto toho .
Kontext dopytu sa odlišuje od kontextu filtra tým, že dopyt generuje _score a nie je uložený do cache. Čo je _score ukážem neskôr.
Filtrovanie podľa dátumu
Používame dopyt v kontexte filter:
# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"filter": {
"range": {
"published_at": { "gte": "2014-09-01" }
}
}
}'Filtrácia podľa tagov
Používame na vyhľadávanie id dokumentov, obsahujúcich zadané slovo:
# найдем все документы, в поле 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" : "Zábavné mačky",
"tags" : [ "mačky", "smiešna história" ]
}
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"title" : "Ako som dostal mačiatko",
"tags" : [ "mačky" ]
}
} ]
}
}Plnotextové vyhľadávanie
Tri naše dokumenty obsahujú v poli content nasledujúce:
<p>Vtipný príbeh o mačiatkach<p><p>Vtipný príbeh o šteniatkach<p><p>Srdcervúci príbeh o chudobnom mačiatku z ulice<p>
Používame na vyhľadávanie id dokumentov, obsahujúcich zadané slovo:
# 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
} ]
}
}Ak však hľadáme „príbehy“ v poli obsah, nič nenájdeme, pretože v indexe sú len pôvodné slová, nie ich základy. Pre kvalitné vyhľadávanie je potrebné nastaviť analyzátor.
Pole _score zobrazuje . Ak je dotaz vykonaný v kontexte filtra, hodnota _score bude vždy rovná 1, čo znamená úplnú zhodu s filtrom.
Analyzátory
sú potrebné na pretransformovanie vstupného textu na súbor tokenov.
Analyzátory sa skladajú z jedného a niekoľkých voliteľných . Tokenizer môže predchádzať niekoľkým . Tokenizer rozdeľuje pôvodný reťazec na tokeny, napríklad podľa medzier a interpunkčných znamienok. TokenFilter môže upravovať tokeny, odstraňovať alebo pridávať nové, napríklad nechať len základ slova, odstraňovať predložky, pridávať synonyme. CharFilter mení celý pôvodný reťazec, napríklad odstraňuje HTML tagy.
V ES je niekoľko . Napríklad analyzátor .
Využijeme a pozrime sa, ako analyzátory standard a russian transformujú reťazec „Veselé príbehy o mačkách“:
# используем анализатор 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" : "veselé",
"start_offset" : 0,
"end_offset" : 7,
"type" : "",
"position" : 0
}, {
"token" : "príbehy",
"start_offset" : 8,
"end_offset" : 15,
"type" : "",
"position" : 1
}, {
"token" : "o",
"start_offset" : 16,
"end_offset" : 18,
"type" : "",
"position" : 2
}, {
"token" : "mačkách",
"start_offset" : 19,
"end_offset" : 26,
"type" : "",
"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" : "vesel",
"start_offset" : 0,
"end_offset" : 7,
"type" : "",
"position" : 0
}, {
"token" : "histor",
"start_offset" : 8,
"end_offset" : 15,
"type" : "",
"position" : 1
}, {
"token" : "kot",
"start_offset" : 20,
"end_offset" : 25,
"type" : "",
"position" : 3
} ]
}Štandardný analyzátor rozdelil reťazec podľa medzier a previedol všetko na malé písmená, analyzátor ruský — odstránil bezvýznamné slová, previedol na malé písmená a ponechal základné slová.
Pozrime sa, aké Tokenizer, TokenFilters, CharFilters používa analyzátor ruský:
{
"filter": {
"russian_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"russian_keywords": {
"type": "keyword_marker",
"keywords": []
},
"russian_stemmer": {
"type": "stemmer",
"language": "russian"
}
},
"analyzer": {
"russian": {
"tokenizer": "standard",
"filter": [
"lowercase",
"russian_stop",
"russian_keywords",
"russian_stemmer"
]
}
}
}Popíšeme svoj analyzátor na základe ruského, ktorý bude odstraňovať html tagy. Nazvime ho default, pretože analyzátor s týmto názvom sa bude používať predvolene.
{
"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"
]
}
}
}Najprv sa zo vstupného reťazca odstránia všetky html tagy, potom ho analyzátor standard rozdelí na tokeny, získané tokeny prejdú na malé písmená, bezvýznamné slová sa odstránia a zostanú len základy slov z zostávajúcich tokenov.
Vytvorenie indexu
Vyššie sme opísali default analyzátor. Ten sa použije na všetky textové polia. Náš príspevok obsahuje pole tagov, a preto budú tagy tiež spracované analyzátorom. Keďže hľadáme príspevky podľa presného zodpovedania tagu, je potrebné vypnúť analýzu pre pole tags.
Vytvorme index blog2 s analyzátorom a mapovaním, v ktorom je vypnutá analýza poľa tags:
curl -XPOST "$ES_URL/blog2" -d'
{
"settings": {
"analysis": {
"filter": {
"sk_stop": {
"type": "stop",
"stopwords": "_slovak_"
},
"sk_stemmer": {
"type": "stemmer",
"language": "slovak"
}
},
"analyzer": {
"default": {
"char_filter": [
"html_strip"
],
"tokenizer": "standard",
"filter": [
"lowercase",
"sk_stop",
"sk_stemmer"
]
}
}
}
},
"mappings": {
"post": {
"properties": {
"content": {
"type": "string"
},
"published_at": {
"type": "date"
},
"tags": {
"type": "string",
"index": "not_analyzed"
},
"title": {
"type": "string"
}
}
}
}
}'Pridáme rovnaké 3 príspevky do tohto indexu (blog2). Tento proces preskočím, pretože je podobný pridaniu dokumentov do indexu blog.
Fulltextové vyhľadávanie s podporou výrazov
Zošívame sa s ďalším typom dopytov:
# найдем документы, в которых встречается слово 'истории'
# 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"
]
}
}
}'Keďže používame analyzátor s slovenským stemmingom, tento dopyt vráti všetky dokumenty, aj keď sa v nich nachádza iba slovo 'história'.
Dopyt môže obsahovať špeciálne znaky, ako napríklad:
""fried eggs" +(baklažán | zemiak) -frittata"Syntax dopytu:
+ znamená operáciu AND
| znamená operáciu OR
- neguje jediné token
" obklopuje množstvo tokenov s cieľom označiť frázu na vyhľadávanie
* na konci termínu označuje prefixový dopyt
( a ) označujú precedence
~N za slovom označuje edit vzdialenosť (fuzzy)
~N za frázou označuje množstvo slopu# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
"query": {
"simple_query_string": {
"query": "-щенки",
"fields": [
"title^3",
"tags^2",
"content"
]
}
}
}'
# получим 2 поста про котиковOdkazy
PS
Ak máte záujem o podobné články-lekcie, máte nápady na nové články alebo máte návrhy na spoluprácu, budem rád za správu do súkromných správ alebo na e-mail m.kuzmin+habr@darkleaf.ru.
Zdroj: habr.com
