Elasticsearch Əsasları

Elasticsearch, Lucene-dən istifadə edən və Java-da yazılmış JSON REST API ilə axtarış motorudur. Bu mühərrikin bütün üstünlüklərinin təsviri burada mövcuddur rəsmi saytBundan sonra biz Elasticsearch-a ES kimi müraciət edəcəyik.

Bu cür mühərriklər dil morfologiyası əsasında axtarış və ya coğrafi koordinatlar üzrə axtarış kimi mürəkkəb sənəd verilənlər bazası axtarışları üçün istifadə olunur.

Bu yazıda mən blog yazılarının indeksləşdirilməsi nümunəsindən istifadə edərək ES-nin əsaslarını əhatə edəcəyəm. Mən sizə sənədləri necə süzgəcdən keçirməyi, çeşidləməyi və axtarmağı göstərəcəyəm.

Əməliyyat sistemindən müstəqil olmaq üçün mən bütün ES sorğularımı CURL istifadə edərək edəcəm. Google Chrome üçün plagin də var mənada.

Sənədlərə və digər mənbələrə keçidlər mətn boyu verilir. Sənədlərə sürətli giriş bağlantıları sonunda təqdim olunur. Tanış olmayan terminlərin təriflərinə burada rast gəlmək olar lüğətlər.

ES quraşdırılması

Bunun üçün bizə ilk olaraq Java lazımdır. Tərtibatçılar məsləhətdir Java 8 yeniləmə 20 və ya Java 7 yeniləmə 55-dən daha yeni Java versiyalarını quraşdırın.

ES paylanması mövcuddur developer saytıArxivi açdıqdan sonra qaçmaq lazımdır bin/elasticsearch. Həmçinin mövcuddur apt və yum üçün paketlər. Var Docker üçün rəsmi şəkil. Quraşdırma haqqında daha çox.

Quraşdırıldıqdan və işə salındıqdan sonra funksionallığı yoxlayaq:

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

curl -X GET $ES_URL

Bu kimi bir cavab alacağıq:

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

İndeksləmə

ES-ə yazı əlavə edək:

# Добавим документ 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"
}'

server cavabı:

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

ES avtomatik yaradılmışdır indeks blog və Tipi post. Təxmini bir bənzətmə çəkmək olar: indeks verilənlər bazasıdır, tip isə həmin verilənlər bazası daxilindəki cədvəldir. Hər növün öz sxemi var - Xəritəçəkmə, eynilə əlaqə cədvəli kimi. Sənəd indeksləşdirildikdə xəritə avtomatik olaraq yaradılır:

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

Server cavabında indekslənmiş sənədin sahə dəyərlərini şərhlərə əlavə etdim:

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

Qeyd etmək lazımdır ki, ES tək dəyər və dəyərlər massivi arasında fərq qoymur. Məsələn, başlıq sahəsində sadəcə başlıq var, teqlər sahəsində isə xəritədə eyni şəkildə təmsil olunsalar da, bir sıra sətirlər var.
Xəritəçəkmə haqqında daha sonra daha ətraflı danışacağıq.

İstək

Sənədin şəxsiyyət vəsiqəsi ilə çıxarılması:

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

Cavabda yeni açarlar göründü: _version и _sourceÜmumiyyətlə, ilə başlayan bütün düymələr _ xidmət obyektləri kimi təsnif edilir.

Açar _version Sənədin versiyasını göstərir. Bu, optimist kilidləmə mexanizminin işləməsi üçün lazımdır. Məsələn, biz 1-ci versiya ilə sənədi dəyişmək istəyirik. Biz dəyişdirilmiş sənədi təqdim edirik və bunun 1-ci versiyaya malik sənədə yenidən baxılması olduğunu bildiririk. Əgər başqası da 1-ci versiyaya malik sənədi redaktə edibsə və dəyişiklikləri bizdən əvvəl təqdim edibsə, ES sənədi 2-ci versiya ilə saxladığı üçün dəyişikliklərimizi qəbul etməyəcək.

Açar _source İndekslədiyimiz sənədi ehtiva edir. ES bu dəyəri axtarış əməliyyatları üçün istifadə etmir, çünki indekslər axtarış üçün istifadə olunur. Yerə qənaət etmək üçün ES orijinal sənədin sıxılmış versiyasını saxlayır. Əgər bizə bütün orijinal sənəd yox, yalnız şəxsiyyət vəsiqəsi lazımdırsa, orijinalın saxlanmasını söndürə bilərik.

Əgər bizə əlavə məlumat lazım deyilsə, biz yalnız _source məzmununu əldə edə bilərik:

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

Siz həmçinin yalnız müəyyən sahələri seçə bilərsiniz:

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

Gəlin daha bir neçə postu indeksləşdirək və daha mürəkkəb sorğular aparaq.

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

Sortlaşdırma

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

Sonuncu postu seçdik. size verilən sənədlərin sayını məhdudlaşdırır. total sorğuya uyğun gələn sənədlərin ümumi sayını göstərir. sort Çıxış çeşidləmənin həyata keçirildiyi tam ədədlər massivindən ibarətdir. Yəni tarix tam ədədə çevrilmişdir. Çeşidləmə haqqında daha çox oxuya bilərsiniz sənədləşdirmə.

