文章目录

  • 一、SearchAPI
  • 二、Query DSL
  • 2.1 基本语法格式
  • 2.2 匹配查询 match
  • 2.3 模糊查询&分页查询
  • 2.4 返回部分字段 _source
  • 2.5 复合查询 bool
  • 2.6 结果过滤 filter
  • 2.7 非全文检索字段 term
  • 2.8 排序 sort
  • 2.9 高亮查询 highlight
  • 2.10 聚合查询 aggs
  • 三、映射 Mapping
  • 3.1 Mapping 简介
  • 3.2 Dynamic Mapping
  • 3.3 数据类型
  • 3.4 nested 扁平化数据问题
  • 3.5 表数据迁移
  • 四、分词
  • 五、Elasticsearch搜索原理


一、SearchAPI

ES支持两种基本方式检索∶

  1. 通过使用REST request URI发送搜索参数(uri+检索参数)
  2. 通过使用REST request body来发送它们(uri+请求体)

① URI

GET bank/_search?q=*&sort=account_number:asc

es 嵌套聚合统计总量 es嵌套聚合api查询_字段

② 请求体

GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "account_number": {
        "order": "desc"
      }
    }
  ]
}

二、Query DSL

2.1 基本语法格式

Elasticsearch提供了一个可以执行查询的Json风格的 DSL (domain-specific language领域特定语言),这个被称为Query DSL,该查询语言非常全面。

  • 一个查询语句的典型结构
QUERY_NAME:{
	ARGUMENT: VALUE,
	ARGUMENT: VALUE,...
}
  • 如果是针对某个字段,那么它的结构如下:
l
QUERY_NAME:{
	FIELD_NAME: [
		ARGUMENT: VALUE,
		ARGUMENT: VALUE,...
	}
}

2.2 匹配查询 match

基本数据类型(非字符串会精确匹配),字符串会分词匹配。

使用match_phrase不进行分词。

GET bank/_search
{
  "query": {
    "match": {
      "lastname": "Barron"
    }
  }
}

es 嵌套聚合统计总量 es嵌套聚合api查询_springboot_02

说明:使用multi_match表示多字段满足其一即返回结果。

GET bank/_search
{
  "query": {
    "multi_match": {
      "query": "mill", 
      "fields": ["state","address"]
    }
  }
}

2.3 模糊查询&分页查询

GET bank/_search
{
  "query": {
    "fuzzy": {
      "city": "Lopezo"
    }
  },
  "from": 0,
  "size": 1
}

2.4 返回部分字段 _source

GET bank/_search
{
  "query": {
    "match": {
      "lastname": "Barron"
    }
  },
  "_source": "{firstname,lastname}"
}

es 嵌套聚合统计总量 es嵌套聚合api查询_springboot_03

2.5 复合查询 bool

mustmust not的条件必须一致,should不要求一致,当时会影响相关得分。

GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "firstname": "Forbes"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "firstname": "Jack"
          }
        }
      ],
      "should": [
        {
          "match": {
            "age": "28"
          }
        }
      ]
    }
  }
}

2.6 结果过滤 filter

filter不会计算相关性得分,只起过滤作用。

按照范围过滤如下:

GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "firstname": "Forbes"
          }
        }
      ],
      "filter": {
        "range": {
          "age": {
            "gte": 10,
            "lte": 30
          }
        }
      }
    }
  }
}

2.7 非全文检索字段 term

match一样。匹配某个属性的值。全文检索字段用match,其他非text字段匹配用term

GET bank/_search
{
  "query": {
    "term": {
      "age": "28"
    }
  }
}

2.8 排序 sort

GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "account_number": {
        "order": "desc"
      }
    }
  ]
}

2.9 高亮查询 highlight

GET bank/_search
{
  "query": {
    "match": {
      "lastname": "Bates"
    }
  },
  "highlight": {
    "fields": {"lastname":{}},
    "pre_tags": ["<span style='color:red'>"],
    "post_tags": ["</span>"]
  }
}

