Elasticsearch Temelleri

Elasticsearch, Lucene kullanan ve Java ile yazılmış, json rest api'ye sahip bir arama motorudur. Bu motorun tüm avantajlarının bir açıklaması şu adreste mevcuttur: resmi sitesi. Aşağıda Elasticsearch'ü ES olarak anacağız.

Benzer motorlar bir belge veritabanındaki karmaşık aramalar için kullanılır. Örneğin, dilin morfolojisini dikkate alarak arama yapın veya coğrafi koordinatlara göre arama yapın.

Bu yazıda blog yazılarını indeksleme örneğini kullanarak ES'nin temelleri hakkında konuşacağım. Size belgeleri nasıl filtreleyeceğinizi, sıralayacağınızı ve arayacağınızı göstereceğim.

İşletim sistemine bağlı kalmamak adına ES'e olan tüm isteklerimi CURL kullanarak yapacağım. Ayrıca Google Chrome için bir eklenti de var. duyu.

Metin, belgelere ve diğer kaynaklara bağlantılar içerir. Sonunda belgelere hızlı erişim için bağlantılar bulunmaktadır. Bilinmeyen terimlerin tanımlarını şurada bulabilirsiniz: sözlükler.

ES'yi yükleme

Bunu yapmak için öncelikle Java'ya ihtiyacımız var. Geliştiriciler Tavsiye Java 8 güncelleme 20 veya Java 7 güncelleme 55'ten daha yeni Java sürümlerini yükleyin.

ES dağıtımı şu adreste mevcuttur: geliştirici web sitesi. Arşivi açtıktan sonra çalıştırmanız gerekir bin/elasticsearch. Ayrıca mevcut apt ve yum için paketler. Var liman işçisi için resmi resim. Kurulum hakkında daha fazla bilgi.

Kurulum ve başlatmadan sonra işlevselliği kontrol edelim:

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

curl -X GET $ES_URL

Bunun gibi bir şey alacağız:

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

indeksleme

ES'e bir yazı ekleyelim:

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

Sunucu cevabı:

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

ES otomatik olarak oluşturuldu indeks blog ve tip postalamak. Koşullu bir benzetme yapabiliriz: dizin bir veritabanıdır ve tür bu veritabanındaki bir tablodur. Her türün kendi şeması vardır – haritalamatıpkı ilişkisel bir tablo gibi. Belge dizine eklendiğinde eşleme otomatik olarak oluşturulur:

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

Sunucu yanıtında, indekslenen belgenin alanlarının değerlerini yorumlara ekledim:

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

ES'nin tek bir değer ile bir değerler dizisi arasında ayrım yapmadığını belirtmekte fayda var. Örneğin, başlık alanı yalnızca bir başlık içerir ve etiketler alanı, eşlemede aynı şekilde temsil edilmelerine rağmen bir dizi dize içerir.
Haritalama hakkında daha sonra daha fazla konuşacağız.

istekler

Bir belgeyi kimliğine göre alma:

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

Yanıtta yeni anahtarlar ortaya çıktı: _version и _source. Genel olarak ile başlayan tüm tuşlar _ resmi olarak sınıflandırılır.

Anahtar _version belge sürümünü gösterir. İyimser kilitleme mekanizmasının çalışması için buna ihtiyaç var. Örneğin, sürümü 1 olan bir belgeyi değiştirmek istiyoruz. Değiştirilen belgeyi gönderiyoruz ve bunun, sürümü 1 olan bir belgenin düzenlemesi olduğunu belirtiyoruz. Başka biri de sürüm 1 olan bir belgeyi düzenlediyse ve değişiklikleri bizden önce gönderdiyse, o zaman ES değişikliklerimizi kabul etmeyecek çünkü belgeyi sürüm 2 ile saklar.

Anahtar _source indekslediğimiz belgeyi içerir. ES bu değeri arama işlemleri için kullanmaz çünkü Aramak için indeksler kullanılır. ES, yerden tasarruf etmek için sıkıştırılmış bir kaynak belgeyi saklar. Kaynak belgenin tamamına değil de yalnızca kimliğe ihtiyacımız varsa, kaynak depolamayı devre dışı bırakabiliriz.

Ek bilgiye ihtiyacımız yoksa yalnızca _source içeriğini alabiliriz:

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

Ayrıca yalnızca belirli alanları da 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" : "Веселые котята"
  }
}

Birkaç gönderiyi daha dizine ekleyelim ve daha karmaşık sorgular çalıştıralım.

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

Son gönderiyi seçtik. size Düzenlenecek belge sayısını sınırlar. total taleple eşleşen toplam belge sayısını gösterir. sort çıktıda sıralamanın gerçekleştirildiği bir tamsayı dizisi bulunur. Onlar. tarih bir tamsayıya dönüştürüldü. Sıralama hakkında daha fazla bilgiyi şu adreste bulabilirsiniz: belgeleme.

