Osnove Elasticsearch

Elasticsearch je iskalnik z json rest api, ki uporablja Lucene in je napisan v Javi. Opis vseh prednosti tega motorja je na voljo na uradna spletna stran. V nadaljevanju bomo Elasticsearch označevali kot ES.

Podobni motorji se uporabljajo za zapletena iskanja v bazi dokumentov. Na primer, iskanje ob upoštevanju morfologije jezika ali iskanje po geografskih koordinatah.

V tem članku bom govoril o osnovah ES na primeru indeksiranja blog objav. Pokazal vam bom, kako filtrirate, razvrščate in iščete dokumente.

Da ne bom odvisen od operacijskega sistema, bom vse zahteve ES poslal s CURL. Obstaja tudi vtičnik za google chrome, imenovan Občutek.

Besedilo vsebuje povezave do dokumentacije in drugih virov. Na koncu so povezave za hiter dostop do dokumentacije. Definicije neznanih izrazov najdete v slovarji.

Namestitev

Za to najprej potrebujemo Javo. Razvijalci priporočam namestite različice Jave, novejše od Jave 8 posodobitev 20 ali Java 7 posodobitev 55.

Distribucija ES je na voljo na spletno mesto razvijalca. Po razpakiranju arhiva morate zagnati bin/elasticsearch. Na voljo tudi paketi za apt in yum. Obstaja uradna slika za docker. Več o namestitvi.

Po namestitvi in ​​zagonu preverimo funkcionalnost:

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

curl -X GET $ES_URL

Prejeli bomo nekaj takega:

