极力推荐: 官网地址: https://www.elastic.co/guide/en/elasticsearch/reference/6.0

肺腑之言,学ES先学原生的语法,SpringData封装的是太好用了,但是没玩过原生的语法,可能不知道Spring提供的API在干什么

在ES中进行搜索是近实时的,意思是数据从写入ES到可以被searchable仅仅需要1秒钟,因此说基于ES执行的搜索和分析可以达到秒级

集群 , 集群是一个或多个node的集合,他们一起保存你存放进去的数据,用户可以在所有的node之间进行检索,一般的每个集群都会有一个唯一的名称标识,默认的名称标识为 elasticsearch , 这个名字很重要,因为node想加入cluster时,需要这个名称信息

确保别在不同的环境中使用相同的集群名称,进而避免node加错集群的情况,一颗考虑下面的集群命名风格logging-stagelogging-devlogging-pro

单台server就是一个node,他和 cluster一样,也存在一个默认的名称,但是它的名称是通过UUID生成的随机串,当然用户也可以定制不同的名称,但是这个名字最好别重复,这个名称对于管理来说很在乎要,因为需要确定,当前网络中的哪台服务器,对应这个集群中的哪个节点

node存在一个默认的设置,默认的,当每一个node在启动时都会自动的去加入一个叫elasticsearch的节点,这就意味着,如果用户在网络中启动了多个node,他们会彼此发现,然后组成集群

在单个的cluster中,你可以拥有任意多的node,假如说你的网络上没有有其他正在运行的节点,然后你启动一个新的节点,这个新的节点自己会组件一个集群

Index是一类拥有相似属性的document的集合,比如你可以为消费者的数据创建一个index,为产品创建一个index,为订单创建一个index

index名称(必须是小写的字符), 当需要对index中的文档执行索引,搜索,更新,删除,等操作时,都需要用到这个index

一个集群中理论上你可以创建任意数量的index

Type可以作为index中的逻辑类别,为了更细的划分,比如用户数据type,评论数据type,博客数据type

在设计时,尽最大努力让拥有更多相同field的document会分为同一个type下

document就是ES中存储的一条数据,就像mysql中的一行记录一样,可以是一条用户的记录,一个商品的记录等等

为什么说这是不严谨的小结呢? 就是说下面三个对应关系只能说的从表面上看起来比较相似,但是ES中的type其实是一个逻辑上的划分,数据在存储是时候依然是混在一起存储的(往下看下文中有写,),然而mysql中的不同表的两个列是绝对没有关系的

Elasticsearch 关系型数据库
Document
type
index 数据库

如果让一个Index自己存储1TB的数据,响应的速度就会下降为了解决这个问题,ES提供了一种将用户的Index进行subdivide的骚操作,就是将index分片, 每一片都叫一个Shards,实现了将整体庞大的数据分布在不同的服务器上存储

shard分成replica shard和primary shard,顾名思义一个是主shard一个是备份shard, 负责容错以及承担部分读请求

shard可以理解成是ES中最小的工作单元,所有shard中的数据之和,才是整个ES中存储的数据, 可以把shard理解成是一个luncene的实现,拥有完成的创建索引,处理请求的能力

下图是两个node,6个shard的组成的集群的划分情况

两个节点的分布情况

大家可以看到,这时无论java应用程序访问的是node1还是node2,其实都能获取到数据

新创建的节点会存在5个primary shard,后续不然能再改动primary shard的值,如果每一个primary shard都对应一个replica shard,按理说单台es启动就会存在10个分片,但是现实是,同一个节点的replica shard和primary shard不能存在于一个server中,因此单台es默认启动后的分片数量还是5个

首先明确一点: 一旦index创建完成了,primary shard的数量就不可能再发生变化

因此横向拓展就得添加replica的数量, 因为replica shard的数量后续是可以改动的, 也就是说,如果后续我们将他的数量改成了2, 就意味着让每个primary shard都拥有了两个replica shard, 计算一下: 5+5*2=15 集群就会拓展成15个节点

如果想让每一个shard都有最多的系统的资源,就增加服务器的数量,让每一个shard独占一个服务器,

shard和replica入门图

上图中存在上下两个node,每一个node,每个node中都有一个 自己的primary shard其他节点的replica shard,为什么是强调自己和其他呢? 因为ES中规定,同一个节点的replica shard和primary shard不能存在于一个server中,但是不同节点的primary shard可以存在于同一个server上

当primary shard宕机时,它对应的replicas在其他的server不会受到影响,可以继续响应用户的读请求,通过这种分片的机制,并且分片的地位相当,假设单个shard可以处理2000/s的请求,通过横向拓展可以在此基础上成倍提升系统的吞吐量,天生分布式,高可用

此外:每一个document肯定存在于一个primary shard和这个primary shard 对应的replica shard中, 绝对不会出现同一个document同时存在于多个primary shard中的情况

  1. GET /_cat/health?v

执行结果如下:

  1. epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
  2. 1572595632 16:07:12 elasticsearch yellow 1 1 5 5 0 0 5 0 - 50.0%

解读上面的信息,默认的集群名是elasticsearch,当前集群的status是yellow,后续列出来的是集群的分片信息,最后一个active_shards_percent表示当前集群中仅有一半shard是可用的

存在三种状态分别是red green yellow

  • green : 表示当前集群所有的节点全部可用
  • yellow: 表示所有的数据是可以访问的,但是并不是所有的replica shard都是可以使用的(我现在是默认启动一个node,而ES又不允许同一个node的primary shard和replica shard共存,因此我当前的node中仅仅存在5个primary shard,为status为黄色)
  • red: 集群宕机,数据不可访问
  1. GET /_cat/indices?v