es 嵌套聚合统计总量 es嵌套聚合api查询_Elastic_04

2.10 聚合查询 aggs

对查询结果进行聚合分析。field是聚合的字段,size代表预期的分类数

① 搜索address中包含mill的所有人的年龄分布以及平均年龄

GET bank/_search
{
  "query": {
    "match": {
      "address": "mill"
    }
  },
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age",
        "size": 100
      }
    },
    "ageAvg": {
      "avg": {
        "field": "age"
      }
    }
  }
}

es 嵌套聚合统计总量 es嵌套聚合api查询_es 嵌套聚合统计总量_05

② 按照年龄聚合,并且请求这些年龄段的这些人的平均薪资

GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age",
        "size": 100
      },
      "aggs": {
        "balanceAvg": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

es 嵌套聚合统计总量 es嵌套聚合api查询_Elastic_06

③ 查出所有年龄分布,并且这些年龄段中M的平均薪资和F的平均薪资以及这个年龄段的总体平均薪资

GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age",
        "size": 100
      },
      "aggs": {
        "genderAgg": {
          "terms": {
            "field": "gender.keyword",
            "size": 2
          },
          "aggs": {
            "balanceAvg": {
              "avg": {
                "field": "balance"
              }
            }
          }
        },
        "balanceAvg2": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

es 嵌套聚合统计总量 es嵌套聚合api查询_字段_07

三、映射 Mapping

3.1 Mapping 简介

定义数据库中的表的结构的定义,通过mapping来控制索引存储数据的设置,不进行配置时,自动创建的mapping

作用:

  • 定义Index下的字段名(Field Name
  • 定义字段的类型,比如数值型、字符串型、布尔型等
  • 定义倒排索引相关的配置,比如documentId、记录position、打分等

创建Mapping:GET /china/_mapping

自定义Mapping:

PUT my_store
{
  "mappings":{
    "book":{
      "dynamic":false, 	
      "properties":{		
        "title":{
          "type":"text"
          , "index_options": "docs"
        },
        "content":{
          "type":"keyword"
          , "index": true
        }
      }
    }
  }
}

属性说明:

① dynamic

  • true:允许自动新增字段(默认的配置)
  • false:不允许自动新增字段,但是文档可以正常写入,无法对字段进行查询操作
  • strict:文档不能写入(如果写入会报错)

② index

Index属性,控制当前字段是否索引,默认为true,即记录索引,false不记录,即不可以搜索,比如:手机号、身份证号等敏感信息,不希望被检索

③ Index_options

用于控制倒排索引记录的内容,有如下4中配置:

  • docs:只记录docid
  • freqs:记录docidterm frequencies(词频)
  • position:记录docidterm frequenciesterm position
  • Offsets:记录docidterm frequenciesterm positioncharacter offsets

text类型默认配置为position,其默认认为docs。记录的内容越多,占用的空间越大。

3.2 Dynamic Mapping

Elasticsearch依靠json文档字段类型来实现自动识别字段类型,支持的类型

JSON类型

es类型

null

忽略

boolean

boolean

浮点类型

float

整数

long

object

object

array

由第一个非null值的类型决定

string

匹配为日期则设为data类型(默认开启) 匹配为数字的话设为float或long类型(默认关闭) 设为text类型,并附带keyword的子字段

注意:mapping中的字段类型一旦设定后,禁止修改

原因:Lucene实现的倒排索引生成后不允许修改(提高效率)。如果要修改字段的类型,需要从新建立索引,然后做reindex操作

3.3 数据类型

① 核心数据类型

字符串型:textkeyword 数值型:longintegershortbytedoublefloathalf_floatscaled_float 日期类型:date 布尔类型:boolean 二进制类型:binary 范围类型:integer_rangefloat_rangelong_rangedouble_rangedate_range

② 复杂数据类型

数组类型:array 对象类型:object 嵌套类型:nested object

③ 地理位置数据类型

geo_point(点)、geo_shape(形状)

④ 专用类型

记录IP地址:ip 实现自动补全:completion 记录分词数:token_count 记录字符串hash值:murmur3

⑤ 多字段特性multi-fields

允许对同一个字段采用不同的配置,比如分词,例如对人名实现拼音搜索,只需要在人名中新增一个子字段为pinyin即可。

3.4 nested 扁平化数据问题

首先我们创建一个数据,并查看她的数据类型

PUT my_index/_doc/1
{
  "group" : "fans",
  "user" : [ 
    {
      "first" : "John",
      "last" :  "Smith"
    },
    {
      "first" : "Alice",
      "last" :  "White"
    }
  ]
}

GET my_index/_mapping

es 嵌套聚合统计总量 es嵌套聚合api查询_Elastic_08


此时我们查询Alice Smith用户竟然能查出来。

es 嵌套聚合统计总量 es嵌套聚合api查询_字段_09


原因如下:

es 嵌套聚合统计总量 es嵌套聚合api查询_Elastic_10


解决这种问题我们需要使用nested类型,删除index重建创建index并查看数据类型

DELETE my_index

PUT my_index
{
  "mappings": {
    "properties": {
      "user": {
        "type": "nested" 
      }
    }
  }
}

GET my_index/_mapping

es 嵌套聚合统计总量 es嵌套聚合api查询_elasticsearch_11


此时已经不会出现扁平化导致的问题。

案例:
表结构

PUT product
{
  "mappings": {
    "properties": {
      "attrs": {
        "type": "nested",
        "properties": {
          "attrId": {
            "type": "long"
          },
          "attrName": {
            "type": "keyword",
            "index": false,
            "doc_values": false
          },
          "attrValue": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

查询语句

--错误
GET /product/_search
{
  "query": {
    "bool": {
      "filter": {
        "term": {
          "attrs.attrId": "1"
        }
      }
    }
  }
}
--正确
GET /product/_search
{
  "query": {
    "bool": {
      "filter": {
        "nested": {
          "path": "attrs",
          "query": {
            "bool": {
              "must": [
                {
                  "term": {
                    "attrs.attrId": {
                      "value": "1"
                    }
                  }
                }
              ]
            }
          }
        }
      }
    }
  }
}

3.5 表数据迁移

POST _reindex
{
  "source": {
    "index": "fromTable"
  },
  "dest": {
    "index": "toTable"
  }
}

四、分词

分词是指将文本转换成一系列单词(term or token)的过程,也可以叫做文本分析,在ElasticSsearch里面称为Analysis

分词机制:

  1. Character Filter:对原始文本进行处理,例:去除html标签、特殊字符等;
  2. Tokenizer:将原始文本进行分词,例:天气预报–>天气,预报;
  3. Token Filters:分词后的关键字进行加工,例:转小写、删除语气词、近义词和同义词等

Elasticsearch自带的分词器:

分词器(Analyzer)

特点

Standard(es默认)

支持多语言,按词切分并做小写处理

Simple

按照非字母切分,小写处理

Whitespace

按照空格来切分

Stop

去除语气助词,如the、an、的、这等

Keyword

不分词

Pattern

正则分词,默认\w+,即非字词符号做分割符

Language

常见语言的分词器(30+)

中文分词:

分词器名称

介绍

特点

地址

IK

实现中英文单词切分

自定义词库

https://github.com/medcl/elasticsearch-analysis-ik

Jieba

python流行分词系统,支持分词和词性标注

支持繁体、自定义、并行分词

http://github.com/sing1ee/elasticsearch-jieba-plugin

Hanlp

由一系列模型于算法组成的java工具包

普及自然语言处理在生产环境中的应用

https://github.com/hankcs/HanLP

THULAC

清华大学中文词法分析工具包

具有中文分词和词性标注功能

https://github.com/microbun/elasticsearch-thulac-plugin

使用standard分词器

standard支持对英文的分词,并不支持中文分词。

es 嵌套聚合统计总量 es嵌套聚合api查询_elasticsearch_12


使用IK分词器

安装:解压IK分词器安装包至elasticsearchplugins文件夹中,并重命名文件夹为analysis-ik

IK提供了两个分词算法ik_smartik_max_word,其中ik_smart为最少切分,ik_max_word为最细粒度划分。

es 嵌套聚合统计总量 es嵌套聚合api查询_Elastic_13

五、Elasticsearch搜索原理

es 嵌套聚合统计总量 es嵌套聚合api查询_springboot_14


[Term、Posting List]

Elasticsearch使用倒排索引提升了查询效率。Elasticsearch分别为每个field都建立了一个倒排索引,KateJohn24Female这些叫term,而[1,2]就是Posting ListPosting List就是一个int的数组,存储了所有符合某个term的文档id

Posting List可以记录的数据:

  1. DocId:文档id,文档的原始信息
  2. TF:单词频率,记录该词再文档中出现的次数,用于后续相关性算分
  3. Position:位置,记录Field分词后,单词所在的位置,从0开始
  4. Offset:偏移量,记录单词在文档中开始和结束位置,用于高亮显示等

[Term Dictionary]
Elasticsearch为了能快速找到某个term,将所有的term排个序,二分法查找termlogN的查找效率,就像通过字典查找一样,这就是Term Dictionary

[Term Index]

B-Tree通过减少磁盘寻道次数来提高查询性能,Elasticsearch也是采用同样的思路,直接通过内存查找term,不读磁盘,但是如果term太多,Term Dictionary也会很大,放内存不现实,于是有了Term Index,就像字典里的索引页一样,A开头的有哪些term,分别在哪页,可以理解Term Index是一颗树:

es 嵌套聚合统计总量 es嵌套聚合api查询_springboot_15


这棵树不会包含所有的term,它包含的是term的一些前缀。通过Term Index可以快速地定位到Term Dictionary的某个offset,然后从这个位置再往后顺序查找。

es 嵌套聚合统计总量 es嵌套聚合api查询_es 嵌套聚合统计总量_16


所以Term Index不需要存下所有的term,而仅仅是他们的一些前缀与Term Dictionaryblock之间的映射关系,再结合FST(Finite State Transducers)的压缩技术,可以使Term Index缓存到内存中。从Term Index查到对应的Term Dictionaryblock位置之后,再去磁盘上找term,大大减少了磁盘随机读的次数。

[压缩]
Elasticsearch使用FST压缩Term Index外,Elasticsearch要求Posting List是有序的,对Posting List也有压缩技巧。 当Posting List至少有百万个文档id时, 此时对其压缩变得极其有意义。

[Frame Of Reference]

Step 1Delta-encode —— 增量编码
只记录元素与元素之间的增量,于是Posting List由[73,300,302,332,343,372]变为[73,227.2,30,11,29]

Step 2Split into blocks —— 分割成块
Lucene里每个块是256个文档ID,这样可以保证每个块,增量编码后,每个元素都不会超过256(1 byte

Step 3Bit packing —— 按需分配空间
对于第一个块,[73, 227, 2],最大元素是227,需要 8bit,只分配 8bit的空间;

但是对于第二个块,[30, 11, 29],最大的元素才30,只需要5 bit,只分配 5bit的空间。

es 嵌套聚合统计总量 es嵌套聚合api查询_elasticsearch_17


[Roaring bitmaps]

Lucene有多个查询条件,需要快速求出多个Posting List的共同交集,通过使用Roaring bitmaps算法后两个Posting List做位运算即可求出交集。

假设有这样一个数组[3,6,7,10],我们使用bitmaps算法表示为[0,0,1,0,0,1,1,0,0,1]

Roaring bitmapsposting list按照65535为界限分块,第一块所包含的文档id范围在0~65535(2^16)之间,第二块的id范围是65536~131071,这样解决了Bitmap的缺点:存储空间随着文档个数线性增长。

es 嵌套聚合统计总量 es嵌套聚合api查询_elasticsearch_18