目录
1. Elasticsearch之Search API介绍
1. SearchAPI概览
- 实现了对es中存储的数据进行查询分析,endpoint为_search
- 查询主要有两种形式
- URI Search
- 操作简便,方便通过命令行测试
- 仅包含部分查询语法
- Request Body Search
- es提供的完备查询语法Query DSL(Domain Specific Language)
- URI Search
2. URI Search详解与演示
- 通过url query参数来实现搜索,常用参数如下:
- q 指定查询的语句,语法为Query String Syntax
- df q中不指定字段时默认查询的字段,如果不指定,es会查询所有字段
- sort 排序
- timeout 指定超时时间,默认不超时
- form,size 用于分页
Query String Syntax
- term与phrase
- alfred way等效于alfred OR way
- "alfred way"词语查询,要求先后顺序
- 泛查询
- alfred等效于在所有字段去匹配该term
- 指定字段
- name:alfred
- Group分组设定,使用括号指定匹配的规则
- (quick OR brown) AND fox
- status:(active OR pending) title:(full text search)
PUT test_search_index
{
"settings": {
"index":{
"number_of_shards": "1"
}
}
}
POST test_search_index/doc/_bulk
{"index":{"_id":"1"}}
{"username":"alfred way","job":"java engineer","age":18,"birth":"1990-01-02","isMarried":false}
{"index":{"_id":"2"}}
{"username":"alfred","job":"java senior engineer and java specialist","age":28,"birth":"1980-05-07","isMarried":true}
{"index":{"_id":"3"}}
{"username":"lee","job":"java and ruby engineer","age":22,"birth":"1985-08-07","isMarried":false}
{"index":{"_id":"4"}}
{"username":"alfred junior way","job":"ruby engineer","age":23,"birth":"1989-08-07","isMarried":false}
- 接下来我们就来做实际的查询了,首先做个泛查询,查询含义是所有字段包含alfred的文档
GET test_search_index/_search?q=alfred
{
"took" : 29,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : 1.2039728,
"hits" : [
{
"_index" : "test_search_index",
"_type" : "doc",
"_id" : "2",
"_score" : 1.2039728,
"_source" : {
"username" : "alfred",
"job" : "java senior engineer and java specialist",
"age" : 28,
"birth" : "1980-05-07",
"isMarried" : true
}
},
{
"_index" : "test_search_index",
"_type" : "doc",
"_id" : "1",
"_score" : 0.33698124,
"_source" : {
"username" : "alfred way",
"job" : "java engineer",
"age" : 18,
"birth" : "1990-01-02",
"isMarried" : false
}
},
{
"_index" : "test_search_index",
"_type" : "doc",
"_id" : "4",
"_score" : 0.27601978,
"_source" : {
"username" : "alfred junior way",
"job" : "ruby engineer",
"age" : 23,
"birth" : "1989-08-07",
"isMarried" : false
}
}
]
}
}
- 我们看下es到底是怎样执行查询条件的
GET test_search_index/_search?q=alfred
{
"profile":true
}
- 按照字段查询
GET test_search_index/_search?q=username:alfred
- 满足任何一个条件就可以了
GET test_search_index/_search?q=username:alfred way
{
"profile":true
}
- 现在有两种改变的方式
GET test_search_index/_search?q=username:"alfred way"
{
"profile":true
}
GET test_search_index/_search?q=username:(alfred way)
{
"profile":true
}
- 布尔操作符
- AND(&&),OR(||),NOT(!)
- name:(tom NOT lee)
- 注意大写,不能小写
- + - 分别对应must和must_not
- name:(tom +lee -alfred)
- +在url中会被解析为空格,要使用encode后的结果才可以,为%2B
GET test_search_index/_search?q=username:alfred AND way
{
"profile":true
}
GET test_search_index/_search?q=username:(alfred AND way)
{
"profile":true
}
GET test_search_index/_search?q=username:(alfred NOT way)
GET test_search_index/_search?q=username:(alfred +way)
{
"profile":true
}
GET test_search_index/_search?q=username:(alfred %2Bway)
{
"profile":true
}
- 范围查询,支持数值和日期
- 区间写法,闭区间用[],开区间用{}
- 算数符号写法
GET test_search_index/_search?q=username:alfred age:>26
GET test_search_index/_search?q=username:alfred AND age:>20
GET test_search_index/_search?q=birth:(>1980 AND <1990)
- 通配符查询
- ?代表一个字符,*代表0或多个字符
- 通配符匹配执行效率低,且占用较多内存,不建议使用
- 如无特殊需求,不要将?/*放在最前面
GET test_search_index/_search?q=username:alf*
- 正则表达式匹配
GET test_search_index/_search?q=username:/[a]?l.*/
- 模糊匹配fuzzy query
- name:roam~1
- 匹配与roam差1个character的词,比如foam roams等
- 近似度查询proximity search
- “fox quick”~5
- 以term为单位进行差异比较
GET test_search_index/_search?q=username:alfed
GET test_search_index/_search?q=username:alfed~1
GET test_search_index/_search?q=username:alfd~2
GET test_search_index/_search?q=job:"java engineer"
GET test_search_index/_search?q=job:"java engineer"~1
GET test_search_index/_search?q=job:"java engineer"~2
3. Query DSL简介
- 将查询语句通过http request body发送到es,主要包含如下参数
- query符合Query DSL语法的查询的语句
- form,size
- timeout
- sort
- …
- 基于JSON定义的查询语言,主要包含如下两种类型:
- 字段类查询
- 如term,match,range等,只针对某一个字段进行查询
- 复合查询
- 如bool查询等,包含一个或多个字段类查询或者复合查询语句
- 字段类查询
4. 字段类查询简介及match-query
- 字段类查询主要包括以下两类:
- 全文匹配
- 针对text类型的字段进行全文检索,会对查询语句先进行分词处理,如match,match_phrase等query类型
- 单词匹配
- 不会对查询语句做分词处理,直接去匹配字段的倒排索引,如term,terms,range等query类型
- 全文匹配
GET test_search_index/_search
{
"query": {
"match": {
"username": "alfred way"
}
}
}
- 通过operator参数可以控制单词间的匹配关系,可选项为or和and
GET test_search_index/_search
{
"query": {
"match": {
"username": {
"query": "alfred way",
"operator": "and"
}
}
}
}
- 通过minimum_should_match参数可以控制需要匹配的单词数
GET test_search_index/_search
{
"query": {
"match": {
"job": {
"query": "java ruby engineer",
"minimum_should_match": "3"
}
}
}
}
5. 相关性算分
- 相关性算分是指文档与查询语句间的相关度,英文为relevance
- 通过倒排索引可以获取与查询语句相匹配的文档列表,那么如何将最符合用户查询需求的文档放到前列呢?
- 本质是一个排序问题,排序的依据是相关性算分
- 相关性算分的几个重要概念如下:
- Term Frequency(TF):词频,即单词在该文档中出现的次数,词频越高,相关度越高
- Document Frequency(DF):文档频率,即单词出现的文档数
- Inverse Document Frequency(IDF):逆向文档频率,与文档频率相反,简单理解为1/DF,即单词出现的文档越少,相关度越高
- Field-length Norm:文档越短,相关性越高
- ES目前主要有两个相关性算分模型,如下:
- TF/IDF模型
- BM25模型:5.X之后的默认模型
- 可以通过explain参数来查看具体的计算方法,但要注意:
- es算分是按照shard进行的,即shard的分数计算是相互独立的,所以在使用explain的时候注意分片数
- 可以通过设置索引的分片数为1来避免这个问题
GET test_search_index/_search
{
"explain":true,
"query": {
"match": {
"username": "alfred way"
}
}
}
- BM25模型中BM指Best Match,25指迭代了25词才计算方法,是针对TF/IDF的一个优化
6. match-phrase-query
- 对字段做检索,有顺序要求
GET test_search_index/_search
{
"query": {
"match_phrase": {
"job": "java engineer"
}
}
}
GET test_search_index/_search
{
"query": {
"match_phrase": {
"job": "engineer java"
}
}
}
- 通过slop参数可以控制单词间的间隔
GET test_search_index/_search
{
"query": {
"match_phrase": {
"job": {
"query": "java engineer",
"slop": "2"
}
}
}
}
7. query-string-query
- 类似于URI Search中的q参数查询
GET test_search_index/_search
{
"profile":true,
"query":{
"query_string": {
"default_field": "username",
"query": "alfred AND way"
}
}
}
GET test_search_index/_search
{
"profile":true,
"query": {
"query_string": {
"fields": [
"username",
"job"
],
"query": "alfred OR (java AND ruby)"
}
}
}
8. simple-query-string-query
- 类似Query String,但是会忽略错误的查询语法,并且仅支持部分查询语法
GET test_search_index/_search
{
"profile":true,
"query":{
"simple_query_string": {
"query": "alfred +way \"java",
"fields": ["username"]
}
}
}
GET test_search_index/_search
{
"query":{
"query_string": {
"default_field": "username",
"query": "alfred +way \"java"
}
}
}
9. term-terms-query
- 将查询语句作为整个单词进行查询,即不对查询语句做分词处理
GET test_search_index/_search
{
"query":{
"term":{
"username":"alfred"
}
}
}
GET test_search_index/_search
{
"query":{
"term":{
"username":"alfred way"
}
}
}
- terms:一个传入多个查询
G`ET test_search_index/_search
{
"query": {
"terms": {
"username": [
"alfred",
"way"
]
}
}
}
10. range-query
- 范围查询主要针对数值和日期类型
GET test_search_index/_search
{
"query":{
"range": {
"age": {
"gte": 10,
"lte": 30
}
}
}
}
GET test_search_index/_search
{
"query":{
"range": {
"birth": {
"gte": "1980-01-01"
}
}
}
}
- 针对日期提供的一种更友好的计算方式
GET test_search_index/_search
{
"query":{
"range": {
"birth": {
"gte": "now-30y"
}
}
}
}
GET test_search_index/_search
{
"query":{
"range": {
"birth": {
"gte": "2010||-20y"
}
}
}
}
11. 复合查询介绍及ConstantScore
- 复合查询是指包含字段类或复合查询的类型,主要包括以下几类:
- constant_score_query
- bool query
- dis_max query
- function_score_query
- boosting query
Constant Score Query
- 该查询将其内部的查询结果文档得分都设定为1或者boost的值
- 多用于结合bool查询实现自定义得分
GET test_search_index/_search
{
"query":{
"constant_score": {
"filter": {
"match":{
"username":"alfred"
}
}
}
}
}
GET test_search_index/_search
{
"query": {
"bool": {
"should": [
{
"constant_score": {
"filter": {
"match": {
"job": "java"
}
}
}
},
{
"constant_score": {
"filter": {
"match": {
"job": "ruby"
}
}
}
}
]
}
}
}
12. bool-query
- 布尔查询由一个或多个布尔子句组成,主要包含如下四个:
- filter:只过滤符合条件的文档,不计算相关性得分
- must:文档必须符合must中的所有条件,会影响相关性得分
- must_not:文档必须不符合must_not中的所有条件
- should:文档可以符合should中的条件,会影响相关性得分
Filter
- Filter查询只过滤符合条件的文档,不会进行相关性算分
- es针对filter会有智能缓存,因此其执行效率很高
- 做简单匹配查询且不考虑算分时,推荐使用filter替代query等
GET test_search_index/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"username": "alfred"
}
}
]
}
}
}
Must
GET test_search_index/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"username": "alfred"
}
},
{
"match": {
"job": "specialist"
}
}
]
}
}
}
Must_Not
GET test_search_index/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"job": "java"
}
}
],
"must_not": [
{
"match": {
"job": "ruby"
}
}
]
}
}
}
should
- 只包含should时,文档必须满足至少一个条件
- minimum_should_match可以控制满足条件的个数或者百分比
GET test_search_index/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"username": "junior"
}
},
{
"match": {
"job": "ruby"
}
}
]
}
}
}
GET test_search_index/_search
{
"query": {
"bool": {
"should": [
{"term": {"job": "java"}},
{"term": {"job": "ruby"}},
{"term": {"job": "specialist"}}
],
"minimum_should_match": 2
}
}
}
- 同时包含should和must时,文档不必要满足should中的条件,但是如果满足条件,会增加相关性得分
GET test_search_index/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"username": "alfred"
}
}
],
"should": [
{
"term": {
"job": "ruby"
}
}
]
}
}
}
- 当一个查询语句位于Query或者Filter上下文时,es执行的结果会不同
- query:查找与查询语句最匹配的文档,对所有文档进行相关性算分并排序
- filter:查找与查询语句相匹配的文档
13. count and source filtering
- 获取符合条件的文档数,endpoint为_count
GET test_search_index/_count
{
"query":{
"match":{
"username": "alfred"
}
}
}
source filtering
- 过滤返回结果中_source中的字段
GET test_search_index/_search
GET test_search_index/_search?_source=username
GET test_search_index/_search
{
"_source": false
}
GET test_search_index/_search
{
"_source": ["username","age"]
}
GET test_search_index/_search
{
"_source": {
"includes": "*i*",
"excludes": "birth"
}
}
2. Elasticsearch之深入了解Search的运行机制
1. Query Then Fetch
- Search执行的时候实际分两个步骤运作的
- Query阶段
- Fetch阶段
2. 相关性算分
- 相关性算分在shard与shard间是相互独立的,也就意味着同一个Term的IDF等值在不同Shard上是不同的,文档的相关性算分和它所处的shard相关
- 在文档数量不多时,会导致相关性算分严重不准的情况发生
POST test_search_relevance/doc
{
"name":"hello"
}
POST test_search_relevance/doc
{
"name":"hello,world"
}
POST test_search_relevance/doc
{
"name":"hello,world!a beautiful world"
}
GET test_search_relevance/_search
{
"explain": true,
"query": {
"match":{
"name":"hello"
}
}
}
- 解决问题的思路有两个:
- 一是设置分片数为1个,从根本上排除问题,在文档数量不多的时候可以考虑该方案,比如百万到千万级别的文档数量
- 二是使用DFS Query-then-Fetch查询方式
- DFS Query-then-Fetch是在拿到所有文档后再重新完整的计算一次相关性得分,耗费更多的cpu和内存,执行性能也比较低下,一般不建议使用,使用方式如下:
GET test_search_relevance/_search?search_type=dfs_query_then_fetch
{
"query": {
"match":{
"name":"hello"
}
}
}
3. sorting doc values fielddata
- es默认会采用相关性算分排序,用户可以通过设定sorting参数来自行设定排序规则
GET test_search_index/_search
{
"query":{
"match": {
"username": "alfred"
}
},
"sort":{
"birth":"desc"
}
}
GET test_search_index/_search
{
"query":{
"match": {
"username": "alfred"
}
},
"sort": [
{
"birth": "desc"
},
{
"_score": "desc"
},
{
"_doc": "desc"
}
]
}
- 按照字符串排序比较特殊,因为es有text和keyword两种类型
- 针对text类型排序,会产生报错
- 针对keyword类型排序,可以返回预期结果
GET test_search_index/_search
{
"sort":{
"username.keyword":"desc"
}
}
排序
- 排序的过程实质是对字段原始内容排序的过程,这个过程中倒排索引无法发挥作用,需要用到正排索引,也就是通过文档Id和字段可以快速得到字段原始内容
- es对此提供了2种实现方式:
- fielddata默认禁用
- doc values默认启用,除了text类型
Fielddata
- Fielddata默认是关闭的,可以通过如下api开启:
- 此时字符串是按照分词后的term排序,往往结果很难符合预期
- 一般是在对分词做聚合分析的时候开启
PUT test_search_index/_mapping/doc
{
"properties": {
"job":{
"type":"text",
"fielddata": true
}
}
}
Doc Values
- Doc Values默认是启用的,可以在创建索引的时候关闭:
- 如果后面要再开启Doc Values,需要做reindex操作
PUT test_doc_values/_mapping/doc
{
"properties": {
"username": {
"type": "keyword",
"doc_values": false
},
"hobby": {
"type": "keyword"
}
}
}
docvalue_fields
- 可以通过该字段获取fielddata或者doc values中存储的内容
GET test_search_index/_search
{
"docvalue_fields": [
"username",
"username.keyword",
"age"
]
}
4. 分页与遍历-fromsize
- es提供了3种方式来解决分页与遍历的问题:
- from/size
- from指明开始位置
- size指明获取总数
- scoll
- search_after
- from/size
- 深度分页是一个经典的问题:在数据分片存储的情况下如何获取前1000个文档?
- 获取990~1000的文档时,会在每个分片都先获取1000个文档,然后再由Coordinating Node聚合所有分片的结果后再排序获取前1000个文档
- 页数越深,处理文档越多,占用内存越多,耗时越长,尽量避免深度分页,es通过index.max_result_window限定最多到10000条数据
GET test_search_index/_search
{
"from":0,
"size":2
}
GET test_search_index/_search
{
"from":10000,
"size":2
}
5. 分页与遍历-scroll
- 遍历文档集的api,以快照的方式来避免深度分页的问题
- 不能用来做实时搜索,因为数据不是实时的
- 尽量不要使用复杂的sort条件,使用_doc最高效
- 使用稍嫌复杂
- 第一步需要发起1个scroll search
- es在收到该请求后根据查询条件创建文档Id合集的快照
GET test_search_index/_search?scroll=5m
{
"size":1 指明每次scroll返回的文档数
}
- 第二步调用scroll search的api,获取文档集合
- 不断迭代调用直到返回hits.hits数组为空时停止
POST _search/scroll
{
"scroll" : "5m",
"scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAABswWX3FLSTZFOF9URFdqWHlvX3gtYmhtdw=="
}
- 因为是快照,所以新的文档无法被检索
PUT test_search_index/doc/10
{
"username":"doc10"
}
-
过多的scroll调用会占用大量内存,可以用clear api删除过多的scroll快照
DELETE _search/scroll/_all
6. 分页与遍历-search_after
- 避免深度分页的性能问题,提供实时的下一页文档获取功能
- 缺点是不能使用from参数,即不能指定页数
- 只能下一页,不能上一页
- 使用简单
- 第一步为正常的搜索,但要指定sort值,并保证值唯一
GET test_search_index/_search
{
"size":1,
"sort":{
"age":"desc",
"_id":"desc"
}
}
- 第二步为使用上一步最后一个文档的sort值进行查询
GET test_search_index/_search
{
"size":1,
"search_after":[23,"4"],
"sort":{
"age":"desc",
"_id":"desc"
}
}
应用场景
- From/Size:需要实时获取顶部的部分文档,且需要自由翻页
- Scroll:需要全部文档,如导出所有数据的功能
- Search_After:需要全部文档,不需要自由翻页
3. Elasticsearch之聚合分析入门
1. 聚合分析简介
- 搜索引擎用来回答如下问题:
- 请告诉我地址为上海的所有订单?
- 请告诉我最近1天内创建但没有付款的所有订单?
- 聚合分析可以回答如下问题:
- 请告诉我最近1周每天的订单成交量有多少?
- 请告诉我最近1个月每天的平均订单金额是多少?
- 请告诉我最近半年卖的最火的前5个商品是那些?
聚合分析
- 聚合分析,英文为Aggregation,是es除搜索功能外提供的针对es数据做统计分析的功能
- 功能丰富,提供Bucket,Metric,Pipeline等多种分析方式,可以满足大部分的分析需求
- 实时性高,所有的计算结果都是及时返回的,而Hadoop等大数据系统一般都是T+1的
分类
- 为了便于理解,es将聚合分析主要分为如下4类
- Bucket,分桶类型,类似SQL中的GROUP BY语法
- Metric,指标分析类型,如计算最大值,最小值,平均值等等
- Pipeline,管道分析类型,基于上一级的聚合分析结果进行再分析
- Matrix,矩阵分析类型
2. metric聚合分析
POST test_search_index/doc/_bulk
{"index":{"_id":"1"}}
{"username":"alfred way","job":"java engineer","age":18,"birth":"1990-01-02","isMarried":false,"salary":10000}
{"index":{"_id":"2"}}
{"username":"tom","job":"java senior engineer","age":28,"birth":"1980-05-07","isMarried":true,"salary":30000}
{"index":{"_id":"3"}}
{"username":"lee","job":"ruby engineer","age":22,"birth":"1985-08-07","isMarried":false,"salary":15000}
{"index":{"_id":"4"}}
{"username":"Nick","job":"web engineer","age":23,"birth":"1989-08-07","isMarried":false,"salary":8000}
{"index":{"_id":"5"}}
{"username":"Niko","job":"web engineer","age":18,"birth":"1994-08-07","isMarried":false,"salary":5000}
{"index":{"_id":"6"}}
{"username":"Michell","job":"ruby engineer","age":26,"birth":"1987-08-07","isMarried":false,"salary":12000}
- 主要分如下两类:
- 单值分析,只输出一个分析结果
- min,max,avg,sum
- cardinality
- 多值分析,输出多个分析结果
- stats,entended stats
- percentile,percentile rank
- top hits
- 单值分析,只输出一个分析结果
Min
- 返回数值类字段的最小值
GET test_search_index/_search
{
"size":0,
"aggs":{
"min_age":{
"min": {
"field": "age"
}
}
}
}
Max
- 返回数值类字段的最大值
GET test_search_index/_search
{
"size":0,
"aggs":{
"max_age":{
"max": {
"field": "age"
}
}
}
}
Avg
- 返回数值类字段的平均值
GET test_search_index/_search
{
"size":0,
"aggs":{
"avg_age":{
"avg": {
"field": "age"
}
}
}
}
Sum
- 返回数值类字段的总和
GET test_search_index/_search
{
"size":0,
"aggs":{
"sum_age":{
"sum": {
"field": "age"
}
}
}
}
- 一次返回多个聚合结果
GET test_search_index/_search
{
"size": 0,
"aggs": {
"min_age": {
"min": {
"field": "age"
}
},
"max_age": {
"max": {
"field": "age"
}
},
"avg_age": {
"avg": {
"field": "age"
}
},
"sum_age": {
"sum": {
"field": "age"
}
}
}
}
Cardinality
- Cardinality,意为集合的势,或者基数,是指不同数值的个数,类似SQL中的distinct count概念
GET test_search_index/_search
{
"size":0,
"aggs":{
"count_of_job":{
"cardinality": {
"field": "job.keyword"
}
}
}
}
Stats
- 返回一系列数值类型的统计值,包含min,max,avg,sum和count
GET test_search_index/_search
{
"size":0,
"aggs":{
"stats_age":{
"stats": {
"field": "age"
}
}
}
}
Extended Stats
- 对stats的扩展,包含了更多的统计数据,如方差,标准差等
GET test_search_index/_search
{
"size":0,
"aggs":{
"exstats_salary":{
"extended_stats": {
"field": "salary"
}
}
}
}
Percentile
- 百分位数统计
GET test_search_index/_search
{
"size":0,
"aggs":{
"per_salary":{
"percentiles": {
"field": "salary"
}
}
}
}
GET test_search_index/_search
{
"size": 0,
"aggs": {
"per_age": {
"percentiles": {
"field": "salary",
"percents": [
95,
99,
99.9
]
}
}
}
}
Percentile Rank
- 百分位数统计
GET test_search_index/_search
{
"size": 0,
"aggs": {
"per_salary": {
"percentile_ranks": {
"field": "salary",
"values": [
11000,
30000
]
}
}
}
}
Top Hits
- 一般用于分桶后获取桶内最匹配的顶部文档列表,即详情数据
GET test_search_index/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field": "job.keyword",
"size": 10
},
"aggs": {
"top_employee": {
"top_hits": {
"size": 10,
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
}
}
}
}
}
3. bucket聚合分析
- Bucket,意为桶,即按照一定的规则将文档分配到不同的桶中,达到分类分析的目的
- 按照Bucket的分桶策略,常见的Bucket聚合分析如下:
- Terms
- Range
- Date Range
- Histogram
- Date Histogram
Terms
- 该分桶策略最简单,直接按照term来分桶,如果是text类型,则按照分词后的结果分桶
GET test_search_index/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field": "job",
"size": 5
}
}
}
}
Range
- 按照指定数值的范围来设定分桶规则
GET test_search_index/_search
{
"size": 0,
"aggs": {
"salary_range": {
"range": {
"field": "salary",
"ranges": [
{
"key":"<10000",
"to": 10000
},
{
"from": 10000,
"to": 20000
},
{
"key":">20000",
"from": 20000
}
]
}
}
}
}
Date Range
- 通过指定日期的范围来设定分桶规则
GET test_search_index/_search
{
"size": 0,
"aggs": {
"date_range": {
"range": {
"field": "birth",
"format": "yyyy",
"ranges": [
{
"from":"1980",
"to": "1990"
},
{
"from": "1990",
"to": "2000"
},
{
"from": "2000"
}
]
}
}
}
}
Histogram
- 直方图,以固定间隔的策略来分割数据
GET test_search_index/_search
{
"size":0,
"aggs":{
"salary_hist":{
"histogram": {
"field": "salary",
"interval": 5000,
"extended_bounds": {
"min": 0,
"max": 40000
}
}
}
}
}
Date Histogram
- 针对日期的直方图或者柱状图,是时序数据分析中常用的聚合分析类型
GET test_search_index/_search
{
"size":0,
"aggs":{
"by_year":{
"date_histogram": {
"field": "birth",
"interval": "year",
"format":"yyyy"
}
}
}
}
4. bucket和metric聚合分析
- Bucket聚合分析允许通过添加子分析来进一步进行分析,该子分析可以是Bucket也可以是Metric,这也使得es的聚合分析能力变得异常强大
- 分桶后再分桶
GET test_search_index/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field": "job.keyword",
"size": 10
},
"aggs": {
"age_range": {
"range": {
"field": "age",
"ranges": [
{
"to": 20
},
{
"from": 20,
"to": 30
},
{
"from": 30
}
]
}
}
}
}
}
}
- 分桶后进行数据分析
GET test_search_index/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field": "job.keyword",
"size": 10
},
"aggs": {
"salary": {
"stats": {
"field": "salary"
}
}
}
}
}
}
5. pipeline聚合分析
- 针对聚合分析的结果进行再次聚合分析,而且支持链式调用,可以回答如下问题:
- 订单月平均销售额是多少?
- Pipeline的分析结果会输出到原结果中,根据输出位置的不同,分为以下两类:
- Parent结果内嵌到现有的聚合分析结果中
- Derivative
- Moving Average
- Cumulative Sum
- Sibling结果与聚合分析结果同级
- Max/Min/Avg/Sum Bucket
- Stats/Extended Stats Bucket
- Percentiles Bucket
- Parent结果内嵌到现有的聚合分析结果中
Min Bucket
GET test_search_index/_search
{
"size":0,
"aggs":{
"jobs":{
"terms": {
"field": "job.keyword",
"size": 10
},
"aggs":{
"avg_salary":{
"avg": {
"field": "salary"
}
}
}
},
"min_salary_by_job":{
"min_bucket": {
"buckets_path": "jobs>avg_salary"
}
}
}
}
Derivative
- 计算Bucket值的导数
GET test_search_index/_search
{
"size": 0,
"aggs": {
"birth": {
"date_histogram": {
"field": "birth",
"interval": "year",
"min_doc_count": 0
},
"aggs": {
"avg_salary": {
"avg": {
"field": "salary"
}
},
"derivative_avg_salary": {
"derivative": {
"buckets_path": "avg_salary"
}
}
}
}
}
}
Moving Average
- 计算Bucket值的移动平均值
Cumulative Sum
- 计算Bucket值得累计加和
6. 作用范围
- es聚合分析默认作用范围是query的结果集,可以通过如下的方式改变其作用范围:
- filter
- post_filter
- global
filter
- 为某个聚合分析设定过滤条件,从而在不更改整体query语句的情况下修改了作用范围
GET test_search_index/_search
{
"size": 0,
"aggs": {
"jobs_salary_small": {
"filter": {
"range": {
"salary": {
"to": 10000
}
}
},
"aggs": {
"jobs": {
"terms": {
"field": "job.keyword"
}
}
}
},
"jobs": {
"terms": {
"field": "job.keyword"
}
}
}
}
post_filter
- 作用于文本过滤,但在聚合分析后生效
GET test_search_index/_search
{
"aggs": {
"jobs": {
"terms": {
"field": "job.keyword"
}
}
},
"post_filter": {
"match":{
"job.keyword":"java engineer"
}
}
}
global
- 无视query过滤条件,基于全部文档进行分析
GET test_search_index/_search
{
"query": {
"match": {
"job.keyword": "java engineer"
}
},
"aggs": {
"java_avg_salary": {
"avg": {
"field": "salary"
}
},
"all": {
"global": {},
"aggs": {
"avg_salary": {
"avg": {
"field": "salary"
}
}
}
}
}
}
7. 排序
- 可以使用自带的关键数据进行排序,比如:
- _count文档数
- _key按照key值排序
GET test_search_index/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field": "job.keyword",
"size": 10,
"order": [
{
"avg_salary": "desc"
}
]
},
"aggs": {
"avg_salary": {
"avg": {
"field": "salary"
}
}
}
}
}
}
4. Elasticsearch 篇之数据建模
1. 数据建模简介
- 英文为Data Modeling,为创建数据模型的过程
- 数据模型(Data Model)
- 对现实世界进行抽象描述的一种工具和方法
- 通过抽象的实体及实体之间的联系的形式去描述业务规则,从而实现对现实世界的映射
数据建模的过程
- 概念模型
- 确定系统的核心需求和范围边界,设计实体和实体间的关系
- 逻辑模型
- 进一步梳理业务需求,确定每个实体的属性,关系和约束等
- 物理模型
- 结合具体的数据库产品,在满足也读读写性能等需求的前提下确定最终的定义
- MySQL,MongoDB,elasticsearch等
- 第三范式
2. ES数据建模配置相关介绍
- ES是基于Lucene以倒排索引为基础实现的存储体系,不遵循关系型数据库中的范式约定
Mapping字段的相关设置
- enbaled
- true | false
- 仅存储,不搜索或聚合分析
- index
- true | false
- 是否构建倒排索引
- index_options
- docs | freqs | positions | offsets
- 存储倒排索引的哪些信息
- norms
- true | false
- 是否存储归一化相关参数,如果字段仅用于过滤和聚合分析,可关闭
- doc_values
- true | false
- 是否启用doc_values,用于排序和聚合分析
- field_data
- false | true
- 是否为text类型启动fielddata,实现排序和聚合分析
- store
- false | true
- 是否存储该字段值
- coerce
- true | false
- 是否开启自动数据类型转换功能,比如字符串转为数字,浮点转为整型等
- multifields 多字段
- 灵活使用多字段特性来解决多样的业务需求
- dynamic
- true | false | strict
- 控制mapping自动更新
- data_detection
- true | false
- 是否自动识别日期类型
设定流程
- 是何种类型?
- 字符串类型
- 枚举类型
- 数值类型
- 其他类型
- 是否需要检索?
- 完全不需要检索,排序,聚合分析的字段
- enabled设置为false
- 不需要检索的字段
- index设置为false
- 需要检索的字段,可以通过如下配置设定需要的存储力度
- index_options结合需要设定
- norms不需要归一化数据时关闭即可
- 完全不需要检索,排序,聚合分析的字段
- 是否需要排序和聚合分析?
- doc_values设定为false
- fielddata设定为false
- 是否需要另行存储?
- 是否需要专门存储当前字段的数据?
3. ES数据建模实例
- 博客文章blog_index
- 标题title
- 发布日期publish_date
- 作者author
- 摘要abstract
- 内容content
- 网络地址url
PUT blog_index
{
"mappings": {
"doc": {
"_source": {
"enabled": false
},
"properties": {
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 100
}
},
"store": true
},
"publish_date": {
"type": "date",
"store": true
},
"author": {
"type": "keyword",
"ignore_above": 100,
"store": true
},
"abstract": {
"type": "text",
"store": true
},
"content": {
"type": "text",
"store": true
},
"url": {
"type": "keyword",
"doc_values":false,
"norms":false,
"ignore_above": 100,
"store": true
}
}
}
}
}
- 查询
GET blog_index/_search
{
"stored_fields": ["title","publish_date","author","abstract","url"],
"query": {
"match": {
"content": "blog"
}
},
"highlight": {
"fields":{
"content": {}
}
}
}
4. Nested_Object
- ES不擅长处理关系型数据库中的关联关系,比如文章表blog与评论表comment之间通过blog_id关联,在ES中可以通过如下两种手段变相解决
- Nested Object
- Parent/Child
关联关系处理
- 文章Id blog_id
- 评论人 username
- 评论日期 date
- 评论内容 content
DELETE blog_index_nested
PUT blog_index_nested
{
"mappings": {
"doc":{
"properties": {
"title":{
"type": "text",
"fields": {
"keyword":{
"type":"keyword",
"ignore_above": 100
}
}
},
"publish_date":{
"type":"date"
},
"author":{
"type":"keyword",
"ignore_above": 100
},
"abstract":{
"type": "text"
},
"url":{
"enabled":false
},
"comments":{
"type":"nested",
"properties": {
"username":{
"type":"keyword",
"ignore_above":100
},
"date":{
"type":"date"
},
"content":{
"type":"text"
}
}
}
}
}
}
}
PUT blog_index_nested/doc/2
{
"title": "Blog Number One",
"author": "alfred",
"comments": [
{
"username": "lee",
"date": "2017-01-02",
"content": "awesome article!"
},
{
"username": "fax",
"date": "2017-04-02",
"content": "thanks!"
}
]
}
GET blog_index_nested/_search
{
"query": {
"nested": {
"path": "comments",
"query": {
"bool": {
"must": [
{
"match": {
"comments.username": "lee"
}
},
{
"match": {
"comments.content": "thanks"
}
}
]
}
}
}
}
}
5. Parent_Child
- ES还提供了类似关系数据库中join的实现方式,使用join数据类型实现
PUT blog_index_parent_child
{
"mappings": {
"doc": {
"properties": {
"join": {
"type": "join",
"relations": {
"blog": "comment"
}
}
}
}
}
}
PUT blog_index_parent_child/doc/1
{
"title":"blog",
"join":"blog" 指明父类型
}
PUT blog_index_parent_child/doc/2
{
"title":"blog2",
"join":"blog"
}
PUT blog_index_parent_child/doc/comment-1?routing=1 指明routing值,确保父子文档在一个分片上,一般使用父文档Id
{
"comment":"comment world",
"join":{
"name":"comment", 指明子类型
"parent":1 指明父文档Id
}
}
PUT blog_index_parent_child/doc/comment-2?routing=2
{
"comment":"comment hello",
"join":{
"name":"comment",
"parent":2
}
}
- 常见query语法包括如下几种:
- parent_id:返回某父文档的子文档
- has_child:返回包含某子文档的父文档
- has_parent:返回包含某父文档的子文档
GET blog_index_parent_child/_search
{
"query":{
"parent_id":{
"type":"comment",
"id":"2"
}
}
}
GET blog_index_parent_child/_search
{
"query":{
"has_child": {
"type": "comment",
"query": {
"match": {
"comment": "world"
}
}
}
}
}
GET blog_index_parent_child/_search
{
"query":{
"has_parent": {
"parent_type": "blog",
"query": {
"match": {
"title": "blog"
}
}
}
}
}
6. nested_vs_parent_child
nested object
- 优点:文档存储在一起,因此读取性能高
- 缺点:更新父或子文档时需要更新整个文档
- 场景:子文档偶尔更新,查询频繁
parent child
- 优点:父子文档可以独立更新,互不影响
- 缺点:为了维护join的关系,需要占用部分内存,读取性能较差
- 场景:子文档更新频繁
建议尽量选择nested object来解决问题
7. reindex
- 指重建所有数据的过程,一般发生在如下情况:
- mapping设置更改,比如字段类型变化,分词器字典更新等
- index设置变更,比如分片数更改等
- 迁移数据
- ES提供了现成的API用于完成该工作
- _update_by_query在现有索引上重建
- _reindex在其他索引上重建
POST blog_index/_update_by_query?conflicts=proceed
POST _reindex
{
"source": {
"index": "blog_index"
},
"dest": {
"index": "blog_new_index"
}
}
- 数据重建的时间受原索引文档规模的影响,当规模越大时,所需时间越多,此时需要通过设定url参数wait_for_completion为false来异步执行,ES以task来描述此类执行任务
POST blog_index/_update_by_query?conflicts=proceed&wait_for_completion=false
GET _tasks/_qKI6E8_TDWjXyo_x-bhmw:11996
8. 其他建议
数据模型版本管理
- 对Mapping进行版本管理
- 包含在代码或者以专门的文件进行管理,添加好注释,并加入Git等版本管理仓库中,方便回顾
- 为每个增加一个metadata字段,在其中维护一些文档相关的元数据,方便对数据进行管理
防止字段过多
- 字段过多主要有如下的坏处:
- 难于维护,当字段成百上千时,基本很难有人明确知道每个字段的含义
- mapping的信息存储在cluster state里面,过多的字段会导致mapping过大,最终导致更新变慢
- 一般字段过多的原因是由于没有高质量的数据建模导致的,比如dynamic设置为true
- 考虑拆分多个索引来解决问题
最后
大家可以关注我的微信公众号一起学习进步。