Elasticsearch — en sökmotor med json rest api som använder Lucene och är skriven i Java. Beskrivningen av alla fördelar med denna motor finns på . Längre fram i texten kommer vi att referera till Elasticsearch som ES.
Sådana motorer används vid avancerad sökning i dokumentbaser. Till exempel, sökning med hänsyn till språkets morfologi eller sökning baserat på geo-koordinater.
I denna artikel kommer jag att berätta om grunderna i ES med hjälp av ett exempel på indexering av blogginlägg. Jag visar hur man filtrerar, sorterar och söker i dokument.
För att inte vara beroende av operativsystemet kommer jag att göra alla förfrågningar till ES med hjälp av CURL. Det finns också ett plugin för Google Chrome som heter .
Texten innehåller länkar till dokumentation och andra källor. I slutet finns länkar för snabb åtkomst till dokumentationen. Definitioner av okända termer kan läsas i .
Installation av ES
För detta behöver vi först Java. Utvecklarna ska installera versioner av Java som är nyare än Java 8 update 20 eller Java 7 update 55.
ES-distributionen finns på . Efter att ha packat upp arkivet behöver vi köra bin/elasticsearch. Det finns också . Det finns . .
Efter installation och start kontrollerar vi funktionaliteten:
# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200
curl -X GET $ES_URLVi får ett svar som ungefär ser ut så här:
{
"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"
}Indexering
Vi lägger till ett inlägg i ES:
# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.
curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
"title": "Веселые котята",
"content": "<p>Rolig historia om kattungar<p>",
"tags": [
"kattungar",
"rolig historia"
],
"published_at": "2014-09-12T20:44:42+00:00"
}'
serverns svar:
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : false
}
ES skapade automatiskt blog och post. Man kan göra en liknande analogi: ett index är en databas, och en typ är en tabell i denna databas. Varje typ har sitt eget schema — , precis som en relationsdatabas tabell. Mapping genereras automatiskt vid dokumentindexering:
# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"I serverns svar har jag lagt till i kommentarerna värdena för fälten i det indexerade dokumentet:
{
"blog" : {
"mappings" : {
"post" : {
"properties" : {
/* "content": "<p>Rolig historia om kattungar<p>", *\/
"content" : {
"type" : "string"
},
/* "published_at": "2014-09-12T20:44:42+00:00" *\/\n "published_at" : {
"type" : "date",
"format" : "strict_date_optional_time||epoch_millis"
},
/* "tags": ["котята", "смешная история"] *\/\n "tags" : {
"type" : "string"
},
/* "title": "Веселые котята" *\/\n "title" : {
"type" : "string"
}
}
}
}
}
}Det är värt att notera att ES inte gör någon åtskillnad mellan ett enda värde och en array av värden. Till exempel innehåller fältet title bara en rubrik, medan fältet tags är en array av strängar, trots att de är presenterade i mapping på samma sätt.
Senare kommer vi att prata om mappning mer ingående.
Förfrågningar
Hämtning av ett dokument efter dess 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>Rolig historia om kattungar<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"
}
}Nya nycklar har dykt upp i svaret: _version och _source. Generellt gäller att alla nycklar som börjar med _ tillhör systemet.
Nyckeln _version visar dokumentversionen. Den behövs för att optimistiska låsningmekanismer ska fungera. Till exempel, om vi vill ändra ett dokument som har version 1. Vi skickar det ändrade dokumentet och anger att det är en ändring av dokumentet med version 1. Om någon annan också har redigerat dokumentet med version 1 och skickade sina ändringar tidigare än oss, kommer ES inte att acceptera våra ändringar eftersom det sparar dokumentet med version 2.
Nyckeln _source innehåller det dokument som vi har indexerat. ES använder inte detta värde för sökoperationer, eftersom sökningar görs med hjälp av index. För att spara utrymme lagrar ES den komprimerade originaldokumentet. Om vi bara behöver id och inte hela originaldokumentet kan vi stänga av lagringen av originalet.
Om vi inte behöver ytterligare information kan vi få endast innehållet i _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>Rolig historia om kattungar<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 också välja enbart vissa fält:
# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Roliga kattungar"
}
}Låt oss indexera ytterligare några inlägg och utföra mer komplexa förfrågningar.
curl -XPUT "$ES_URL/blog/post/2" -d'
{
"title": "Roliga valpar",
"content": "<p>En rolig historia om valpar<p>",
"tags": [
"valpar",
"rolig historia"
],
"published_at": "2014-08-12T20:44:42+00:00"
}'curl -XPUT "$ES_URL/blog/post/3" -d'
{
"title": "Hur jag fick min kattunge",
"content": "<p>En rörande historia om en fattig kattunge från gatan<p>",
"tags": [
"kattungar"
],
"published_at": "2014-07-21T20:44:42+00:00"
}'Sortera
# найдем последний пост по дате публикации и извлечем поля 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" : "Roliga kattungar",
"published_at" : "2014-09-12T20:44:42+00:00"
},
"sort" : [ 1410554682000 ]
} ]
}
}Vi har valt det senaste inlägget. size begränsar antalet dokument i resultatet. total visar det totala antalet dokument som matchar förfrågan. sort i resultatet innehåller en array av heltal som sorteringen utförs på. Dvs. datumet har omvandlats till ett heltal. Mer om sortering kan läsas i .
Filter och förfrågningar
ES skelner inte längre på filter och förfrågningar sedan version 2, istället .
Frågekontexten skiljer sig från filterkontexten genom att förfrågan genererar _score och inte cachas. Vad _score är kommer jag att visa senare.
Filtrering efter datum
Vi använder förfrågan i kontexten filter:
# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"filter": {
"range": {
"published_at": { "gte": "2014-09-01" }
}
}
}'Filtrering efter taggar
Används för att söka id för dokument som innehåller det angivna ordet:
# найдем все документы, в поле 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" : "Roliga kattungar",
"tags" : [ "kattungar", "rolig historia" ]
}
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"title" : "Hur jag fick en kattunge",
"tags" : [ "kattungar" ]
}
} ]
}
}Fulltextsökning
Tre av våra dokument innehåller följande i fältet content:
<p>Rolig historia om kattungar<p><p>En rolig historia om valpar<p><p>En rörande historia om en fattig kattunge från gatan<p>
Används för att söka id för dokument som innehåller det angivna ordet:
# 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 om vi söker efter "historier" i fältet content, hittar vi ingenting, eftersom indexet endast innehåller originalord och inte deras stammar. För att göra en kvalificerad sökning måste analysatorn ställas in.
Fältet _score visar . Om förfrågan utförs i filter context, kommer värdet _score alltid att vara 1, vilket innebär fullständig överensstämmelse med filtret.
Analysatorer
behövs för att omvandla ursprunglig text till en uppsättning tokens.
Analysatorer består av en och flera valfria . Tokenizer kan föregå flera . Tokenizer delar upp den ursprungliga strängen i tokens, till exempel vid mellanslag och skiljetecken. TokenFilter kan modifiera tokens, ta bort eller lägga till nya, till exempel behålla endast stammen av ett ord, ta bort prepositioner, lägga till synonymer. CharFilter - ändrar hela den ursprungliga strängen, till exempel genom att ta bort HTML-taggar.
I ES finns det flera . till exempel analysatorn .
Låt oss använda och vi ska se hur analysatorerna standard och russian omvandlar strängen "Roliga historier om kattungar":
# используем анализатор 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" : "roliga",
"start_offset" : 0,
"end_offset" : 7,
"type" : "",
"position" : 0
}, {
"token" : "historier",
"start_offset" : 8,
"end_offset" : 15,
"type" : "",
"position" : 1
}, {
"token" : "om",
"start_offset" : 16,
"end_offset" : 19,
"type" : "",
"position" : 2
}, {
"token" : "kattungar",
"start_offset" : 20,
"end_offset" : 25,
"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" : "",
"position" : 0
}, {
"token" : "histor",
"start_offset" : 8,
"end_offset" : 15,
"type" : "",
"position" : 1
}, {
"token" : "katt",
"start_offset" : 20,
"end_offset" : 25,
"type" : "",
"position" : 3
} ]
}Den standardanalysatorn delade upp strängen vid mellanslag och konverterade allt till gemener. Den ryska analysatorn tog bort betydelselösa ord, konverterade till gemener och behöll ordens stam.
Låt oss se vilka Tokenizer, TokenFilters och CharFilters den ryska analysatorn använder:
{
"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 saknas */
}
}
}Låt oss beskriva vår analysator baserad på ryska, som kommer att ta bort HTML-taggar. Vi kallar den default, eftersom en analysator med detta namn kommer att användas som standard.
{
"filter": {
"ru_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"ru_stemmer": {
"type": "stemmer",
"language": "russian"
}
},
"analyzer": {
"default": {
/* vi lägger till borttagning av HTML-taggar */
"char_filter": ["html_strip"],
"tokenizer": "standard",
"filter": [
"lowercase",
"ru_stop",
"ru_stemmer"
]
}
}
}Först kommer alla HTML-taggar att tas bort från den ursprungliga strängen, sedan kommer den att delas upp i tokens av tokenizer standard, de resulterande tokensen kommer att konverteras till gemener, betydelselösa ord tas bort och av de återstående tokens kommer ordstammen att finnas kvar.
Skapa index
Ovan har vi beskrivit den default analysatorn. Den kommer att tillämpas på alla strängfält. Vårt inlägg innehåller en array av taggar, därför kommer också taggarna att behandlas av analysatorn. Eftersom vi söker inlägg efter exakt matchning av taggen, så behöver vi inaktivera analysen för fältet tags.
Vi skapar index blog2 med analysator och mappning, där analysen av fältet tags är avstängd:
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"
}
}
}
}
}'Vi lägger till samma 3 inlägg i denna index (blog2). Jag kommer att hoppa över denna process, eftersom den är liknande att lägga till dokument i index blog.
Fulltextsökning med stöd för uttryck
Låt oss bekanta oss med en annan typ av förfrågningar:
# найдем документы, в которых встречается слово 'истории'
# 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"
]
}
}
}'Eftersom vi använder en analysator med rysk stemming, kommer denna förfrågan att returnera alla dokument, även om de endast innehåller ordet 'historia'.
Förfrågningen kan innehålla specialtecken, till exempel:
""fried eggs" +(eggplant | potato) -frittata"Förfrågningss Syntax:
+ betyder OCH-operation
| betyder ELLER-operation
- negerar enskild token
" omger ett antal tokens för att ange en fras för sökning
* i slutet av en term anger en prefixfråga
( och ) anger prioritet
~N efter ett ord anger redigeringsavstånd (fuzzy)
~N efter en fras anger mängden slopp# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
"query": {
"simple_query_string": {
"query": "-щенки",
"fields": [
"title^3",
"tags^2",
"content"
]
}
}
}'
# получим 2 поста про котиковLänkar
PS
Om du är intresserad av liknande artiklar-lektioner, har idéer för nya artiklar eller har förslag om samarbeten, ser jag fram emot att få meddelanden i privat eller via e-post m.kuzmin+habr@darkleaf.ru.
Källa: habr.com
