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

如果您对类似的文章课程感兴趣,对新文章有想法,或者有合作建议,那么我将很高兴通过个人消息或电子邮件收到消息 [电子邮件保护].

来源: habr.com