Elasticsearch është një motor kërkimi me json rest api, që përdor Lucene dhe është shkruar në Java. Të gjitha përfitimet e këtij motori janë në dispozicion në . Më tej do ta quajmë Elasticsearch si ES.
Motoret e tillë përdoren për kërkime komplekse në një bazë dokumentash. Për shembull, kërkimi duke marrë parasysh morfologjinë e gjuhës ose kërkimi sipas koordinatave geo.
Në këtë artikull do të flas për bazat e ES në shembullin e indeksonit të postimeve të blogut. Do të tregoj si të filtroj, rendit dhe kërkoj dokumente.
Për të mos qenë të varur nga sistemi operativ, të gjitha kërkesat në ES do t'i bëj me ndihmën e CURL. Ka gjithashtu një plugin për google chrome të quajtur .
Në tekst janë vendosur lidhje me dokumentacionin dhe burime të tjera. Në fund janë vendosur lidhje për qasje të shpejtë në dokumentacion. Definimet e termave të panjohur mund të lexohen në .
Instalimi i ES
Për këtë, së pari na nevojitet Java. Zhvilluesit të instaloni versione Java më të reja se Java 8 update 20 ose Java 7 update 55.
Distroja e ES është në dispozicion në . Pas shpërbërjes së arkivit, duhet të startoni bin/elasticsearch. Gjithashtu janë në dispozicion . Ekziston . .
Pas instalimit dhe aktivizimit, do të verifikojmë funksionimin:
# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200
curl -X GET $ES_URLDo të marrim një përgjigje 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" : "E dini, për Kërkimin"
}Indeksimi
Do të shtojmë postin në ES:
# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.
curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
"title": "Веселые котята",
"content": "<p>Një histori qesharake për kotele<p>",
"tags": [
"kotele",
"histori qesharake"
],
"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 e krijoi automatikisht blog dhe post. Mund të bëjmë një analogji të thjeshtë: indeksi është baza e të dhënave, ndërsa lloji është tabela në këtë DB. Çdo lloj ka skemën e tij — , ashtu si një tabelë relationale. Mapping-u gjenerohet automatikisht gjatë indeksimit të dokumentit:
# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"Në përgjigjen e serverit, kam shtuar në komentet vlerat e fushave të dokumentit të indeksuar:
{
"blog" : {
"mappings" : {
"post" : {
"properties" : {
"content" : {
"type" : "string"
},
"published_at" : {
"type" : "date",
"format" : "strict_date_optional_time||epoch_millis"
},
"tags" : {
"type" : "string"
},
"title" : {
"type" : "string"
}
}
}
}
}
}<p>Një histori qesharake për kotele<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": ["mace", "historie qesharake"] */
"tags" : {
"type" : "string"
},
/* "title": "Mace të qeshura" */
"title" : {
"type" : "string"
}
}
}
}
}
}Dhe duhet theksuar se ES nuk bën dallim mes një vlerë të vetme dhe një vargu vlerash. Për shembull, fusha title përmban thjesht titullin, ndërsa fusha tags është një varg shënjash, megjithëse ato paraqiten në mapping njëlloj.
Më vonë do të flasim për mapping në një mënyrë më të detajuar.
Kërkesat
Marrja e dokumentit sipas id-së së 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": "\u0412\u0435\u0441\u0435\u043b\u044b\u0435 \u043a\u043e\u0442\u044f\u0442\u0430",
"content": "<p>Nj\u00eb histori qesharake p\u00ebr kotele<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"
}
}Kthimi ka sjellë çelësa të rinj: _version dhe _source. Në përgjithësi, të gjithë çelësat që fillojnë me _ i përkasin shërbimeve.
Çelësi _version tregon versionin e dokumentit. Ai është i nevojshëm për funksionimin e mekanizmit të bllokimeve optimiste. Për shembull, nëse duam të ndryshojmë një dokument që ka version 1. Ne dërgojmë dokumentin e modifikuar dhe tregojmë se kjo është një redaktim i dokumentit me version 1. Nëse dikush tjetër gjithashtu ka redaktuar dokumentin me version 1 dhe ka dërguar ndryshimet para nesh, atëherë ES nuk do të pranojë ndryshimet tona, pasi ruan dokumentin me version 2.
Çelësi _source përmban atë dokument që kemi indeksuar. ES nuk e përdor këtë vlerë për operacione kërkimi, pasi përdoren indekset për kërkimin. Për të kursyer hapësirë, ES ruan dokumentin origjinal të kompresuar. Nëse na nevojitet vetëm id, dhe jo e gjithë dokumenti origjinal, atëherë mund të çaktivizojmë ruajtjen e origjinalit.
Nëse nuk na nevojitet informacion shtesë, mund të marrim vetëm përmbajtjen e _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>Nj\u00eb histori qesharake p\u00ebr kotele<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"
}
Gjithashtu mund të zgjedhim vetëm fusha të caktuara:
# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Gatot e Lumura"
}
}Le të indeksojmë disa postime të tjera dhe të kryejmë kërkesa më të komplikuara.
curl -XPUT "$ES_URL/blog/post/2" -d'
{
"title": "Këlyshë të gëzuar",
"content": "<p>Një histori qesharake për këlyshët<p>",
"tags": [
"këlyshë",
"histori qesharake"
],
"published_at": "2014-08-12T20:44:42+00:00"
}'curl -XPUT "$ES_URL/blog/post/3" -d'
{
"title": "Si si më doli një katund",
"content": "<p>Një histori prekëse për një katund të varfër nga rruga<p>"
"tags": [
"katundë"
],
"published_at": "2014-07-21T20:44:42+00:00"
}'Renditja
# найдем последний пост по дате публикации и извлечем поля 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" : "Macesh të Lumtur",
"published_at" : "2014-09-12T20:44:42+00:00"
},
"sort" : [ 1410554682000 ]
} ]
}
}Zgjedhëm postimin e fundit. size kufizon numrin e dokumenteve në rezultat. total tregon numrin total të dokumenteve që përputhen me kërkesën. sort në rezultat përmban një varg numrash të plotë, sipas të cilëve bëhet renditja. Kjo do të thotë, data është transformuar në një numër të plotë. Më shumë rreth renditjes mund të lexoni në .
Filtër dhe Kërkesa
ES që nga versioni 2 nuk dallon filtrat nga kërkesat, përkundrazi .
Konteksti i kërkesës ndryshon nga konteksti i filtrit në atë që kërkesa gjeneron _score dhe nuk ruhet. Çfarë është _score do ta tregoj më vonë.
Filtrimi sipas datës
Përdorim kërkesën në kontekstin e filtrit:
# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"filter": {
"range": {
"published_at": { "gte": "2014-09-01" }
}
}
}'Filtrimi sipas etiketave
Përdorim për të kërkuar id dokumentesh që përmbajnë fjalën e 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" : "Kuzhinë të Gëzuara",
"tags" : [ "macet", "historia qesharake" ]
}
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"title" : "Si doja një macë",
"tags" : [ "macet" ]
}
} ]
}
}Kërkimi në tekst të plotë
Të tre dokumentet tanë përmbajnë në fushën e përmbajtjes sa vijon:
<p>Një histori qesharake për kotele<p><p>Një histori qesharake për këlyshët<p><p>Një histori prekëse për një katund të varfër nga rruga<p>
Përdorim për të kërkuar id dokumentesh që përmbajnë fjalën e 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ërkoni 'historitë' në fushën e përmbajtjes, ne nuk do të gjejmë asgjë, pasi indeksi përmban vetëm fjalët origjinale dhe jo rrënjët e tyre. Për të bërë një kërkim cilësor, duhet të konfigurohet analizatori.
Fusha _score tregon . Nëse kërkesa ekzekutohet në kontekstin e filtrit, atëherë vlera _score gjithmonë do të jetë barabartë me 1, që do të thotë përputhje të plotë me filtrin.
Analizatorët
janë të nevojshëm për të shndërruar tekstin burimor në një grup tokenësh.
Analizatorët përbëhen nga një dhe disa opsionale . Tokenizer mund të parashikohet nga disa . Tokenizer ndan vargun burimor në tokenë, për shembull, në hapësira dhe simbole pikësimi. TokenFilter mund të modifikojë tokenët, të heqë ose të shtojë të rinj, për shembull, të lërë vetëm rrënjën e fjalës, të heqë parafjalët, të shtojë sinonime. CharFilter — modifikon vargun burimor në përgjithësi, për shembull, heq etiketat html.
Në ES ka disa . Për shembull, analizatori .
Le të përdorim dhe të shohim se si analizatorët standard dhe russian shndërrojnë vargun "Histori argëtuese mbi macet":
# используем анализатор 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" : "histori",
"start_offset" : 0,
"end_offset" : 7,
"type" : "",
"position" : 0
}, {
"token" : "argëtuese",
"start_offset" : 8,
"end_offset" : 15,
"type" : "",
"position" : 1
}, {
"token" : "për",
"start_offset" : 16,
"end_offset" : 19,
"type" : "",
"position" : 2
}, {
"token" : "macet",
"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" : "i gëzuar",
"start_offset" : 0,
"end_offset" : 7,
"type" : "",
"position" : 0
}, {
"token" : "historik",
"start_offset" : 8,
"end_offset" : 15,
"type" : "",
"position" : 1
}, {
"token" : "mace",
"start_offset" : 20,
"end_offset" : 25,
"type" : "",
"position" : 3
} ]
}Analizatori standard e ndau stringun sipas hapësirave dhe e kthye të gjitha në të vogël, analizatori russian — eliminon fjalët e parëndësishme, e kthen në të vogël dhe e lë rrënjën e fjalëve.
Të shohim se cilat Tokenizer, TokenFilters, CharFilters përdor analizatori russian:
{
"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 nuk ka */
}
}
}Le të përshkruajmë analizatorin tonë bazuar në russian, që do të heqë etiketat html. Do ta emërtojmë default, pasi analizatori me këtë emër do të përdoret si standard.
{
"filter": {
"ru_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"ru_stemmer": {
"type": "stemmer",
"language": "russian"
}
},
"analyzer": {
"default": {
/* shtojme heqjen e etiketave html */
"char_filter": ["html_strip"],
"tokenizer": "standard",
"filter": [
"lowercase",
"ru_stop",
"ru_stemmer"
]
}
}
}Filli do të hiqen të gjitha etiketat html nga stringu origjinal, pastaj ajo do të ndahet në tokena me tokenizer standard, tokenat e marra do të kalojnë në shkronja të vogla, do të hiqen fjalët e parëndësishme dhe nga tokenat e mbetur do të mbetet rrënja e fjalës.
Krijimi i indeksit
Më lart përshkruam analizuesin default. Ai do të aplikohet për të gjitha fusha tekstuale. Postimi ynë përmban një varg etiketash, për rrjedhojë, etiketat gjithashtu do të përpunohen nga analizuesi. Duke qenë se ne kërkojmë postimet sipas përputhjes së saktë me etiketën, është e nevojshme të çaktivizohet analiza për fushën tags.
Do të krijojmë indeksin blog2 me analizuesin dhe hartimin, në të cilin është çaktivizuar analiza e fushës tags:
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"
}
}
}
}
}'Do të shtojmë të njëjtat 3 postime në këtë indekse (blog2). Do ta kaloj këtë proces, sepse është i ngjashëm me shtimin e dokumenteve në indeksin blog.
Kërkimi i plotë me mbështetje për shprehjet
Le të njihemi me një tjetër lloj kërkesash:
# найдем документы, в которых встречается слово 'истории'
# 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"
]
}
}
}'Pasi përdorim një analist me stamë ruse, kjo kërkesë do të kthejë të gjitha dokumentet, megjithëse në to është vetëm fjala 'histori'.
Kërkesa mund të përmbajë simbole speciale, për shembull:
""vejca të fërguara" +(brinjë | patate) -frittata"Sintaksa e kërkesës:
+ tregon operacionin E DHE
| tregon operacionin O
- mohon një token të vetme
" përfshin një numër tokensh për të treguar një frazë që do të kërkohet
* në fund të një termi tregon një pyetje prefikse
( dhe ) tregojnë prioritetin
~N pas një fjale tregon distancën e editimit (përkatësinë)
~N pas një fraze tregon shumën e slopt# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
"query": {
"simple_query_string": {
"query": "-щенки",
"fields": [
"title^3",
"tags^2",
"content"
]
}
}
}'
# получим 2 поста про котиковLinke
PS
Nëse jeni të interesuar për artikuj dhe mësime të ngjashme, keni ide për artikuj të rinj ose propozime për bashkëpunim, do të isha i lumtur të merrja një mesazh në privat ose në email m.kuzmin+habr@darkleaf.ru.
Burimi: habr.com
