Elasticsearch 是一个分布式、可扩展、实时的搜索与数据分析引擎,并且可以进行结构化数据的实时统计和数据分析。

交互方式

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

  1. Get 'http://localhost:9200/web/people'
  2. {
  3. "query": {
  4. }
  5. }

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

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

    _mapping 映射

  • 相当于字段的数据类型,并且告诉ES如何索引数据,有时候查询语句是对的,查不出结果,可能是mapping不对

  • Get http://localhost:9200/articles/magazine/_mapping?pretty

    {
     "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:9200/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:9200/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:9200/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
}