Elasticsearch — en søgemaskine med JSON REST API, der bruger Lucene og er skrevet i Java. Beskrivelsen af alle fordelene ved denne motor er tilgængelig på . I det følgende vil vi kalde Elasticsearch for ES.
Sådanne motorer bruges til avanceret søgning i dokumentdatabaser. For eksempel søgning med hensyntagen til sprogform eller søgning efter geo-koordinater.
I denne artikel vil jeg fortælle om grundlæggende ES med eksemplet på indeksering af blogindlæg. Jeg viser, hvordan man filtrerer, sorterer og søger dokumenter.
For ikke at være afhængig af operativsystemet, vil jeg lave alle forespørgsler til ES ved hjælp af CURL. Der er også et plugin til Google Chrome, der hedder .
Teksten indeholder links til dokumentation og andre kilder. Til sidst er der links til hurtig adgang til dokumentationen. Definitioner af ukendte termer kan læses i .
Installation af ES
For dette har vi først brug for Java. Udviklerne vil installere versioner af Java, der er nyere end Java 8 update 20 eller Java 7 update 55.
ES-distributionen er tilgængelig på . Efter udpakkning af arkivet skal du starte bin/elasticsearch. Der er også . Der er . .
Efter installation og opstart skal vi kontrollere, om det fungerer:
# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200
curl -X GET $ES_URLVi vil modtage et svar, der cirka ser således ud:
{
"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"
}Indeksering
Lad os tilføje et indlæg i ES:
# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.
curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
"title": "Веселые котята",
"content": "<p>En sjov historie om kattekillinger<p>"tags": [
"kattekillinger",
"sjov historie"
],
"published_at": "2014-09-12T20:44:42+00:00"
}'
serverens svar:
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : false
}
ES har automatisk oprettet blog og post. Man kan lave en betinget analogi: et indeks er en database, og en type er en tabel i denne database. Hver type har sit eget skema — , ligesom en relationel tabel. Mapping genereres automatisk ved indeksering af dokumentet:
# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"I serverens svar har jeg tilføjet værdierne for de indekserede dokumentfelter i kommentarerne:
{
"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>En sjov historie om kattekillinger<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": ["killinger", "sjov historie"] *\/
"tags" : {
"type" : "string"
},
\/\* "title": "Sjove killinger" *\/
"title" : {
"type" : "string"
}
}
}
}
}
}Det skal bemærkes, at ES ikke skelner mellem en enkelt værdi og et array af værdier. For eksempel indeholder feltet title blot overskriften, mens feltet tags er et array af strenge, selvom de præsenteres i mapping på samme måde.
Senere vil vi tale mere om mapping.
Anmodninger
Hentning af dokumentet ved dets 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>En sjov historie om kattekillinger<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"
}
}I svaret er der kommet nye nøgler: _version og _source. Generelt, alle nøgler, der begynder med _ , tilhører systemnøgler.
Nøgle _version viser versionen af dokumentet. Den er nødvendig for at arbejde med mekanismen for optimistiske låsninger. For eksempel, hvis vi ønsker at ændre et dokument med version 1, sender vi det ændrede dokument og angiver, at det er en ændring af dokumentet med version 1. Hvis en anden også har redigeret dokumentet med version 1 og har sendt ændringer tidligere end os, vil ES ikke acceptere vores ændringer, da den opbevarer dokumentet med version 2.
Nøgle _source indeholder det dokument, som vi har indekseret. ES bruger ikke denne værdi til søgerelaterede operationer, da indekserne anvendes til søgning. For at spare plads opbevarer ES det komprimerede originaldokument. Hvis vi kun har brug for id'et, og ikke hele originaldokumentet, kan opbevaringen af originalen slås fra.
Hvis vi ikke har brug for supplerende information, kan vi kun hente indholdet fra _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>En sjov historie om kattekillinger<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"
}
Vi kan også vælge kun bestemte felter:
# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Glade katte"
}
}Lad os indeksere endnu flere indlæg og udføre mere komplekse forespørgsler.
curl -XPUT "$ES_URL/blog/post/2" -d'
{
"title": "Sjove hvalpe",
"content": "<p>En sjov historie om hvalpe<p>"
"tags": [
"hvalpe",
"sjov historie"
],
"published_at": "2014-08-12T20:44:42+00:00"
}'curl -XPUT "$ES_URL/blog/post/3" -d'
{
"title": "Hvordan jeg fik min killing",
"content": "<p>En hjerteskærende historie om en stakkels killing fra gaden<p>",
"tags": [
"killinger"
],
"published_at": "2014-07-21T20:44:42+00:00"
}'Sortering
# найдем последний пост по дате публикации и извлечем поля 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" : "Glade katte",
"published_at" : "2014-09-12T20:44:42+00:00"
},
"sort" : [ 1410554682000 ]
} ]
}
}Vi har valgt det seneste indlæg. størrelse begrænser antallet af dokumenter i resultatet. total viser det samlede antal dokumenter, der passer med forespørgslen. sort i resultatet indeholder et array af heltal, som sorteringen udføres efter. Dvs. datoen er konverteret til et heltal. Læs mere om sortering i .
Filtre og forespørgsler
ES adskiller ikke længere mellem filtre og forespørgsler siden version 2, i stedet .
Forespørgselskontexten adskiller sig fra filterkontexten ved, at forespørgslen genererer _score og ikke bliver cachet. Hvad _score er, vil jeg vise senere.
Filtrering efter dato
Bruger forespørgslen i konteksten filter:
# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"filter": {
"range": {
"published_at": { "gte": "2014-09-01" }
}
}
}'Filtrering efter tags
Vi bruger til at søge id'er af dokumenter, der indeholder det givne ord:
# найдем все документы, в поле 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" : "Sjove katte",
"tags" : [ "katte", "sjov historie" ]
}
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"title" : "Hvordan jeg fik en killing",
"tags" : [ "katte" ]
}
} ]
}
}Fuldtekst søgning
Tre af vores dokumenter indeholder følgende i feltet content:
<p>En sjov historie om kattekillinger<p><p>En sjov historie om hvalpe<p><p>En hjerteskærende historie om en stakkels killing fra gaden<p>
Vi bruger til at søge id'er af dokumenter, der indeholder det givne ord:
# 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
} ]
}
}Men hvis vi søger efter «historier» i feltet content, så finder vi ingenting, da indekset kun indeholder de originale ord, ikke deres basisformer. For at lave en kvalitetsøgning skal analyseren justeres.
Felt _score viser . Hvis forespørgslen udføres i filterkonteksten, vil værdien _score altid være lig med 1, hvilket betyder fuld overensstemmelse med filteret.
Analyser
er nødvendige for at konvertere den oprindelige tekst til et sæt tokens.
Analyser består af en og flere valgfrie . Tokenizer kan være forud for flere . Tokenizer opdeler den oprindelige streng i tokens, f.eks. på mellemrum og tegnsætning. TokenFilter kan ændre tokens, slette eller tilføje nye, for eksempel kun beholde basisformen af ordet, fjerne præpositioner, tilføje synonymer. CharFilter ændrer den oprindelige streng helt, f.eks. skærer HTML-tags væk.
I ES er der flere . For eksempel analysatoren .
Lad os bruge og se, hvordan analyserne standard og russian konverterer strengen «Sjove historier om katte»:
# используем анализатор 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" : "sjove",
"start_offset" : 0,
"end_offset" : 5,
"type" : "",
"position" : 0
}, {
"token" : "historier",
"start_offset" : 6,
"end_offset" : 14,
"type" : "",
"position" : 1
}, {
"token" : "om",
"start_offset" : 15,
"end_offset" : 17,
"type" : "",
"position" : 2
}, {
"token" : "katte",
"start_offset" : 18,
"end_offset" : 23,
"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" : "glad",
"start_offset" : 0,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 0
}, {
"token" : "histor",
"start_offset" : 8,
"end_offset" : 15,
"type": "<ALPHANUM>",
"position" : 1
}, {
"token" : "kat",
"start_offset" : 20,
"end_offset" : 25,
"type": "<ALPHANUM>",
"position" : 3
} ]
}Den standardanalyse opdeler strengen ved mellemrum og konverterer alt til små bogstaver; analysatoren russian fjerner ubetydelige ord, konverterer til små bogstaver og beholder rodformen af ord.
Lad os se, hvilke Tokenizer, TokenFilters, CharFilters den russiske analysator bruger:
{
"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"
]
}
}
}Lad os beskrive vores analysator baseret på russian, som vil fjerne html-tags. Vi kalder den default, fordi den analysator med dette navn vil blive brugt som standard.
{
"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"
]
}
}
}Først fjernes alle html-tags fra den oprindelige streng, derefter opdeles den i tokens af tokenizer standard, de opnåede tokens konverteres til små bogstaver, ubetydelige ord fjernes, og fra de tilbageværende tokens bevares rodformen af ordet.
Oprettelse af indeks
Ovenfor har vi beskrevet default analysatoren. Den vil blive anvendt på alle strengfelter. Vores indlæg indeholder et array af tags, derfor vil tags også blive behandlet af analysatoren. Da vi søger efter indlæg ved præcise matches med tag, er det nødvendigt at deaktivere analysen for feltet tags.
Lad os oprette indeks blog2 med en analysator og mapping, hvor analysen for feltet tags er deaktiveret:
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"
}
}
}
}
}'Lad os tilføje de samme 3 indlæg til denne indeks (blog2). Jeg vil springe denne proces over, da den er analog med at tilføje dokumenter til indeks blog.
Fuldtekst søgning med støtte for udtryk
Lad os lære en anden type forespørgsel at kende:
# найдем документы, в которых встречается слово 'истории'
# 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"
]
}
}
}'Da vi bruger en analyzer med russisk stemming, vil denne forespørgsel returnere alle dokumenter, selvom de kun indeholder ordet 'historie'.
Forespørgslen kan indeholde specielle tegn, for eksempel:
""fried eggs" +(eggplant | potato) -frittata"Forespørgselssyntaks:
+ angiver AND operation
| angiver OR operation
- negater et enkelt token
" indkapsler flere tokens for at angive en sætning til søgning
* i slutningen af et term angiver en præfiksforespørgsel
( og ) angiver rækkefølge
~N efter et ord angiver redigeringsafstand (fuzzy)
~N efter en sætning angiver slop mængde# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
"query": {
"simple_query_string": {
"query": "-щенки",
"fields": [
"title^3",
"tags^2",
"content"
]
}
}
}'
# получим 2 поста про котиковLinks
PS
Hvis du er interesseret i lignende artikler-undervisning, har idéer til nye artikler, eller har forslag til samarbejde, vil jeg være glad for en besked i privat eller til mail m.kuzmin+habr@darkleaf.ru.
Kilde: habr.com
