ElasticSearch

最近在学习es;
ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

交互方式

基于HTTP协议,以JSON为数据格式的RESTful API

Get 'http://localhost:3000/web/people'
{
    "query": {
    }
}

以上语句查询了web库people表的所有数据

存储的结构和基本概念.
  • _index:相当于关系型数据库中的数据库的概念,上一个查询的index就是web
  • _type:相当于关系型数据库中表的概念,上一个查询的type就是people
  • _id: 数据的唯一id
  • _source:该参数声明了你要查询哪些字段,不传默认显示全部字段。

_mapping 映射

{
   "articles": {
      "mappings": {
         "magazine": {
            "properties": {
               "date": {
                  "type": "date",
                  "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
               },
               "title": {
                  "type": "text"
               },
               "article_id": {
                  "type": "long"
               },
               "type": {
                  "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                }
            }
         }
      }
   }
}

这是查询magazine这个表的mapping,如果想要查询整个数据库的mapping,去掉表名即可

说一下常用的几个类型

  • text 和 keyword 都是string类型,区别在于keyword在存储的时候不会进行分词,用于精确精确检索。
  • long 和 integer 都是number类型
  • date 是时间类型

query查询和filter查询区别

  • query查询会进行计算_score,来进行相关度的评分
  • filter查询,用于过滤,不会参与到评分的计算中,并且会进行缓存,所以filter查询更快

如何选择查询和过滤

  • 使用 查询(query)语句来进行 全文 搜索或者其它任何需要影响 相关性得分 的搜索。除此以外的情况都使用过滤(filters)。

简单查询实例

从people表中查询type等于12的前10条数据,并且更根据view进行desc排序

Get 'http://localhost:3000/web/people'
{
    "query":{
        "bool":{
            "filter":[
                {
                    "term":{
                        "type":12
                    }
                }
            ]
        }
    },
    "sort":[
        {
            "view":{
                "order":"desc"
            }
        }
    ],
    "from":0,
    "size":10,
    "_source":[
        "people_id",
        "title"
    ]
}

等价sql:

SELECT
	people_id, title
FROM
	people 
WHERE
	type = 12 
ORDER BY
	view  DESC
	LIMIT 10 OFFSET 0

当我们想有更多的过滤条件时,就可以在filter中添加更多的查询条件(子查询)

  • term 是一个值的精确查询,这些精确值可能是数字、时间、布尔或者那些 not_analyzed(例如keyword类型) 的字符串
  • terms 是多个值的精确查询
  • range
  • 是范围查询
    • gt: > 大于(greater than)
    • lt: < 小于(less than)
    • gte: >= 大于或等于(greater than or equal to)
    • lte: <= 小于或等于(less than or equal to)
Get 'http://localhost:3000/web/people'
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "type": 11
          }
        },
        {
          "range": {
            "times": {
              "gte": 5,
              "lte": 10
            }
          }
        },
        {
          "terms": {
            "car_type": [
              1,
              2,
              4
            ]
          }
        }
      ]
    }
  }
}

我们往往会需要更复杂的查询,就需要用到bool组合查询支持下面四个分类数:

must

文档 必须 匹配这些条件才能被包含进来。

must_not

文档 必须不 匹配这些条件才能被包含进来。

should

如果满足这些语句中的任意语句,将增加 _score ,否则,无任何影响。它们主要用于修正每个文档的相关性得分。

filter

必须 匹配,但它以不评分、过滤模式来进行。这些语句对评分没有贡献,只是根据过滤标准来排除或包含文档。

{
   "query":{
        "bool": {
            "must":     { "match": { "title": "学习Elasticsearch" }},
            "must_not": { "match": { "comment":   "写的不好" }},
            "should": [
                { "match": { "comment": "写的很棒" }}
            ],
            "filter": {
              "bool": { 
                  "must": [
                      { "range": { "date": { "gte": "2018-01-01" }}}
                  ],
                  "must_not": [
                      { "term": { "category": "education" }}
                  ]
              }
            }
        }
   }
}

这是一个很好的简单易懂的例子:

它查询了时间大于等于2018-01-01、分类不是education的、并且title中含有"学习Elasticsearch"、comment中一定不含有"写的不好"的文章

,看起来其中的should好像并没有什么用呢,其实不是,should条件参加相关度的评分,让更符合目标的结果(commen中含有"写的很棒")排在前面。

这其中有个小点要注意下:

如果没有 must 语句,那么至少需要能够匹配其中的一条 should 语句。但,如果存在至少一条 must 语句,则对 should 语句的匹配没有要求。

刚才对这个查询的描述其实并不是很准确,我们需要了解一下这个match查询

如果你在一个全文字段上使用 match 查询,在执行查询前,它将用正确的分析器去分析查询字符串,这里涉及到一个新的概念分析器

分析器

一个分析器(analyzer)包含了一个或多个字符过滤器(char_filter)、一个分词器(tokenizer)、一个或多个词单元过滤器(filter)

重点说一下分词器es默认的分析器是standard,以后关于分析器还需要详细讲一下,这里标准的分析器会把字符串分割成单独的字词,如果检索的是英文的话,倒是没有什么问题,如果是中文的话,会把检索字符串的每一个汉字分成一个词,如下:

