Elasticsearch on JSON REST API-d kasutav otsimootor, mis põhineb Lucene'il ja on kirjutatud Java's. Kõik selle mootori eelised on saadaval . Edaspidi nimetame Elasticsearchi ES-iks.
Sarnaseid mootoreid kasutatakse keeruliste dokumentide andmebaasiotsingute korral. Näiteks keele morfoloogia arvesse võtmine või geokoordinaatide järgi otsimine.
Selles artiklis räägin ES'i põhitõdedest blogipostituste indekseerimise näitel. Näitan, kuidas dokumente filtreerida, sorteerida ja otsida.
Selleks, et mitte sõltuda operatsioonisüsteemist, teen kõik päringud ES-ile CURL'i abil. Samuti on olemas Google Chrome'i plugin nimega .
Artiklis on viidatud dokumentatsioonile ja teistele allikatele. Lõpus on lingid kiireks juurdepääsuks dokumentatsioonile. Uute terminite definitsioone saab lugeda .
ES'i installimine
Selleks vajame esmalt Java't. Arendajad installida Java versioonid, mis on uuemad kui Java 8 update 20 või Java 7 update 55.
ES'i distributsioon on saadaval . Pärast arhiivi laht unpackimist tuleb käivitada bin/elasticsearch. Samuti on saadaval . On . .
Pärast installimist ja käivitamist kontrollime töökindlust:
# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200
curl -X GET $ES_URLMeile tuleb umbes selline vastus:
{
"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" : "Sa tead, otsimiseks"
}Indekseerimine
Lisame postituse ES-sse:
# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.
curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
"title": "Веселые котята",
"content": "<p>Naljakas lugu kassidest<p>",
"tags": [
"kassid",
"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 lõi automaatselt blog ja post. Võib teha tingimusliku analoogia: indeks on andmebaas ja tüüp on tabel selles andmebaasis. Igal tüübil on oma skeem — , nagu suheline tabel. Mapping genereeritakse automaatselt dokumendi indekseerimise ajal:
# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"Serveri vastuses lisasin kommenteeritud alasse indekseeritud dokumendi väljade väärtused:
{
"blog" : {
"mappings" : {
"post" : {
"properties" : {
"content" : {
"type" : "string"
},
"published_at" : {
"type" : "date",
"format" : "strict_date_optional_time||epoch_millis"
},
"tags" : {
"type" : "string"
},
"title" : {
"type" : "string"
}
}
}
}
}
}<p>Naljakas lugu kassidest<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": ["kassid", "naljakas lugu"] */
"tags" : {
"type" : "string"
},
/* "title": "Lõbusad kassid" */
"title" : {
"type" : "string"
}
}
}
}
}
}Tasub märkida, et ES ei tee erinevusi ühe väärtuse ja väärtuste massiivi vahel. Näiteks väli title sisaldab lihtsalt pealkirja ja väli tags — stringide massiivi, kuigi need on mappingus esitatud ühtemoodi.
Hiljem räägime mappimisest rohkem.
Päringud
Dokumendi väljastamine 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 kassidest<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"
}
}Vastus sisaldab uusi võtmeid: _version ja _source. Üldiselt viitavad kõik võtmed, mis algavad sõnaga _ teenuslikele.
Ala _version näitab dokumendi versiooni. Seda vajatakse optimistlike lukustuste mehhanismi toimimiseks. Näiteks, kui soovime muuta dokumenti, mille versioon on 1. Me saadame muudetud dokumendi ja ütleme, et see on redaktsioon dokumendist, mille versioon on 1. Kui keegi teine on samuti redigeerinud dokumenti versiooniga 1 ja saatis muudatused enne meid, siis ES ei aktsepteeri meie muudatusi, sest see säilitab dokumendi versiooniga 2.
Ala _source sisaldab seda dokumenti, mida me indekseerisime. ES ei kasuta seda väärtust otsingutegevuseks, kuna otsimiseks kasutatakse indeksit. Ruumi säästmiseks hoiab ES kokku surutud algdokumenti. Kui vajame vaid id-d, mitte kogu algdokumenti, siis saab algdokumentide säilitamise 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 kassidest<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" : "Lõbusad kassipojad"
}
}Indekseerime veel paar postitust ja teeme keerukamaid 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 mul kassipoeg tekkis",
"content": "<p>Loodud südantlõhestav lugu vaesest tänavakassist<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" : "Naljakad kassid",
"published_at" : "2014-09-12T20:44:42+00:00"
},
"sort" : [ 1410554682000 ]
} ]
}
}Valisime viimase postituse. size piirab dokumentide arvu tulemuses. kokku näitab dokumentide koguarvu, mis vastavad päringule. sort tulemustes sisaldab terveid numbreid, mille alusel toimub sortimine. St, kuupäev on muudetud terviseks numbriks. Lisainfot sortimise kohta leiad .
Filtrid ja päringud
ES versioonist 2 ei erista filtre ja päringute vahel, selle asemel .
Päringu kontekst erineb filtrikontekstist selle poolest, et päring genereerib _score ja ei salvestata vahemälu. Mis on _score, näitan hiljem.
Kuupäeva filtrimine
Kasutame päringut filtri kontekstis:
# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"filter": {
"range": {
"published_at": { "gte": "2014-09-01" }
}
}
}'Siltide järgi filtrimine
Kasutame dokumentide id-de otsimiseks, mis sisaldavad määratud 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 mulle kassipoeg tuli",
"tags" : [ "kassipojad" ]
}
} ]
}
}Täisteksti otsing
Meie kolm dokumenti sisaldavad sisus järgmist:
<p>Naljakas lugu kassidest<p><p>Naljakas lugu kutsikatest<p><p>Loodud südantlõhestav lugu vaesest tänavakassist<p>
Kasutame dokumentide id-de otsimiseks, mis sisaldavad määratud 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, siis me ei leia midagi, kuna indeksis sisaldub ainult sõnumite originaalsõnad, mitte nende juured. Kvaliteetse otsingu tagamiseks tuleb seadistada analüsaator.
Väli _score näitab . Kui päring viiakse ellu filter kontekstis, on väärtus _score alati 1, mis tähendab, et see vastab filtrile täielikult.
Analüsaatorid
on vajalikud, et muuta algtekst tokenite kogumiks.
Analüsaatorid koosnevad ühest ja mitmest valikulisest . Tokenizer võib eelnevalt olla mitme . Tokenizer jagab algse rea tokeniteks, näiteks tühikute ja kirjavahemärkide põhjal. TokenFilter võib muuta tokenite sisu, eemaldada või lisada uusi, näiteks jätta alles ainult sõnajuurte, eemaldada eessõnu, lisada sünonüüme. CharFilter muudab kogu algse rea, näiteks eemaldab HTML sildid.
Elasticsearchis on mitu . Näiteks analüsaator .
Kasutame seda ja vaatame, kuidas analüsaatorid standard ja russian muudavad lausest "Looduslikud lood kassidest":
# используем анализатор 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" : "looduslikud",
"start_offset" : 0,
"end_offset" : 12,
"type" : "",
"position" : 0
}, {
"token" : "lood",
"start_offset" : 13,
"end_offset" : 17,
"type" : "",
"position" : 1
}, {
"token" : "kassidest",
"start_offset" : 18,
"end_offset" : 27,
"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" : 13,
"type" : "",
"position" : 1
}, {
"token" : "kass",
"start_offset" : 20,
"end_offset" : 25,
"type" : "",
"position" : 3
} ]
}Tavaline analüsaator jagas rea tühikute kaupa ja konverteeris kõik väiketähtedeks; venekeelne analüsaator eemaldas ebaolulised sõnad, muudab need väiketähtedeks ja säilitab sõnade alused.
Vaadakem, milliseid Tokenizer'eid, TokenFilter'eid ja CharFilter'eid kasutab venekeelne analüsaator:
{
"filter": {
"estonian_stop": {
"type": "stop",
"stopwords": "_estonian_"
},
"estonian_keywords": {
"type": "keyword_marker",
"keywords": []
},
"estonian_stemmer": {
"type": "stemmer",
"language": "estonian"
}
},
"analyzer": {
"estonian": {
"tokenizer": "standard",
/* TokenFilters */
"filter": [
"lowercase",
"estonian_stop",
"estonian_keywords",
"estonian_stemmer"
]
/* CharFilters puuduvad */
}
}
}Kirjeldame oma analüsaatorit, mis põhineb venekeelsel, mis eemaldab HTML sildid. Nimeks saame anda default, kuna sellise nimega analüsaatorit kasutatakse vaikeanalüsaatorina.
{
"filter": {
"et_stop": {
"type": "stop",
"stopwords": "_estonian_"
},
"et_stemmer": {
"type": "stemmer",
"language": "estonian"
}
},
"analyzer": {
"default": {
/* eemaldame html sildid */
"char_filter": ["html_strip"],
"tokenizer": "standard",
"filter": [
"lowercase",
"et_stop",
"et_stemmer"
]
}
}
}Esmalt eemaldatakse algsest stringist kõik html sildid, seejärel jagatakse see standardse tokenizer'iga tokeniteks, saadud tokenid muudetakse väikesteks tähtedeks, eemaldatakse ebaolulised sõnad ja jääb alles sõna juur.
Indeksi loomine
Ülaltoodud oleme kirjeldanud vaikimisi analüsaatorit. See rakendub kõigile stringi väljadele. Meie postitus sisaldab massiivi silte, seega töödeldakse ka silte analüsaatoriga. Kuna otsime postitusi täpsete siltide vastavuse järgi, tuleb siltide välja analüüs keelata.
Loome indeksi blog2 analüsaatoriga ja kaardistamisega, kus on keelatud sildi välja analüüs:
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 postitust ka sellesse indeksisse (blog2). Jätan selle protsessi vahele, kuna see on analoogne dokumentide lisamisele indeksisse blog.
Täisteksti otsing koos väljendite toetusega
Tutvume veel ühe päringute tüübiga:
# найдем документы, в которых встречается слово 'истории'
# 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 steemingu analüsaatorit, tagastab see päring kõik dokumendid, isegi kui 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ähis tähendab JA operatsiooni
| tähis tähendab VÕI operatsiooni
- tühistab ühe märgi
" ümbritseb mitu märki, et tähendada fraasi otsinguteks
* termina lõpus tähistab prefiksi päringut
( ja ) tähistavad prioriteeti
~N sõna järel tähistab redigeerimise kaugust (udusus)
~N fraasi järel tähistab kalde hulka# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
"query": {
"simple_query_string": {
"query": "-щенки",
"fields": [
"title^3",
"tags^2",
"content"
]
}
}
}'
# получим 2 поста про котиковLingid
PS
Kui olete huvitatud sarnastest artiklitest-tundidest, on teil ideid uute artiklite jaoks või koostöö ettepanekute tegemiseks, siis ootan rõõmuga teie sõnumeid isiklikus sõnumis või e-postil m.kuzmin+habr@darkleaf.ru.
Allikas: habr.com
