Osnove Elasticsearcha

Elasticsearch je tražilica s json rest api, koja koristi Lucene i napisana je u Javi. Opis svih prednosti ovog motora dostupan je na službene web stranice. U nastavku ćemo Elasticsearch nazivati ​​ES.

Slični se strojevi koriste za složena pretraživanja u bazi podataka dokumenata. Na primjer, pretražite uzimajući u obzir morfologiju jezika ili pretražite po geokoordinatama.

U ovom ću članku govoriti o osnovama ES-a na primjeru indeksiranja postova na blogu. Pokazat ću vam kako filtrirati, sortirati i pretraživati ​​dokumente.

Kako ne bih ovisio o operativnom sustavu, sve ću zahtjeve ES-u uputiti pomoću CURL-a. Postoji i dodatak za google chrome pod nazivom osjećaj.

Tekst sadrži poveznice na dokumentaciju i druge izvore. Na kraju se nalaze poveznice za brzi pristup dokumentaciji. Definicije nepoznatih pojmova mogu se pronaći u glossarii.

Instalacija ES-a

Da bismo to učinili, prvo nam je potrebna Java. Programeri Preporučiti instalirajte verzije Java novije od Java 8 update 20 ili Java 7 update 55.

ES distribucija dostupna je na stranica za programere. Nakon raspakiranja arhive morate pokrenuti bin/elasticsearch. Također dostupan paketi za apt i yum, Postoji službena slika za docker. Više o montaži.

Nakon instalacije i pokretanja, provjerimo funkcionalnost:

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

curl -X GET $ES_URL

Dobit ćemo nešto poput ovoga:

{
  "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"
}

indeksiranje

Dodajmo post u 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"
}'

odgovor poslužitelja:

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

ES automatski stvoren indeks blog i тип objaviti. Možemo povući uvjetnu analogiju: indeks je baza podataka, a tip je tablica u ovoj bazi podataka. Svaki tip ima svoju shemu − kartografija, baš kao relacijska tablica. Mapiranje se generira automatski kada se dokument indeksira:

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

U odgovoru poslužitelja dodao sam vrijednosti polja indeksiranog dokumenta u komentarima:

{
  "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"
          }
        }
      }
    }
  }
}

Vrijedno je napomenuti da ES ne razlikuje jednu vrijednost od niza vrijednosti. Na primjer, polje naslova jednostavno sadrži naslov, a polje oznaka sadrži niz nizova, iako su oni predstavljeni na isti način u mapiranju.
Kasnije ćemo više govoriti o mapiranju.

Upiti

Dohvaćanje dokumenta prema njegovom ID-u:

# извлечем документ с 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"
  }
}

U odgovoru su se pojavili novi ključevi: _version и _source. Općenito, svi ključevi koji počinju s _ klasificirani su kao službeni.

ključ _version prikazuje verziju dokumenta. Potreban je za rad optimističnog mehanizma za zaključavanje. Na primjer, želimo promijeniti dokument koji ima verziju 1. Predajemo promijenjeni dokument i označavamo da je ovo uređivanje dokumenta s verzijom 1. Ako je netko drugi također uredio dokument s verzijom 1 i poslao izmjene prije nas, tada ES neće prihvatiti naše izmjene, jer pohranjuje dokument s verzijom 2.

ključ _source sadrži dokument koji smo indeksirali. ES ne koristi ovu vrijednost za operacije pretraživanja jer Za pretraživanje se koriste indeksi. Radi uštede prostora, ES pohranjuje komprimirani izvorni dokument. Ako trebamo samo ID, a ne cijeli izvorni dokument, tada možemo onemogućiti izvornu pohranu.

Ako nam ne trebaju dodatne informacije, možemo dobiti samo sadržaj _source:

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

Također možete odabrati samo određena polja:

# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"
{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "title" : "Веселые котята"
  }
}

Indeksirajmo još nekoliko postova i pokrenimo složenije upite.

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"
}'

sortiranje

# найдем последний пост по дате публикации и извлечем поля 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 ]
    } ]
  }
}

Izabrali smo zadnji post. size ograničava broj dokumenata koji se izdaju. total prikazuje ukupan broj dokumenata koji odgovaraju zahtjevu. sort u izlazu sadrži niz cijelih brojeva po kojima se vrši sortiranje. Oni. datum je pretvoren u cijeli broj. Više informacija o sortiranju možete pronaći u dokumentacija.

