Elasticsearch հիմունքներ

Elasticsearch-ը որոնողական համակարգ է json rest api-ով, օգտագործում է Lucene և գրված է Java լեզվով։ Այս համակարգի բոլոր առավելությունների նկարագրությունը հասանելի է հետևյալ հղումով՝ պաշտոնական կայքըԱյսուհետ մենք Elasticsearch-ը կանվանենք ES։

Այսպիսի շարժիչները օգտագործվում են փաստաթղթերի տվյալների բազայում բարդ որոնումների համար։ Օրինակ՝ լեզվի ձևաբանությունը հաշվի առնող որոնումներ կամ աշխարհագրական կոորդինատներով որոնումներ։

Այս հոդվածում ես ձեզ կպատմեմ ES-ի հիմունքների մասին՝ օգտագործելով բլոգի գրառումների ինդեքսավորման օրինակը: Ես ձեզ կցույց տամ, թե ինչպես զտել, տեսակավորել և որոնել փաստաթղթերը:

Օպերացիոն համակարգից անկախ լինելու համար ես ES-ին ուղղված բոլոր հարցումները կկատարեմ CURL-ի միջոցով: Google Chrome-ի համար կա նաև մի հավելված, որը կոչվում է իմաստ.

Տեքստում ներկայացված են փաստաթղթերի և այլ աղբյուրների հղումներ։ Վերջում կան հղումներ՝ փաստաթղթերին արագ մուտք գործելու համար։ Անծանոթ տերմինների սահմանումները կարելի է գտնել բառարաններ.

ES-ի տեղադրում

Դրա համար մեզ նախ անհրաժեշտ է Java: Մշակողներ Խորհուրդ տալ Տեղադրեք Java-ի տարբերակներ, որոնք ավելի նոր են, քան Java 8 թարմացում 20-ը կամ Java 7 թարմացում 55-ը։

ES բաշխումը հասանելի է հետևյալ հասցեով՝ մշակողի կայքԱրխիվը բացելուց հետո դուք պետք է գործարկեք bin/elasticsearchՆաև հասանելի է փաթեթներ apt-ի և yum-ի համար. կա Docker-ի պաշտոնական պատկերը. Ավելին տեղադրման մասին.

Տեղադրումից և գործարկումից հետո եկեք ստուգենք ֆունկցիոնալությունը.

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

curl -X GET $ES_URL

Մենք կստանանք պատասխան, որը նման կլինի մոտավորապես հետևյալին.

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

Ինդեքսավորում

Եկեք 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"
}'

սերվերի պատասխանը՝

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

ES-ը ավտոմատ կերպով ստեղծվել է ինդեքս բլոգ և Тип գրառում։ Կարող ենք պայմանական անալոգիա անցկացնել. ինդեքսը տվյալների բազա է, իսկ տեսակը՝ աղյուսակ այս տվյալների բազայում։ Յուրաքանչյուր տեսակ ունի իր սեփական սխեման՝ քարտեզագրման, ինչպես ռելացիոն աղյուսակը։ Քարտեզագրումը ստեղծվում է ավտոմատ կերպով, երբ փաստաթուղթը ինդեքսավորվում է։

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

Սերվերի պատասխանում ես մեկնաբանություններում ավելացրեցի ինդեքսավորված փաստաթղթի դաշտերի արժեքները.

