Bazat e Elasticsearch

Elasticsearch është një motor kërkimi me json rest api, duke përdorur Lucene dhe i shkruar në Java. Një përshkrim i të gjitha avantazheve të këtij motori është në dispozicion në faqen zyrtare të internetit. Në atë që vijon do t'i referohemi Elasticsearch si ES.

Motorë të ngjashëm përdoren për kërkime komplekse në një bazë të dhënash dokumentesh. Për shembull, kërkoni duke marrë parasysh morfologjinë e gjuhës ose kërkoni sipas koordinatave gjeografike.

Në këtë artikull do të flas për bazat e ES duke përdorur shembullin e indeksimit të postimeve në blog. Unë do t'ju tregoj se si të filtroni, renditni dhe kërkoni dokumente.

Për të mos varur nga sistemi operativ, do t'i bëj të gjitha kërkesat ES duke përdorur CURL. Ekziston edhe një shtojcë për google chrome që quhet kuptim.

Teksti përmban lidhje me dokumentacionin dhe burime të tjera. Në fund ka lidhje për akses të shpejtë në dokumentacion. Përkufizimet e termave të panjohur mund të gjenden në fjalorë.

Instalimi

Për ta bërë këtë, së pari na duhet Java. Zhvilluesit rekomandoj instaloni versionet Java më të reja se përditësimi 8 i Java 20 ose përditësimi 7 i Java 55.

Shpërndarja ES është në dispozicion në uebsajti i zhvilluesit. Pas shpaketimit të arkivit, duhet të ekzekutoni bin/elasticsearch. Gjithashtu në dispozicion pako për apt dhe yum... ka imazhi zyrtar për docker. Më shumë rreth instalimit.

Pas instalimit dhe nisjes, le të kontrollojmë funksionalitetin:

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

curl -X GET $ES_URL

Do të marrim diçka të tillë:

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

Indeksimi

Le të shtojmë një postim në 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"
}'

Përgjigja e serverit:

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

ES u krijua automatikisht indeks blog dhe një tip postim. Ne mund të nxjerrim një analogji të kushtëzuar: një indeks është një bazë të dhënash, dhe një lloj është një tabelë në këtë bazë të dhënash. Çdo lloj ka skemën e vet − planifikim, ashtu si një tabelë relacionale. Hartimi gjenerohet automatikisht kur dokumenti indeksohet:

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

Në përgjigjen e serverit, shtova vlerat e fushave të dokumentit të indeksuar në komente:

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

Vlen të përmendet se ES nuk bën dallim midis një vlere të vetme dhe një grupi vlerash. Për shembull, fusha e titullit thjesht përmban një titull, dhe fusha e etiketave përmban një grup vargjesh, megjithëse ato përfaqësohen në të njëjtën mënyrë në hartografi.
Ne do të flasim më shumë për hartën më vonë.

Kërkesat

Marrja e një dokumenti me ID-në e tij:

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

Në përgjigje u shfaqën çelësa të rinj: _version и _source. Në përgjithësi, të gjithë çelësat duke filluar me _ klasifikohen si zyrtare.

Ключ _version tregon versionin e dokumentit. Është e nevojshme që mekanizmi optimist i mbylljes të funksionojë. Për shembull, ne duam të ndryshojmë një dokument që ka versionin 1. Ne dorëzojmë dokumentin e ndryshuar dhe tregojmë se ky është një modifikim i një dokumenti me versionin 1. Nëse dikush tjetër gjithashtu ka redaktuar një dokument me versionin 1 dhe ka paraqitur ndryshime para nesh, atëherë ES nuk do të pranojë ndryshimet tona, sepse ruan dokumentin me versionin 2.

Ключ _source përmban dokumentin që kemi indeksuar. ES nuk e përdor këtë vlerë për operacionet e kërkimit sepse Indekset përdoren për kërkim. Për të kursyer hapësirë, ES ruan një dokument burim të ngjeshur. Nëse na duhet vetëm ID-ja dhe jo i gjithë dokumenti burimor, atëherë mund të çaktivizojmë ruajtjen e burimit.

Nëse nuk kemi nevojë për informacion shtesë, mund të marrim vetëm përmbajtjen e _source:

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

Ju gjithashtu mund të zgjidhni vetëm disa fusha:

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

Le të indeksojmë disa postime të tjera dhe të drejtojmë pyetje më komplekse.

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

klasifikim

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

Ne zgjodhëm postimin e fundit. size kufizon numrin e dokumenteve që do të lëshohen. total tregon numrin total të dokumenteve që përputhen me kërkesën. sort në dalje përmban një grup numrash të plotë me të cilin kryhet renditja. Ato. data u konvertua në një numër të plotë. Më shumë informacion rreth renditjes mund të gjeni në dokumentacionin.