Filtri i upiti

Umjesto toga, ES od verzije 2 ne razlikuje filtre i upite uvodi se pojam konteksta.
Kontekst upita razlikuje se od konteksta filtra po tome što upit generira _score i nije predmemoriran. Kasnije ću vam pokazati što je _score.

Filtriraj po datumu

Koristimo zahtjev domet u kontekstu filtera:

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

Filtriraj po oznakama

Koristimo termin upit za traženje ID-ova dokumenata koji sadrže zadanu riječ:

# найдем все документы, в поле 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" : [ "котята" ]
      }
    } ]
  }
}

Pretraživanje cijelog teksta

Tri naša dokumenta sadrže sljedeće u polju sadržaja:

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

Koristimo podudaranje upita za traženje ID-ova dokumenata koji sadrže zadanu riječ:

# 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
    } ]
  }
}

Međutim, ako tražimo “priče” u polju sadržaja, nećemo pronaći ništa, jer Indeks sadrži samo izvorne riječi, a ne njihov korijen. Kako biste izvršili kvalitetno pretraživanje, morate konfigurirati analizator.

Polje _score pokazuje relevantnost. Ako se zahtjev izvršava u kontekstu filtra, tada će vrijednost _score uvijek biti jednaka 1, što znači potpuno podudaranje s filtrom.

analizatori

analizatori potrebni su za pretvaranje izvornog teksta u skup tokena.
Analizatori se sastoje od jednog Tokenizator i nekoliko izbornih TokenFilters. Tokenizeru može prethoditi nekoliko CharFilters. Tokenizatori rastavljaju izvorni niz na tokene, kao što su razmaci i interpunkcijski znakovi. TokenFilter može mijenjati tokene, brisati ili dodavati nove, na primjer, ostaviti samo korijen riječi, ukloniti prijedloge, dodati sinonime. CharFilter - mijenja cijeli izvorni niz, na primjer, izrezuje html oznake.

ES ih ima nekoliko standardni analizatori. Na primjer, analizator ruski.

Iskoristimo api i pogledajmo kako standardni i ruski analizatori transformiraju niz "Smiješne priče o mačićima":

# используем анализатор 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
  } ]
}

Standardni analizator podijelio je niz razmacima i sve pretvorio u mala slova, ruski analizator uklonio je nevažne riječi, pretvorio ih u mala slova i ostavio korijen riječi.

Da vidimo koji Tokenizer, TokenFilters, CharFilters koristi ruski analizator:

{
  "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 отсутствуют */
    }
  }
}

Opišimo naš analizator temeljen na ruskom, koji će izrezati html oznake. Nazovimo to zadanim, jer analizator s ovim nazivom koristit će se prema zadanim postavkama.

{
  "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"
      ]
    }
  }
}

Najprije će se sve HTML oznake ukloniti iz izvornog niza, zatim će ga standard tokenizera podijeliti na tokene, rezultirajući tokeni premjestit će se u mala slova, beznačajne riječi će biti uklonjene, a preostali tokeni ostat će korijen riječi.

Izrada indeksa

Gore smo opisali zadani analizator. Primijenit će se na sva polja nizova. Naš post sadrži niz oznaka, tako da će oznake također biti obrađene od strane analizatora. Jer Tražimo objave prema točnom podudaranju s oznakom, a zatim moramo onemogućiti analizu za polje oznaka.

Kreirajmo indeksni blog2 s analizatorom i mapiranjem, u kojem je analiza polja oznaka onemogućena:

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"
        }
      }
    }
  }
}'

Dodajmo ista 3 posta u ovaj indeks (blog2). Izostavit ću ovaj proces jer... to je slično dodavanju dokumenata u indeks bloga.

Pretraživanje cijelog teksta s podrškom za izraze

Pogledajmo drugu vrstu zahtjeva:

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

Jer Koristimo analizator s ruskim korijenom, tada će ovaj zahtjev vratiti sve dokumente, iako sadrže samo riječ 'povijest'.

Zahtjev može sadržavati posebne znakove, na primjer:

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

Sintaksa zahtjeva:

+ 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 поста про котиков

reference

PS

Ako ste zainteresirani za slične članke-lekcije, imate ideje za nove članke ili imate prijedloge za suradnju, bit će mi drago primiti poruku u osobnoj poruci ili e-poštom [e-pošta zaštićena].

Izvor: www.habr.com