{
  "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 objavo v 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 strežnika:

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

ES samodejno ustvarjen kazalo blog in Tip post. Lahko potegnemo pogojno analogijo: indeks je zbirka podatkov, tip pa je tabela v tej bazi podatkov. Vsak tip ima svojo shemo − kartiranje, tako kot relacijska tabela. Preslikava se ustvari samodejno, ko je dokument indeksiran:

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

V odgovoru strežnika sem v komentarje dodal vrednosti polj indeksiranega dokumenta:

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

Omeniti velja, da ES ne razlikuje med eno vrednostjo in nizom vrednosti. Polje z naslovom na primer preprosto vsebuje naslov, polje z oznakami pa vsebuje niz nizov, čeprav so v preslikavi predstavljeni na enak način.
O kartiranju bomo več govorili kasneje.

zahteve

Pridobivanje dokumenta po ID-ju:

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

V odgovoru so se pojavili novi ključi: _version и _source. Na splošno so vsi ključi, ki se začnejo z _ so razvrščeni kot uradni.

Ključ _version prikazuje različico dokumenta. Potreben je za delovanje optimističnega zaklepnega mehanizma. Na primer, želimo spremeniti dokument, ki ima različico 1. Oddamo spremenjeni dokument in označimo, da gre za urejanje dokumenta z različico 1. Če je nekdo drug urejal tudi dokument z različico 1 in predložil spremembe pred nami, potem ES ne bo sprejel naših sprememb, ker shrani dokument z različico 2.

Ključ _source vsebuje dokument, ki smo ga indeksirali. ES te vrednosti ne uporablja za iskalne operacije, ker Indeksi se uporabljajo za iskanje. Da prihrani prostor, ES shrani stisnjen izvorni dokument. Če potrebujemo samo ID in ne celotnega izvornega dokumenta, lahko onemogočimo izvorno shranjevanje.

Če ne potrebujemo dodatnih informacij, lahko dobimo samo vsebino _source:

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

Izberete lahko tudi samo določ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 še nekaj objav in zaženimo bolj zapletene poizvedbe.

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

Izbrali smo zadnjo objavo. size omejuje število dokumentov, ki jih je treba izdati. total prikazuje skupno število dokumentov, ki ustrezajo zahtevi. sort v izhodu vsebuje matriko celih števil, po katerih se izvaja razvrščanje. Tisti. datum je bil pretvorjen v celo število. Več informacij o razvrščanju najdete v dokumentacijo.

Filtri in poizvedbe

ES od različice 2 ne razlikuje med filtri in poizvedbami uveden je koncept kontekstov.
Kontekst poizvedbe se od konteksta filtra razlikuje po tem, da poizvedba ustvari _score in ni predpomnjena. Kasneje vam bom pokazal, kaj je _score.

Filtriraj po datumu

Uporabimo zahtevo območje v kontekstu filtra:

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

Filtriraj po oznakah

Uporaba izrazna poizvedba za iskanje ID-jev dokumentov, ki vsebujejo dano besedo:

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

Iskanje po celotnem besedilu

Trije naši dokumenti v polju vsebine vsebujejo naslednje:

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

Uporaba ujemaj poizvedbo za iskanje ID-jev dokumentov, ki vsebujejo dano besedo:

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

Če pa iščemo »zgodbe« v vsebinskem polju, ne bomo našli ničesar, ker Kazalo vsebuje samo izvirne besede, ne pa njihovih korenin. Za kakovostno iskanje morate konfigurirati analizator.

Polje _score kaže ustreznost. Če se zahteva izvede v kontekstu filtra, bo vrednost _score vedno enaka 1, kar pomeni popolno ujemanje s filtrom.

Analizatorji

Analizatorji so potrebni za pretvorbo izvornega besedila v nabor žetonov.
Analizatorji so sestavljeni iz enega Tokenizer in več izbirnih TokenFilters. Pred Tokenizerjem je lahko več CharFilters. Tokenizerji razdelijo izvorni niz na žetone, kot so presledki in ločila. TokenFilter lahko spremeni žetone, izbriše ali doda nove, na primer pusti samo koren besede, odstrani predloge, doda sinonime. CharFilter - spremeni celoten izvorni niz, na primer izreže oznake html.

ES jih ima več standardni analizatorji. Na primer, analizator russian.

Izkoristimo API in poglejmo, kako standardni in ruski analizatorji preoblikujejo niz "Smešne zgodbe o muckah":

# используем анализатор 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 je razdelil niz na presledke in vse pretvoril v male črke, ruski analizator je odstranil nepomembne besede, jih pretvoril v male črke in pustil koren besed.

Poglejmo, kateri Tokenizer, TokenFilters, CharFilters uporablja 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, ki temelji na ruščini, ki bo izrezal oznake html. Recimo temu privzeto, ker privzeto bo uporabljen analizator s tem imenom.

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

Najprej bodo vse oznake HTML odstranjene iz izvornega niza, nato ga bo standard tokenizerja razdelil na žetone, nastali žetoni se bodo premaknili v male črke, nepomembne besede bodo odstranjene, preostali žetoni pa bodo ostali jedro besede.

Ustvarjanje indeksa

Zgoraj smo opisali privzeti analizator. Veljal bo za vsa polja nizov. Naša objava vsebuje niz oznak, zato bo analize obdelal tudi oznake. Ker Objave iščemo po natančnem ujemanju z oznako, nato pa moramo onemogočiti analizo za polje oznak.

Ustvarimo indeks blog2 z analizatorjem in preslikavo, v katerem je onemogočena analiza polja oznak:

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 iste 3 objave v ta indeks (blog2). Ta postopek bom izpustil, ker... je podobno dodajanju dokumentov v kazalo spletnega dnevnika.

Iskanje po celotnem besedilu s podporo za izraze

Oglejmo si še eno vrsto zahtev:

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

Ker Uporabljamo analizator z ruskim korenom, potem bo ta zahteva vrnila vse dokumente, čeprav vsebujejo samo besedo 'zgodovina'.

Zahteva lahko vsebuje posebne znake, na primer:

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

Sintaksa zahteve:

+ 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

Če vas zanimajo podobni članki-lekcije, imate ideje za nove članke ali imate predloge za sodelovanje, bom vesel sporočila v osebnem sporočilu ali po e-pošti [e-pošta zaščitena].

Vir: www.habr.com