Elasticsearch 基礎知識

Elasticsearch是一個帶有json Rest api的搜尋引擎,使用Lucene並用Java編寫。 有關該引擎所有優點的描述,請訪問 官方網站。 下面我們將Elasticsearch簡稱為ES。

類似的引擎用於文件資料庫中的複雜搜尋。 例如,考慮語言形態的搜尋或透過地理座標的搜尋。

在本文中,我將使用索引部落格文章的範例來討論 ES 的基礎知識。 我將向您展示如何過濾、排序和搜尋文件。

為了不依賴作業系統,我將使用CURL向ES發出所有請求。 還有一個適用於 google chrome 的插件,名為 .

文字包含文件和其他來源的連結。 最後有用於快速存取文件的連結。 不熟悉術語的定義可以在 詞彙表.

安裝ES

為此,我們首先需要 Java。 開發商 推薦 安裝高於 Java 8 update 20 或 Java 7 update 55 的 Java 版本。

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,這表示與過濾器完全匹配。

分析儀

分析儀 需要將原始文字轉換為一組標記。
分析儀由一個 分詞器 以及幾個可選的 令牌過濾器。 分詞器前面可能有幾個 字元過濾器。 分詞器將來源字串分解為標記,例如空格和標點字元。 TokenFilter 可以更改標記、刪除或新增標記,例如,僅保留詞幹、刪除介詞、新增同義詞。 CharFilter - 變更整個來源字串,例如,刪除 html 標籤。

ES有幾個 標準分析儀。 例如,分析儀 .

讓我們充分利用 API 讓我們看看標準分析器和俄羅斯分析器如何轉換字串“Funny Stories about kittens”:

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

如果您對類似的文章課程感興趣,對新文章有想法,或者有合作建議,那麼我將很高興透過個人訊息或電子郵件收到訊息 [電子郵件保護].

來源: www.habr.com