Grundlæggende om Elasticsearch

Elasticsearch er en søgemaskine med json rest api, der bruger Lucene og skrevet i Java. En beskrivelse af alle fordelene ved denne motor er tilgængelig på det officielle site. I det følgende vil vi referere til Elasticsearch som ES.

Lignende motorer bruges til komplekse søgninger i en dokumentdatabase. For eksempel søgning under hensyntagen til sprogets morfologi eller søgning på geokoordinater.

I denne artikel vil jeg tale om det grundlæggende i ES ved at bruge eksemplet med indeksering af blogindlæg. Jeg viser dig, hvordan du filtrerer, sorterer og søger i dokumenter.

For ikke at være afhængig af operativsystemet, vil jeg sende alle anmodninger til ES ved hjælp af CURL. Der er også et plugin til google chrome kaldet forstand.

Teksten indeholder links til dokumentation og andre kilder. Til sidst er der links til hurtig adgang til dokumentationen. Definitioner af ukendte termer kan findes i ordlister.

Installation af ES

For at gøre dette skal vi først have Java. Udviklere anbefale installere Java-versioner, der er nyere end Java 8-opdatering 20 eller Java 7-opdatering 55.

ES-distributionen er tilgængelig på udvikler site. Efter udpakning af arkivet skal du køre bin/elasticsearch. Også tilgængelig pakker til apt og yum. der er officielt billede for docker. Mere om installation.

Efter installation og start, lad os tjekke funktionaliteten:

# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200

curl -X GET $ES_URL

Vi vil modtage noget som dette:

{
  "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 til ES:

# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.

curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
  "title": "Веселые котята",
  "content": "<p>Смешная история про котят<p>",
  "tags": [
    "котята",
    "смешная история"
  ],
  "published_at": "2014-09-12T20:44:42+00:00"
}'

server svar:

{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "created" : false
}

ES oprettes automatisk indeks blog og typen stolpe. Vi kan tegne en betinget analogi: et indeks er en database, og en type er en tabel i denne database. Hver type har sit eget skema − kortlægning, ligesom en relationel tabel. Mapping genereres automatisk, når dokumentet indekseres:

# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"

I serversvaret tilføjede jeg værdierne af felterne i det indekserede dokument i kommentarerne:

