Elasticsearch – Lucene-dən istifadə edən və Java-da yazılmış json rest api ilə axtarış motorudur. Bu motorun bütün üstünlükləri Azərbaycan dili üzrə ES olaraq adlandırılacaq.
Belə motorlar, sənəd bazası üzrə mürəkkəb axtarışlarda istifadə olunur. Məsələn, dil morfologiyasını nəzərə alaraq axtarış və ya geo koordinatlara əsasən axtarış.
Bu məqalədə blog yazılarının indeksasiyası nümunəsində ES-in əsaslarından danışacağam. Sənədləri necə süzgəc etməyi, sıralamağı və axtarmağı göstərəcəyəm.
Əməliyyat sistemindən asılı qalmaq üçün, bütün ES sorğularını CURL vasitəsilə edəcəyəm. Həmçinin, google chrome üçün .
Mətn boyunca sənədləşdirmə və digər mənbələrə keçidlər yerləşdirilmişdir. Sonda, sənədləşdirməyə tez erişim üçün keçidlər yerləşdirilib. Tanımadığınız terminlərin təriflərini .
ES-in Quraşdırılması
Bunun üçün əvvəlcə Java tələb olunur. İnkişaf etdiricilər Java 8 update 20-dən və ya Java 7 update 55-dən sonrakı versiyalarını quraşdırsınlar.
ES-in distributivi . Arxivdən çıxardıqdan sonra bin/elasticsearch. Həmçinin -də bir meta-paket yoxdur. . .
Quraşdırıb işə saldıqdan sonra, işləkliyi yoxlayaq:
# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200
curl -X GET $ES_URLBizə belə bir cavab gələcək:
{
"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" : "Axtarış üçün, bilirsinizki"
}İndeksasiya
ES-ə bir yazı əlavə edirik:
# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.
curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
"title": "Веселые котята",
"content": "<p>Maraqlı bir pişik hekayəsi<p>",
"tags": [
"pişiklər",
"maraqlı hekayə"
],
"published_at": "2014-09-12T20:44:42+00:00"
}'
server cavabı:
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : false
}
ES avtomatik olaraq və mapping Server cavabında, indeksasiya olunmuş sənədin sahələrinin dəyərlərini şərh qoyaraq əlavə etdim:
# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"Qeyd etmək lazımdır ki, ES tək dəyər və dəyər massivləri arasında fərq qoymur. Məsələn, title sahəsi sadəcə başlıqdır, tags sahəsi isə sətirlərdə massivdir, baxmayaraq ki, onlar mappingdə eyni şəkildə təqdim edilir.
{
"blog" : {
"mappings" : {
"post" : {
"properties" : {
/* "content": "<p>Maraqlı bir pişik hekayəsi<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"
}
}
}
}
}
}Sonradan mapping haqqında daha dərin danışacağıq.
Sorğular
Sənədin id-sinə görə çıxarılması:
Cavabda yeni açarların olduğu göründü:
# извлечем документ с 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>Maraql\u0131 bir pi\u015fik hekay\u0259si<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"
}
}_version _source və . Əslində, bütün açarlarilə başlayır. _ xidmət açarlarına aiddir.
Açar _source sənədin versiyasını göstərir. Bu, optimist kilit mekanizminin işləməsi üçün tələb olunur. Məsələn, biz 1 versiyalı sənədi dəyişmək istəyirik. Dəyişdirilmiş sənədi göndərir və 1 versiyalı sənədin düzəlişi olduğunu bildirmiş oluruq. Əgər başqa biri də 1 versiyalı sənədi redaktə edibsə və dəyişiklikləri bizdən əvvəl göndəribsə, onda ES bizim dəyişikliklərimizi qəbul etməyəcək, çünki o, 2 versiyalı sənədi saxlayır.
Açar . Əslində, bütün açarlar indekslediğimiz o belgeyi içerir. ES, indekslerin kullanıldığı için arama işlemlerinde bu değeri kullanmaz. Alan tasarrufu için ES, sıkıştırılmış orijinal belgeyi depolar. Sadece id'ye ihtiyacımız varsa, orijinal belgenin saklanmasını devre dışı bırakabiliriz.
Ek bir bilgiye ihtiyaç yoksa, yalnızca _source içeriğini alabiliriz:
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>Maraql\u0131 bir pi\u015fik hekay\u0259si<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"
}
Ayrıca yalnızca belirli alanları seçebilirsiniz:
# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Komik Kediler"
}
}Daha fazla gönderi indeksleyelim ve daha karmaşık sorgular gerçekleştirelim.
curl -XPUT "$ES_URL/blog/post/2" -d'
{
"title": "Şən itciklər",
"content": "<p>İtclərin gülməli hekayəsi<p>",
"tags": [
"itciklər",
"gülməli hekayə"
],
"published_at": "2014-08-12T20:44:42+00:00"
}'curl -XPUT "$ES_URL/blog/post/3" -d'
{
"title": "Pişiyimin necə ortaya çıxdı",
"content": "<p>Fakir küçə pişiyinə dair təsirli hekayə<p>",
"tags": [
"pişiylər"
],
"published_at": "2014-07-21T20:44:42+00:00"
}'Sıralama
# найдем последний пост по дате публикации и извлечем поля 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" : "Komik Kediler",
"published_at" : "2014-09-12T20:44:42+00:00"
},
"sort" : [ 1410554682000 ]
} ]
}
}Son gönderiyi seçtik. boyut çıktıdaki belge sayısını sınırlar. toplam sorguya uyan toplam belge sayısını gösterir. sıralama sonuçta sıralama için kullanılması gereken tam sayılardan oluşan bir dizi içerir. Yani tarih bir tam sayıya dönüştürülmüştür. Sıralama hakkında daha fazla bilgi okuyabilirsiniz .
Filtreler ve sorgular
ES, 2. sürümden itibaren filtreler ve sorguları ayırt etmez, bunun yerine .
Sorgu bağlamı, filtre bağlamından farklıdır çünkü sorgu _score üretir ve önbelleğe alınmaz. _score'un ne olduğunu daha sonra göstereceğim.
Tarih filtreleme
sorgu kullanıyoruz filter bağlamında:
# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"filter": {
"range": {
"published_at": { "gte": "2014-09-01" }
}
}
}'Etiketler ile filtreleme
İstifadə olunur belirtilen kelimeyi içeren belgelerin id'sini bulmak için:
# найдем все документы, в поле 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" : "Komik Kediler",
"tags" : [ "kediler", "komik hikaye" ]
}
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"title" : "Kedim Nasıl Oldu",
"tags" : [ "kediler" ]
}
} ]
}
}Tam metin araması
Üç belgelerimiz content alanında şunları içeriyor:
<p>Maraqlı bir pişik hekayəsi<p><p>İtclərin gülməli hekayəsi<p><p>Fakir küçə pişiyinə dair təsirli hekayə<p>
İstifadə olunur belirtilen kelimeyi içeren belgelerin id'sini bulmak için:
# 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
} ]
}
}Ancak, içerik alanında "hikayeler" ararsak, hiçbir şey bulamayız çünkü indeks sadece orijinal kelimeleri içerir, köklerini değil. Kaliteli bir arama yapmak için analizörü ayarlamak gerekir.
Alan _score gösterir . Eğer sorgu filter bağlamında çalışıyorsa, _score değeri her zaman 1'e eşit olacak, bu da filtrenin tam eşleşmesini ifade eder.
Analizörler
gərəkdir, ilkin mətni tokenlər dəstinə çevirmək üçün.
Analizatorlar birindən ibarətdir və bir neçə əlavə . Tokenizer bir neçə ilə müşayiət oluna bilər . Tokenizer ilkin mətni tokenlərə bölür, məsələn, boşluqlar və durğu işarələri ilə. TokenFilter tokenləri dəyişə, silə və ya yeni tokenlər əlavə edə bilər, məsələn, yalnız sözün kökünü saxlamaq, bağlayıcıları silmək, sinonimlər əlavə etmək. CharFilter isə ilkin mətnə tamamilə təsir edir, məsələn, html etiketlərini kəsir.
ES-də bir neçə . Məsələn, analizator .
İstifadə edəcəyik və standart və russian analizatorlarının «Şən pişik hekayələri» cümləsini necə çevirdiyinə baxacağıq:
# используем анализатор 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" : "şən",
"start_offset" : 0,
"end_offset" : 7,
"type" : "",
"position" : 0
}, {
"token" : "hekayələr",
"start_offset" : 8,
"end_offset" : 15,
"type" : "",
"position" : 1
}, {
"token" : "haqqında",
"start_offset" : 16,
"end_offset" : 19,
"type" : "",
"position" : 2
}, {
"token" : "pişiklər",
"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" : "şən",
"start_offset" : 0,
"end_offset" : 7,
"type" : "",
"position" : 0
}, {
"token" : "hekayə",
"start_offset" : 8,
"end_offset" : 15,
"type" : "",
"position" : 1
}, {
"token" : "pişik",
"start_offset" : 20,
"end_offset" : 25,
"type" : "",
"position" : 3
} ]
}Standart analizator cümləni boşluqlara görə parçaladı və hər şeyi kiçik hərflərə çevirdi, russian analizatoru — əhəmiyyətsiz sözləri sildi, kiçik hərflərə çevirdi və sözlərin kökünü saxladı.
Russian analizatorunun istifadə etdiyi Tokenizer, TokenFilters, CharFilters haqqında bir nümunəyə baxaq:
{
"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 yoxdur */
}
}
}HTML etiketlərini kəsən öz analizatorumuzu russian əsasında təsvir edək. Buna default adını verəcəyik, çünki bu adla olan analizator standart olaraq istifadə ediləcək.
{
"filter": {
"ru_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"ru_stemmer": {
"type": "stemmer",
"language": "russian"
}
},
"analyzer": {
"default": {
/* html etiketlərini kəsməni əlavə edirik */
"char_filter": ["html_strip"],
"tokenizer": "standard",
"filter": [
"lowercase",
"ru_stop",
"ru_stemmer"
]
}
}
}İlk olaraq, ilkin mətndən bütün html etiketləri silinəcək, daha sonra onu tokenizer standard ilə tokenlərə böləcək, əldə olunan tokenlər kiçik hərflərə çevriləcək, əhəmiyyətsiz sözlər silinəcək və qalan tokenlərdən sözlərin kökü qalacaq.
İndeksin yaradılması
Yuxarıda biz default analizatoru təsvir etmişik. Bu, bütün string sahələrinə tətbiq olunacaq. Bizim postumuz tagların massivini ehtiva edir, buna görə taglar da analizatorla işlənəcək. Əgər biz tag üzrə dəqiq uyğunluqla postları axtarırıqsa, o zaman tags sahəsi üçün analizi dayandırmaq vacibdir.
tags sahəsinin analizini dayandırdığımız blog2 indeksini və xəritələşdirməni yaradacağıq:
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"
}
}
}
}
}'Eyni 3 postu bu indeksə (blog2) əlavə edəcəyik. Bu prosesi atlayacağam, çünki o, blog indeksinə sənədlərin əlavə olunması ilə eynidir.
Tam mətni axtarış ifadələrinin dəstəklənməsi ilə
Başqa bir sorğu növü ilə tanış olaq:
# найдем документы, в которых встречается слово 'истории'
# 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"
]
}
}
}'Biz rus kökdən istifadə edən analizatoru işlətdiyimiz üçün bu sorğu bütün sənədləri qaytaracaq, baxmayaraq ki, onlarda yalnız 'tarix' sözü var.
Sorğuda xüsusi simvollar ola bilər, məsələn:
""fried eggs" +(eggplant | potato) -frittata"Sorğu sintaksisi:
+ VƏ əməliyyatını ifadə edir
| VƏ ya əməliyyatını ifadə edir
- bir tək tokeni inkar edir
" bir neçə tokeni bir axtarış ifadəsi kimi ifadə etmək üçün sarır
* terminin sonunda prefiks sorğusunu ifadə edir
( və ) prioriteti ifadə edir
~N bir sözün redaktə məsafəsini (bulanıqlıq) ifadə edir
~N bir ifadənin slop miqdarını ifadə edir# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
"query": {
"simple_query_string": {
"query": "-щенки",
"fields": [
"title^3",
"tags^2",
"content"
]
}
}
}'
# получим 2 поста про котиковBağlantılar
PS
Belə məqalə-dərslər maraqlandırırsa, yeni məqalə ideyaları varsa və ya əməkdaşlıq təklifləriniz varsa, xahiş edirəm, şəxsi mesajla və ya m.kuzmin+habr@darkleaf.ru elektron poçtuna yazın.
Mənbə: habr.com