结果:

  1. health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
  2. yellow open ai_answer_question cl_oJNRPRV-bdBBBLLL05g 5 1 203459 0 172.3mb 172.3mb

显示,状态yellow表示存在replica shard不可用, 存在5个primary shard,并且每一个primary shard都有一个replica shard , 一共20多万条文档,未删除过文档,文档占用的空间情况为172.3兆

  1. PUT /customer?pretty

ES 使用的RestfulAPI,新增使用put,这是个很亲民的举动

如果是ES中没有过下面的数据则添加进去,如果存在了id=1的元素就修改(全量替换)

  • 格式:PUT /index/type/id

全量替换时,原来的document是没有被删除的,而是被标记为deleted,被标记成的deleted是不会被检索出来的,当ES中数据越来越多时,才会删除它

  1. PUT /customer/_doc/1?pretty
  2. {
  3. "name": "John Doe"
  4. }

响应:

  1. {
  2. "_index": "customer",
  3. "_type": "_doc",
  4. "_id": "1",
  5. "_version": 1,
  6. "result": "created",
  7. "_shards": {
  8. "total": 2,
  9. "successful": 1,
  10. "failed": 0
  11. },
  12. "_seq_no": 0,
  13. "_primary_term": 1
  14. }

强制创建,加添_create或者?op_type=create

  1. PUT /customer/_doc/1?op_type=create
  2. PUT /customer/_doc/1/_create
  • 局部更新(Partial Update)

不指定id则新增document

  1. POST /customer/_doc?pretty
  2. {
  3. "name": "Jane Doe"
  4. }

指定id则进行doc的局部更新操作

  1. POST /customer/_doc/1?pretty
  2. {
  3. "name": "Jane Doe"
  4. }

并且POST相对于上面的PUT而言,不论是否存在相同内容的doc,只要不指定id,都会使用一个随机的串当成id,完成doc的插入

Partial Update先获取document,再将传递过来的field更新进document的json中,将老的doc标记为deleted,再将创建document,相对于全量替换中间会省去两次网络请求

格式: GET /index/type/

  1. GET /customer/_doc/1?pretty

响应:

  1. {
  2. "_index": "customer",
  3. "_type": "_doc",
  4. "_id": "1",
  5. "_version": 1,
  6. "found": true,
  7. "_source": {
  8. "name": "John Doe"
  9. }
  10. }

删除一条document

大部分情况下,原来的document不会被立即删除,而是被标记为deleted,被标记成的deleted是不会被检索出来的,当ES中数据越来越多时,才会删除它

  1. DELETE /customer/_doc/1

响应:

  1. {
  2. "_index": "customer",
  3. "_type": "_doc",
  4. "_id": "1",
  5. "_version": 2,
  6. "result": "deleted",
  7. "_shards": {
  8. "total": 2,
  9. "successful": 1,
  10. "failed": 0
  11. },
  12. "_seq_no": 1,
  13. "_primary_term": 1
  14. }

删除index

  1. DELETE /index1
  2. DELETE /index1,index2
  3. DELETE /index*
  4. DELETE /_all
  5. 可以在elasticsearch.yml中将下面这个设置置为ture,表示禁止使用 DELETE /_all
  6. action.destructive_required_name:true

响应

  1. {
  2. "acknowledged": true
  3. }

上面说了POST关键字,可以实现不指定id就完成document的插入, POST + _update关键字可以实现更新的操作

  1. POST /customer/_doc/1/_update?pretty
  2. {
  3. "doc": { "name": "changwu" }
  4. }

**POST+_update进行更新的动作依然需要执行id, 但是它相对于PUT来说,当使用POST进行更新时,id不存在的话会报错,而PUT则会认为这是在新增**

