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. կա պաշտոնական պատկեր դոկերի համար. Ավելին տեղադրման մասին.

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

# для удобства запомним адрес в переменную
#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-ին տարբերակով և փոփոխություններ է ներկայացրել մեզ մոտ, ապա ԷՍ-ը չի ընդունի մեր փոփոխությունները, քանի որ այն պահում է փաստաթուղթը 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" }
    }
  }
}'

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

Մենք օգտագործում ենք ժամկետային հարցում տվյալ բառ պարունակող փաստաթղթերի նույնացուցիչներ որոնելու համար.

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

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

# 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-ի, ինչը նշանակում է ամբողջական համապատասխանություն ֆիլտրին:

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

Անալիզատորներ անհրաժեշտ են սկզբնաղբյուր տեքստը մի շարք նշանների վերածելու համար:
Անալիզատորները բաղկացած են մեկից Տոկենիզատոր և մի քանի ընտրովի TokenFilters. Tokenizer-ին կարող են նախորդել մի քանիսը CharFilters. Tokenizers-ը սկզբնաղբյուրի տողը բաժանում է նշանների, ինչպիսիք են բացատները և կետադրական նշանները: 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 պիտակները կհեռացվեն սկզբնաղբյուր տողից, այնուհետև tokenizer ստանդարտը այն կբաժանի նշանների, արդյունքում ստացված նշանները կտեղափոխվեն փոքրատառերի, աննշան բառերը կհեռացվեն, իսկ մնացած նշանները կմնան բառի բունը:

Ինդեքս ստեղծելը

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

Եկեք անալիզատորով և քարտեզագրմամբ ստեղծենք ինդեքսային բլոգ2, որում պիտակների դաշտի վերլուծությունն անջատված է.

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) ավելացնենք նույն 2 գրառումները։ Ես բաց կթողնեմ այս գործընթացը, քանի որ... դա նման է բլոգի ինդեքսում փաստաթղթեր ավելացնելուն:

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

Դիտարկենք մեկ այլ տեսակի խնդրանք.

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

Եթե ​​դուք հետաքրքրված եք նմանատիպ հոդված-դասերով, ունեք գաղափարներ նոր հոդվածների համար, կամ ունեք համագործակցության առաջարկներ, ապա ես ուրախ կլինեմ հաղորդագրություն ստանալ անձնական հաղորդագրությամբ կամ էլ. [էլեկտրոնային փոստով պաշտպանված].

Source: www.habr.com