part 1
<!–more–>
前言
emmmmm…我一个前端学习es我也很为难啊。elasticsearch涉及的内容非常多,分布式相关的章节我都是跳过的。因为我真的看不懂😭
参考文章以及相关资料
Elastic Stack and Product Documentation
安装,启动,汉化
首先你的电脑需要拥有Java8的环境
// 使用Homebrew一键安装
brew update
brew install elasticsearch
// kibana是一个可视化工具
brew install kibana
🀄️kibana的汉化, 可以使用GitHub上的提供的包(需要安装python环境)
RESTful API
可以使用RESTful API通过9200端口与Elasticsearch进行交互
基本概念
Index
索引类似于数据库中表,是用来存储文档的地方。将数据存储到es的行为也可以叫做索引
type
💡 Mapping types will be completely removed in Elasticsearch 7.0.0. (type的概念将在7.0中被删除)
💡 The first alternative is to have an index per document type (更好的做法是, 每一种type将会有单独的Index, 不同的type存储到不同的索引中)
Document
Index(索引)中的每一条记录被称为文档, 许多的Document组成了Index
搜索
空搜索
返回所有索引下的所有文档
GET /_search
# 等同于,匹配所有的文档
GET /_search
{
"query": {
"match_all": {}
}
}
简单搜索
通过查询字符串query-string搜索, 通过URL参数的形式, 传递查询参数
# 查询test_index索引中, name字段包含lihang的文档
GET test_index/_search?q=name:lihang
# 查询test_index索引中, name字段包含zhangyue或者liyubo
GET test_index/_search?q=name:(zhangyue OR liyubo)
QueryDSL
Query DSL指定了一个JSON进行检索, 它支持构建更加复杂和健壮的查询
# match查询, 会将查询语句进行分词处理, 查询name字段中包含lihang的文档
GET test_index/_search
{
"query": {
"match": {
"name": "lihang"
}
}
}
全文搜索
使用match查询全文字段,可以对查询语句进行分词操作,返回所有的相关的文档,查询结果按照相关度算分排序。如果查询的是数字,日期,Boolean,match则会进行精确匹配
# 查询job字段中和engineer相关的文档
GET test_index/_search
{
"query": {
"match": {
"job": "engineer"
}
}
}
短语搜索
使用match_phrase可以查询job字段中包含查询短语的文档
GET test_index/_search
{
"query": {
"match_phrase": {
"job": "node engineer"
}
}
}
# 通过设置slop相似度, 可以允许查询文档与差异语句有一些差异
# 设置slop为2, java engineer 和 java web engineer 可以匹配
GET test_index/_search
{
"query": {
"match_phrase": {
"job": {
"query": "java engineer",
"slop": 2
}
}
}
}
高亮搜索
会对检索的匹配的结果中,匹配的部分做出高亮的展示, 使用标签em包裹
GET test_index/_search
{
"query": {
"match": {
"job": "engineer"
}
},
"highlight": {
"fields": {
"job": {}
}
}
}
// 返回的部分结果
{
"_index": "test_index",
"_type": "doc",
"_id": "3",
"_score": 0.2876821,
"_source": {
"name": "houjiaying",
"job": "java engineer",
"age": 22
},
"highlight": {
"job": [
"java <em>engineer</em>"
]
}
}
多索引搜索
GET /test_index, test2_index/_search
🌈 针对多个索引进行搜索操作, 文档中也是类似的语法。但是我不太清楚为什么返回错误给我,如果有好心人请告诉我
🌈 Multi-Index search (autocomplete) (折中的解决方案)
# 使用_msearch方法多索引搜索
GET _msearch
{"index":"test_index"}
{"query":{"term":{"age":{"value":23}}}}
{"index":"test2_index"}
{"query":{"term":{"_id":{"value":1}}}}
分页
使用form指定查询的起始位置, size指定查询的数量
GET _all/_search?size=5&from=1
GET _all/_search
{
"from": 0,
"size": 1
}
聚合
# 统计索引中全部文档的age,并会返回聚合的结果
GET /test_index/doc/_search
{
"aggs": {
// 返回聚合结果到all_age字段中, 聚合terms匹配的age字段
"all_age": {
"terms": { "field": "age" }
}
}
}
// 返回的聚合结果
"aggregations": {
// all_age是自己定义的
"all_age": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 24,
"doc_count": 2
},
{
"key": 22,
"doc_count": 1
},
{
"key": 23,
"doc_count": 1
},
{
"key": 30,
"doc_count": 1
}
]
}
}
聚合分级汇总
# 添加测试用数据
DELETE test_index
PUT test_index
POST test_index/doc/_bulk
{"index":{"_id":1}}
{"name":"zhangyue","age":23,"job":"web"}
{"index":{"_id":2}}
{"name":"lihange","age":23,"job":"web"}
{"index":{"_id":3}}
{"name":"liyubo","age":22,"job":"web"}
{"index":{"_id":4}}
{"name":"houjiaying","age":22,"job":"java"}
{"index":{"_id":5}}
{"name":"xiaodong","age":26,"job":"java"}
# 聚合查询分级汇总
# 汇总所有的职业并计算职业的评价年龄
GET test_index/doc/_search
{
"aggs": {
"job_info": {
"terms": {
"field": "job"
},
"aggs": {
"avg_age": {
"avg": {
"field": "age"
}
}
}
}
}
}
💡 解决方案
💡 Fielddata is disabled on text fields by default. Set fielddata=true
💡 在text字段中聚合是聚合是禁用的,需要设置索引的mapping, 设置字段的fielddata为true
# 设置job字段的fielddata为true
PUT test_index/_mapping/doc
{
"doc": {
"properties": {
"job": {
"type": "text",
"fielddata": true
}
}
}
}
Document
元数据
- _index 文档存放的索引
- _type 文档的类型
- _id 文档的唯一标识
创建文档
可以分为指定ID创建, 和不指定ID创建(Elasticsearch会自动创建一个唯一标示)。指定ID使用PUT请求, 不指定ID使用POST请求
查询指定ID文档
GET /you_index/you_type/you_document_id
指定返回的字段
# 只返回文档的name字段
GET test_index/doc/1?_source=name
# 只返回文档的age字段
GET test_index/doc/_search
{
"query": {
"range": {
"age": {
"gt": 23
}
}
},
"_source": {
// 返回的字段
"includes": ["age"],
// 不返回的字段
"excludes": []
}
}
检查文档是否存在
使用HEAD方法,HEAD不返回响应体
# 检查ID为1的文档是否存在
HEAD test_index/doc/1
更新文档
POST test_index/doc/1/_update
{
"doc": {
"name": "zhang yue"
}
}
🌈 更新文档后的返回结果, 可以看的_version变为了2,_version字段的含义是文档更新了几次, _version可以实现乐观锁的功能
{
"_index": "test_index",
"_type": "doc",
"_id": "1",
"_version": 2,
"found": true,
"_source": {
"name": "zhangyueHI",
"age": 23,
"job": "web"
}
}
删除文档
# 删除ID为2的文档
DELETE test_index/doc/2
乐观锁
更新的时候,指定_version的大小,当更新文档的时候_version必须等于我们指定的大小,更新操作才会成功
# version为2时更新操作才会成功
POST test_index/doc/1/_update?version=2
{
"doc": {
"name": "zhangyue"
}
}
批量检索
# 检索同一个索引下的多个的文档
GET /test_index/doc/_mget
{
"docs": [
{
"_id": 1
},
{
"_id": 3
}
]
}
# 直接通过不同的id, 检索同一个索引下的多个的文档
GET /test_index/doc/_mget
{
"ids": [1, 3, 4]
}
# 检索不同索引下的多个文档
GET /_mget
{
"docs": [
{
"_index": "test_index",
"_type": "doc",
"_id": 1
},
{
"_index": "test2_index",
"_type": "doc",
"_id": 1
}
]
}
批量创建
批量创建的JSON中, 不能包含未转义的换行符,因为他们将会对解析造成干扰。必须使用Kibana自动格式化JSON,才可以正确的执行_bulk操作
批量操作关键字, index(创建, id重复时覆盖), delete(删除), create(创建, id重复时报错), update(更新)
# 批量操作
POST /test_index/doc/_bulk
{"index":{"_index":"test_index","_type":"doc","_id":2}}
{"name":"songmin","age":20}
{"delete":{"_index":"test_index","_type":"doc","_id":2}}
{"create":{"_index":"test_index","_type":"doc","_id":2}}
{"name":"songmin","age":18}
{"update":{"_index":"test_index","_type":"doc","_id":2}}
{"doc":{"age":22}}
Mapping与分析
精确值与全文
精确值,通常指的是日期, 用户ID, 邮箱地址。对于精确值, java 与 java web是不同的。精确值,要么匹配查询,要么不匹配。
全文, 通常指的是文本数据, 匹配时是查询的匹配的程度
倒排索引
Elasticsearch中使用倒排索引的方式,实现快速的全文搜索
倒排索引是由文档中, 是所有可以被分词的字段的复词列表组成的。将所有文档的复词列表,用来创建一个没有重复分词的复词列表。并且记录了,每一个分词出现在那一个文档中。
例如搜索短语"dog over", 在倒排索引中在Doc_1, Doc_2都会匹配, 但Doc_1文档的匹配度会更高
Term Doc_1 Doc_2
-------------------------
Quick | | X
The | X |
brown | X | X
dog | X |
dogs | | X
fox | X |
foxes | | X
in | | X
jumped | X |
lazy | X | X
leap | | X
over | X | X
quick | X |
summer | | X
the | X |
------------------------
分析器
分析器由以下三种功能组成:
- 字符过滤器, 例如过滤文本中HTML标签
- 分词器, 将文本拆分成单独的词条
- Token过滤器, 过滤分词, 例如转化大小写, 删除语气组词
Elasticsearch内置了多种的分析器, 例如空格分析器, 可以按照空格分词。语言分析器, 可以删除多余的语气助词
如果希望指定索引中字段的分析器,则需要我手动的指定索引的Mapping
analyze API
# 测试standard分析器
GET /_analyze
{
"analyzer": "standard",
"text": "Text to analyze"
}
Mapping
Mapping的定义可以类比为Mongo中Schema, 在Mapping中定义了索引里每个字段的类型以及分析器等
# 查看索引的Mapping
GET test_index/doc/_mapping
自定义Mapping
💡 Elasticsearch中, 已经不存在string, object类型, 使用text类型。
💡 字段的type被设置为"text"类型, 字段通常会被全文检索。如果设置为"keyword"类型, 字段会则需要精确查找。
💡 当字段的type被设置为"text"类型, 可以另外设置index(是否可以被搜索)和analyzer(分析器)
# 自定义索引的Mapping
PUT m2y_index
{
"mappings": {
"doc": {
"properties": {
"info": {
type: "text"
}
}
}
}
}
更新Mapping
不能直接更新索引的Mapping, 只能在创建索引的时候指定Mapping, 以及添加新的字段的Mapping
复杂类型
- 数组类型, 数组的内容必须类型一致, 检索时, 所有包含在数组中的内容都可以被检索
- null, 空值不会被索引
- Object, 可以通过以下的方式定义Object类型的Mapping(通过嵌套properties)
# 定义Object类型的Mapping
POST test3_index/doc
{
"mappings": {
"doc": {
"properties": {
"info": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "text"
},
"hobbies": {
"properties": {
"one": {
"type": "text"
},
"two": {
"type": "text"
}
}
}
}
}
}
}
}
}
查询
match查询
match查询时,如果查询是全文字段,会对查询内容分词后进行全文搜索。如果指定的是数字, 日期, 布尔字段, match查询则会进行精准匹配
multi_match查询
对多个字段(fields数组中的字段), 执行相同的match查询
GET test_index/doc/_search
{
"query": {
"multi_match": {
"query": "web",
"fields": ["job", "name"]
}
}
}
range查询
进行范围查询操作, 关键字gt(大于), gte(大于等于), lt(小于), lte(小于等于)
GET test_index/doc/_search
{
"query": {
"range": {
"age": {
"gte": 22,
"lte": 30
}
}
}
}
# 日期字段进行range查询
# 添加测试数据
PUT range_index
POST range_index/doc
{
"mappings": {
"doc": {
"date": {
"type": "date"
}
}
}
}
POST range_index/doc/_bulk
{"index":{"_id":1}}
{"name":"zhangyue","date":"1994-06-07"}
{"index":{"_id":2}}
{"name":"lihang","date":"1994-10-22"}
{"index":{"_id":3}}
{"name":"zhaochen","date":"1994-07-01"}
# 查询日期范围, 日期小于1994年7月
GET range_index/doc/_search
{
"query": {
"range": {
"date": {
"lt": "1994-07"
}
}
}
}
term查询
term查询用于精准匹配, 对查询的内容, 不进行分词处理
terms查询
与term查询一致,区别在于terms提供了一个数组, 可以允许多值匹配
组合多查询(bool)
bool支持多种条件的查询和过滤
bool支持多种参数, must(必须包含的条件), must_no(必须不包含的条件), should(满足should中的内容可以增加匹配得分), filter(必须匹配, 但是不影响匹配得分)
# bool查询中, 文档必须满足must中的全部条件,并进行相关性算分。filter会过滤不符合条件的文档, 但不进行相关性算分。
GET test_index/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "lihang"
}
}
],
"filter": {
"range": {
"age": {
"gt": 20
}
}
}
}
}
}
验证查询
验证查询语句是否合法
GET zc_index/doc/_validate/query
{
"query": {
"range": {
"age": {
"gt1e": 2
}
}
}
}
// 当查询非法的时候,valid会返回false
{
"valid": false
}
排序
- desc 降序
- asc 升序
// 查询job中包含web的,并不进行相关性算分, 按照age的升序排序
GET zc_index/doc/_search
{
"query": {
"bool": {
"filter": {
"match": {
"job": "web"
}
}
}
},
"sort": [
{
"age": {
"order": "asc"
}
}
]
}
多值字段(不是多字段)排序。当字段内拥有多个值的时候,可以使用mode,对这些值进行min,max,sum,avg等操作。然后,对计算后的结果进行排序
"sort": {
"dates": {
"order": "asc",
"mode": "min"
}
}
字符串排序
有时候,我们希望对字符串进行排序(例如: 通过首字母的方式排序字符串), 但是对于text类型, elasticsearch会进行分词处理。如果需要对字符串进行排序,我们不能进行分词处理。但是如果需要全文检索,我们又需要对字符串进行分词处理。
我们可以通过fields对相同的字段,以不同的目的,设置不同的type类型
// job字段设置为text类型,进行全文检索
// job.row 设置为keyword类型, 可以进行排序,精确匹配
PUT zy_index
{
"mappings": {
"doc": {
"properties": {
"job": {
"type": "text",
"fields": {
"raw": {
"type": "keyword"
}
}
}
}
}
}
}
// 添加一些测试数据
POST zy_index/doc/_bulk
{"index":{"_id":1}}
{"name":"zhangyue","job":"web node"}
{"index":{"_id":2}}
{"name":"lihang","job":"node java"}
{"index":{"_id":3}}
{"name":"liyubo","job":"web"}
{"index":{"_id":4}}
{"name":"houjiaying","job":"java"}
// 全文检索job含有node
// 通过job.row进行首字母排序
GET zy_index/doc/_search
{
"query": {
"match": {
"job": "node"
}
},
"sort": [
{
"job.raw": {
"order": "asc"
}
}
]
}
文档的相关性
_score表示文档的相关性,_score越高文档与查询的条件匹配程度越高。
_score的评分标准:
- 检索词频率,检索词在字段出现的频率越高,文档的相关性越高
- 反向文档频率,检索词在索引中出现的越高,文档的相关性越低(因为大多数文档都出现了该检索词)
- 字段长度准则, 检索词出现的字段越长, 相关性越低
索引
# 创建索引
PUT test_index
# 查看索引
GET test_index
# 删除索引
DELETE test_index
创建索引
通过索引一篇文档可以自动创建索引, 索引采用默认的配置。字段会通过动态映射添加类型,当然我们可以指定索引的mapping
删除索引
// 删除全部的索引
DELETE /_all
DELETE /*
动态映射 dynamic mapping
Elasticsearch会将mapping中没有定义的字段,通过dynamic mapping确定字段的数据类型,这是默认的行为。如果对没有出现的未定义字段作出其他的操作,可以通过定义mapping的dynamic配置项目
- dynamic: true 会自动为新增的字段添加类型
- dynamic: false 忽略新增的字段
- dynamic: strict 会抛出异常
PUT hello_index
{
"mappings": {
"doc": {
"dynamic": true
}
}
}
重新索引数据
在es中mapping定义后是无法修改的,如果想要修改mapping只能创建新的索引。更多内容Reindex API
POST _reindex
{
"source": {
"index": "zc_index"
},
"dest": {
"index": "zc2_index"
}
}
part2
<!–more–>
导入
使用elasticsearch-dump导入文件数据到Es中
// dump的使用方法
elasticdump \
// JSON文件路径
--input=/Users/zhangyue/Documents/swmgame-activity-2018.06/swmgame-activity-2018.06.mapping.json \
// 导入的路径
--output=http://127.0.0.1:9200/my_index_type \
--type=mapping
elasticdump \
--input=/Users/zhangyue/Documents/swmgame-activity-2018.06/swmgame-activity-2018.06.data.json \
--output=http://127.0.0.1:9200/my_index_type \
--type=data
搜索
精确值搜索
term查询数字
term查询会查找指定的精确值, 如果不想计算相关度算分, 可以使用filter可以更快的执行操作
// 检索passStatus为2的文档
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"passStatus": {
"value": 2
}
}
}
]
}
}
}
// 检索passStatus等于2并且gameId等于G09, 并且查询不计算相关度算分
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"bool": {
// 不计算相关度算分
"filter": {
"bool": {
"must": [
{
"term": {
"passStatus": {
"value": 2
}
}
},
{
"term": {
"gameId.keyword": {
"value": "G09"
}
}
}
]
}
}
}
}
}
term查询文本
😔 使用term检索文本的时候, 检索的结果可能与我们预期的结果并不一致, 这是什么原因导致的呢?
💡 我们通过analyzeAPI可以看到, 文档中的text类型的字段, 已经被分词。如果使用term精确查找全文, 是不会匹配到数据的。因为这些全文并没有被存储在倒排索引中。
📖 有两种解决的办法, 解决办法1, 在es中es默认会为字段创建名为keyword的keyword类型的fields字段, 于字段一致但是类型并不一致。可以使用keyword的fields字段进行term查询。解决办法2, 创建新的索引使用新的mapping, 指定字段为keyword类型
// 解决办法1
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"gameId.keyword": {
"value": "G09"
}
}
}
]
}
}
}
// 解决办法2
DELETE test5_index
PUT test5_index
{
"mappings": {
"doc": {
"properties": {
"gameId": {
"type": "keyword"
}
}
}
}
}
term查询范围类型
DELETE test_index
PUT test_index
{
"mappings": {
"doc": {
"properties": {
// 定义age字段为范围类型
"age": {
"type": "integer_range"
}
}
}
}
}
// 添加测试数据
POST test_index/doc/_bulk
{"index":{"_id":1}}
{"age":{"gte":10,"lte":20}}
{"index":{"_id":2}}
{"age":{"gte":21,"lte":30}}
// 查询文档
GET test_index/doc/_search
{
"query": {
"term": {
"age": {
"value": 11
}
}
}
}
组合过滤器
组件过滤器的组成:
- must 必须满足的一组条件
- must_not 必须不满足的一组条件
- should 可以满足的一组的条件, 如果满足可以增加相关性算分(如果组合过滤器中只有should, 则文档必须满足should中的一项条件)
// 嵌套的bool查询
// 检索gameId等于G09, 或者passStatus等于2并且source等于ios
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"gameId.keyword": {
"value": "G09"
}
}
},
{
"bool": {
"must": [
{
"term": {
"passStatus": {
"value": "2"
}
}
},
{
"term": {
"source.keyword": {
"value": "ios"
}
}
}
]
}
}
]
}
}
}
terms
terms查询与term查询类似,terms匹配的是一组精确值, 是一个精确值数组
// terms匹配的是一组数组
// 检索passStatus为1或者2的文档
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"bool": {
"filter": {
"terms": {
"passStatus": [
1,2
]
}
}
}
}
}
range
- gt 大于
- gte 大于等于
- lt 小于
- lte 小于等于
数字范围查询 🔢
// passStatus大于等于1并且小于2的文档
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"bool": {
"filter": {
"range": {
"passStatus": {
"gte": 1,
"lt": 2
}
}
}
}
}
}
日期范围查询 📅
在使用日期范围查询的时候,需要预先将日期的字段的mapping的type类型设置为date
DELETE test_index
PUT test_index
{
"mappings": {
"doc": {
"properties": {
// 设置为time类型
"time": {
"type": "date"
}
}
}
}
}
// 添加测试数据
POST test_index/doc/_bulk
{"index":{"_id":1}}
{"time":"1994-06-07"}
{"index":{"_id":2}}
{"time":"1994-10-22"}
{"index":{"_id":3}}
{"time":"1993-07-01"}
{"index":{"_id":4}}
{"time":"2005-01-01"}
// 查询文档的time字段的日期范围是大于1994-10-22, 小于等于2005-01-01
GET test6_index/doc/_search
{
"query": {
"bool": {
"filter": {
"range": {
"time": {
"gt": "1994-10-22",
"lte": "2005-01-01"
}
}
}
}
}
}
字符串范围查询 📖
字符串范围使用的是字典顺序(lexicographically)的排序方式。
🏠 什么是字典序?
比如说有一个随机变量X包含1, 2, 3三个数值。
其字典排序就是123 132 213 231 312 321
DELETE test_index
// 添加测试数据
POST test_index/doc/_bulk
{"index":{"_id":1}}
{"name":"abce fg"}
{"index":{"_id":2}}
{"name":"cba twa"}
{"index":{"_id":3}}
{"name":"a cb"}
{"index":{"_id":4}}
{"name":"gfw wfw"}
{"index":{"_id":5}}
{"name":"bgfw wfw"}
GET test_index/doc/_search
{
"query": {
"bool": {
"filter": {
"range": {
"name": {
"gte": "a",
"lt": "c"
}
}
}
}
}
}
处理Null值
存在查询
检索文档中至少包含一个非空的文档。(🐶全部为空或者没有该字段的除外)
// 查找game字段至少包含一个非空文档的文档
GET test_index/doc/_search
{
"query": {
"exists": {
"field": "game"
}
}
}
缺失查询
查询文档中为null, 或者没有该字段的文档
💡 缺失查询已经在ElasticSearch2.2.0中废弃。可以使用以下的方法代替
GET test_index/doc/_search
{
"query": {
"bool": {
"must_not": {
"exists": {
"field": "game"
}
}
}
}
}
全文搜索
基本概念
基于词项
如term查询不会经过分析阶段,只会在倒排索引中查找精确的词项,并用TF/DF算法为文档进行相关性评分_score。term查询只对倒排索引的词项精确匹配,而不会对查询词进行多样性处理。
基于全文
如match查询和query_string查询。对于日期和整数会将查询字符串当作整数对待。如果查询的是keyword类型的字段,会将查询字符串当作单个单词对待。如果是全文字段,会将查询字符串使用合适的分析器,作出处理,生成一个查询词项的列表,查询会对词项列表中的每一项进行查询,再将结果进行合并。
匹配查询
match查询即可以进行精确检索也可以进行全文检索,以下是match查询的具体的过程:
- 检查字段类型, 如果检索的字段是text类型, 那么查询字符串也应当被分析
- 分析查询字符串, 将查询字符串经过默认的分析器的处理, match执行的是当个底层的term查询
- term查询会在倒排索引中对分析后的查询字符串进行检索
- 为匹配的文档评分(检索词频率, 反向文档频率, 字段长度准则)
- 为什么在这里测试只创建一个分片, 后面会有介绍
// 只创建一个主分片
PUT /swmgame-activity-2018.06
{ "settings": { "number_of_shards": 1 }}
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"bool": {
"filter": {
"match": {
"ipInfo.city": "蚌埠"
}
}
}
}
}
多词查询
match查询如果查询的是text类型的全文字段, 那么也会对查询字符串进行分析
// match查询会分别查询蚌埠和龙子湖区两个字段
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"bool": {
"filter": {
"match": {
"ipInfo.city": "蚌埠 龙子湖区"
}
}
}
}
}
如果希望查询的文档, 同时包含所有的关键词, 则可以使用operator使用and操作符
// 查询包含蚌埠并且包含龙子湖区两个字段
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"bool": {
"filter": {
"match": {
"ipInfo.city": "蚌埠 龙子湖区",
"operator": "and"
}
}
}
}
}
最小匹配数
match查询可以设置minimum_should_match参数,即最小匹配数,如果match查询有四个词项,如果minimum_should_match设置为50%,那么最少匹配2个词项
组合查询
组合查询接受must, must_not, should。如果当DSL中只包含should的时候, should至少有一个必须匹配。同时可以指定should的minimum_should_match参数, 控制查询的精度。控制必须匹配的数量。
多词查询中, 使用or操作符号, 和使用should查询是等价的
// 两条查询是等价的
GET test_index/doc/_search
{
"query": {
"match": {
"title": "fox dog"
}
}
}
GET test_index/doc/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"title": {
"value": "fox"
}
}
},
{
"term": {
"title": {
"value": "dog"
}
}
}
]
}
}
}
多词查询中, 使用and操作符, 和使用must查询是等价的
// 两条查询是等价的
GET test_index/doc/_search
{
"query": {
"match": {
"title": {
"query": "fox dog",
"operator": "and"
}
}
}
}
GET test_index/doc/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"title": {
"value": "fox"
}
}
},
{
"term": {
"title": {
"value": "dog"
}
}
}
]
}
}
}
权重
设置boost可以设置查询语句的权重, 大于1权重增大, 0到1之间权重逐渐降低。匹配到权重越高的查询语句, 相关性算分越高
// 安徽的查询语句的权重为3, 匹配到的文档的得分更高
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"passStatus": {
"value": 2,
"boost": 2
}
}
},
{
"match": {
"ipInfo.province.keyword": {
"query": "安徽",
"boost": 3
}
}
}
]
}
}
}
默认的分析器
索引时的分析器:
- 首先使用字段映射的分析器
- 其次是index默认的分析器
- 最后是默认为standard分析器
搜索时的分析器:
- 优先查找定义的分析器
- 其次是字段映射的分析器
- 最后是索引默认的default的分析器, 默认为standard分析器
相关度被破坏
Q: 为什么在之前的测试索引时,需要指定只为这个索引创建一个主分片?
A: 为了避免相关度被破坏。在elasticsearch中计算相关度使用TF/IDF算法,词频/逆向文档词频的算法。如果有5个包含”fox”的文档位于分片1,一个包含“fox”的文档位于分片2。就会导致一个问题, fox在分片1的相关度会降低, 而在分片2的相关度就会非常高。但是在实际的场景中这并不是一个问题,因为在真实的环境中,数据量通常是非常大的,分片之间的差异会被迅速均化,以上的问题只是因为数据量太少了。
最佳字段
什么是最佳字段,我们需要首先了解什么是竞争关系。
下面是两条文档, 假设我们需要搜索"Brown fox"两个关键词并且需要同时检索"title"和"body"两个字段的时候。文档2的body字段虽然同时匹配了两个关键词, 但是文档2的title没有匹配到任何的关键词。所以相关性算分, 文档2是低于文档1的。
但是如果取最佳字段的评分,文档2的body字段为最佳字段, 文档2的评分是高于文档1的评分的
{
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
{
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
dis_max
// 在最后计算文档的相关性算分的时候, 只会取queries中的相关性的最大值
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"dis_max": {
"queries": [
{
"match": {
"FIELD1": "TEXT1"
}
},
{
"match": {
"FIELD2": "TEXT2"
}
}
]
}
}
}
tie_breaker
使用dis_max最佳字段可能存在另外的问题就是。如果同时查询多个字段, 如果一个文档同时匹配了多个字段,但是由于文档的相关性算分取的是最佳字段, 可能导致该文档的相关性算分并不是最高的。
使用tie_breaker参数,可以将其他匹配语句的评分也计算在内。将其他匹配语句的评分结果与tie_breaker相乘, 最后与最佳字段的评分求和得出文档的算分。
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"dis_max": {
"tie_breaker": 0.3,
"queries": [
{
"match": {
"FIELD1": "TEXT1"
}
},
{
"match": {
"FIELD2": "TEXT2"
}
}
]
}
}
}
multi_match
多字段的match查询, 为多个字段执行相同的match查询。multi_match查询默认取最佳字段的相关性算分
// 下面两种查询是等同的
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"dis_max": {
"queries": [
{
"match": {
"FIELD1": "TEXT1"
}
},
{
"match": {
"FIELD2": "TEXT1"
}
}
]
}
}
}
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"multi_match": {
"query": "TEXT1",
"type": "best_fields", // 取最佳字段的相关性算分
"fields": [
"FIELD1",
"FIELD2",
"FIELD3^2" // 可以指定的查询字段的权重, 如果匹配FIELD3字段得分将会更高
]
}
}
}
fields
fields是对同一个字段索引多次, 每一个字段不同的fields可以定义不同的type以及分析器,用做不同的用途
// title为text类型
// title.keyword为keyword类型
// title.english使用english的分析器
PUT test3_index
{
"mappings": {
"doc": {
"properties": {
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
},
"english": {
"type": "text",
"analyzer": "english"
}
}
}
}
}
}
}
跨字段实体搜索
比如一个人的信息,包含姓名,电话号码,家庭住址等多个字段。如果想要搜索人的信息,可能需要检索多个字段。
// 姓名, 手机号, 家庭住址字段都包含了一个人的信息
{
"name": "Frank",
"mobi": "13053127868",
"hours": "bengbu"
}
最直接的检索的方式是通过multi_match或者bool的should实现对多个字段的检索
GET test_index/doc/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"name": "Frank 1305312786"
}
},
{
"mathch": {
"mobi": "Frank 13053127868"
}
},
{
"mathch": {
"hours": "Frank 13053127868"
}
}
]
}
}
}
{
"query": {
"multi_match": {
"query": "Frank 1305312786",
"type": "most_fields", // 合并匹配字段的评分, 不使用最佳字段的评分
"fields": [ "name", "mobi", "hours" ]
}
}
}
跨字段检索的问题
- 在同一个字段匹配到多个关键词的相关性算分,要小于多个字段匹配同一个关键词的算分。
- 使用and操作符, 会导致所有查询变为所有关键词都必须要在同一个字段匹配。
- 逆向文档频率的问题, 当一个查询具有较低的逆向文档频率(相关度较高)。可能会削弱较高的逆向文档频率(相关度较低)的作用。
_all
使用copy_to的功能,将多个字段内容拷贝到文档的一个字段中。检索时使用合并的字段进行检索。
PUT test3_index
{
"mappings": {
"doc": {
"properties": {
"name": {
"type": "text",
"copy_to": "info"
},
"mobi": {
"type": "text",
"copy_to": "info"
},
"hours": {
"type": "text",
"copy_to": "info"
},
"info": {
"type": "text"
}
}
}
}
}
// 查询时可以直接通过info字段查询
GET test3_index/doc/_search
{
"query": {
"match": {
"info": "Frank 1305312786"
}
}
}
cross-fields
当multi_match的type等于cross-fields时, 它将所有fields中的字段当成一个大字段,并在这个大字段中进行检索。
同时也会解决逆向文档频率的问题, 因为cross-fields会取不同字段中最高的逆向文档频率(相关度最低的)的值。
cross-fields时,所有字段必须为同一种分析器。如果包括了不同分析器的字段,它们会以best_fields的相同方式被加入到查询结果中
// abc efg必须同时出现在FIELD1或者FIELD2文档中
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"multi_match": {
"query": "abc efg",
"fields": ["FIELD1", "FIELD2"],
"type": "most_fields",
"operator": "and"
}
}
}
// abc efg必须出现但是可以在FIELD1或者FIELD2不同的字段中
GET swmgame-activity-2018.06/my_index/_search
{
"query": {
"multi_match": {
"query": "abc efg",
"fields": ["FIELD1^2", "FIELD2"], // 可以提高字段的权重
"type": "cross_fields",
"operator": "and"
}
}
}
part3
<!–more–>
聚合
基本概念
桶
桶指的是满足特定条件的文档的集合, (比如李航属于陕西桶), 聚合开始执行, 符合条件的文档会放入对应的桶。
指标
对桶内的文档进行统计计算, 例如:最小值, 平均值, 汇总等
指标与桶
桶是可以被嵌套的, 可以实现非常复杂的组合。
国家桶 -> 性别桶 -> 年龄段桶 -> 不同国家不同性别不同年龄段的平均工资
初步聚合
简单的聚合
// 对每一个省份的数据进行聚合
GET swmgame-activity-2018.06/my_index/_search
{
"size": 0,
"aggs": {
"province_info": {
"terms": {
"field": "ipInfo.province.keyword",
"size": 10
}
}
}
}
简单的指标
// 聚合每一个省份的文档, 并对每一个省份的得分求平均值
GET swmgame-activity-2018.06/my_index/_search
{
"size": 0,
"aggs": {
"province_info": {
"terms": {
"field": "ipInfo.province.keyword",
"size": 100
},
"aggs": {
"avg_score": {
"avg": {
"field": "score"
}
}
}
}
}
}
嵌套桶
对聚合的结果进一步聚合。每一层的聚合都可以添加单独的指标, 每一层聚合的指标之间相互独立。
// 首先对每一个国家进行聚合, 并计算每一个国家的平均得分
// 其次对每一个省份进行聚合, 并计算每一个省份的平均得分
GET swmgame-activity-2018.06/my_index/_search
{
"size": 0,
"aggs": {
"country_info": {
"terms": {
"field": "ipInfo.country.keyword",
"size": 10
},
"aggs": {
"avg_score": {
"avg": {
"field": "score"
}
},
"province_info": {
"terms": {
"field": "ipInfo.province.keyword",
"size": 100
},
"aggs": {
"avg_score": {
"avg": {
"field": "score"
}
}
}
}
}
}
}
}
直方图
什么是直方图
直方图就是柱状图, 创建直方图需要指定一个区间, elasticsearch可以对每一个区间进行聚合操作
// 根据durationCallAll字段, 从最小值开始, 每10000为1个区间进行聚合
GET crm_statistics-2018.06/my_index/_search
{
"size": 0,
"aggs": {
"day_duration_call_all": {
"histogram": {
"field": "durationCallAll",
"interval": 10000,
"min_doc_count": 0
}
}
}
}
// 返回的部分结果
// ......
{
"key": 20000,
"doc_count": 3
},
{
"key": 30000,
"doc_count": 3
},
{
"key": 40000,
"doc_count": 9
}
// ......
按时间统计
根据时间统计, 可以根据时间类型的字段进行聚合, 区间可以是每一日, 每一周, 每一月, 每一季度等等
GET crm_statistics-2018.06/my_index/_search
{
"size": 0,
"aggs": {
"at": {
"date_histogram": {
"field": "at", // 根据时间类型的字段at进行聚合
"interval": "day", // 区间是每一天
"min_doc_count": 0, // 可以强制返回空的桶
"extended_bounds": { // 时间区间
"min": "2018-05-30",
"max": "2018-06-22"
}
}
}
}
}
返回空的桶
- min_doc_count, 可以强制返回长度为空的桶
- extended_boundss, 可以设置返回的时间区间(因为elasticsearch默认只返回最小值到最大值之间的桶)
扩展例子
🌰 按时间统计的直方图上进行聚合操作, 并计算度量指标的例子。
下面的DSL, 按时间进行统计直方图, 以每一天作为区间, 并且计算区间内, 每一个省份的平均分数, 以及每一个省份下每一个城市的平均得分
GET swmgame-activity-2018.06/my_index/_search
{
"size": 0,
"aggs": {
"at_date_histogram": {
"date_histogram": {
"field": "at",
"interval": "day",
"min_doc_count": 0,
"extended_bounds": {
"min": "2018-05-01",
"max": "2018-06-22"
}
},
"aggs": {
"province_info": {
"terms": {
"field": "ipInfo.province.keyword",
"size": 50
},
"aggs": {
"avg_score": {
"avg": {
"field": "score"
}
},
"city_info": {
"terms": {
"field": "ipInfo.city.keyword",
"size": 100
},
"aggs": {
"avg_score": {
"avg": {
"field": "score"
}
}
}
}
}
}
}
}
}
}
限定范围聚合
没有指定query的情况下, 聚合操作针对的是整个索引
// 限定安徽和福建两个省份进行聚合操作
GET swmgame-activity-2018.06/my_index/_search
{
"size": 0,
"query": {
"bool": {
"should": [
{
"term": {
"ipInfo.province.keyword": {
"value": "安徽"
}
}
},
{
"term": {
"ipInfo.province.keyword": {
"value": "福建"
}
}
}
]
}
},
"aggs": {
"city_info": {
"terms": {
"field": "ipInfo.city.keyword",
"size": 100
},
"aggs": {
"avg_socre": {
"avg": {
"field": "score"
}
}
}
}
}
}
全局桶
使用全局桶(global)可以在查询范围内聚合, 也可以全局文档上聚合
GET swmgame-activity-2018.06/my_index/_search
{
"size": 0,
"query": {
"bool": {
"filter": {
"term": {
"ipInfo.province.keyword": "安徽"
}
}
}
},
"aggs": {
// 查询限定的范围内聚合
"city_info": {
"terms": {
"field": "ipInfo.city.keyword",
"size": 100
}
},
"all": {
"global": {},
// 在全局范围内聚合
"aggs": {
"city": {
"terms": {
"field": "ipInfo.city.keyword",
"size": 1000
},
"aggs": {
"all_avg_score": {
"avg": {
"field": "score"
}
}
}
}
}
}
}
}
过滤和聚合
过滤
参考限定范围的聚合 🚗️ ✈️
过滤桶
对聚合的结果进行过滤
为什么使用过滤桶?
我们可能遇到这种情况, 我们想把查询条件a查询到数据显示到前端页面上, 但同时又想把查询条件a+b的聚合结果显示到页面上。所以在过滤的时候,我们并不能直接使用过滤条件a+b。而聚合桶就可以对聚合的结果进行过滤
// 查询显示的结果的过滤条件是 "安徽"
// 聚合显示的结果的过滤条件是 "安徽" + "蚌埠"
GET swmgame-activity-2018.06/my_index/_search
{
"size": 20,
"query": {
"bool": {
"filter": {
"term": {
"ipInfo.province.keyword": "安徽"
}
}
}
},
"aggs": {
"city_info": {
"filter": {
"term": {
"ipInfo.city.keyword": "阜阳"
}
},
"aggs": {
"city_info": {
"terms": {
"field": "ipInfo.city.keyword",
"size": 20
}
}
}
}
}
}
后过滤器
对查询的结果进行过滤
为什么需要后过滤器?
我们可能遇到这种情况, 我们想在查询a条件的情况下对结果作出聚合, 但是又想在查询a+b条件下下显示结果。这种情况下可以使用后过滤器, 对查询的结果进行过滤。
// 查询的条件是 福建 + 厦门
// 聚合的过滤条件是厦门
GET swmgame-activity-2018.06/my_index/_search
{
"size": 5,
"query": {
"bool": {
"filter": {
"term": {
"ipInfo.province.keyword": "福建"
}
}
}
},
"post_filter": {
"term": {
"ipInfo.city.keyword": "厦门"
}
},
"aggs": {
"city_info": {
"terms": {
"field": "ipInfo.city.keyword",
"size": 20
}
}
}
}
聚合排序
内置排序
-
_count 按照文档的数量排序
-
_term 按词项的字符串值的字母顺序排序
-
_key 按每个桶的键值数值排序。只在histogram和date_histogram内使用
-
desc 降序
-
asc 升序
// 文档的内容升序进行排序
GET swmgame-activity-2018.06/my_index/_search
{
"size": 0,
"aggs": {
"province": {
"terms": {
"field": "ipInfo.province.keyword",
"size": 50,
"order": {
"_count": "asc"
}
}
}
}
}
// 按照字符串的顺序进行排序, 只能用在term
GET swmgame-activity-2018.06/my_index/_search
{
"size": 0,
"aggs": {
"game_id_info": {
"terms": {
"field": "gameId.keyword",
"size": 50,
"order": {
"_term": "asc"
}
}
}
}
}
// 按照date_histogram或者histogram的key排序
GET swmgame-activity-2018.06/my_index/_search
{
"size": 0,
"aggs": {
"date": {
"date_histogram": {
"field": "at",
"interval": "day",
"min_doc_count": 0,
// 时间区间按照降序排序
"order": {
"_key": "desc"
}
}
}
}
}
度量排序
直接使用度量的key作为排序的字段
单度量值排序
GET swmgame-activity-2018.06/my_index/_search
{
"size": 0,
"aggs": {
"province_info": {
"terms": {
"field": "ipInfo.province.keyword",
"size": 50,
"order": {
"avg_score": "asc"
}
},
"aggs": {
"avg_score": {
"avg": {
"field": "score"
}
}
}
}
}
}
多度量值排序
使用点操作符号, 选择单个度量值进行排序
GET swmgame-activity-2018.06/my_index/_search
{
"size": 0,
"aggs": {
"province_info": {
"terms": {
"field": "ipInfo.province.keyword",
"size": 50,
"order": {
"info.avg": "asc"
}
},
"aggs": {
"info": {
"extended_stats": {
"field": "score"
}
}
}
}
}
}
近似聚合
对于一些复杂的操作,需要在精准性和实时性上作出权衡
近似聚合 —— 去重
cardinality可以实现去重操作, 但是数据量巨大的情况下精确性上可能存在误差, 但是可以设置更大的内存提供去重的精确性
// 查看有多少个城市
GET swmgame-activity-2018.06/my_index/_search
{
"size": 0,
"aggs": {
"city_count": {
"cardinality": {
"field": "ipInfo.city.keyword"
}
}
}
}
// 统计每一个省份的城市数量
GET swmgame-activity-2018.06/my_index/_search
{
"size": 0,
"aggs": {
"country_info": {
"terms": {
"field": "ipInfo.country.keyword",
"size": 10
},
"aggs": {
"province_info": {
"terms": {
"field": "ipInfo.province.keyword",
"size": 50
},
"aggs": {
"city": {
"terms": {
"field": "ipInfo.city.keyword",
"size": 50
}
},
"city_count": {
"cardinality": {
"field": "ipInfo.city.keyword"
}
}
}
}
}
}
}
}
precision_threshold
cardinality精确性与内存的使用量有关,通过配置precision_threshold参数,设置去重需要的固定内存使用量。内存使用量只与你配置的精确度相关。
precision_threshold 设置为100的时候,去重的需要的内存是 100 * 8 的字节。precision_threshold 接受的范围在0–40,000之间
// 统计每一个国家的数量
GET swmgame-activity-2018.06/my_index/_search
{
"size": 0,
"aggs": {
"country_count": {
"cardinality": {
"field": "ipInfo.country.keyword"
}
}
}
}
近似聚合 —— 百分位
什么是百分位
以下是百度百科的内容
统计学术语,如果将一组数据从小到大排序,并计算相应的累计百分位,则某一百分位所对应数据的值就称为这一百分位的百分位数。可表示为:一组n个观测值按数值大小排列。如,处于p%位置的值称第p百分位数。
百分位通常用第几百分位来表示,如第五百分位,它表示在所有测量数据中,测量值的累计频次达5%。以身高为例,身高分布的第五百分位表示有5%的人的身高小于此测量值,95%的身高大于此测量值。
高等院校的入学考试成绩经常以百分位数的形式报告。比如,假设某个考生在入学考试中的语文部分的原始分数为54分。相对于参加同一考试的其他学生来说,他的成绩如何并不容易知道。但是如果原始分数54分恰好对应的是第70百分位数,我们就能知道大约70%的学生的考分比他低,而约30%的学生考分比他高。
百分位计算
// 添加测试数据, 网络延迟数据
PUT test9_index
POST /test9_index/doc/_bulk
{ "index": {}}
{ "latency" : 100, "zone" : "US", "timestamp" : "2014-10-28" }
{ "index": {}}
{ "latency" : 80, "zone" : "US", "timestamp" : "2014-10-29" }
{ "index": {}}
{ "latency" : 99, "zone" : "US", "timestamp" : "2014-10-29" }
{ "index": {}}
{ "latency" : 102, "zone" : "US", "timestamp" : "2014-10-28" }
{ "index": {}}
{ "latency" : 75, "zone" : "US", "timestamp" : "2014-10-28" }
{ "index": {}}
{ "latency" : 82, "zone" : "US", "timestamp" : "2014-10-29" }
{ "index": {}}
{ "latency" : 100, "zone" : "EU", "timestamp" : "2014-10-28" }
{ "index": {}}
{ "latency" : 280, "zone" : "EU", "timestamp" : "2014-10-29" }
{ "index": {}}
{ "latency" : 155, "zone" : "EU", "timestamp" : "2014-10-29" }
{ "index": {}}
{ "latency" : 623, "zone" : "EU", "timestamp" : "2014-10-28" }
{ "index": {}}
{ "latency" : 380, "zone" : "EU", "timestamp" : "2014-10-28" }
{ "index": {}}
{ "latency" : 319, "zone" : "EU", "timestamp" : "2014-10-29" }
计算网络延迟的百分位和网络延迟的平均数
GET test9_index/doc/_search
{
"size": 0,
"aggs": {
// 计算网络延迟的平均数
"avg_ping": {
"avg": {
"field": "latency"
}
},
// 计算网络延迟的百分位
"percentiles_ping": {
"percentiles": {
"field": "latency"
}
}
}
}
下面是返回的聚合结果,可以看到平均数有时并不能体现异常的数据。约有25%的用户的网络延迟是大于289的
"aggregations": {
"avg_ping": {
"value": 199.58333333333334
},
"percentiles_ping": {
"values": {
"1.0": 75.55,
"5.0": 77.75,
"25.0": 94.75,
"50.0": 101,
"75.0": 289.75,
"95.0": 489.34999999999985,
"99.0": 596.2700000000002
}
}
}
查看网络的延迟是否和地区有关,在聚合的基础上进行百分位计算。根据结果可以得知, 欧洲用户(EU)的网络延迟更高
GET test9_index/doc/_search
{
"size": 0,
"aggs": {
"zone_info": {
"terms": {
"field": "zone.keyword",
"size": 10
},
"aggs": {
"zone_ping": {
"percentiles": {
"field": "latency"
}
}
}
}
}
}
百分位等级
得知具体的数值所占的百分位。以下是查看ping为75, 623的用户所占的百分位。
GET test9_index/doc/_search
{
"size": 0,
"aggs": {
"ping": {
"percentile_ranks": {
"field": "latency",
"values": [
75,
623
]
}
}
}
}
可以看到如下的结果, ping小于等于75的用户占到了4.16%, 有22.5%的用户ping是大于623的
"aggregations": {
"ping": {
"values": {
"75.0": 4.166666666666666,
"623.0": 87.5
}
}
}