文章目录
一、SearchAPI
ES
支持两种基本方式检索∶
- 通过使用
REST request URI
发送搜索参数(uri
+检索参数) - 通过使用
REST request body
来发送它们(uri
+请求体)
① URI
GET bank/_search?q=*&sort=account_number:asc
② 请求体
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"
}
}
}
说明:使用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}"
}
2.5 复合查询 bool
must
和must 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>"]
}
}
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"
}
}
}
}
② 按照年龄聚合,并且请求这些年龄段的这些人的平均薪资
GET bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"balanceAvg": {
"avg": {
"field": "balance"
}
}
}
}
}
}
③ 查出所有年龄分布,并且这些年龄段中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"
}
}
}
}
}
}
三、映射 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
:记录docid
和term frequencies
(词频)position
:记录docid
、term frequencies
、term position
Offsets
:记录docid
、term frequencies
、term position
、character 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 数据类型
① 核心数据类型
字符串型:text
、keyword
数值型:long
、integer
、short
、byte
、double
、float
、half_float
、scaled_float
日期类型:date
布尔类型:boolean
二进制类型:binary
范围类型:integer_range
、float_range
、long_range
、double_range
、date_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
此时我们查询Alice Smith
用户竟然能查出来。
原因如下:
解决这种问题我们需要使用nested
类型,删除index
重建创建index
并查看数据类型
DELETE my_index
PUT my_index
{
"mappings": {
"properties": {
"user": {
"type": "nested"
}
}
}
}
GET my_index/_mapping
此时已经不会出现扁平化导致的问题。
案例:
表结构
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
。
分词机制:
- Character Filter:对原始文本进行处理,例:去除html标签、特殊字符等;
- Tokenizer:将原始文本进行分词,例:天气预报–>天气,预报;
- 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
支持对英文的分词,并不支持中文分词。
使用IK
分词器
安装:解压IK
分词器安装包至elasticsearch
下plugins
文件夹中,并重命名文件夹为analysis-ik
。
IK
提供了两个分词算法ik_smart
和ik_max_word
,其中ik_smart
为最少切分,ik_max_word
为最细粒度划分。
五、Elasticsearch搜索原理
[Term、Posting List]
Elasticsearch
使用倒排索引提升了查询效率。Elasticsearch
分别为每个field
都建立了一个倒排索引,Kate
、 John
、 24
、 Female
这些叫term
,而[1,2]
就是Posting List
。Posting List
就是一个int
的数组,存储了所有符合某个term
的文档id
。
Posting List
可以记录的数据:
- DocId:文档
id
,文档的原始信息 - TF:单词频率,记录该词再文档中出现的次数,用于后续相关性算分
- Position:位置,记录
Field
分词后,单词所在的位置,从0开始 - Offset:偏移量,记录单词在文档中开始和结束位置,用于高亮显示等
[Term Dictionary]
Elasticsearch
为了能快速找到某个term
,将所有的term
排个序,二分法查找term
,logN
的查找效率,就像通过字典查找一样,这就是Term Dictionary
。
[Term Index]
B-Tree
通过减少磁盘寻道次数来提高查询性能,Elasticsearch
也是采用同样的思路,直接通过内存查找term
,不读磁盘,但是如果term
太多,Term Dictionary
也会很大,放内存不现实,于是有了Term Index
,就像字典里的索引页一样,A
开头的有哪些term
,分别在哪页,可以理解Term Index
是一颗树:
这棵树不会包含所有的term
,它包含的是term
的一些前缀。通过Term Index
可以快速地定位到Term Dictionary
的某个offset
,然后从这个位置再往后顺序查找。
所以Term Index
不需要存下所有的term
,而仅仅是他们的一些前缀与Term Dictionary
的block
之间的映射关系,再结合FST
(Finite State Transducers
)的压缩技术,可以使Term Index
缓存到内存中。从Term Index
查到对应的Term Dictionary
的block
位置之后,再去磁盘上找term
,大大减少了磁盘随机读的次数。
[压缩]
Elasticsearch
使用FST
压缩Term Index
外,Elasticsearch
要求Posting List
是有序的,对Posting List
也有压缩技巧。 当Posting List
至少有百万个文档id
时, 此时对其压缩变得极其有意义。
[Frame Of Reference]
Step 1:Delta-encode
—— 增量编码
只记录元素与元素之间的增量,于是Posting List
由[73,300,302,332,343,372]变为[73,227.2,30,11,29]
Step 2:Split into blocks
—— 分割成块
Lucene
里每个块是256个文档ID
,这样可以保证每个块,增量编码后,每个元素都不会超过256(1 byte
)
Step 3:Bit packing
—— 按需分配空间
对于第一个块,[73, 227, 2],最大元素是227,需要 8bit
,只分配 8bit
的空间;
但是对于第二个块,[30, 11, 29],最大的元素才30,只需要5 bit
,只分配 5bit
的空间。
[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 bitmaps
将posting list
按照65535为界限分块,第一块所包含的文档id范围在0~65535(2^16)
之间,第二块的id范围是65536~131071
,这样解决了Bitmap
的缺点:存储空间随着文档个数线性增长。