Filtrlər və sorğular

Versiya 2-dən bəri ES filtrlər və sorğular arasında fərq qoymur kontekstlər anlayışı təqdim edilir.
Sorğu konteksti filtr kontekstindən ona görə fərqlənir ki, sorğu _score yaradır və keşlənmir. Hesabın nə olduğunu sonra izah edəcəyəm.

Tarixə görə süzün

Sorğunun istifadəsi silsilə filtr kontekstində:

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

Teqlərə görə filtrləmə

Biz istifadə edirik müddətli sorğu Verilmiş sözü ehtiva edən sənəd identifikatorlarını axtarmaq üçün:

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

Tam mətn axtarışı

Üç sənədimiz məzmun sahəsində aşağıdakıları ehtiva edir:

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

Biz istifadə edirik uyğun sorğu Verilmiş sözü ehtiva edən sənəd identifikatorlarını axtarmaq üçün:

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

Bununla belə, məzmun sahəsində "hekayələri" axtarsaq, heç nə tapa bilmərik, çünki indeksdə yalnız orijinal sözlər var, onların kökləri deyil. Keyfiyyətli axtarış aparmaq üçün analizatoru konfiqurasiya etməliyik.

Sahə _score göstərir aktuallıqSorğu filtr kontekstində yerinə yetirilirsə, _score dəyəri həmişə 1 olacaq, yəni filtr tam uyğundur.

Analizatorlar

Analizatorlar mənbə mətni işarələr toplusuna çevirmək üçün lazımdır.
Analizatorlar birdən ibarətdir Tokenizator və bir neçə isteğe bağlı Token FiltrləriTokenizer bir neçədən əvvəl ola bilər CharFiltersTokenizatorlar mənbə sətirini işarələrə, məsələn, boşluqlara və durğu işarələrinə bölür. TokenFilters tokenləri dəyişdirə, silə və ya yenilərini əlavə edə bilər, məsələn, yalnız sözün kökünü tərk etməklə, ön sözləri silməklə və ya sinonimlər əlavə etməklə. CharFilters, məsələn, HTML teqlərini silməklə mənbə sətirini tamamilə dəyişdirir.

ES-də bir neçə var standart analizatorlarMəsələn, analizator rus.

Gəlin yararlanaq Api və standart və rus analizatorlarının "Kediciklər haqqında məzəli hekayələr" sətirini necə çevirdiyini görək:

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

Standart analizator sətri boşluqlara böldü və hər şeyi kiçik hərflərə çevirdi, rus analizatoru əhəmiyyətsiz sözləri çıxardı, onları kiçik hərflərə çevirdi və sözün köklərini buraxdı.

Rus analizatorunun hansı Tokenizer, TokenFilters və CharFilters istifadə etdiyinə 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 отсутствуют */
    }
  }
}

Gəlin HTML teqlərini siləcək öz rus əsaslı analizatorumuzu təsvir edək. Biz onu defolt adlandıracağıq, çünki bu, standart analizator olacaq.

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

Əvvəlcə bütün HTML teqləri mənbə sətirindən silinəcək, sonra o, tokenizer standartı ilə tokenlərə bölünəcək, nəticədə yaranan tokenlər kiçik hərflərə çevriləcək, əhəmiyyətsiz sözlər silinəcək və qalan tokenlər sözün kökü olacaq.

İndeksin yaradılması

Yuxarıda standart analizatoru təsvir etdik. O, bütün sətir sahələrinə tətbiq olunacaq. Yazımızda bir sıra etiketlər var, ona görə də teqlər analizator tərəfindən işlənəcək. Dəqiq etiketə uyğun yazılar axtardığımız üçün "teqlər" sahəsi üçün təhlili deaktiv etməliyik.

Analizator və xəritəçəkmə ilə blog2 indeksi yaradaq, burada etiketlər sahəsinin təhlili qeyri-aktivdir:

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

Gəlin bu indeksə (blog2) eyni üç yazı əlavə edək. Bu prosesi atlayacağam, çünki bu, blog indeksinə sənədlər əlavə etməyə bənzəyir.

İfadə dəstəyi ilə tam mətn axtarışı

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

Rus mənşəli analizatordan istifadə etdiyimiz üçün bu sorğu bütün sənədləri qaytaracaq, baxmayaraq ki, onlar yalnız "tarix" sözünü ehtiva edir.

Sorğuda xüsusi simvollar ola bilər, məsələn:

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

Sorğu sintaksisi:

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

References

PS

Bənzər dərslik məqalələri ilə maraqlanırsınızsa, yeni məqalələr üçün ideyalarınız varsa və ya əməkdaşlıq təklifləriniz varsa, şəxsi mesaj və ya m.kuzmin+habr@darkleaf.ru elektron poçtu vasitəsilə sizinlə əlaqə saxlamaqdan məmnun olaram.

Mənbə: www.habr.com