اصول Elasticsearch

Elasticsearch یک موتور جستجو با Json Rest API است که از Lucene استفاده می کند و بر روی جاوا نوشته شده است. شرح تمام مزایای این موتور در دسترس است سایت رسمی. علاوه بر این، متن Elasticsearch به عنوان ES نامیده می شود.

موتورهای مشابه برای جستجوهای پیچیده در پایگاه داده اسناد استفاده می شود. به عنوان مثال، جستجو با در نظر گرفتن مورفولوژی زبان یا جستجو بر اساس مختصات جغرافیایی.

در این مقاله به شما در مورد اصول ES در مثال نمایه سازی پست های وبلاگ خواهم گفت. من نحوه فیلتر کردن، مرتب سازی و جستجوی اسناد را نشان خواهم داد.

برای اینکه به سیستم عامل وابسته نباشم، تمام درخواست ها را با استفاده از Curl به ES می کنم. همچنین یک افزونه برای گوگل کروم به نام وجود دارد حس.

پیوندهایی به اسناد و سایر منابع روی متن قرار داده شده است. در پایان پیوندهایی برای دسترسی سریع به اسناد وجود دارد. تعاریف اصطلاحات ناآشنا را می توان در اینجا خواند واژه نامه.

نصب ES

برای این کار ابتدا به جاوا نیاز داریم. توسعه دهندگان توصیه جاوا را بیش از جاوا 8 به روز رسانی 20 یا جاوا 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 هیچ تفاوتی بین یک مقدار واحد و یک آرایه از معانی ایجاد نمی کند. به عنوان مثال، فیلد Title فقط حاوی یک عنوان است و فیلد Tags آرایه ای از خطوط است، اگرچه آنها در Mapping به همین شکل ارائه می شوند.
بعداً در مورد نقشه برداری بیشتر صحبت خواهیم کرد.

درخواست ها

بازیابی یک سند با شناسه آن:

# извлечем документ с 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 یک سند منبع فشرده را ذخیره می کند. اگر فقط به شناسه نیاز داشته باشیم و نه کل سند منبع، می‌توانیم ذخیره‌سازی منبع را غیرفعال کنیم.

اگر به اطلاعات اضافی نیاز نداریم، فقط می توانید مطالب را دریافت کنید _ منبع:

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 خواهد بود که به معنای تطابق کامل با فیلتر است.

آنالایزرها

آنالایزرها برای تبدیل متن منبع به مجموعه ای از نشانه ها مورد نیاز است.
آنالایزرها از یک عدد تشکیل شده اند توکنایزر و چندین اختیاری فیلترهای توکن. توکنایزر ممکن است قبل از چندین مورد باشد CharFilters. توکن سازها رشته منبع را به نشانه هایی مانند فاصله و کاراکترهای نقطه گذاری تقسیم می کنند. 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
  } ]
}

تحلیلگر استاندارد رشته را با فاصله تقسیم کرد و همه چیز را به حروف کوچک تبدیل کرد، تحلیلگر روسی کلمات بی اهمیت را حذف کرد، آن را به حروف کوچک تبدیل کرد و ریشه کلمات را رها کرد.

بیایید ببینیم از چه توکنایزر، فیلتر توکن، 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 Standard به توکن‌ها تقسیم می‌شود، توکن‌های به‌دست‌آمده به ثبت پایین‌تر می‌روند، کلمات بی‌اهمیت حذف می‌شوند و اساس کلمه از توکن‌های باقی‌مانده باقی می‌ماند. .

ایجاد یک شاخص

در بالا، ما تحلیلگر پیش فرض را توضیح دادیم. برای تمام فیلدهای رشته اعمال خواهد شد. پست ما شامل آرایه ای از برچسب ها است، به ترتیب، برچسب ها نیز توسط تحلیلگر پردازش می شوند. زیرا ما به دنبال پست هایی با انطباق دقیق با برچسب هستیم، سپس لازم است تجزیه و تحلیل را برای قسمت TAGS خاموش کنیم.

نمایه BLOG2 را با یک تحلیلگر و یک نقشه ایجاد کنید، که در آن تجزیه و تحلیل فیلد TAGS غیرفعال است:

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). من این روند را حذف می کنم، زیرا این شبیه به افزودن اسناد به فهرست BLOG است.

جستجوی متن کامل با پشتیبانی از عبارات

بیایید با نوع دیگری از درخواست آشنا شویم:

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

اگر چنین مقالاتی جالب هستند، ایده هایی از مقالات جدید وجود دارد یا پیشنهادهایی برای همکاری وجود دارد، خوشحال خواهم شد که از طریق شخصی یا پستی گزارش دهید. [ایمیل محافظت شده].

منبع: www.habr.com