此外: 针对这种更新操作,ES会先删除原来的doc,然后插入这个新的doc

  • 检索所有索引下面的所有数据
  1. /_search
  • 搜索指定索引下的所有数据
  1. /index/_search
  • 更多模式
  1. /index1/index2/_search
  2. /*1/*2/_search
  3. /index1/index2/type1/type2/_search
  4. /_all/type1/type2/_search
  • 在docs中指定_index,_type,_id
  1. GET /_mget
  2. {
  3. "docs" : [
  4. {
  5. "_index" : "test",
  6. "_type" : "_doc",
  7. "_id" : "1"
  8. },
  9. {
  10. "_index" : "test",
  11. "_type" : "_doc",
  12. "_id" : "2"
  13. }
  14. ]
  15. }
  • 在URL中指定index
  1. GET /test/_mget
  2. {
  3. "docs" : [
  4. {
  5. "_type" : "_doc",
  6. "_id" : "1"
  7. },
  8. {
  9. "_type" : "_doc",
  10. "_id" : "2"
  11. }
  12. ]
  13. }
  • 在URL中指定 index和type
  1. GET /test/type/_mget
  2. {
  3. "docs" : [
  4. {
  5. "_id" : "1"
  6. },
  7. {
  8. "_id" : "2"
  9. }
  • 在URL中指定index和type,并使用ids指定id范围
  1. GET /test/type/_mget
  2. {
  3. "ids" : ["1", "2"]
  4. }
  • 为不同的doc指定不同的过滤规则
  1. GET /_mget
  2. {
  3. "docs" : [
  4. {
  5. "_index" : "test",
  6. "_type" : "_doc",
  7. "_id" : "1",
  8. "_source" : false
  9. },
  10. {
  11. "_index" : "test",
  12. "_type" : "_doc",
  13. "_id" : "2",
  14. "_source" : ["field3", "field4"]
  15. },
  16. {
  17. "_index" : "test",
  18. "_type" : "_doc",
  19. "_id" : "3",
  20. "_source" : {
  21. "include": ["user"],
  22. "exclude": ["user.location"]
  23. }
  24. }
  25. ]
  26. }
  1. {"action":{"metadata"}}\n
  2. {"data"}\n

存在哪些类型的操作可以执行呢?

  • delete: 删除文档

  • create: _create 强制创建

  • index: 表示普通的put操作,可以是创建文档也可以是全量替换文档

  • update: 局部替换

上面的语法中并不是人们习惯阅读的json格式,但是这种单行形式的json更具备高效的优势

ES如何处理普通的json如下:

  • 将json数组转换为JSONArray对象,这就意味着内存中会出现一份一模一样的拷贝,一份是json文本,一份是JSONArray对象

但是如果上面的单行JSON,ES直接进行切割使用,不会在内存中整一个数据拷贝出来

delete比较好看仅仅需要一行json就ok

  1. { "delete" : { "_index" : "test", "_type" : "_doc", "_id" : "2" } }

两行json,第一行指明我们要创建的json的index,type以及id

第二行指明我们要创建的doc的数据

  1. { "create" : { "_index" : "test", "_type" : "_doc", "_id" : "3" } }
  2. { "field1" : "value3" }

相当于是PUT,可以实现新建或者是全量替换,同样是两行json

第一行表示将要新建或者是全量替换的json的index type 以及 id

第二行是具体的数据

  1. { "index" : { "_index" : "test", "_type" : "_doc", "_id" : "1" } }
  2. { "field1" : "value1" }

表示 parcial update,局部替换

他可以指定一个retry_on_conflict的特性,表示可以重试3次

  1. POST _bulk
  2. { "update" : {"_id" : "1", "_type" : "_doc", "_index" : "index1", "retry_on_conflict" : 3} }
  3. { "doc" : {"field" : "value"} }
  4. { "update" : { "_id" : "0", "_type" : "_doc", "_index" : "index1", "retry_on_conflict" : 3} }
  5. { "script" : { "source": "ctx._source.counter += params.param1", "lang" : "painless", "params" : {"param1" : 1}}, "upsert" : {"counter" : 1}}
  6. { "update" : {"_id" : "2", "_type" : "_doc", "_index" : "index1", "retry_on_conflict" : 3} }
  7. { "doc" : {"field" : "value"}, "doc_as_upsert" : true }
  8. { "update" : {"_id" : "3", "_type" : "_doc", "_index" : "index1", "_source" : true} }
  9. { "doc" : {"field" : "value"} }
  10. { "update" : {"_id" : "4", "_type" : "_doc", "_index" : "index1"} }
  11. { "doc" : {"field" : "value"}, "_source": true}

滚动查询技术和分页技术在使用场景方面还是存在出入的,这里的滚动查询技术同样适用于系统在海量数据中进行检索,比如过一次性存在10条数据被命中可以被检索出来,那么性能一定会很差,这时可以选择使用滚动查询技术,一批一批的查询,直到所有的数据被查询完成他可以先搜索一批数据再搜索一批数据

采用基于_doc的排序方式会获得较高的性能

每次发送scroll请求,我们还需要指定一个scroll参数,指定一个时间窗口,每次搜索只要在这个时间窗口内完成就ok

示例

  1. GET /index/type/_search?scroll=1m
  2. {
  3. "query":{
  4. "match_all":{}
  5. },
  6. "sort":["_doc"],
  7. "size":3
  8. }

响应

  1. {
  2. "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAACNFlJmWHZLTkFhU0plbzlHX01LU2VzUXcAAAAAAAAAkRZSZlh2S05BYVNKZW85R19NS1Nlc1F3AAAAAAAAAI8WUmZYdktOQWFTSmVvOUdfTUtTZXNRdwAAAAAAAACQFlJmWHZLTkFhU0plbzlHX01LU2VzUXcAAAAAAAAAjhZSZlh2S05BYVNKZW85R19NS1Nlc1F3",
  3. "took": 9,
  4. "timed_out": false,
  5. "_shards": {
  6. "total": 5,
  7. "successful": 5,
  8. "skipped": 0,
  9. "failed": 0
  10. },
  11. "hits": {
  12. "total": 2,
  13. "max_score": null,
  14. "hits": [
  15. {
  16. "_index": "my_index",
  17. "_type": "_doc",
  18. "_id": "2",
  19. "_score": null,
  20. "_source": {
  21. "title": "This is another document",
  22. "body": "This document has a body"
  23. },
  24. "sort": [
  25. 0
  26. ]
  27. },
  28. {
  29. "_index": "my_index",
  30. "_type": "_doc",
  31. "_id": "1",
  32. "_score": null,
  33. "_source": {
  34. "title": "This is a document"
  35. },
  36. "sort": [
  37. 0
  38. ]
  39. }
  40. ]
  41. }
  42. }

再次滚动查询

  1. GET /_search/scroll
  2. {
  3. "scroll":"1m",
  4. "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAACNFlJmWHZLTkFhU0plbzlHX01LU2VzUXcAAAAAAAAAkRZSZlh2S05BYVNKZW85R19NS1Nlc1F3AAAAAAAAAI8WUmZYdktOQWFTSmVvOUdfTUtTZXNRdwAAAAAAAACQFlJmWHZLTkFhU0plbzlHX01LU2VzUXcAAAAAAAAAjhZSZlh2S05BYVNKZW85R19NS1Nlc1F3"
  5. }

_searchAPI + 将请求写在URI中

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

同样使用的是RestfulAPI, q=* ,表示匹配index=bank的下的所有doc,sort=account_number:asc表示告诉ES,结果按照account_number字段升序排序,pretty是告诉ES,返回一个漂亮的json格式的数据

上面的q还可以写成下面这样

  1. GET /bank/_search?q=自定义field:期望的值
  2. GET /bank/_search?q=+自定义field:期望的值
  3. GET /bank/_search?q=-自定义field:期望的值

响应:

  1. {
  2. "took" : 63, // 耗费的时间
  3. "timed_out" : false, // 是否超时了
  4. "_shards" : { // 分片信息
  5. "total" : 5, // 总共5个分片,它的搜索请求会被打到5个分片上去,并且都成功了
  6. "successful" : 5, //
  7. "skipped" : 0, // 跳过了0个
  8. "failed" : 0 // 失败了0个
  9. },
  10. "hits" : { //命中的情况
  11. "total" : 1000, // 命中率 1000个
  12. "max_score" : null, // 相关性得分,越相关就越匹配
  13. "hits" : [ {
  14. "_index" : "bank", // 索引
  15. "_type" : "_doc", // type
  16. "_id" : "0", // id
  17. "sort": [0],
  18. "_score" : null, // 相关性得分
  19. // _source里面存放的是数据
  20. "_source" : {"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"}
  21. }, {
  22. "_index" : "bank",
  23. "_type" : "_doc",
  24. "_id" : "1",
  25. "sort": [1],
  26. "_score" : null,
  27. "_source" : {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
  28. }, ...
  29. ]
  30. }
  31. }

指定超时时间: GET /_search?timeout=10ms 在进行优化时,可以考虑使用timeout, 比如: 正常来说我们可以在10s内获取2000条数据,但是指定了timeout,发生超时后我们可以获取10ms中获取到的 100条数据

下面我仅仅列出来了一点点, 更多的示例,参见官网 点击进入官网

_searchAPI +将请求写在请求体中

  1. GET /bank/_search
  2. {
  3. "query": { "match_all": {} }, # 查询全部
  4. "query": { "match": {"name":"changwu zhu"} }, # 全文检索,户将输入的字符串拆解开,去倒排索引中一一匹配, 哪怕匹配上了一个也会将结果返回
  5. # 实际上,上面的操作会被ES转换成下面的格式
  6. #
  7. # {
  8. # "bool":{
  9. # "should":[
  10. # {"term":{"title":"changwu"}},
  11. # {"term":{"title":"zhu"}}
  12. # ]
  13. # }
  14. # }
  15. #
  16. "query": {
  17. "match": { # 手动控制全文检索的精度,
  18. "name":{
  19. "query":"changwu zhu",
  20. "operator":"and", # and表示,只有同时出现changwu zhu 两个词的doc才会被命中
  21. "minimum_should_match":"75%" # 去长尾,控制至少命中3/4才算是真正命中
  22. }
  23. }
  24. }, # 全文检索,operator 表示
  25. # 添加上operator 操作会被ES转换成下面的格式,将上面的should转换成must
  26. #
  27. # {
  28. # "bool":{
  29. # "must":[
  30. # {"term":{"title":"changwu"}},
  31. # {"term":{"title":"zhu"}}
  32. # ]
  33. # }
  34. # }
  35. #
  36. # 添加上 minimum_should_match 操作会被ES转换成下面的格式
  37. #
  38. # {
  39. # "bool":{
  40. # "should":[
  41. # {"term":{"title":"changwu"}},
  42. # {"term":{"title":"zhu"}}
  43. # ],
  44. # "minimum_should_match":3
  45. # }
  46. # }
  47. #
  48. "query": {
  49. "match": { #控制权重,
  50. "name":{
  51. "query":"changwu zhu",
  52. "boost":3 # 将name字段的权重提升成3,默认情况下,所有字段的权重都是样的,都是1
  53. }
  54. }
  55. },
  56. "query": {
  57. # 这种用法不容忽略
  58. "dis_max": { # 直接取下面多个query中得分最高的query当成最终得分
  59. "queries":[
  60. {"match":{"name":"changwu zhu"}},
  61. {"match":{"content":"changwu"}}
  62. ]
  63. }
  64. },
  65. # best field策略
  66. "query": { # 基于 tie_breaker 优化dis_max
  67. # tie_breaker可以使dis_max考虑其他field的得分影响
  68. "multi_match":{
  69. "query":"用于去匹配的字段",
  70. "type":"most_fields",# 指定检索的策略most_fields
  71. "fields":["field1","field2","field3"]
  72. }
  73. },
  74. # most field 策略, 优先返回命中更多关键词的doc, (忽略从哪个,从多少个field中命中的,只要命中就行)
  75. "query": { # 基于 tie_breaker 优化dis_max
  76. # tie_breaker可以使dis_max考虑其他field的得分影响
  77. "dis_max": { # 直接取下面多个query中得分最高的query当成最终得分, 这也是best field策略
  78. "queries":[
  79. {"match":{"name":"changwu zhu"}},
  80. {"match":{"content":"changwu"}}
  81. ],
  82. "tie_breaker":0.4
  83. }
  84. },
  85. "query": { "match_none": {} }
  86. "query": { "term": {"test_field":"指定值"} } # 精确匹配
  87. "query": { "exits": {"field":"title"} } # title不为空(但是这时ES2.0中的用法,现在不再提供了)
  88. "query": { # 短语检索
  89. # 顺序的保证是通过 term position来保证的
  90. # 精准度很高,但是召回率低
  91. "match_phrase": { # 只有address字段中包含了完整的 mill lane (相连,顺序也不能变) 时,这个doc才算命中
  92. "address": "mill lane"
  93. }
  94. },
  95. "query": { # 短语检索
  96. "match_phrase": {
  97. "address": "mill lane",
  98. # 指定了slop就不再要求搜索term之间必须相邻,而是可以最多间隔slop距离
  99. # 在指定了slop参数的情况下,离关键词越近,移动的次数越少, relevance score 越高
  100. # match_phrase + slop 和 proximity match 近似匹配作用类似
  101. # 平衡精准度和召回率
  102. "slop":1 # 指定搜索文本中的几个term经过几次移动后可以匹配到一个doc
  103. }
  104. },
  105. # 混合使用match和match_phrase 平衡精准度和召回率
  106. "query": {
  107. "bool": {
  108. "must": {
  109. # 全文检索虽然可以匹配到大量的文档,但是它不能控制词条之间的距离
  110. # 可能java elasticsearch在Adoc中距离很近,但是它却被ES排在结果集的后面
  111. # 它的性能比match_phrase高10倍,比proximity高20倍
  112. "match": {
  113. "address": "java elasticsearch"
  114. }
  115. },
  116. "should": {
  117. # 借助match_phrase+slop可以感知term position的功能,为距离相近的doc贡献分数,让它们靠前排列
  118. "match_phrase":{
  119. "title":{
  120. "query":"java elasticsearch",
  121. "slop":50
  122. }
  123. }
  124. }
  125. },
  126. # 重打分机制
  127. "query": {
  128. "match":{
  129. "title":{
  130. "query":"java elasticsearch",
  131. "minimum_should_match":"50%"
  132. }
  133. },
  134. "rescore":{ # 对全文检索的结果进行重新打分
  135. "window_size":50, # 对全文检索的前50条进行重新打分
  136. "query": {
  137. "rescore_query":{ # 关键字
  138. "match_phrase":{ # match_phrase + slop 感知 term persition,贡献分数
  139. "title":{
  140. "query":"java elasticsearch",
  141. "slop":50
  142. }
  143. }
  144. }
  145. }
  146. }
  147. # 前缀匹配, 相对于全文检索,前缀匹配是不会进行分词的,而且每次匹配都会扫描整个倒排索引,直到扫描完一遍才会停下来
  148. # 不会计算相关性得分,前缀越短拼配到的越多,性能越不好
  149. "query": { # 查询多个, 在下面指定的两个字段中检索含有 `this is a test`的doc
  150. "multi_match" : {
  151. "query": "this is a test",
  152. "fields": [ "subject", "message" ]
  153. }
  154. },
  155. "query": { # 前缀搜索,搜索 user字段以ki开头的 doc
  156. "prefix" : { "user" : "ki" }
  157. },
  158. "query": { # 前缀搜索 + 添加权重
  159. "prefix" : { "user" : { "value" : "ki", "boost" : 2.0 } }
  160. },
  161. # 通配符搜索
  162. "query": {
  163. "wildcard" : { "user" : "ki*y" }
  164. },
  165. "query": {
  166. "wildcard" : { "user" : { "value" : "ki*y", "boost" : 2.0 } }
  167. }
  168. # 正则搜索
  169. "query": {
  170. "regexp":{
  171. "name.first": "s.*y"
  172. }
  173. },
  174. "query": {# 正则搜索
  175. "regexp":{
  176. "name.first":{
  177. "value":"s.*y",
  178. "boost":1.2
  179. }
  180. }
  181. },
  182. # 搜索推荐, 类似于百度,当用户输入一个词条后,将其他符合条件的词条的选项推送出来
  183. # 原理和match_pharse相似,但是唯一的区别就是会将最后一个term当作前缀去搜索
  184. # 下例中: 使用quick brown进行match 使用f进行前缀搜索,使用slop调整term persition,贡献得分
  185. "query": {
  186. "match_phrase_prefix" : {# 前缀匹配
  187. "message" : {
  188. "query" : "quick brown f",
  189. "max_expansions" : 10, # 指定前缀最多匹配多少个term,超过这个数量就不在倒排索引中检索了,提升性能
  190. "slop":10
  191. }
  192. }
  193. },
  194. # Function Score Query
  195. # 用户可以自定义一个function_secore 函数,然后将某个field的值和ES计算出来的分数进行运算
  196. # 最终实现对自己指定的field进行分数的增强功能
  197. "query": {
  198. "function_score": {
  199. "query": { "match_all": {} },
  200. "boost": "5",
  201. "random_score": {},
  202. "boost_mode":"multiply"
  203. }
  204. },
  205. # Fuzzy Query 模糊查询会提供容错的处理
  206. "query": {
  207. "fuzzy" : {
  208. "user" : {
  209. "value": "ki",
  210. "boost": 1.0,
  211. "fuzziness": 2, # 做大的纠错数量
  212. "prefix_length": 0,# 不会被“模糊化”的初始字符数。这有助于减少必须检查的术语的数量。默认值为0
  213. "max_expansions": 100 # 模糊查询将扩展到的最大项数。默认值为50
  214. transpositions:true # 是否支持模糊变换(ab→ba)。默认的是假的
  215. }
  216. }
  217. }
  218. "query": {
  219. "bool": { # 布尔查询, 最终通过将它内置must,should等查询的得分加起来/should,must的总数, 得到最终的得分
  220. "must": [ # 必须匹配到XXX, 并且会得出相关性得分
  221. { "match": { "address": "mill" } }, # address中必须包含mill
  222. ],
  223. # 在满足must的基础上,should条件不满足也可以,但是如果也匹配上了,相关性得分会增加
  224. # 如果没有must的话,should中的条件必须满足一个
  225. "should": [ # 指定可以包含的值, should是可以影响相关性得分的
  226. { "match": { "address": "lane" } }
  227. ],
  228. "must_not": [ # 一定不包含谁
  229. { "match": { "address": "mill" } },
  230. ],
  231. "filter": { # 对数据进行过滤
  232. "range": { # 按照范围过滤
  233. "balance": { # 指定过滤的字段
  234. "gte": 20000, # 高于20000
  235. "lte": 30000 # 低于30000
  236. }
  237. }
  238. }
  239. }
  240. }

在上面的组合查询中,每一个子查询都会计算一下他的相关性分数,然后由最外层的bool综合合并一个得分,但是 filter是不会计算分数的

默认的排序规则是按照score降序排序,但像上面说的那样,如果全部都是filter的话他就不会计算得分,也就是说所有的得分全是1,这时候就需要定制排序规则,定义的语法我在上面写了

比如下面的高亮,排序,分页,以及_source 指定需要的字段都可以进一步作用在query的结果上

  1. "highlight":{ # 高亮显示
  2. "fields":{ # 指定高亮的字段
  3. "balance":{}
  4. },
  5. "sort": [ # 指定排序条件
  6. { "account_number": "asc" } # 按照账户余额降序
  7. ],
  8. "from": 0, # 分页
  9. "size": 10, # 每页的大小4,通过执行size=0,可以实现仅显示聚合结果而不显示命中的信息详情
  10. "_source": ["account_number", "balance"], # 默认情况下,ES会返回全文JSON,通过_source可以指定返回的字段

聚合分析是基于doc value这样一个数据结果进行的,前面有说过,这个doc value 其实就是正排索引, 聚合分析就是根据某一个字段进行分组,要求这个字段是不能被分词的,如果被聚合的字段被分词,按照倒排索引的方式去索引的话,就不得不去扫描整个倒排索引(才可能将被聚合的字段找全,效率很低)

更多Aggregate 点击进入ES官网

三个概念:

  • 什么是bucket?

bucket就是聚合得到的结果

  • 什么是metric?

metric就是对bucket进行分析,如最最大值,最小值,平均值

  • 什么是下钻?

下钻就是在现有的分好组的bucket继续分组,比如一个先按性别分组,再按年龄分组

聚合的关键字: aggsquery地位并列

  1. # 使用聚合时,天然存在一个metric,就是当前bucket的count
  2. "aggs": { # 聚合
  3. "group_by_state": { # 自定义的名字
  4. "term": {
  5. "field": "balance" # 指定聚合的字段, 意思是 group by balance
  6. },
  7. "terms": { # terms
  8. "field": {"value1","value2","value3"} # 指定聚合的字段, 意思是 group by balance
  9. }
  10. }
  11. },
  12. "aggs": { # 聚合中嵌套聚合
  13. "group_by_state": {
  14. "terms": {
  15. "field": "field1"
  16. },
  17. "aggs": { # 聚合中嵌套聚合
  18. "average_balance": {
  19. "avg": {
  20. "field": "field2"
  21. }
  22. }
  23. }
  24. }
  25. },
  26. "aggs": { #嵌套聚合,并且使用内部聚合的结果集
  27. "group_by_state": {
  28. "terms": {
  29. "field": "state.keyword",
  30. "order": {
  31. "average_balance": "desc" # 使用的下面聚合的结果集
  32. }
  33. },
  34. "aggs": {
  35. "average_balance": {
  36. "avg": { # avg 求平均值 metric
  37. "field": "balance"
  38. }
  39. },
  40. "min_price": {
  41. "min": { # metric 求最小值
  42. "field": "price"
  43. }
  44. },
  45. "max_price": {
  46. "max": { # metric 求最大值
  47. "field": "price"
  48. }
  49. },
  50. "sum_price": {
  51. "sum": { # metric 计算总和
  52. "field": "price"
  53. }
  54. },
  55. }
  56. }
  57. },
  58. "aggs": { # 先按照年龄分组,在按照性别分组,再按照平均工资聚合
  59. # 最终的结果就得到了每个年龄段,每个性别的平均账户余额
  60. "group_by_age": {
  61. "range": {
  62. "field": "age",
  63. "ranges": [
  64. {
  65. "from": 20,
  66. "to": 30
  67. }
  68. ]
  69. },
  70. "aggs": {
  71. "group_by_gender": {
  72. "terms": {
  73. "field": "gender.keyword"
  74. },
  75. "aggs": {
  76. "average_balance": {
  77. "avg": {
  78. "field": "balance"
  79. }
  80. }
  81. }
  82. }
  83. },
  84. # histogram,类似于terms, 同样会进行bucket分组操作,接受一个field,按照这个field的值的各个范围区间进行分组操作
  85. # 比如我们指定为2000, 它会划分成这样 0-2000 2000-4000 4000-6000 ...
  86. "aggs": { # 聚合中嵌套聚合
  87. "group_by_price": {
  88. "histogram": {
  89. "field": "price",
  90. "interval":2000
  91. },
  92. "aggs": { # 聚合中嵌套聚合
  93. "average_price": {
  94. "avg": {
  95. "field": "price"
  96. }
  97. }
  98. }
  99. }
  100. },
  101. "aggs" : {
  102. "sales_over_time" : { # 根据日期进行聚合
  103. "date_histogram" : {
  104. "field" : "date",
  105. "interval" : "1M",# 一个月为一个跨度
  106. "format" : "yyyy-MM-dd",
  107. "min_doc_count":0 #即使这个区间中一条数据都没有,这个区间也要返回
  108. }
  109. }
  110. }
  111. }
  112. }
  113. }

过滤加聚合,统计type=t-shirt的平均价格

  1. POST /sales/_search?size=0
  2. {
  3. "aggs" : {
  4. "t_shirts" : {
  5. "filter" : { "term": { "type": "t-shirt" } },
  6. "aggs" : {
  7. "avg_price" : { "avg" : { "field" : "price" } }
  8. }
  9. }
  10. }
  11. }

说一个应用于场景: 我们检索电影的评论, 但是我们先按照演员分组聚合,在按照评论的数量进行聚合

分析: 如果我们选择深度优先的话, ES在构建演员电影相关信息时,会顺道计算出电影下面评论数的信息,假如说有10万个演员的话, 10万*10=100万个电影 每个电影下又有很多影评,接着处理影评, 就这样内存中可能会存在几百万条数据,但是我们最终就需要50条,这种开销是很大的

广度优先的话,是我们先处理电影数,而不管电影的评论数的聚合情况,先从10万演员中干掉99990条数据,剩下10个演员再聚合

  1. "aggs":{
  2. "target_actors":{
  3. "terms":{
  4. "field":"actors",
  5. "size":10,
  6. "collect_mode":"breadth_first" # 广度优先
  7. }
  8. }
  9. }

全局聚合,下面先使用query进行全文检索,然后进行聚合, 下面的聚合实际上是针对两个不同的结果进行聚合,第一个聚合添加了global关键字,意思是ES中存在的所有doc进行聚合计算得出t-shirt的平均价格

第二个聚合针对全文检索的结果进行聚合

  1. POST /sales/_search?size=0
  2. {
  3. "query" : {
  4. "match" : { "type" : "t-shirt" }
  5. },
  6. "aggs" : {
  7. "all_products" : {
  8. "global" : {},
  9. "aggs" : {
  10. "avg_price" : { "avg" : { "field" : "price" } }
  11. }
  12. },
  13. "t_shirts": { "avg" : { "field" : "price" } }
  14. }
  15. }

作用类似于count(distcint),会对每一个bucket中指定的field进行去重,然后取去重后的count

虽然她会存在5%左右的错误率,但是性能特别好

  1. POST /sales/_search?size=0
  2. {
  3. "aggs" : {
  4. "type_count" : {
  5. "cardinality" : { # 关键字
  6. "field" : "type"
  7. }
  8. }
  9. }
  10. }

对Cardinality Aggregate的性能优化, 添加 precision_threshold 优化准确率和内存的开销

下面的示例中将precision_threshold的值调整到100意思是当 type的类型小于100时,去重的精准度为100%, 此时内存的占用情况为 100*8=800字节

加入我们将这个值调整为1000,意思是当type的种类在1000个以内时,去重的精准度100%,内存的占用率为1000*8=80KB

官方给出的指标是, 当将precision_threshold设置为5时,错误率会被控制在5%以内

  1. POST /sales/_search?size=0
  2. {
  3. "aggs" : {
  4. "type_count" : {
  5. "cardinality" : { # 关键字
  6. "field" : "type",
  7. "precision_threshold":100
  8. }
  9. }
  10. }
  11. }

进一步优化,Cardinality底层使用的算法是 HyperLogLog++, 可以针对这个算法的特性进行进一步的优化,因为这个算法的底层会对所有的 unique value取hash值,利用这个hash值去近似的求distcint count, 因此我们可以在创建mapping时,将这个hash的求法设置好,添加doc时,一并计算出这个hash值,这样 HyperLogLog++ 就无需再计算hash值,而是直接使用

  1. PUT /index/
  2. {
  3. "mappings":{
  4. "my_type":{
  5. "properties":{
  6. "my_field":{
  7. "type":"text",
  8. "fields":{
  9. "hash":{
  10. "type":"murmu3"
  11. }
  12. }
  13. }
  14. }
  15. }
  16. }
  17. }

先按照颜色聚合,在聚合的结果上,再根据价格进行聚合, 最终的结果中,按照价格聚合的分组中升序排序, 这算是个在下转分析时的排序技巧

  1. GET /index/type/_search
  2. {
  3. "size":0,
  4. "aggs":{
  5. "group_by_color":{
  6. "term":{
  7. "field":"color",
  8. "order":{ #
  9. "avg_price":"asc"
  10. }
  11. }
  12. },
  13. "aggs":{
  14. "avg_price":{
  15. "avg":{
  16. "field":"price"
  17. }
  18. }
  19. }
  20. }
  21. }

计算百分比, 常用它计算如,在200ms内成功访问网站的比率,在500ms内成功访问网站的比例,在1000ms内成功访问网站的比例, 或者是销售价为1000元的商品,占总销售量的比例, 销售价为2000元的商品占总销售量的比例等等

示例: 针对doc中的 load_time字段, 计算出在不同百分比下面的 load_time_outliner情况

  1. GET latency/_search
  2. {
  3. "size": 0,
  4. "aggs" : {
  5. "load_time_outlier" : {
  6. "percentiles" : {
  7. "field" : "load_time"
  8. }
  9. }
  10. }
  11. }

响应 : 解读: 在百分之50的加载请求中,平均load_time的时间是在445.0, 在99%的请求中,平均加载时间980.1

  1. {
  2. ...
  3. "aggregations": {
  4. "load_time_outlier": {
  5. "values" : {
  6. "1.0": 9.9,
  7. "5.0": 29.500000000000004,
  8. "25.0": 167.5,
  9. "50.0": 445.0,
  10. "75.0": 722.5,
  11. "95.0": 940.5,
  12. "99.0": 980.1000000000001
  13. }
  14. }
  15. }
  16. }

还可以自己指定百分比跨度间隔

  1. GET latency/_search
  2. {
  3. "size": 0,
  4. "aggs" : {
  5. "load_time_outlier" : {
  6. "percentiles" : {
  7. "field" : "load_time",
  8. "percents" : [95, 99, 99.9]
  9. }
  10. }
  11. }
  12. }

优化: percentile底层使用的是 TDigest算法,用很多个节点执行百分比计算,近似估计,有误差,节点越多,越精准

可以设置compression的值, 默认是100 , ES限制节点的最多是 compression*20 =2000个node去计算 , 因为节点越多,性能就越差

一个节点占用 32字节, 1002032 = 64KB

  1. GET latency/_search
  2. {
  3. "size": 0,
  4. "aggs" : {
  5. "load_time_outlier" : {
  6. "percentiles" : {
  7. "field" : "load_time",
  8. "percents" : [95, 99, 99.9],
  9. "compression":100 # 默认值100
  10. }
  11. }
  12. }
  13. }
  • 第一种方式:

在content字段中全文检索 java elasticsearch时,给title中同时出现java elasticsearch的doc的权重加倍

  1. "query": {
  2. "bool" : {# 前缀匹配
  3. "match":{
  4. "content":{
  5. "query":"java elasticsearch"
  6. }
  7. },
  8. "should":[
  9. "match":{
  10. "title":{
  11. "query":"java elasticsearch",
  12. "boost":2
  13. }
  14. }
  15. ]
  16. }
  17. }
  • 第二种: 更换写法,改变占用的权重比例
  1. GET my_index/_doc/_search
  2. {
  3. "query":{
  4. "should":[
  5. { "match":{"title":"this is"}}, # 1/3
  6. { "match":{"title":"this is"}}, # 1/3
  7. {
  8. "bool":{
  9. "should":[
  10. {"match":{"title":"this is"}}, # 1/6
  11. {"match":{"title":"this is"}} # 1/6
  12. ]
  13. }
  14. }
  15. ]
  16. }
  17. }
  • 第三种: 如果不希望使用相关性得分,使用下面的语法
  1. GET my_index/_doc/_search
  2. {
  3. "query": {
  4. "constant_score" : {
  5. "filter" : {
  6. "term" : { "title" : "this"} #
  7. },
  8. "boost" : 1.2
  9. }
  10. }
  11. }
  • 第四种: 灵活的查询

查询必须包含XXX,必须不包含YYY的doc

  1. GET my_index/_doc/_search
  2. {
  3. "query":{
  4. "bool": {
  5. "must":{
  6. "match":{
  7. "title":"this is a "
  8. }
  9. },
  10. "must_not":{
  11. "match":{
  12. "title":"another"
  13. }
  14. }
  15. }
  16. }
  17. }
  • 第五种: 查询必须包含XXX,可以包含YYY,但是包含了YYY后它的权重就会减少指定的值
  1. GET my_index/_doc/_search
  2. {
  3. "query":{
  4. "boosting": {
  5. "positive":{
  6. "match":{
  7. "title":"this is a "
  8. }
  9. },
  10. "negative":{
  11. "match":{
  12. "title":"another"
  13. }
  14. },
  15. "negative_boost": 0.2
  16. }
  17. }
  18. }
  • 第六种: 重打分机制
  1. "query": {
  2. "match":{
  3. "title":{
  4. "query":"java elasticsearch",
  5. "minimum_should_match":"50%"
  6. }
  7. },
  8. "rescore":{ # 对全文检索的结果进行重新打分
  9. "window_size":50, # 对全文检索的前50条进行重新打分
  10. "query": {
  11. "rescore_query":{ # 关键字
  12. "match_phrase":{ # match_phrase + slop 感知 term persition,贡献分数
  13. "title":{
  14. "query":"java elasticsearch",
  15. "slop":50
  16. }
  17. }
  18. }
  19. }
  20. }
  • 第七种: 混用match和match_phrase提高召回率
  1. "query": {
  2. "bool": {
  3. "must": {
  4. # 全文检索虽然可以匹配到大量的文档,但是它不能控制词条之间的距离
  5. # 可能java elasticsearch在Adoc中距离很近,但是它却被ES排在结果集的后面
  6. # 它的性能比match_phrase高10倍,比proximity高20倍
  7. "match": {
  8. "address": "java elasticsearch"
  9. }
  10. },
  11. "should": {
  12. # 借助match_phrase+slop可以感知term position的功能,为距离相近的doc贡献分数,让它们靠前排列
  13. "match_phrase":{
  14. "title":{
  15. "query":"java elasticsearch",
  16. "slop":50
  17. }
  18. }
  19. }
  20. }

版权声明:本文为ZhuChangwu原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/ZhuChangwu/p/11793877.html