Filtra dhe pyetje

ES që nga versioni 2 nuk bën dallim midis filtrave dhe pyetjeve, përkundrazi prezantohet koncepti i konteksteve.
Konteksti i pyetjes ndryshon nga një kontekst filtri në atë që pyetja gjeneron një _score dhe nuk ruhet në memorie. Unë do t'ju tregoj se çfarë është _score më vonë.

Filtro sipas datës

Ne përdorim kërkesën varg në kontekstin e filtrit:

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

Filtro sipas etiketave

Ne përdorim pyetje termi për të kërkuar ID dokumentesh që përmbajnë një fjalë të caktuar:

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

Kërkimi i tekstit të plotë

Tre nga dokumentet tona përmbajnë sa vijon në fushën e përmbajtjes:

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

Ne përdorim pyetje përputhje për të kërkuar ID dokumentesh që përmbajnë një fjalë të caktuar:

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

Megjithatë, nëse kërkojmë për “histori” në fushën e përmbajtjes, nuk do të gjejmë asgjë, sepse Indeksi përmban vetëm fjalët origjinale, jo rrënjët e tyre. Për të bërë një kërkim me cilësi të lartë, duhet të konfiguroni analizuesin.

Fushë _score tregon rëndësinë. Nëse kërkesa ekzekutohet në një kontekst filtri, atëherë vlera _score do të jetë gjithmonë e barabartë me 1, që do të thotë një përputhje e plotë me filtrin.

analizues

analizues nevojiten për të kthyer tekstin burimor në një grup shenjash.
Analizatorët përbëhen nga një Tokenizues dhe disa opsionale TokenFilters. Tokenizuesi mund të paraprihet nga disa Charfilters. Tokenizuesit thyejnë vargun burimor në shenja, të tilla si hapësira dhe karaktere pikësimi. TokenFilter mund të ndryshojë shenja, të fshijë ose të shtojë të reja, për shembull, të lërë vetëm rrënjën e fjalës, të heqë parafjalët, të shtojë sinonime. CharFilter - ndryshon të gjithë vargun e burimit, për shembull, shkurton etiketat html.

ES ka disa analizues standarde. Për shembull, një analizues rusisht.

Le të përfitojmë api dhe le të shohim se si analizuesit standardë dhe rusë transformojnë vargun "Tregime qesharake për kotele":

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

Analizatori standard ndau vargun në hapësira dhe konvertoi gjithçka në shkronja të vogla, analizuesi rus hoqi fjalët e parëndësishme, e shndërroi atë në shkronja të vogla dhe la bazën e fjalëve.

Le të shohim se cilët Tokenizer, TokenFilters, CharFilters përdor analizuesi rus:

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

Le të përshkruajmë analizuesin tonë bazuar në Rusisht, i cili do të presë etiketat html. Le ta quajmë të paracaktuar, sepse Një analizues me këtë emër do të përdoret si parazgjedhje.

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

Së pari, të gjitha etiketat HTML do të hiqen nga vargu burimor, më pas standardi i tokenizuesit do ta ndajë atë në shenja, shenjat që rezultojnë do të kalojnë në shkronja të vogla, fjalët e parëndësishme do të hiqen dhe shenjat e mbetura do të mbeten burimi i fjalës.

Krijimi i një Indeksi

Më sipër kemi përshkruar analizuesin e paracaktuar. Do të zbatohet për të gjitha fushat e vargut. Postimi ynë përmban një sërë etiketash, kështu që etiketat do të përpunohen gjithashtu nga analizuesi. Sepse Ne po kërkojmë postime sipas përputhjes së saktë me një etiketë, atëherë duhet të çaktivizojmë analizën për fushën e etiketave.

Le të krijojmë një blog indeks2 me një analizues dhe hartografi, në të cilin analiza e fushës së etiketave është e çaktivizuar:

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

Le të shtojmë të njëjtat 3 postime në këtë indeks (blog2). Do ta heq këtë proces sepse... është e ngjashme me shtimin e dokumenteve në indeksin e blogut.

Kërkimi i tekstit të plotë me mbështetje të shprehjes

Le të hedhim një vështrim në një lloj tjetër kërkese:

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

Sepse Ne po përdorim një analizues me origjinë ruse, atëherë kjo kërkesë do të kthejë të gjitha dokumentet, megjithëse ato përmbajnë vetëm fjalën "histori".

Kërkesa mund të përmbajë karaktere të veçanta, për shembull:

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

Kërko sintaksë:

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

Referencat

PS

Nëse jeni të interesuar për artikuj-mësime të ngjashme, keni ide për artikuj të rinj, ose keni propozime për bashkëpunim, atëherë do të jem i lumtur të marr një mesazh në një mesazh personal ose me email [email mbrojtur].

Burimi: www.habr.com