一、安装 ElasticSearch
在 dokcer 中安装 elasticsearch
1. 下载 elasticsearch 和 kibana
1 2 docker pull elasticsearch:7.6.2 docker pull kibana:7.6.2
2. 配置
1 2 3 4 mkdir -p /mydata/elasticsearch/configmkdir -p /mydata/elasticsearch/dataecho "http.host: 0.0.0.0" >/mydata/elasticsearch/config/elasticsearch.ymlchmod -R 770 /mydata/elasticsearch/
3. 启动 elasticsearch
1 2 3 4 5 6 7 docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \ -e "discovery.type=single-node" \ -e ES_JAVA_OPTS="-Xms64m -Xmx512m" \ -v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \ -v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \ -v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \ -d elasticsearch:7.6.2
设置开机启动 elasticsearch
1 docker update elasticsearch --restart=always
4. 启动 kibana
1 docker run --name kibana -e ELASTICSEARCH_HOSTS=http://172.17.0.3:9200 -p 5601:5601 -d kibana:7.6.2
设置开机启动 kibana
1 docker update kibana --restart=always
5. 测试
查看 elasticsearch 版本信息:http://172.17.0.3:9200/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { "name" : "0adeb7852e00" , "cluster_name" : "elasticsearch" , "cluster_uuid" : "9gglpP0HTfyOTRAaSe2rIg" , "version" : { "number" : "7.6.2" , "build_flavor" : "default" , "build_type" : "docker" , "build_hash" : "ef48eb35cf30adf4db14086e8aabd07ef6fb113f" , "build_date" : "2020-03-26T06:34:37.794943Z" , "build_snapshot" : false , "lucene_version" : "8.4.0" , "minimum_wire_compatibility_version" : "6.8.0" , "minimum_index_compatibility_version" : "6.0.0-beta1" } , "tagline" : "You Know, for Search" }
访问 Kibana:http://172.17.0.3:5601/app/kibana
二、初步检索
CAT
GET /_cat/nodes: 查看所有节点,* 表示集群中的主节点
1 2 3 10.15.199.49 74 87 1 0.00 0.01 0.05 cdhilmrstw * es-cn-tl32bf5ty00412mma-a50144d1-0001 10.15.199.48 32 90 14 0.01 0.04 0.05 cdhilmrstw - es-cn-tl32bf5ty00412mma-a50144d1-0002 10.15.199.50 57 91 0 0.00 0.02 0.05 cdhilmrstw - es-cn-tl32bf5ty00412mma-a50144d1-0003
GET /_cat/health: 查看 es 健康状况,green 表示健康值正常
1 1588332616 11:30:16 elasticsearch green 1 1 3 3 0 0 0 0 - 100.0%
1 vfpgxbusTC6-W3C2Np31EQ 127.0.0.1 127.0.0.1 0adeb7852e00
GET /_cat/indices?v: 查看所有索引,等价于 mysql 数据库的 show databases
1 2 3 health status index uuid pri rep docs.count docs.deleted store.size pri.store.size green open .kibana_task_manager_1 KWLtjcKRRuaV9so_v15WYg 1 0 2 0 39.8 kb 39.8 kb green open .kibana_1 PqK_LdUYRpWMy4fK0tMSPw 1 0 7 0 31.2 kb 31.2 kb
三、索引 index 和 映射 mapping
创建索引
只创建索引,映射关系 es 自己猜出来(当允许自动创建索引时):
1 2 3 4 5 6 7 PUT /index-name { "settings" : { "number_of_shards" : 3 , "number_of_replicas" : 1 } }
结果:
1 2 3 4 5 6 { "acknowledged" : true , "shards_acknowledged" : true , "index" : "index_name" }
创建索引同时添加 1 个文档(当允许自动创建索引时):
1 2 3 4 PUT index-name/_doc/1 { "name" : "zhangsan" }
查和删
查看索引:GET /index-name/
删除索引:DELETE /index-name
只查看 mapping 信息:GET bank/_mapping
创建映射
Maping 是用来提前定义一个文档(document),以及它所包含的属性(field)是如何存储和索引的。
比如:使用 mapping 来定义:
哪些字符串属性应该被看做全文本属性(full text fields);
哪些属性包含数字,日期或地理位置;
文档中的所有属性是否都能被索引(all 配置);
日期的格式;
自定义映射规则来执行动态添加属性;
创建一个索引并同时定义映射信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 PUT /my_index { "settings" : { "number_of_shards" : 5 , "number_of_replicas" : 1 } , "mappings" : { "properties" : { "name" : { "type" : "text" , "analyzer" : "ik_max_word" , "index" : true , "store" : false } , "author" : { "type" : "keyword" } , "count" : { "type" : "long" } , "on-sale" : { "type" : "date" , "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" } , "descr" : { "type" : "text" , "analyzer" : "ik_max_word" , "search_analyzer" : "ik_smart" } } } , "aliases" : { "default_index" : { } } }
添加字段
添加新的字段映射:
1 2 3 4 5 6 7 8 9 PUT /my_index/_mapping { "properties" : { "employee-id" : { "type" : "keyword" , "index" : false } } }
更新映射
对于已经存在的字段映射,我们不能更新。更新必须创建新的索引,进行数据迁移。
重新绑定别名 alias
1 2 3 4 5 6 7 POST /_aliases { "actions" : [ { "remove" : { "index" : "my_index_v1" , "alias" : "my_index" } } , { "add" : { "index" : "my_index_v2" , "alias" : "my_index" } } ] }
数据迁移 reindex
先创建 new_index 的正确映射。然后使用如下方式进行数据迁移。
1 2 3 4 5 6 7 8 9 POST _reindex { "source" : { "index" : "my_index" } , "dest" : { "index" : "new_index" } }
四、对文档的增删改查操作
新增和修改:
1 2 3 4 5 PUT /index_name/_doc/2 { "name" : "zhangsan2" }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 POST /index_name/_doc { "name" : "zhangsan" , "age" : 18 } POST /index_name/_doc/1 { "name" : "zhangsan" , "age" : 18 } POST /index_name/_update/1 { "doc" : { "name" : "zhangsan123" } }
查询一条:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 GET /index_name/_doc/1 { "_index" : "index_name" , "_type" : "_doc" , "_id" : "1" , "_version" : 3 , "_seq_no" : 6 , "_primary_term" : 1 , "found" : true , "_source" : { "name" : "zhangsan" } }
乐观锁:通过 if_seq_no=6&if_primary_term=1,当序列号匹配的时候,才进行修改,否则不修改。
1 2 3 4 PUT /index_name/_doc/1 ?if_seq_no=6 &if_primary_term=1 { "name" : "zhangsan1" }
删除:
1 2 3 4 5 6 7 8 9 10 11 12 DELETE /index_name/_doc/1 POST /index_name/_delete_by_query { "query" : { "match" : { "name" : "zhangsan2" } } }
五、bulk 批量导入
批量操作,当某一条执行失败时,其他的数据仍然能够接着执行,也就是说彼此之间是独立的。
bulk api 按顺序执行所有的 action(动作)。如果一个单个的动作因任何原因失败,它将继续处理它后面剩余的动作。
当 bulk api 返回时,它将提供每个动作的状态(与发送的顺序相同),所以您可以检查某一个指定的动作是否失败了。
语法格式:
1 2 3 4 5 {action:{metadata}}\n {request body}\n {action:{metadata}}\n {request body}\n
示例,批量新增两条数据:
1 2 3 4 5 POST /index_name/_bulk {"index" :{"_id" :"1" }} {"name" :"John Doe" } {"index" :{"_id" :"2" }} {"name" :"John Doe" }
示例:复杂一些的:对整个索引执行批量操作
1 2 3 4 5 6 7 8 9 10 11 12 POST /_bulk {"delete" :{"_index" :"index_name" ,"_id" :"1" }} {"create" :{"_index" :"index_name" ,"_id" :"2" }} {"title" :"my first blog" } {"index" :{"_index" :"index_name_2" , "_id" :"123" }} {"title" :"my second blog" } {"update" :{"_index" :"index_name_2" ,"_id" :"123" }} {"doc" :{"title" :"my updated blog post" }}}
六、检索 query
测试数据样本:https://gitee.com/xlh_blog/common_content/blob/master/es测试数据.json
批量导入
1 2 3 4 POST bank/account/_bulk ... ... ...
Query DSL 查询语法
Elasticsearch 提供了一个可以执行查询的 Json 风格的 DSL(domain-specific language 领域特定语言)。这个被称为 Query DSL。该查询语言非常全面,并且刚开始的时候感觉有点复杂,真正学好它的方法是从一些基础的示例开始的。
1 2 3 4 5 6 7 8 9 10 11 12 GET /bank/_search?q=*&sort =account_number:asc GET /bank/_search { "query" : {"match_all" : {} }, "sort" : [ {"account_number" : "asc" }, {"balance" :"desc" } ] }
分页查询 from size
使用 from 和 size 可以指定查询
1 2 3 4 5 6 7 8 9 10 GET /bank/_search { "query" : {"match_all" : {} }, "sort" : [ {"account_number" : "asc" }, {"balance" :"desc" } ], "from" : 20, "size" : 10 }
返回部分字段 source
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 GET bank/_search { "query" : { "match_all" : {} }, "from" : 0, "size" : 5, "sort" : [ { "account_number" : { "order" : "desc" } } ], "_source" : ["balance" ,"firstname" ] }
精确检索 term & terms
term 和 terms 的异同:查询机制是一样,都不会将指定的查询关键字进行分词,直接去分词库中匹配,找到相应文档内容。
区别:
term 一次匹配一个值,比如 where name = "张三"
terms 一次可以同时匹配多个值,比如where name = "张三" and name= "李四"
注意 :应避免使用 term 对 text 类型进行检索,应使用 match
全文检索字段用 match, 其他非 text 字段匹配,用 term。
1 2 3 4 5 6 7 8 9 GET bank/_search { "query" : { "term" : { "balance" : 32838 } } }
1 2 3 4 5 6 7 8 9 10 11 12 GET bank/_search { "query" : { "terms" : { "lastname.keyword" : [ "Bates" , "Duke" ] } } }
查询 match
match 查询属于高层查询,他会根据你查询的字段类型不一样,采用不同的查询方式。
如果查询的是日期或者是数值的话,他会将你基于的字符串查询内容转换为日期或者数值对待。
如果查询的内容是一个不能被分词的内容(keyword) , match 查询不会对你指定的查询关键字进行分词。
如果查询的内容是一个可以被分词的内容 (text) , match 会将你指定的查询内容根据一定的方式去分词,去分词库中匹配指定的内容。
match 查询,实际底层就是多个 term 查询,将多个 term 查询的结果给你封装到了一起。
1 2 3 4 5 6 7 8 GET bank/_search { "query" : { "match" : { "account_number" : 20 } } }
1 2 3 4 5 6 7 GET bank/_search { "query" : { "match" : { "address" : "kings place" } }
查询全部 match_all
查询全部内容,不指定任何查询条件。
1 2 3 4 5 6 POST /bank/_search { "query" : { "match_all" : { } } }
布尔 match
基于一个 Field 匹配的内容,采用 and 或者 or 的方式连接
1 2 3 4 5 6 7 8 9 10 11 POST /bank/_search { "query" : { "match" : { "address" : { "query" : "490 Madison" , "operator" : "and" } } } }
多字段匹配(or)multi_match
multi_match 针对多个 field 进行检索,多个 field 对应一个 text
1 2 3 4 5 6 7 8 9 GET bank/_search { "query" : { "multi_match" : { "query" : "mill" , "fields" : [ "address" , "city" ] } } }
短语匹配 match_phrase
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 GET bank/_search { "query" : { "match_phrase" : { "address" : "kings place" } } } GET bank/_search { "query" : { "match" : { "address.keyword" : "kings place" } } }
多条件查询 bool
must、must_not、should
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 GET bank/_search { "query" : { "bool" : { "must" : [ { "match" : { "gender" : "F" } } , { "match" : { "address" : "Madison" } } ] , "must_not" : [ { "match" : { "age" : 38 } } ] , "should" : [ { "match" : { "lastname" : "Lynch" } } ] } } }
查询 filter
query: 根据你的查询条件,去计算文档的匹配度得到一个分数,并且根据分数进行排序,不会做缓存的。
filter: 根据你的查询条件去查询文档,不去计算分数,而且 filter 会对经常被过滤的数据进行缓存。
如果对结果没有得分排序需求,使用 filter 的效率更高
ES系列之利用filter让你的查询效率飞起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 POST /party-huya-gift/_search { "query" : { "bool" : { "filter" : [ { "term" : { "gift_name" : "虎粮" } } , { "term" : { "gift_total" : "100" } } ] } } }
filter 也可以对 query 查询的结果过滤,和 must 和 should 一起使用,不影响得分
filter 对得分没有任何影响,而 must 和 should 会影响得分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 GET bank/_search { "query" : { "bool" : { "must" : [ { "match" : { "gender" : "F" } } , { "match" : { "address" : "Madison" } } ] , "must_not" : [ { "match" : { "age" : 38 } } ] , "should" : [ { "match" : { "lastname" : "Lynch" } } ] , "filter" : { "range" : { "age" : { "gte" : 23 , "lte" : 35 } } } } } }
对上面的例子分析下:
query 参数表示整个语句是处于 query context 中
bool 和 match 语句被用在 query context 中,也就是说它们会计算每个文档的匹配度(_score)
filter 参数则表示这个子查询处于 filter context 中
filter 语句中的 term 和 range 语句用在 filter context 中,它们只起到过滤的作用,并不会计算文档的得分。
直接批量查询 ids
对 id 直接进行批量查询
1 2 3 4 5 6 7 8 POST /bank/_search { "query" : { "ids" : { "values" : [ 1 , 2 , 3 ] } } }
范围查询 range
只针对数值类型,对某一个 Field 进行大于或者小于的范围指定。
1 2 3 4 5 6 7 8 9 10 11 POST /party-huya-gift/_search { "query" : { "range" : { "price_total" : { "gte" : 2000 , "lte" : 3000 } } } }
前缀匹配 prefix
匹配前缀,支持在 keyword 类型的字段中查找
Ps : prefix, fuzzy, wildcard 和 regexp 查询效率相对比较低,要求效率比较高时,避免去使用
1 2 3 4 5 6 7 8 9 10 POST /bank/_search { "query" : { "prefix" : { "address" : { "value" : "16" } } } }
查询(模糊) fuzzy
模糊查询,我们输入字符的大概,ЕS 就可以去根据输入的内容大概去匹配一下结果。
可以有个别的错别字,结果不是稳定的
1 2 3 4 5 6 7 8 9 10 11 12 POST /bank/_search { "query" : { "fuzzy" : { "name" : { "value" : "盒马先生" , "prefix_length" : 3 } } } }
通配查询 wildcard
和 MySQL 中的 like 是一个套路,可以在查询时,在字符串中指定通配符 * 和占位符 ?
1 2 3 4 5 6 7 8 9 10 POST /party-huya-gift/_search { "query" : { "wildcard" : { "send_name" : { "value" : "我是一颗???" } } } }
正则查询 regexp
通过你编写的正则表达式去匹配内容。
1 2 3 4 5 6 7 8 POST /party-huya-gift/_search { "query" : { "regexp" : { "room_id" : "1314[0-9]+" } } }
We no longer recommend using the scroll API for deep pagination. If you need to preserve the index state while paging through more than 10,000 hits, use the search_after parameter with a point in time (PIT).
我们不再建议使用 scroll API 进行深度分页。如果在分页超过 10000 次点击时需要保留索引状态,请使用带有时间点(PIT)的 search_after 参数。
search_after 请看 http://arganzheng.life/deep-pagination-in-elasticsearch.html
ES 对 from + size 有限制的,from + size 之和 不能大于 1W,超过后效率会十分低下。而 Scroll 可以被用来检索大量的结果(甚至所有的结果)。
原理:
from + size 在 ES 查询数据的方式:
第一步先将用户指定的关键进行分词。
第二步将词汇去分词库中进行检索,得到多个文档的 id。
第三步去各个分片中去拉取指定的数据。耗时较长。
第四步将数据根据 score 进行排序。耗时较长。
第五步根据 from 的值,将查询到的数据舍弃一部分。
第六步返回结果。
scroll + size 在 ES 查询数据的方式:
第一步现将用户指定的关键进行分词。
第二步将词汇去分词库中进行检索,得到多个文档的 id。
第三步将文档的 id 存放在一个 Es 的上下文中(也就是内存中)。
第四步根据你指定的 size 的个数去 Es 中检索指定个数的数据,拿完数据的文档 id,会从上下文中移除。
第五步如果需要下一页数据,直接去 Es 的上下文中,找后续内容。
第六步循环第四步和第五步
注意:Scroll 的查询方式,不适合做实时的查询,因为是在内存中循环查询
为了使用 scroll 查询,需要指定 scroll 参数,这个参数会告诉 Elasticsearch 将 search context 保存多久。例如:?scroll=1m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 POST /party-huya-gift/_search?scroll=1 m { "query" : { "match_all" : { } } , "size" : 2 , "sort" : [ { "price_total" : { "order" : "desc" } } ] }
根据 scroll_id,继续查下一个数据
1 2 3 4 5 6 POST /_search/scroll { "scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDnF1ZXJ5VGhlbkZldGNoAxZPSU5JTlpUdFRGcVV2OThOWjhSR1Z3AAAAAAALmugWdTROSEJ6enRUV2E5VTd6Nkg2djdHURZSUFJRSzc4R1FQQ2tpMC1mdnFobVVnAAAAAAANVNwWNEtzM3VSZDBTbHEtM0R4STdVM2YwURZSUFJRSzc4R1FQQ2tpMC1mdnFobVVnAAAAAAANVN0WNEtzM3VSZDBTbHEtM0R4STdVM2YwUQ==" , "scroll" : "1s" }
删除 scrol1 在 ES 上下文中的数据
1 DELETE /_search/scroll/FGluY2x1ZGVfY29udGV4dF91dWlkDnF1ZXJ5VGhlbkZldGNoAxZPSU5JTlpUdFRGcVV2OThOWjhSR1Z3AAAAAAALmugWdTROSEJ6enRUV2E5VTd6Nkg2djdHURZSUFJRSzc4R1FQQ2tpMC1mdnFobVVnAAAAAAANVNwWNEtzM3VSZDBTbHEtM0R4STdVM2YwURZSUFJRSzc4R1FQQ2tpMC1mdnFobVVnAAAAAAANVN0WNEtzM3VSZDBTbHEtM0R4STdVM2YwUQ==
delete-by-query 根据term, match等查询方式去删除大量的文档 Ps:如果你需要删除的内容,是index下的大部分数据,推荐创建一个全新的index,将保留的文档内容,添加 到全新的索引
匹配删除 delete-by-query
根据 term、match、range 等查询方式去删除大量的文档。
Ps: 如果你需要删除的内容,是 index 下的大部分数据,推荐创建一个全新的 index,将保留的文档内容,添加到全新的索引。
1 2 3 4 5 6 7 8 9 10 11 POST /party-huya-gift/_delete_by_query { "query" : { "range" : { "gift_total" : { "gte" : 202 , "lte" : 203 } } } }
调整得分的查询 boosting
boosting 查询可以帮助我们去影响查询后的 score。
positive: 只有匹配上 positive 的查询的内容,才会被放到返回的结果集中。
negative: 如果匹配上 positive 并且也匹配上了 negative,就可以降低这样文档的 score。
negative_boost: 指定系数,必须小于 1.0(经过测试,也可以比 1 高)
关于查询时,分数是如何计算的,有几个参考值:
搜索的关键字在文档中出现的频次越高,分数就越高
指定的文档内容越短,分数就越高
我们在搜索时,指定的关键字也会被分词,这个被分词的内容,被分词库匹配的个数越多,分数越高
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 GET /party-huya-gift/_search { "query" : { "boosting" : { "positive" : { "match" : { "send_name" : "阿纠纠" } } , "negative" : { "match" : { "gift_name" : "虎粮" } } , "negative_boost" : 0.2 } } }
高亮查询
高亮查询就是你用户输入的关键字,以一定的特殊样式展示给用户,让用户知道为什么这个结果被检索出来。
高亮展示的数据,本身就是文档中的一个 Field,单独将 Field 以 highlight 的形式返回给你。
ES 提供了一个 highlight 属性,和 query 同级别的。
fragment_size: 指定高亮数据展示多少个字符回来。
pre_tags: 指定前缀标签,举个栗子 <font color="red">
post_tags: 指定后缀标签,举个栗子 </font>
fields: 指定哪个 Field 以高亮形式返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 POST /party-huya-danmaku/_search { "query" : { "match" : { "content" : "这也" } } , "highlight" : { "fields" : { "content" : { } } , "fragment_size" : 3 , "pre_tags" : "<font color='red'>" , "post_tags" : "</font>" } }
七、聚合 Aggregation
聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于 SQL Group by 和 SQL 聚合函数。在 elasticsearch 中,执行搜索返回 hits(命中结果),并且同时返回聚合结果,把以响应中的所有 hits(命中结果)分隔开的能力。这是非常强大且有效的,你可以执行查询和多个聚合,并且在一次使用中得到各自的(任何一个的)返回结果,使用一次简洁和简化的 API 来避免网络往返。
示例:搜索 address 中包含 mill 的所有人的年龄分布以及平均年龄,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 GET bank/_search { "query" : { "match" : { "address" : "mill" } } , "aggs" : { "ageAgg" : { "terms" : { "field" : "age" , "size" : 10 } } , "ageAvg" : { "avg" : { "field" : "age" } } , "balanceAvg" : { "avg" : { "field" : "balance" } } } , "size" : 0 }
查询结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 { "took" : 7 , "timed_out" : false , "_shards" : { "total" : 1 , "successful" : 1 , "skipped" : 0 , "failed" : 0 } , "hits" : { "total" : { "value" : 4 , "relation" : "eq" } , "max_score" : 5.4032025 , "hits" : [ ] } , "aggregations" : { "ageAgg" : { "doc_count_error_upper_bound" : 0 , "sum_other_doc_count" : 0 , "buckets" : [ { "key" : 38 , "doc_count" : 2 } , { "key" : 28 , "doc_count" : 1 } , { "key" : 32 , "doc_count" : 1 } ] } , "ageAvg" : { "value" : 34.0 } , "balanceAvg" : { "value" : 25208.0 } } }
再复杂一点,子聚合示例 :先按照年龄聚合,并且算出每个年龄段中的平均薪资
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 GET bank/_search { "query" : { "match_all" : { } } , "aggs" : { "ageAgg" : { "terms" : { "field" : "age" , "size" : 3 } , "aggs" : { "balanceAvg" : { "avg" : { "field" : "balance" } } } } } , "size" : 0 }
查看结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 { "took" : 3 , "timed_out" : false , "_shards" : { "total" : 1 , "successful" : 1 , "skipped" : 0 , "failed" : 0 } , "hits" : { "total" : { "value" : 1000 , "relation" : "eq" } , "max_score" : null , "hits" : [ ] } , "aggregations" : { "ageAgg" : { "doc_count_error_upper_bound" : 0 , "sum_other_doc_count" : 820 , "buckets" : [ { "key" : 31 , "doc_count" : 61 , "balanceAvg" : { "value" : 28312.918032786885 } } , { "key" : 39 , "doc_count" : 60 , "balanceAvg" : { "value" : 25269.583333333332 } } , { "key" : 26 , "doc_count" : 59 , "balanceAvg" : { "value" : 23194.813559322032 } } ] } } }
子聚合内多个条件示例 :查出所有年龄分布,并且这些年龄段中 M 的平均薪资和 F 的平均薪资,以及这个年龄段的总体平均薪资
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 GET bank/_search { "query" : { "match_all" : { } } , "aggs" : { "ageAgg" : { "terms" : { "field" : "age" , "size" : 2 } , "aggs" : { "genderAgg" : { "terms" : { "field" : "gender.keyword" , "size" : 2 } , "aggs" : { "gander_balanceAvg" : { "avg" : { "field" : "balance" } } } } , "balanceAvg" : { "avg" : { "field" : "balance" } } } } } , "size" : 0 }
查看结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 { "took" : 3 , "timed_out" : false , "_shards" : { "total" : 1 , "successful" : 1 , "skipped" : 0 , "failed" : 0 } , "hits" : { "total" : { "value" : 1000 , "relation" : "eq" } , "max_score" : null , "hits" : [ ] } , "aggregations" : { "ageAgg" : { "doc_count_error_upper_bound" : 0 , "sum_other_doc_count" : 879 , "buckets" : [ { "key" : 31 , "doc_count" : 61 , "genderAgg" : { "doc_count_error_upper_bound" : 0 , "sum_other_doc_count" : 0 , "buckets" : [ { "key" : "M" , "doc_count" : 35 , "gander_balanceAvg" : { "value" : 29565.628571428573 } } , { "key" : "F" , "doc_count" : 26 , "gander_balanceAvg" : { "value" : 26626.576923076922 } } ] } , "balanceAvg" : { "value" : 28312.918032786885 } } , { "key" : 39 , "doc_count" : 60 , "genderAgg" : { "doc_count_error_upper_bound" : 0 , "sum_other_doc_count" : 0 , "buckets" : [ { "key" : "F" , "doc_count" : 38 , "gander_balanceAvg" : { "value" : 26348.684210526317 } } , { "key" : "M" , "doc_count" : 22 , "gander_balanceAvg" : { "value" : 23405.68181818182 } } ] } , "balanceAvg" : { "value" : 25269.583333333332 } } ] } } }
去重计数查询
去重计数,即 Cardinality, 第一步先将返回的文档中的一个指定的 field 进行去重,统计一共有多少条
1 2 3 4 5 6 7 8 9 10 POST /party-huya-gift/_search { "aggs" : { "gift_name_count" : { "cardinality" : { "field" : "gift_name" } } } }
结果有 29 种:
1 2 3 4 5 "aggregations" : { "gift_name_count" : { "value" : 29 } }
范围统计 range
统计一定范围内出现的文档个数,比如,针对某一个 Field 的值在 0~100,100~200,200~300 之间文档出现的个数分别是多少。
范围统计有可以针对普通的数值(range),针对时间类型(date_range),针对 ip 类型(ip-range)都可以做相应的统计。
1. 数值统计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 POST /party-huya-gift/_search { "aggs" : { "price_agg" : { "range" : { "field" : "price_total" , "ranges" : [ { "from" : 0 , "to" : 500 } , { "from" : 500 , "to" : 1000 } , { "from" : 1000 , "to" : 10000 } ] } } } }
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 "aggregations" : { "price_agg" : { "buckets" : [ { "key" : "0.0-500.0" , "from" : 0.0 , "to" : 500.0 , "doc_count" : 936388 } , { "key" : "500.0-1000.0" , "from" : 500.0 , "to" : 1000.0 , "doc_count" : 5663 } , { "key" : "1000.0-10000.0" , "from" : 1000.0 , "to" : 10000.0 , "doc_count" : 410 } ] } }
时间范围统计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 POST /party-huya-gift/_search { "aggs" : { "create_time_agg" : { "date_range" : { "field" : "create_time" , "ranges" : [ { "from" : "now-2d" , "to" : "now-1d" } , { "from" : "now-1d" , "to" : "now" } ] } } } , "size" : 0 }
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 "aggregations" : { "create_time_agg" : { "buckets" : [ { "key" : "1633068433.307-1633154833.307" , "from" : 1.633068433307E12 , "from_as_string" : "1633068433.307" , "to" : 1.633154833307E12 , "to_as_string" : "1633154833.307" , "doc_count" : 168548 } , { "key" : "1633154833.307-1633241233.307" , "from" : 1.633154833307E12 , "from_as_string" : "1633154833.307" , "to" : 1.633241233307E12 , "to_as_string" : "1633241233.307" , "doc_count" : 192609 } ] } }
统计聚合查询
使用 extended_stats,他可以帮你查询指定 Field 的最大值,最小值,平均值,平方和。。。
1 2 3 4 5 6 7 8 9 10 11 POST /party-huya-gift/_search { "aggs" : { "gift_count_agg" : { "extended_stats" : { "field" : "gift_total" } } } , "size" : 0 }
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 "aggregations" : { "gift_count_agg" : { "count" : 949932 , "min" : 1.0 , "max" : 2500.0 , "avg" : 8.680017095960553 , "sum" : 8245426.0 , "sum_of_squares" : 4.59571672E8 , "variance" : 408.4515874362822 , "variance_population" : 408.4515874362822 , "variance_sampling" : 408.4520174165517 , "std_deviation" : 20.210185240028903 , "std_deviation_population" : 20.210185240028903 , "std_deviation_sampling" : 20.21019587773834 , "std_deviation_bounds" : { "upper" : 49.100387576018356 , "lower" : -31.740353384097254 , "upper_population" : 49.100387576018356 , "lower_population" : -31.740353384097254 , "upper_sampling" : 49.10040885143723 , "lower_sampling" : -31.740374659516128 } } }
八、分词
ik 分词
默认的分词器对中文支持不好
1 2 3 4 5 POST _analyze { "analyzer" : "standard" , "text" : "这是一句话" }
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 { "tokens" : [ { "token" : "这" , "start_offset" : 0 , "end_offset" : 1 , "type" : "<IDEOGRAPHIC>" , "position" : 0 } , { "token" : "是" , "start_offset" : 1 , "end_offset" : 2 , "type" : "<IDEOGRAPHIC>" , "position" : 1 } , { "token" : "一" , "start_offset" : 2 , "end_offset" : 3 , "type" : "<IDEOGRAPHIC>" , "position" : 2 } , { "token" : "句" , "start_offset" : 3 , "end_offset" : 4 , "type" : "<IDEOGRAPHIC>" , "position" : 3 } , { "token" : "话" , "start_offset" : 4 , "end_offset" : 5 , "type" : "<IDEOGRAPHIC>" , "position" : 4 } ] }
使用 ik 分词器,对中文友好:
1 2 3 4 5 POST _analyze { "analyzer" : "ik_smart" , "text" : "这是一句话" }
结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 { "tokens" : [ { "token" : "这是" , "start_offset" : 0 , "end_offset" : 2 , "type" : "CN_WORD" , "position" : 0 } , { "token" : "一句话" , "start_offset" : 2 , "end_offset" : 5 , "type" : "CN_WORD" , "position" : 1 } ] }
ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民大会堂,人民大会,大会堂”,会穷尽各种可能的组合;
ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国,人民大会堂”。
ik 分词器的使用
存储时,使用 ik_max_word,尽量多的和其他组成倒排索引。而搜索时,使用 ik_smart,避免过度分词后搜索到太多无用数据
1 2 3 4 5 6 7 8 9 10 11 12 PUT /my_index { "mappings" : { "properties" : { "text" : { "type" : "text" , "analyzer" : "ik_max_word" , "search_analyzer" : "ik_smart" } } } }
自定义词库
修改 /usr/share/elasticsearch/plugins/ik/config 中的 IKAnalyzer.cfg.xml /usr/share/elasticsearch/plugins/ik/config
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd" > <properties > <comment > IK Analyzer 扩展配置 </comment > <entry key ="ext_dict" > </entry > <entry key ="ext_stopwords" > </entry > <entry key ="remote_ext_dict" > http://#/es/fenci.txt</entry > </properties >
修改完成后,需要重启 elasticsearch 容器,否则修改不生效。
更新完成后,es 只会对于新增的数据用更新分词。历史数据是不会重新分词的。如果想要历史数据重新分词,需要执行:
1 POST my_index/_update_by_query?conflicts=proceed