Elasticsearch — otsingumootor, millel on json rest-API, mis kasutab Lucene'i ja on kirjutatud Java keeles. Selle mootori kõigi eeliste kirjeldus on saadaval . Edaspidi nimetame Elasticsearch'i lühidalt ES-iks.
Taolisi mootoreid kasutatakse keeruliste otsingute jaoks dokumentide andmebaasis. Näiteks keele morfoloogia arvestav otsing või geo-koordinaatide järgi otsimine.
Selles artiklis räägin ES-i põhialustest blogipostituste indikeerimise näitel. Näitan, kuidas filtreerida, sorteerida ja otsida dokumente.
Kuna ma ei soovi sõltuda operatsioonisüsteemist, teen kõik päringud ES-ile CURL-i abil. Samuti on olemas Google Chrome'i pistikprogramm nimega .
Artiklis on lingid dokumentatsioonile ja muudele allikatele. Lõpus on kiireks juurdepääsuks lingid dokumentatsioonile. Tundmatute terminite määratlusi saab lugeda .
ES-i paigaldamine
Selleks vajame esmalt Java-d. Arendajad peavad paigaldama Java versioonid, mis on uuemad kui Java 8 update 20 või Java 7 update 55.
ES-i distributsioon on saadaval . Pärast arhiivi lahtipakkimist tuleb käivitada bin/elasticsearch. Samuti on saadaval . On olemas . .
Pärast paigaldamist ja käivitamist kontrollime tööd:
# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200
curl -X GET $ES_URLSaame umbkaudu sellise vastuse:
{
"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"
}Indekseerimine
Lisame postituse ES-i:
# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.
curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
"title": "Веселые котята",
"content": "<p>Naljakas lugu kassipoegadest<p>",
"tags": [
"kassipojad",
"naljakas lugu"
],
"published_at": "2014-09-12T20:44:42+00:00"
}'
serveri vastus:
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : false
}
ES on automaatselt loonud blog ja post. Saame teha tingimusliku analoogia: indeks on andmebaas ja tüüp on tabel selles andmebaasis. Igal tüübil on oma skeem — , samuti nagu rikkaalne tabel. Mapping genereeritakse automaatselt dokumendi indekseerimisel:
# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"Serveri vastuses olen lisanud kommentaaridesse väärtused indekseeritud dokumendi väljadest:
{
"blog" : {
"mappings" : {
"post" : {
"properties" : {
/* "content": "<p>Naljakas lugu kassipoegadest<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"
}
}
}
}
}
}Tasub märkida, et ES ei tee vahet üksikväärtuse ja väärtuste massiivi vahel. Näiteks väli title sisaldab lihtsalt pealkirja, samas kui väli tags — on stringide massiiv, kuigi need on mappingus esitatud sarnaselt.
Hiljem räägime mapping'ust rohkem.
Päringud
Dokumendi väljavõtmine selle ID järgi:
# извлечем документ с 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>Naljakas lugu kassipoegadest<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"
}
}Vastuses ilmusid uued võtmed: _version ja _source. Üldiselt kuuluvad kõik võtmed, mis algavad _ , teenuslikud.
Võti _version näitab dokumendi versiooni. See on vajalik optimistlike lukustamise mehhanismi tööks. Näiteks tahame muuta dokumenti, mille versioon on 1. Saadame muudetud dokumendi ja märkime, et see on redaktsioon dokumendist versiooniga 1. Kui keegi teine on ka redigeerinud dokumenti versiooniga 1 ja saatsid muudatused enne meid, siis ES ei aktsepteeri meie muudatusi, kuna ta salvestab dokumendi versiooniga 2.
Võti _source sisaldab dokumendi, mille me oleme indekseerinud. ES ei kasuta seda väärtust otsimisoperatsioonide jaoks, kuna otsingu jaoks kasutatakse indekseid. Ruumi kokkuhoidmiseks hoiab ES komprimeeritud algdokumenti. Kui me vajame ainult id-d, mitte kogu algdokument, saame algdokumentide salvestamise välja lülitada.
Kui me ei vaja täiendavat teavet, saame kätte ainult _source sisu:
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>Naljakas lugu kassipoegadest<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"
}
Samuti saab valida ainult teatud väljad:
# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Looduslikud kassipojad"
}
}Indekseerime veel mõned postitused ja teeme keerulisemaid päringuid.
curl -XPUT "$ES_URL/blog/post/2" -d'
{
"title": "Lõbusad kutsikad",
"content": "<p>Naljakas lugu kutsikatest<p>",
"tags": [
"kutsikad",
"naljakas lugu"
],
"published_at": "2014-08-12T20:44:42+00:00"
}'curl -XPUT "$ES_URL/blog/post/3" -d'
{
"title": "Kuidas mu kassipoeg ilmus",
"content": "<p>Kurblik lugu vaesest tänavakassipojast<p>",
"tags": [
"kassipojad"
],
"published_at": "2014-07-21T20:44:42+00:00"
}'Sorteerimine
# найдем последний пост по дате публикации и извлечем поля 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" : "Looduslikud kassipojad",
"published_at" : "2014-09-12T20:44:42+00:00"
},
"sort" : [ 1410554682000 ]
} ]
}
}Valisime viimase postituse. size piirab dokumentide arvu väljastuses. total näitab dokumentide koguarvu, mis vastavad päringule. sort väljastuses sisaldab terve arvu massiivi, mille alusel toimub sortimine. See tähendab, et kuupäev muudeti täisarvuks. Lisateavet sortimise kohta saab lugeda .
Filtrid ja päringud
ES versioonist 2 ei erista filtreid ja päringuid, selle asemel .
Päringu kontekst erineb filtrite kontekstist selle poolest, et päring genereerib _score ja ei salvestata vahemällu. Mis on _score, näitan hiljem.
Kuupäeva filtreerimine
Kasutame päringut filtri kontekstis:
# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"filter": {
"range": {
"published_at": { "gte": "2014-09-01" }
}
}
}'Filtreerimine silte mööda
Kasutame dokumentide id leidmiseks, mis sisaldavad antud sõna:
# найдем все документы, в поле 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" : "Lõbusad kassipojad",
"tags" : [ "kassipojad", "naljakas lugu" ]
}
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"title" : "Kuidas ma kassipoja sain",
"tags" : [ "kassipojad" ]
}
} ]
}
}Täisteksti otsing
Meie kolme dokumendi sisu sisaldab järgmist:
<p>Naljakas lugu kassipoegadest<p><p>Naljakas lugu kutsikatest<p><p>Kurblik lugu vaesest tänavakassipojast<p>
Kasutame dokumentide id leidmiseks, mis sisaldavad antud sõna:
# 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
} ]
}
}Kuid kui otsida 'lood' sisu väljas, ei leia me midagi, kuna indeks sisaldab ainult originaalsõnu, mitte nende juure. Kvaliteetse otsingu tegemiseks tuleb seadistada analüsaator.
Väli _score teavitust akna mõõtmetest. . Kui päring toimub filter konteksis, siis väärtus _score on alati 1, mis näitab täiuslikku vastavust filtrile.
Analüsaatorid
on vajalikud, et muuta algtekst tokenite kogumiks.
Analüsaatorid koosnevad ühest ja mitmest valikulisest . Tokenizer võib eelnevalt olla mitme . Tokenizer jagab algstringi tokeniteks, näiteks tühikute ja kirjavahemärkide järgi. TokenFilter võib muuta, eemaldada või lisada uusi tokeneid, näiteks jätta alles ainult sõna juure, eemaldada eesliiteid, lisada sünonüüme. CharFilter muudab tervet algstringi, näiteks lõikab HTML-märgistusi.
ES-is on mitmeid . Näiteks analüsaator .
Kasutame ja vaatame, kuidas analüsaatorid standard ja russian muudavad stringi ‘Lõbusad lood kassipojad’:
# используем анализатор 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" : "lõbusad",
"start_offset" : 0,
"end_offset" : 7,
"type" : "",
"position" : 0
}, {
"token" : "lood",
"start_offset" : 8,
"end_offset" : 14,
"type" : "",
"position" : 1
}, {
"token" : "kassipojad",
"start_offset" : 15,
"end_offset" : 25,
"type" : "",
"position" : 2
} ]
}# используем анализатор 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" : "naljakas",
"start_offset" : 0,
"end_offset" : 7,
"type" : "",
"position" : 0
}, {
"token" : "lugu",
"start_offset" : 8,
"end_offset" : 15,
"type" : "",
"position" : 1
}, {
"token" : "kass",
"start_offset" : 20,
"end_offset" : 25,
"type" : "",
"position" : 3
} ]
}Tavaline analüsaator jagab rea tühikutest ja viib kõik väiketähtedeks, venekeelne analüsaator eemaldab ebaolulised sõnad, viib väiketähtedeks ja jätab sõnade alused.
Vaatame, milliseid Tokenizer-e, TokenFilters-e, CharFilters-e kasutab venekeelne analüsaator:
{
"filter": {
"venelase_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"venelase_keywords": {
"type": "keyword_marker",
"keywords": []
},
"venelase_stemmer": {
"type": "stemmer",
"language": "russian"
}
},
"analyzer": {
"venelane": {
"tokenizer": "standard",
"filter": [
"lowercase",
"venelase_stop",
"venelase_keywords",
"venelase_stemmer"
]
}
}
}Kirjeldame oma analüsaatorit venekeelsete põhjal, mis eemaldab html sildid. Nimetame seda defaultiks, kuna seda nimega analüsaatorit kasutatakse vaikimisi.
{
"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"
]
}
}
}Esmalt eemaldatakse algsest reast kõik html sildid, seejärel jagab see standard tokenizer-iga tokendeks, saadud tokendid muudetakse väiketähtedeks, eemaldatakse ebaolulised sõnad ja alles jääb sõnade aluseks.
Indeksi loomine
Eelnevalt kirjeldasime default analüsaatorit. See rakendub kõigile stringi väljadele. Meie postitus sisaldab siltide massiivi, seega töödeldakse sildid ka analüsaatoriga. Kuna me otsime postitusi sildi täpse vaste järgi, tuleb väli siltidelt analüüs välja lülitada.
Loome indeksit blog2 koos analüsaatori ja mappimisega, kus siltide analüüs on välja lülitatud:
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"
}
}
}
}
}'Lisame need 3 postit sellele indeksile (blog2). Jätan selle protsessi kõrvale, kuna see on analooge dokumentide lisamisele indeksisse blog.
Täisteksti otsing väljendite toega
Tutvume veel ühe tüübi päringutega:
# найдем документы, в которых встречается слово 'истории'
# 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"
]
}
}
}'Kuna kasutame vene keele stemmimist analüsaatoris, tagastab see päring kõik dokumendid, kuigi neis esineb ainult sõna 'ajalugu'.
Päring võib sisaldada erimärke, näiteks:
""fried eggs" +(eggplant | potato) -frittata"Päringu süntaks:
+ tähistab AND operatsiooni
| tähistab OR operatsiooni
- negatsioon üksikule tokenile
" ümbritseb mitut tokenit fraasina otsimiseks
* termina lõpus tähistab prefiksipäringut
( ja ) tähistavad eelist
~N sõna järel tähistab redigeerimise kaugust (hägusus)
~N fraasi järel tähistab slop'i suurust# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
"query": {
"simple_query_string": {
"query": "-щенки",
"fields": [
"title^3",
"tags^2",
"content"
]
}
}
}'
# получим 2 поста про котиковViidatud lingid
PS
Kui teie jaoks on huvitavad sellised artiklid-õpetused, on ideid uute artiklite jaoks või koostööettepanekud, olen rõõmus, kui võtate minuga ühendust isiklikult või e-posti teel m.kuzmin+habr@darkleaf.ru.
Allikas: habr.com