Post http://localhost:3000/web/_analyze?analyzer=standard
{
    "tokens": [
        {
            "token": "学",
            "start_offset": 0,
            "end_offset": 1,
            "type": "",
            "position": 0
        },
        {
            "token": "习",
            "start_offset": 1,
            "end_offset": 2,
            "type": "",
            "position": 1
        },
        {
            "token": "elasticsearch",
            "start_offset": 2,
            "end_offset": 15,
            "type": "",
            "position": 2
        }
    ]
}
{ "match": { "title": "学习Elasticsearch" }

所以这个代表是在title中任意包含“学”、“习”、‘elasticsearch’中任意一个的都被检索了出来,所以需要match_phrase才能满足我们的需求,代表的是整个短语的匹配,要求 “学”、“习”、‘elasticsearch’三个词都必须出现,“习”的position比“学”大1,‘elasticsearch’比“习”的position大2,必须同时满足这3个条件。所以把上面的match换成match_phrase才是符合刚才的释义

相关度评分

  • 词频:词在文档中出现的频度是多少? 频度越高,权重 越高 。 5 次提到同一词的字段比只提到 1 次的更相关.如果不在意词在某个字段中出现的频次,而只在意是否出现过,则可以在字段映射中禁用词频统计:"index_options": "docs"
  • 逆向文档频率:词在集合所有文档里出现的频率是多少?频次越高,权重 越低 。 常用词如 and 或 the 对相关度贡献很少,因为它们在多数文档中都会出现
  • 字段长度归一值:字段越短,字段的权重 越高 。如果词出现在类似标题 title 这样的字段,要比它出现在内容 body 这样的字段中的相关度更高。可以通过修改字段映射禁用归一值"norms": { "enabled": false }
  • 结合使用:以上三个因素——词频(term frequency)、逆向文档频率(inverse document frequency)和字段长度归一值(field-length norm)——是在索引时计算并存储的。 最后将它们结合在一起计算单个词在特定文档中的 权重

中文检索【中文分析器】,为什么要用中文分词器

假设有一个文档【这几天天气变热,风里也透着热浪】,采用默认分词器,它会把每一个汉字分成一个词,当你用match_phrase进行检索「热风」的时候,该文档也会被检索出来,这明显不是我们想要。不仅仅是这个原因,才有中文分析器可以分析出更符合汉语的词。

  • 这个GitHub上比较火的ik分析器
  • ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合;
  • ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。

自定义分词器

一个分析器(analyzer)包含了0个或多个字符过滤器(char_filter)、一个分词器(tokenizer)、一个或多个词单元过滤器(filter)

分词器详细介绍

{
    "settings":{
        "analysis":{
            "analyzer":{
                "keywordAnalyzer":{
                    "type":"pattern",
                    "pattern":",",
                    "filter":[
                        "standard",
                        "stop",
                        "trim"
                    ]
                }
            }
        }
    }
}

该分析器是以","作为分隔符进行分词,【"standard","stop","trim"】这个三个作为过滤器

关键词推荐

现在关键词字段内容格式如下,以英文逗号分开的,多个词语

帆船,美女,红酒,大海,海螺,腰带
期望检索出来和关键最相近的数据,设置如下的mapping,禁用了词频统计和长度归一值的计算

{
    "keywords":{
        "type":"text",
        "norms":false,
        "index_options":"docs",
        "analyzer":"keywordAnalyzer"
    }
}

采用如下检索

{
    "query":{
        "match":{
            "keywords":{
                "query":"帆船,美女,红酒,大海,海螺,腰带"
            }
        }
    }
}

中文多词检索

我想要两个词都出现的是排在前面

{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "Quick pets" }},
                { "match": { "body":  "Quick pets" }}
            ],
            "tie_breaker": 0.3
        }
    }
}

我们回想一下,如果是英文这样是不是就OK了?那么中文呢?

和上面不相干,说另外一个东西,看下面

{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "Quick pets" }},
                { "match": { "body":  "Quick pets" }}
            ]
        }
    }
}
------------------------
{
    "multi_match": {
        "query":                "Quick pets",
        "type":                 "best_fields", 
        "fields":               [ "title", "body" ]
    }
}

上面这个查询两个相等

但是上面的和下面的却完全不一样

{
    "query":{
        "bool":{
            "should":[
                 { "match": { "title": "Quick pets" }},
                 { "match": { "body":  "Quick pets" }}
            ]
        }
    }
}

回到刚才的问题 如果是英文刚才那个查询dis_max(multi_match)一定程度是ok的,但是中文却是不行的,为啥?因为match,虽然可以是100%匹配,但是他并没有像match_phrase字和字之间紧紧相连。所以只能拿出最终的神奇了query_string

{
    "query":{
        "bool":{
            "should":[
                {
                    "query_string":{
                        "fields":[
                            "article_content"
                        ],
                        "analyzer":"ik_smart",
                        "query":"迪奥 香水",
                        "auto_generate_phrase_queries":true,
                        "default_operator":"OR",
                         "use_dis_max":true,
                        "boost":1
                    }
                }
            ]
        }
    }
}

这个查询代表了用ik_smart分析器对迪奥 香水进行分词(mapping是ik_max_word),然后对article_content字段进行检索,要求迪奥和香水这个两个词必须出现一个或者出现两个,采用"use_dis_max":true使同时出现两个词的结果排在前面。

查看检索逻辑

添加 "profile": true这个参数,可以相对清楚看出es是按照什么逻辑来检索的

{
  "query": {
    "match": {
      "relevant_keywords": {
        "query": "米歇尔,美国,奥巴马,人物,女儿,总统,家庭"
      }
    }
  },
  "size": 7,
  "from": 0,
  "_source": [
    "article_id",
    "title",
    "relevant_keywords",
    "belong_program"
  ],
  "profile": true
}
原文地址:https://www.cnblogs.com/mapleChain/p/11527895.html