{
  "blog" : {
    "mappings" : {
      "post" : {
        "properties" : {
          /* "content": "<p>Смешная история про котят<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"
          }
        }
      }
    }
  }
}

Det er værd at bemærke, at ES ikke skelner mellem en enkelt værdi og en række værdier. For eksempel indeholder titelfeltet blot en titel, og tags-feltet indeholder en række strenge, selvom de er repræsenteret på samme måde i kortlægning.
Vi taler mere om kortlægning senere.

anmodninger

Hentning af et dokument efter 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" : "Веселые котята",
    "content" : "<p>Смешная история про котят<p>",
    "tags" : [ "котята", "смешная история" ],
    "published_at" : "2014-09-12T20:44:42+00:00"
  }
}

Nye nøgler dukkede op i svaret: _version и _source. Generelt er alle nøgler, der starter med _ er klassificeret som officielle.

nøgle _version viser dokumentversionen. Det er nødvendigt for at den optimistiske låsemekanisme kan fungere. For eksempel vil vi ændre et dokument, der har version 1. Vi indsender det ændrede dokument og angiver, at dette er en redigering af et dokument med version 1. Hvis en anden også har redigeret et dokument med version 1 og indsendt ændringer før os, så ES vil ikke acceptere vores ændringer, pga den gemmer dokumentet med version 2.

nøgle _source indeholder det dokument, vi indekserede. ES bruger ikke denne værdi til søgeoperationer pga Indekser bruges til at søge. For at spare plads gemmer ES et komprimeret kildedokument. Hvis vi kun har brug for id'et og ikke hele kildedokumentet, så kan vi deaktivere kildelagring.

Hvis vi ikke har brug for yderligere oplysninger, kan vi kun få indholdet af _source:

curl -XGET "$ES_URL/blog/post/1/_source?pretty"
{
  "title" : "Веселые котята",
  "content" : "<p>Смешная история про котят<p>",
  "tags" : [ "котята", "смешная история" ],
  "published_at" : "2014-09-12T20:44:42+00:00"
}

Du kan også kun vælge 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" : "Веселые котята"
  }
}

Lad os indeksere nogle flere indlæg og køre mere komplekse forespørgsler.

curl -XPUT "$ES_URL/blog/post/2" -d'
{
  "title": "Веселые щенки",
  "content": "<p>Смешная история про щенков<p>",
  "tags": [
    "щенки",
    "смешная история"
  ],
  "published_at": "2014-08-12T20:44:42+00:00"
}'
curl -XPUT "$ES_URL/blog/post/3" -d'
{
  "title": "Как у меня появился котенок",
  "content": "<p>Душераздирающая история про бедного котенка с улицы<p>",
  "tags": [
    "котята"
  ],
  "published_at": "2014-07-21T20:44:42+00:00"
}'

Сортировка

# найдем последний пост по дате публикации и извлечем поля 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" : "Веселые котята",
        "published_at" : "2014-09-12T20:44:42+00:00"
      },
      "sort" : [ 1410554682000 ]
    } ]
  }
}

Vi valgte det sidste indlæg. size begrænser antallet af dokumenter, der skal udstedes. total viser det samlede antal dokumenter, der matcher anmodningen. sort i outputtet indeholder et array af heltal, som sorteringen udføres efter. De der. datoen blev konverteret til et heltal. Mere information om sortering kan findes i dokumentation.

Filtre og forespørgsler

ES, da version 2 i stedet ikke skelner mellem filtre og forespørgsler begrebet kontekster introduceres.
En forespørgselskontekst adskiller sig fra en filterkontekst ved, at forespørgslen genererer en _score og ikke cachelagres. Jeg vil vise dig, hvad _score er senere.

Filtrer efter dato

Vi bruger anmodningen rækkevidde i forbindelse med filter:

# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "filter": {
    "range": {
      "published_at": { "gte": "2014-09-01" }
    }
  }
}'

Filtrer efter tags

Vi bruger term forespørgsel for at søge efter dokument-id'er, der indeholder et givet 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" : "Веселые котята",
        "tags" : [ "котята", "смешная история" ]
      }
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "3",
      "_score" : 1.0,
      "_source" : {
        "title" : "Как у меня появился котенок",
        "tags" : [ "котята" ]
      }
    } ]
  }
}

Fuld tekstsøgning

Tre af vores dokumenter indeholder følgende i indholdsfeltet:

  • <p>Смешная история про котят<p>
  • <p>Смешная история про щенков<p>
  • <p>Душераздирающая история про бедного котенка с улицы<p>

Vi bruger match forespørgsel for at søge efter dokument-id'er, der indeholder et givet 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 på “historier” i indholdsfeltet, finder vi ikke noget, pga Indekset indeholder kun de originale ord, ikke deres stammer. For at foretage en søgning i høj kvalitet skal du konfigurere analysatoren.

Field _score shows relevans. Hvis anmodningen udføres i en filterkontekst, vil _score-værdien altid være lig med 1, hvilket betyder et komplet match til filteret.

Analysatorer

Analysatorer er nødvendige for at konvertere kildeteksten til et sæt tokens.
Analysatorer består af én Tokenizer og flere valgfrie TokenFilters. Tokenizer kan indledes med flere CharFilters. Tokenizere opdeler kildestrengen i tokens, såsom mellemrum og tegnsætningstegn. TokenFilter kan ændre tokens, slette eller tilføje nye, for eksempel lad kun ordets stamme stå, fjerne præpositioner, tilføje synonymer. CharFilter - ændrer hele kildestrengen, for eksempel skærer html-tags ud.

ES har flere standard analysatorer. For eksempel en analysator Russisk.

Lad os drage fordel api og lad os se, hvordan de standard- og russiske analysatorer forvandler strengen "Sjove historier om killinger":

# используем анализатор 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" : "веселые",
    "start_offset" : 0,
    "end_offset" : 7,
    "type" : "<ALPHANUM>",
    "position" : 0
  }, {
    "token" : "истории",
    "start_offset" : 8,
    "end_offset" : 15,
    "type" : "<ALPHANUM>",
    "position" : 1
  }, {
    "token" : "про",
    "start_offset" : 16,
    "end_offset" : 19,
    "type" : "<ALPHANUM>",
    "position" : 2
  }, {
    "token" : "котят",
    "start_offset" : 20,
    "end_offset" : 25,
    "type" : "<ALPHANUM>",
    "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" : "весел",
    "start_offset" : 0,
    "end_offset" : 7,
    "type" : "<ALPHANUM>",
    "position" : 0
  }, {
    "token" : "истор",
    "start_offset" : 8,
    "end_offset" : 15,
    "type" : "<ALPHANUM>",
    "position" : 1
  }, {
    "token" : "кот",
    "start_offset" : 20,
    "end_offset" : 25,
    "type" : "<ALPHANUM>",
    "position" : 3
  } ]
}

Standardanalysatoren opdelte strengen med mellemrum og konverterede alt til små bogstaver, den russiske analysator fjernede uvæsentlige ord, konverterede det til små bogstaver og forlod ordenes stamme.

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",
      /* TokenFilters */
      "filter": [
        "lowercase",
        "russian_stop",
        "russian_keywords",
        "russian_stemmer"
      ]
      /* CharFilters отсутствуют */
    }
  }
}

Lad os beskrive vores analysator baseret på russisk, som vil skære html-tags ud. Lad os kalde det standard, fordi en analysator med dette navn vil blive brugt som standard.

{
  "filter": {
    "ru_stop": {
      "type":       "stop",
      "stopwords":  "_russian_"
    },
    "ru_stemmer": {
      "type":       "stemmer",
      "language":   "russian"
    }
  },
  "analyzer": {
    "default": {
      /* добавляем удаление html тегов */
      "char_filter": ["html_strip"],
      "tokenizer":  "standard",
      "filter": [
        "lowercase",
        "ru_stop",
        "ru_stemmer"
      ]
    }
  }
}

Først vil alle HTML-tags blive fjernet fra kildestrengen, derefter vil tokenizer-standarden opdele det i tokens, de resulterende tokens vil flytte til små bogstaver, ubetydelige ord vil blive fjernet, og de resterende tokens forbliver stammen af ​​ordet.

Oprettelse af et indeks

Ovenfor har vi beskrevet standardanalysatoren. Det vil gælde for alle strengfelter. Vores indlæg indeholder en række tags, så taggene vil også blive behandlet af analysatoren. Fordi Vi leder efter indlæg efter nøjagtig match til et tag, så skal vi deaktivere analyse for tags-feltet.

Lad os oprette en indeksblog2 med en analysator og kortlægning, hvor analysen af ​​tags-feltet 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 dette indeks (blog2). Jeg vil undlade denne proces, fordi... det svarer til at tilføje dokumenter til blogindekset.

Fuldtekstsøgning med udtryksunderstøttelse

Lad os tage et kig på en anden type anmodning:

# найдем документы, в которых встречается слово 'истории'
# 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"
      ]
    }
  }
}'

Fordi Vi bruger en analysator med russisk stemming, så vil denne anmodning returnere alle dokumenter, selvom de kun indeholder ordet 'historie'.

Anmodningen kan indeholde specialtegn, for eksempel:

""fried eggs" +(eggplant | potato) -frittata"

Anmodningssyntaks:

+ signifies AND operation
| signifies OR operation
- negates a single token
" wraps a number of tokens to signify a phrase for searching
* at the end of a term signifies a prefix query
( and ) signify precedence
~N after a word signifies edit distance (fuzziness)
~N after a phrase signifies slop amount
# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
  "query": {
    "simple_query_string": {
      "query": "-щенки",
      "fields": [
        "title^3",
        "tags^2",
        "content"
      ]
    }
  }
}'

# получим 2 поста про котиков

RЎSЃS <P "RєRё

PS

Hvis du er interesseret i lignende artikler-lektioner, har ideer til nye artikler, eller har forslag til samarbejde, så modtager jeg gerne en besked i en personlig besked eller på e-mail [e-mail beskyttet].

Kilde: www.habr.com