{
  "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-ը չի տարբերակում մեկ արժեքը և արժեքների զանգվածը։ Օրինակ, վերնագրի դաշտը պարունակում է միայն վերնագիր, իսկ պիտակների դաշտը՝ տողերի զանգված, չնայած դրանք ներկայացված են նույն ձևով։
Քարտեզագրման մասին ավելի մանրամասն կխոսենք ավելի ուշ։

հարցումները

Փաստաթղթի արդյունահանում իր ID-ով.

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

Պատասխանում հայտնվեցին նոր բանալիներ. _version и _sourceԸնդհանուր առմամբ, բոլոր ստեղները, որոնք սկսվում են -ով _ դասակարգվում են որպես սպասարկման առարկաներ։

Բանալին _version ցույց է տալիս փաստաթղթի տարբերակը։ Այն անհրաժեշտ է լավատեսական կողպման մեխանիզմի աշխատանքի համար։ Օրինակ, մենք ուզում ենք փոխել փաստաթուղթը 1-ին տարբերակով։ Մենք ուղարկում ենք փոփոխված փաստաթուղթը և նշում, որ սա 1-ին տարբերակով փաստաթղթի խմբագրում է։ Եթե ինչ-որ մեկը նույնպես խմբագրել է 1-ին տարբերակով փաստաթուղթը և ուղարկել է փոփոխությունները մեզանից առաջ, ապա ES-ը չի ընդունի մեր փոփոխությունները, քանի որ այն պահպանում է փաստաթուղթը 2-րդ տարբերակով։

Բանալին _source պարունակում է մեր ինդեքսավորված փաստաթուղթը: ES-ը չի օգտագործում այս արժեքը որոնման գործողությունների համար, քանի որ որոնման համար օգտագործվում են ինդեքսներ: Տեղ խնայելու համար ES-ը պահպանում է սեղմված սկզբնաղբյուր փաստաթուղթը: Եթե մեզ անհրաժեշտ է միայն id-ն, այլ ոչ թե ամբողջ սկզբնաղբյուր փաստաթուղթը, ապա կարող ենք անջատել սկզբնաղբյուրի պահպանումը:

Եթե ​​մեզ լրացուցիչ տեղեկատվություն պետք չէ, կարող ենք ստանալ միայն _source-ի պարունակությունը։

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

Կարող եք նաև ընտրել միայն որոշակի դաշտեր՝

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

Եկեք ինդեքսավորենք ևս մի քանի գրառում և կատարենք ավելի բարդ հարցումներ։

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

Մենք ընտրել ենք վերջին գրառումը։ size սահմանափակում է տրամադրված փաստաթղթերի քանակը։ total ցույց է տալիս հարցմանը համապատասխանող փաստաթղթերի ընդհանուր քանակը։ sort ելքում պարունակում է ամբողջ թվերի զանգված, որի միջոցով կատարվում է տեսակավորումը։ Այսինքն՝ ամսաթիվը վերածվել է ամբողջ թվի։ Դուք կարող եք ավելին կարդալ տեսակավորման մասին փաստաթղթավորում.

Ֆիլտրեր և հարցումներ

ES, քանի որ 2-րդ տարբերակը չի տարբերակում ֆիլտրերը և հարցումները, փոխարենը Ներկայացվում է համատեքստերի հասկացությունը.
Հարցման համատեքստը տարբերվում է ֆիլտրի համատեքստից նրանով, որ հարցումը ստեղծում է _score և չի պահվում քեշում: Ես ձեզ ցույց կտամ, թե ինչ է _score-ը ավելի ուշ:

Զտել ըստ ամսաթվի

Մենք օգտագործում ենք հարցումը շարք ֆիլտրի համատեքստում՝

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

Զտել ըստ պիտակների

Մենք օգտագործում ենք տերմինային հարցում տվյալ բառը պարունակող փաստաթղթերի ID-ներ որոնելու համար՝

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

Ամբողջական տեքստի որոնում

Մեր երեք փաստաթղթերը «բովանդակություն» դաշտում պարունակում են հետևյալը.

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

Մենք օգտագործում ենք համընկնման հարցում տվյալ բառը պարունակող փաստաթղթերի ID-ներ որոնելու համար՝

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

Սակայն, եթե բովանդակության դաշտում որոնենք «պատմություններ», ոչինչ չենք գտնի, քանի որ ինդեքսը պարունակում է միայն բնօրինակ բառեր, այլ ոչ թե դրանց հիմքերը։ Որակյալ որոնում կատարելու համար անհրաժեշտ է կարգավորել վերլուծիչը։

Դաշտ _score ցույց է տալիս կարևորությունԵթե ​​հարցումը կատարվում է ֆիլտրի համատեքստում, _score արժեքը միշտ կլինի 1, ինչը նշանակում է ֆիլտրի հետ լիարժեք համապատասխանություն։

Անալիզատորներ

Անալիզատորներ անհրաժեշտ են սկզբնաղբյուր տեքստը տոկենների հավաքածուի վերածելու համար։
Անալիզատորները բաղկացած են մեկից՝ Տոկենիզատոր և մի քանի ընտրովի Նշանների ֆիլտրերՏոկենիզատորին կարող են նախորդել մի քանիսը CharFiltersTokenizer-ը սկզբնաղբյուր տողը բաժանում է տոկենների, օրինակ՝ բացատների և կետադրական նշանների միջոցով: TokenFilter-ը կարող է փոխել տոկենները, ջնջել կամ ավելացնել նորերը, օրինակ՝ թողնել միայն բառի արմատը, հեռացնել նախդիրները, ավելացնել հոմանիշներ: CharFilter - ամբողջությամբ փոխում է սկզբնաղբյուր տողը, օրինակ՝ կտրում է html թեգերը:

ES-ում կան մի քանիսը ստանդարտ վերլուծիչներՕրինակ՝ վերլուծիչ ռուսերեն.

Եկեք օգտվենք API Եվ տեսնենք, թե ինչպես են ստանդարտ և ռուսական վերլուծիչները վերափոխում «Զվարճալի պատմություններ կատվիկների մասին» տողը.

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

Ստանդարտ վերլուծիչը տողը բաժանեց բացատների և ամեն ինչ վերածեց փոքրատառի, ռուսական վերլուծիչը հեռացրեց անկարևոր բառերը, վերածեց փոքրատառի և թողեց բառերի արմատները։

Եկեք տեսնենք, թե ինչ է օգտագործում Tokenizer-ը, TokenFilters-ը, CharFilters-ը՝ ռուսական վերլուծիչը։

{
  "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 թեգերը։ Անվանենք այն լռելյայն, քանի որ այս անունով վերլուծիչը կօգտագործվի լռելյայն։

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

Սկզբում բոլոր html թեգերը կհեռացվեն սկզբնական տողից, այնուհետև այն կբաժանվի տոկենների՝ տոկենիզատորի ստանդարտով, արդյունքում ստացված տոկենները կփոխարկվեն փոքրատառերի, աննշան բառերը կհեռացվեն, իսկ մնացած տոկենները կլինեն բառի արմատը։

Ինդեքսի ստեղծում

Վերևում մենք նկարագրեցինք լռելյայն վերլուծիչը։ Այն կկիրառվի բոլոր տողային դաշտերի համար։ Մեր գրառումը պարունակում է թեգերի զանգված, ուստի թեգերը նույնպես կմշակվեն վերլուծիչի կողմից։ Քանի որ մենք փնտրում ենք գրառումներ թեգի ճշգրիտ համընկնմամբ, անհրաժեշտ է անջատել թեգերի դաշտի վերլուծությունը։

Եկեք ստեղծենք blog2 ինդեքս՝ վերլուծիչով և մապինգով, որտեղ թեգերի դաշտի վերլուծությունը անջատված է։

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

Եկեք նույն 3 գրառումները ավելացնենք այս ինդեքսին (blog2): Ես կբաց թողնեմ այս գործընթացը, քանի որ այն նման է բլոգի ինդեքսում փաստաթղթեր ավելացնելուն:

Լրիվ տեքստի որոնում՝ արտահայտությունների աջակցությամբ

Եկեք ծանոթանանք հարցումների մեկ այլ տեսակի հետ.

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

Քանի որ մենք օգտագործում ենք ռուսական ծագմամբ վերլուծիչ, այս հարցումը կվերադարձնի բոլոր փաստաթղթերը, նույնիսկ եթե դրանք պարունակում են միայն «պատմություն» բառը։

Հարցումը կարող է պարունակել հատուկ նիշեր, օրինակ՝

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

Հարցման շարահյուսություն՝

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

Սայլակ

PS

Եթե ​​հետաքրքրված եք նման հոդված-դասերով, ունեք նոր հոդվածների գաղափարներ կամ համագործակցության առաջարկներ, ուրախ կլինեմ հաղորդագրություն ստանալ անձնական հաղորդագրությամբ կամ m.kuzmin+habr@darkleaf.ru էլեկտրոնային հասցեով։

Source: www.habr.com