Filtreler ve sorgular

Sürüm 2'den bu yana ES, filtreler ve sorgular arasında ayrım yapmamaktadır. bağlam kavramı tanıtıldı.
Sorgu bağlamı, sorgunun bir _score oluşturması ve önbelleğe alınmaması açısından filtre bağlamından farklıdır. _score'un ne olduğunu size daha sonra göstereceğim.

Tarihe göre filtrele

İsteği kullanıyoruz menzil filtre bağlamında:

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

Etiketlere göre filtrele

Kullanırız terim sorgusu Belirli bir kelimeyi içeren belge kimliklerini aramak 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" : "Веселые котята",
        "tags" : [ "котята", "смешная история" ]
      }
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "3",
      "_score" : 1.0,
      "_source" : {
        "title" : "Как у меня появился котенок",
        "tags" : [ "котята" ]
      }
    } ]
  }
}

Tam metin araması

Dokümanlarımızdan üçü içerik alanında aşağıdakileri içermektedir:

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

Kullanırız eşleşme sorgusu Belirli bir kelimeyi içeren belge kimliklerini aramak 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ü Dizin yalnızca orijinal kelimeleri içerir, köklerini içermez. Yüksek kaliteli bir arama yapabilmek için analizörü yapılandırmanız gerekir.

Tarla _score gösterileri alaka. İstek bir filtre bağlamında yürütülürse _score değeri her zaman 1'e eşit olacaktır; bu, filtreyle tam bir eşleşme anlamına gelir.

analiz cihazları

analiz cihazları kaynak metni bir dizi simgeye dönüştürmek için gereklidir.
Analizörler bir taneden oluşur Tokenizatör ve birkaç isteğe bağlı Jeton Filtreleri. Tokenizer'ın önünde birkaç tane bulunabilir Karakter Filtreleri. Belirteçler, kaynak dizeyi boşluklar ve noktalama işaretleri gibi belirteçlere böler. TokenFilter belirteçleri değiştirebilir, silebilir veya yenilerini ekleyebilir, örneğin kelimenin yalnızca kökünü bırakabilir, edatları kaldırabilir, eşanlamlıları ekleyebilir. CharFilter - kaynak dizenin tamamını değiştirir, örneğin html etiketlerini keser.

ES'nin birkaç tane var standart analizörler. Örneğin bir analizör Rusça.

hadi kullanalım api ve standart ve Rus analizcilerin "Kediler hakkında komik hikayeler" dizesini nasıl dönüştürdüklerini görelim:

# используем анализатор 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 analizör dizeyi boşluklara böldü ve her şeyi küçük harfe dönüştürdü; Rus analizör, önemsiz kelimeleri çıkardı, küçük harfe dönüştürdü ve kelimelerin kökünü bıraktı.

Rus analizcinin hangi Tokenizer, TokenFilters, CharFilters kullandığını görelim:

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

Html etiketlerini kesecek analizörümüzü Rusçaya dayalı olarak tanımlayalım. Buna varsayılan diyelim, çünkü bu ada sahip bir analizör varsayılan olarak kullanılacaktır.

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

Öncelikle kaynak dizedeki tüm HTML etiketleri kaldırılacak, ardından tokenizer standardı onu tokenlara bölecek, ortaya çıkan tokenlar küçük harfe geçecek, önemsiz kelimeler kaldırılacak ve kalan tokenlar kelimenin kökü olarak kalacak.

Dizin oluşturma

Yukarıda varsayılan analizörü anlattık. Tüm dize alanlarına uygulanacaktır. Gönderimiz bir dizi etiket içerdiğinden etiketler analizör tarafından da işlenecektir. Çünkü Gönderileri bir etiketle tam eşleşmeye göre arıyoruz, ardından etiketler alanı için analizi devre dışı bırakmamız gerekiyor.

Etiketler alanının analizinin devre dışı bırakıldığı, analizör ve haritalama içeren bir blog2 indeksi oluşturalım:

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

Aynı 3 gönderiyi bu dizine (blog2) ekleyelim. Bu işlemi atlayacağım çünkü... blog dizinine belge eklemeye benzer.

İfade desteğiyle tam metin araması

Başka bir istek türüne bakalım:

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

Çünkü Rusça kökenli bir analizör kullanıyoruz, bu durumda bu istek, yalnızca 'geçmiş' kelimesini içermesine rağmen tüm belgeleri döndürecektir.

İstek özel karakterler içerebilir, örneğin:

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

Sözdizimi isteği:

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

referanslar

PS

Benzer makaleler-derslerle ilgileniyorsanız, yeni makaleler için fikirleriniz varsa veya işbirliği önerileriniz varsa, kişisel mesaj veya e-posta yoluyla bir mesaj almaktan memnuniyet duyarım. [e-posta korumalı].

Kaynak: habr.com