Elasticsearch搜索引擎使用

一 Elasticsearch7.2.0安装

安装Elasticsearch,需要保证elasticsearch、kibana、ik分词器这些的版本一致,否则会带来一些不兼容的问题。以下是容器安装单机Elasticsearch的教程

# docker安装es
docker run -d --name=elasticsearch7.2.0 -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms64m -Xmx512m"  elasticsearch:7.2.0

#ik分词器安装,版本要保持和es一致
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.2.0/elasticsearch-analysis-ik-7.2.0.zip
#拷贝ik分词器到容器内部插件
docker cp ./ik elasticsearch7.2.0:/usr/share/elasticsearch/plugins/
#重启es容器
docker restart elasticsearch7.2.0

#安装kibana
docker run -d --name kibana7.2.0 -p 5601:5601 kibana:7.2.0
#需要配置kibana.yml文件中的es地址
elasticsearch.hosts: [ "你的es的地址" ]
#重启kibana
docker restart kibana7.2.0

二.基础概念

1.倒排索引

在这里插入图片描述

  1. 根据分词器进行分词
  2. 数据去重,记录单词出现的文档位置
  3. 根据字典序排序
  4. es还会在term词项的基础上利用term的前缀或者后缀构建term index,用于对term本身进行索引
  5. 倒排索引项构成
    • 文档ID
    • 词频TF-该单词在文档中出现的次数,用于相关性得分
    • 位置-单词在文档中分词的位置。用于短语搜索
    • 偏移-记录单词的开始结束位置,实现高亮显示

2.相关性算分

  1. es5.x之前的TF-IDF算分
    • TF是词频,检索词在文档中出现的频率越高,该条文档的相关性也越高
    • IDF逆向文本频率:每个检索词在整个索引库中出现的频率越高,该条文档的相关性越低
    • 字段长度归一值:字段的长度越短,检索词所在的文档相关性越高。
  2. es5.x之后采用BM25的算法,主要位来改进TF-IDF的算分会随着TF词频增加而急剧的变大,BM25将这个增大趋势降低了,趋于平缓。

细节点

  • elasticsearch的json文档中的每个字段,默认都有自己的倒排索引。可以手动指定字段不索引,这样就无法对该列进行搜索,好处在于节省存储空间。
  • elasticsearch搜索非实时的原因,数据写入时先写到内存buffer缓冲区,每隔1s(刷新间隔时间refresh_interval)或者buffer满了才会将buffer数据写到segment file文件,这时候才可以查询到。可以通过refresh参数指定强制刷新,数据实时可见。

三.基础操作

1.索引操作

新建索引

#新建索引,在索引不存在时候才能执行成功
PUT /es_db

#新建索引,指定分片数和副本数
PUT /es_db
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  }
}

更新索引

#修改索引的副本数量
PUT /es_db/_settings
{
  "number_of_replicas": 2
}

查询索引

#查询索引,可以查看索引具体信息
GET /es_db

#判断索引是否存在
HEAD /es_db

删除索引

DELETE /es_db

2.文档操作

文档新建

#新建索引,指定默认分词器
PUT /es_db
{
  "settings": {
    "analysis": {
      "analysis.analyzer.default.type": "ik_max_word"
    }
  }
}

#新建一个指定id的文档
PUT /es_db/_doc/1
{
  "name": "张三",
  "sex": 1,
  "age": 25,
  "address": "广州天河公园",
  "remark": "java developer"
}

#指定create进行创建文档
PUT /es_db/_create/1
{
  "name": "张三",
  "sex": 1,
  "age": 25,
  "address": "广州天河公园",
  "remark": "java developer"
}

#已经存在id为1的文档,继续操作是更新操作,而且是全量更新,会先删除原有文档,再新建新文档
PUT /es_db/_doc/1
{
  "name": "张三",
  "sex": 1,
  "age": 25
}


注意:PUT和POST操作都可以进行文档的新增/更新,PUT需要指定id才能操作,而POST在不指定id时候就是新增,指定id时候可以是新增/创建。
文档更新
全量更新,整个json都会替换,格式: [PUT | POST] /索引名称/_doc/id。如果文档存在,现有文档会被删除,新的文档会被索引

#全量更新
PUT /es_db/_doc/1/
{
  "name": "张三",
  "sex": 1,
  "age": 25
}

使用_update部分更新,格式: POST /索引名称/_update/id。update不会删除原来的文档,而是实现真正的数据更新

#只更新需要更新的字段,id必须指定
POST /es_db/_update/1
{
  "doc": {
    "age": 28
  }
}

使用 _update_by_query 更新文档。更新查询到的文档

POST /es_db/_update_by_query
{
  "query": {
    "match": {
      "_id": 1
    }
  },
  "script": {
    "source": "ctx._source.age = 30"
  }
}

并发场景下修改文档,采取乐观锁的机制去更新。
_seq_no和_primary_term是对_version的优化,7.X版本的ES默认使用这种方式控制版本,所以当在高并发环境下使用乐观锁机制修改文档时,要带上当前文档的_seq_no和_primary_term进行更新

POST /es_db/_doc/1?if_seq_no=21&if_primary_term=6
{
  "name": "李四xxx"
}

查询文档
简单文档查询,DSL语句查询放在后面讲解

GET /es_db/_doc/1

删除文档

DELETE /es_db/_doc/1

批量操作

  1. 批量操作可以减少网络开销
  2. 在一次批量操作过程中,可以操作多个索引库
  3. 操作单条失败,不影响其他结果
  4. 返回结果包含每一条操作过程的执行结果
  5. 批量操作_bulk的api
POST _bulk
#指定操作的动作(create、index、update、delete),索引库,类型(_doc),id
{"actionName":{"_index":"indexName", "_type":"typeName","_id":"id"}}
#操作的数据内容
{"field1":"value1", "field2":"value2"}

批量创建文档create

POST _bulk
{"create":{"_index":"article","_type":"_doc","_id":3}}
{"id":3,"title":"老师A","content":"老师A666","tags":["java", "面向对象"],"create_time":1554015482530}
{"create":{"_index":"article","_type":"_doc","_id":4}}
{"id":4,"title":"老师B","content":"老师B-NB","tags":["java", "面向对象"],"create_time":1554015482530}

普通创建或全量替换index。如果原文档不存在,则是创建;如果原文档存在,则是替换(全量修改原文档)

POST _bulk
{"index":{"_index":"article","_type":"_doc","_id":1}}
{"id":3,"title":"老师A","content":"老师A666","tags":["java", "面向对象"],"create_time":1554015482530}
{"index":{"_index":"article","_type":"_doc","_id":4}}
{"id":4,"title":"老师B","content":"老师B-NB","tags":["java", "面向对象"],"create_time":1554015482530}

批量删除delete

POST _bulk
{"delete":{"_index":"article","_type":"_doc","_id":3}}
{"delete":{"_index":"article","_type":"_doc","_id":4}}

批量修改update

POST _bulk
{"update":{"_index":"article","_type":"_doc","_id":3}}
{"doc":{"title":"ES大法必修内功"}}
{"update":{"_index":"article","_type":"_doc","_id":4}}
{"doc":{"create_time":1554018421008}}

多种类型结合操作

#更新和删除一起执行
POST _bulk
{"update":{"_index":"article","_type":"_doc","_id":3}}
{"doc":{"title":"ES大法必修内功1"}}
{"delete":{"_index":"article","_type":"_doc","_id":4}}

批量读取mget和msearch
mget需要指定id,可以指定不同的index,也可以指定返回值source

#指定索引和id查询
GET _mget
{
  "docs": [
    {
      "_index": "es_db",
      "_id": 1
    },
    {
      "_index": "article",
      "_id": 3
    }
  ]
}

#索引放在url上进行查询
GET /article/_mget
{
  "docs": [
    {
      "_id": 1
    },
    {
      "_id": 3
    }
  ]
}

#简化方式
GET /article/_mget
{
  "ids": [
    "1",
    "3"
  ]
}

四.文档映射Mapping

动态映射
在未定义Mapping映射关系时,在文档中插入数据,es会根据文档字段自动识别类型,称为动态映射。动态映射有可能有时无法推算出正确的类型,影响后面的使用。

JSON类型Elasticsearch类型
字符串匹配日期格式,设置成Date;匹配数字设置为float或long,该选项默认关闭;其余设置为Text,并且增加keyword子类型
布尔值boolean
浮点数float
整数long
对象Object
数组由第一个非空数值的类型所决定
空值忽略

静态映射
静态映射时elasticsearch事先定义好mapping的字段关系。

能否在后期更改Mapping的字段类型

  1. 新增加字段
    • dynamic设为true时,有新增字段的文档写入时,mapping也会同时被更新
    • dynamic设为false,Mapping不会被更新,新增字段的数据无法被索引,只存在_source中
    • dynamic设为strict(严格控制策略),文档写入失败,抛出异常
  2. 对已有字段,一旦有数据写入,就不再支持修改字段的定义。只能通过reindex API来重建索引

索引重建流程

  1. 新建一个静态索引
  2. 把旧索引的数据通过reindex导入到新索引
  3. 删除原来的索引
  4. 为新索引起个别名,为原索引名
#数据导入,重建索引
POST _reindex
{
  "source": {
    "index": "user"
  },
  "dest": {
    "index": "user2"
  }
}

#设置别名
PUT /user2/_alias/user

1.常用Mapping参数设置

  • index:设置当前字段是否被索引,默认true。设置为false时该字段不可被搜索。
  • index_options:控制倒排索引记录的内容。
    • docs:记录doc id (除text类型外的其他类型默认配置
    • freqs:记录doc id和term frequencies(词频)
    • positions:记录doc id / term frequencies / term position(text类型默认配置
    • offsets: doc id / term frequencies / term posistion / character offsets
  • null_value:需要对null值进行搜索,只有keywork类型支持设置null_value
  • copy_to设置:将字段的数值拷贝到目标字段,满足一些特定的搜索需求。copy_to的目标字段不出现在_source中。
#配置provice和city的copy_to到full_address上
PUT /address
{
  "mappings": {
    "properties": {
      "province": {
        "type": "keyword",
        "copy_to": "full_address"
      },
      "city": {
        "type": "text",
        "copy_to": "full_address"
      }
    }
  },
  "settings": {
    "index": {
      "analysis.analyzer.default.type": "ik_max_word"
    }
  }
}

#导入数据
PUT /address/_bulk
{"index":{"_id":"1"}}
{"province":"湖南","city":"长沙"}
{"index":{"_id":"2"}}
{"province":"湖南","city":"常德"}
{"index":{"_id":"3"}}
{"province":"广东","city":"广州"}
{"index":{"_id":"4"}}
{"province":"湖南","city":"邵阳"}

#根据copy_to的字段查询
GET /address/_search
{
  "query": {
    "match": {
      "full_address": {
        "query": "湖南常德",
        "operator": "and"
      }
    }
  }
}

五.索引模板Index Template

Index Templates可以帮助你设定Mappings和Settings,并按照一定的规则,自动匹配到新创建的索引之上

  • 模版仅在一个索引被新创建时,才会产生作用。修改模版不会影响已创建的索引
  • 模板应用顺序
    1. 应用Elasticsearch 默认的settings 和mappings
    2. 应用order数值低的lndex Template 中的设定
    3. 应用order高的 Index Template 中的设定,之前的设定会被覆盖
PUT /_template/template_default
{
  "index_patterns": [
    "*"
  ],
  "order": 0,
  "version": 1,
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  }
}


PUT /_template/template_test
{
  "index_patterns": [
    "test*"
  ],
  "order": 1,
  "settings": {
    "number_of_shards": 2,
    "number_of_replicas": 1
  },
  "mappings": {
    "date_detection": false,
    "numeric_detection": true
  }
}

6.动态模板Dynamic Template

用于定义某个索引的mapping中。匹配规则的字段,会动态映射成模板里面指定的规则

PUT my_index
{
  "mappings": {
    "dynamic_templates": [
      {
      #动态映射成Boolean的规则
        "strings_as_boolean": {
          "match_mapping_type": "string",
          "match": "is*",
          "mapping": {
            "type": "boolean"
          }
        }
      },
      {
      #动态映射成keywork的规则
        "strings_as_keywords": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "keyword"
          }
        }
      }
    ]
  }
}


#结合路径
PUT /my_test_index
{
  "mappings": {
    "dynamic_templates": [
      {
        "full_name": {
          "path_match": "name.*",
          "path_unmatch": "*.middle",
          "mapping": {
            "type": "text",
            "copy_to": "full_name"
          }
        }
      }
    ]
  }
}

7.高级查询Query DSL

构建数据

PUT /es_db
{
  "settings": {
    "index": {
      "analysis.analyzer.default.type": "ik_max_word"
    }
  }
}

PUT /es_db/_bulk
{"index":{"_id":"1"}}
{"name":"张三","sex":1,"age":25,"address":"广州天河公园","remark":"java developer"}
{"index":{"_id":"2"}}
{"name":"李四","sex":1,"age":28,"address":"广州荔湾大厦","remark":"java assistant"}
{"index":{"_id":"3"}}
{"name":"王五","sex":0,"age":26,"address":"广州白云山公园","remark":"php developer"}
{"index":{"_id":"4"}}
{"name":"赵六","sex":0,"age":22,"address":"长沙橘子洲","remark":"python assistant"}

查询所有match_all和深度分页,排序

默认分页大小size=10,from索引下标

POST /es_db/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 2
}

深度分页
from+size默认最大只能10000。可以通过设置_settings的index.max_result_window改变这个大小。这个主要用来限制内存的消耗,不建议修改。当深度分页查询时候,不建议使用from+size来查询。

深度分页查询scroll

  1. 查询命令增加scroll参数,指定游标查询窗口时间。会将符合条件的数据生成快照。
  2. 后面查询通过第一步返回scroll_id来进行查询,需要在窗口时间失效前使用。
  3. 缺点:
    • 因为查询的是快照,数据如果存在更新,快照查的是旧数据。
    • 查询需要在窗口有效期内执行,而且没办法往前翻页
    • 大量快照存在时候也会占据比较多内存,可以手动释放
GET /es_db/_search?scroll=30s
{
  "query": {
    "match_all": {}
  },
  "size": 1
}



GET /_search/scroll
 {
 "scroll": "30s",
 "scroll_id" :"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAd2AWZEstSEx4b3JUV09tdG5Ia2U2QWt4Zw=="
 }

#删除scroll快照,释放内存
DELETE /_search/scroll
{
  "scroll_id":["DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAd2AWZEstSEx4b3JUV09tdG5Ia2U2QWt4Zw=="]
}

排序
操作排序不进行算分

#排序,分页
GET /es_db/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "age": "desc"
    }
  ],
  "from": 0,
  "size": 5
}

指定返回字段_source

GET /es_db/_search
{
  "query": {
    "match_all": {}
  },
  "_source": [
    "name",
    "address"
  ]
}

match

  1. match在匹配时会对所查找的关键词进行分词,然后按分词匹配查找。
  2. match支持参数
    • query:指定匹配的值
    • operator : 匹配条件类型。and / or(默认)
    • minmum_should_match:用来指定匹配分词的最小个数
GET /es_db/_search
{
  "query": {
    "match": {
      "address": {
        "query": "广州白云山公园",
        "operator": "OR",
        "minimum_should_match": 2
      }
    }
  }
}

短语查询match_phrase

match_phrase 会将检索关键词分词。match_phrase的分词结果必须在被检索字段的分词中都包含,而且顺序必
须相同,而且默认必须都是连续的。可以通过slop参数来配置分词相隔多远依然匹配。

#广州、白云两个分词在广州白云山公园的position位置相隔2位,通过配置slop来匹配
GET /es_db/_search
{
  "query": {
    "match_phrase": {
      "address": {
        "query": "广州云山",
        "slop": 2
      }
    }
  }
}

多字段查询multi_match

同时匹配文档的多个字段。查询字段分词就将查询条件分词查询,查询字段不分词将查询条件不分词查询

GET /es_db/_search
{
  "query": {
    "multi_match": {
      "query": "长沙张龙",
      "fields": [
        "address",
        "name"
      ]
    }
  }
}

query_string对整个文档内容查询

在单个查询字符串中指定AND | OR | NOT条件。支持多字段搜索。查询字段分词就将查询条件分词查询,查询字段不分词将查询条件不分词查询

GET /es_db/_search
{
  "query": {
  #不指定字段就是查询整个文档
    "query_string": {
      "fields": [
        "name",
        "address"
      ],
      "query": "张三 OR (广州 AND 王五)"
    }
  }
}

simple_query_string

类似Query String,但是会忽略错误的语法,同时只支持部分查询语法,不支持AND OR
NOT,会当作字符串处理。支持部分逻辑:

  • +替代AND
  • | 替代OR
  • -替代NOT
GET /es_db/_search
{
  "query": {
    "simple_query_string": {
      "fields": [
        "name",
        "address"
      ],
      "query": "广州公园",
      "default_operator": "AND"
    }
  }
}

GET /es_db/_search
{
  "query": {
    "simple_query_string": {
      "fields": [
        "name",
        "address"
      ],
      "query": "广州 + 公园"
    }
  }
}

关键词查询Term

term查询对查询条件不进行分词,所以在有些text字段类型的字段上,如果想要精准匹配整个字段,可以使用子类型的keyword来查询

GET /es_db/_search
{
  "query": {
    "term": {
      "address.keyword": {
        "value": "广州白云山公园"
      }
    }
  }
}

英文在建立倒排索引时候,单词会被转成小写,如果查询条件有大写字母时候会匹配不上。可以通过建立索引时忽略大小写。

PUT /product
{
  "settings": {
    "analysis": {
      "normalizer": {
      #大小写忽略配置
        "es_normalizer": {
          "filter": [
            "lowercase",
            "asciifolding"
          ],
          "type": "custom"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "productId": {
        "type": "text"
      },
      "productName": {
        "type": "keyword",
        "normalizer": "es_normalizer",#配置忽略大小写
        "index": "true"
      }
    }
  }
}

可以通过 Constant Score 将查询转换成一个 Filtering,避免算分,并利用缓存,提高性能

GET /es_db/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "term": {
          "address.keyword": "广州白云山公园"
        }
      }
    }
  }
}

term处理多值字段,term查询是包含,不是等于。比如term查询数组字段时候。

前缀查询prefix

  1. 它不会分析要搜索字符串,传入的前缀就是想要查找的前缀
  2. 默认状态下,前缀查询不做相关度分数计算,它只是将所有匹配的文档返回。它的行为更像是一个过滤器而不是查询。两者实际的区别就是过滤器是可以被缓存的,而前缀查询不行。
  3. prefix的原理:需要遍历所有倒排索引,并比较每个term是否已所指定的前缀开头。
GET /es_db/_search
{
  "query": {
    "prefix": {
      "address": {
        "value": "广州"
      }
    }
  }
}

范围查询range

  • range:范围关键字
  • gte 大于等于
  • lte 小于等于
  • gt 大于
  • lt 小于
  • now 当前时间
POST /es_db/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 25,
        "lte": 28
      }
    }
  }
}

模糊查询fuzzy

在Elasticsearch中,我们可以使用fuzziness属性来进行模糊查询,从而达到搜索有错别字的情形。
fuzziness表示允许的错误字符数,指新增一个字符、删除一个字符、修改一个字符
prefix_length:表示限制输入关键字和ES对应查询field的内容开头的第n个字符必须完全匹配,不允许错别字匹配

GET /es_db/_search
{
  "query": {
    "fuzzy": {
      "address": {
        "value": "山",
        "fuzziness": 1
      }
    }
  }
}

注意: fuzzy 模糊查询 最大模糊错误 必须在0-2之间

  • 搜索关键词长度为 2,不允许存在模糊
  • 搜索关键词长度为3-5,允许1次模糊
  • 搜索关键词长度大于5,允许最大2次模糊

高亮highlight

可以让符合条件的文档中的关键词高亮。
highlight相关属性:

  • pre_tags 前缀标签
  • post_tags 后缀标签
  • tags_schema 设置为styled可以使用内置高亮样式
  • require_field_match 多字段高亮需要设置为false
GET /products/_search
{
  "query": {
    "term": {
      "name": {
        "value": "牛仔"
      }
    }
  },
  "highlight": {
  #指定高亮的前后标签
    "pre_tags": [
      "<font color='red'>"
    ],
    "post_tags": [
      "<font/>"
    ],
    "require_field_match": "false",
    # 指定需要高亮的字段
    "fields": {
      "name": {},
      "desc": {}
    }
  }
}

Boosting控制相关性得分

参数boost的含义:

  • 当boost > 1时,打分的权重相对性提升
  • 当0 < boost <1时,打分的权重相对性降低
  • 当boost <0时,贡献负分
    返回匹配positive查询的文档并降低匹配negative查询的文档相似度分。
GET /test_score/_search
{
  "query": {
    "boosting": {
      "positive": {
        "term": {
          "content": "elasticsearch"
        }
      },
      #匹配消极的
      "negative": {
        "term": {
          "content": "like"
        }
      },
      #消极的会乘上这个系数
      "negative_boost": 0.2
    }
  }
}

布尔查询bool Query

  1. 一个bool查询,是一个或者多个查询子句的组合,总共包括4种子句,其中2种会影响算分,2种不影响算分。
    • must: 相当于&& ,必须匹配,贡献算分
    • should: 相当于|| ,选择性匹配,贡献算分
    • must_not: 相当于! ,必须不能匹配,不贡献算分
    • filter: 必须匹配,不贡献算法
  2. 在Elasticsearch中,有Query和 Filter两种不同的Context
    • Query Context: 相关性算分
    • Filter Context: 不需要算分 ,可以利用Cache,获得更好的性能
  3. 如果多条查询子句被合并为一条复合查询语句,比如 bool查询,则每个查询子句计算得出的评分会被合并到总的相关性评分中。

单字符串多字段查询

最佳字段(Best Fields)

  1. 当字段之间相互竞争,又相互关联。例如,对于博客的 title和 body这样的字段,评分来自最匹配字段。
  2. 最佳字段查询Dis Max Query,通过tie_breaker参数调整。Tier Breaker是一个介于0-1之间的浮点数。0代表使用最佳匹配;1代表所有语句同等重要。
    • 获得最佳匹配语句的评分_score 。
    • 将其他匹配语句的评分与tie_breaker相乘
    • 对以上评分求和并规范化
#构建数据
PUT /blogs/_doc/1
{
  "title": "Quick brown rabbits",
  "body": "Brown rabbits are commonly seen."
}

PUT /blogs/_doc/2
{
  "title": "Keeping pets healthy",
  "body": "My quick brown fox eats rabbits on a regular basis."
}

#使用bool should的算法,得到的id为1的分比较高,而id为2的body有完全匹配的缺排在后面
POST /blogs/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "title": "Brown fox"
          }
        },
        {
          "match": {
            "body": "Brown fox"
          }
        }
      ]
    }
  }
}

#采用dis_max可以得到想要的结果
POST /blogs/_search
{
  "query": {
    "dis_max": {
      "queries": [
        {
          "match": {
            "title": "Quick pets"
          }
        },
        {
          "match": {
            "body": "Quick pets"
          }
        }
      ],
      "tie_breaker": 0.2
    }
  }
}

bool should的算法过程:

  • 查询should语句中的两个查询
  • 加和两个查询的评分
  • 乘以匹配语句的总数
  • 除以所有语句的总数

Multi Match Query最佳字段(Best Fields)搜索
Best Fields是默认类型,可以不用指定

POST /blogs/_search
{
  "query": {
    "multi_match": {
      "type": "best_fields",
      "query": "Quick pets",
      "fields": [
        "title",
        "body"
      ],
      "tie_breaker": 0.2
    }
  }
}

使用多数字段(Most Fields)搜索

#title字段使用english分词器,title的子字段std使用standard分词器
#给title字段的评分进行*10
GET /titles/_search
{
  "query": {
    "bool": {
      "query": "barking dogs",
      "type": "most_fields",
      "fields": [
        "title^10",
        "title.std"
      ]
    }
  }
}

跨字段(Cross Field)搜索

  1. Cross Field可以跨多个字段进行匹配,对比copy_to,可以节省空间
GET /address/_search
{
  "query": {
    "multi_match": {
      "query": "湖南常德",
      "type": "cross_fields",
      "operator": "and",
      "fields": [
        "province",
        "city"
      ]
    }
  }
}

8.聚合操作

数据准备

#创建索引库
PUT /employees
{
  "mappings": {
    "properties": {
      "age": {
        "type": "integer"
      },
      "gender": {
        "type": "keyword"
      },
      "job": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 50
          }
        }
      },
      "name": {
        "type": "keyword"
      },
      "salary": {
        "type": "integer"
      }
    }
  }
}

#插入数据
PUT /employees/_bulk
{"index":{"_id":"1"}}
{"name":"Emma","age":32,"job":"Product Manager","gender":"female","salary":35000}
{"index":{"_id":"2"}}
{"name":"Underwood","age":41,"job":"Dev Manager","gender":"male","salary":50000}
{"index":{"_id":"3"}}
{"name":"Tran","age":25,"job":"WebDesigner","gender":"male","salary":18000}
{"index":{"_id":"4"}}
{"name":"Rivera","age":26,"job":"Web Designer","gender":"female","salary":22000}
{"index":{"_id":"5"}}
{"name":"Rose","age":25,"job":"QA","gender":"female","salary":18000}
{"index":{"_id":"6"}}
{"name":"Lucy","age":31,"job":"QA","gender":"female","salary":25000}
{"index":{"_id":"7"}}
{"name":"Byrd","age":27,"job":"QA","gender":"male","salary":20000}
{"index":{"_id":"8"}}
{"name":"Foster","age":27,"job":"Java Programmer","gender":"male","salary":20000}
{"index":{"_id":"9"}}
{"name":"Gregory","age":32,"job":"Java Programmer","gender":"male","salary":22000}
{"index":{"_id":"10"}}
{"name":"Bryant","age":20,"job":"Java Programmer","gender":"male","salary":9000}
{"index":{"_id":"11"}}
{"name":"Jenny","age":36,"job":"Java Programmer","gender":"female","salary":38000}
{"index":{"_id":"12"}}
{"name":"Mcdonald","age":31,"job":"JavaProgrammer","gender":"male","salary":32000}
{"index":{"_id":"13"}}
{"name":"Jonthna","age":30,"job":"JavaProgrammer","gender":"female","salary":30000}
{"index":{"_id":"14"}}
{"name":"Marshall","age":32,"job":"Javascript Programmer","gender":"male","salary":25000}
{"index":{"_id":"15"}}
{"name":"King","age":33,"job":"Java Programmer","gender":"male","salary":28000}
{"index":{"_id":"16"}}
{"name":"Mccarthy","age":21,"job":"Javascript Programmer","gender":"male","salary":16000}
{"index":{"_id":"17"}}
{"name":"Goodwin","age":25,"job":"Javascript Programmer","gender":"male","salary":16000}
{"index":{"_id":"18"}}
{"name":"Catherine","age":29,"job":"JavascriptProgrammer","gender":"female","salary":20000}
{"index":{"_id":"19"}}
{"name":"Boone","age":30,"job":"DBA","gender":"male","salary":30000}
{"index":{"_id":"20"}}
{ "name" : "Kathy","age":29,"job":"DBA","gender":"female","salary": 20000}

Metric Aggregation,可以对文档字段进行统计分析,类比Mysql中的 min(), max(), sum() 操作

POST /employees/_search
{
  "size": 0,
  "aggs": {
    "max_salary": {
      "max": {
        "field": "salary"
      }
    },
    "min_salary": {
      "min": {
        "field": "salary"
      }
    },
    "avg_salary": {
      "avg": {
        "field": "salary"
      }
    },
    "stats_salary": {
      "stats": {
        "field": "salary"
      }
    }
  }
}

#去重
POST /employees/_search
{
  "size": 0,
  "aggs": {
    "cardinate": {
      "cardinality": {
        "field": "job.keyword"
      }
    }
  }
}

Bucket Aggregation分桶聚合

  1. Terms,需要字段支持filedata,keyword 默认支持fielddata,text也可以开启fielddata,但对文档分词后分桶没啥作用。
  2. 数字类型,可以Range / Data Range,Histogram(直方图) / Date Histogram进行分桶
  3. 支持嵌套: 也就在桶里再做分桶
#先进行分桶,然后对桶内进行聚合
POST employees/_search
{
  "size": 0,
  "aggs": {
    "Job_salary_stats": {
      "terms": {
        "field": "job.keyword"
      },
      "aggs": {
        "salary": {
          "stats": {
            "field": "salary"
          }
        }
      }
    }
  }
}

#按范围进行分桶
POST employees/_search
{
  "size": 0,
  "aggs": {
    "salary_range": {
      "range": {
        "field": "salary",
        "ranges": [
          {
            "to": 10000
          },
          {
            "from": 10000,
            "to": 20000
          },
          {
            "key": ">20000",
            "from": 20000
          }
        ]
      }
    }
  }
}

#直方图分桶,以5000为一个区间
POST employees/_search
{
  "size": 0,
  "aggs": {
    "salary_histrogram": {
      "histogram": {
        "field": "salary",
        "interval": 5000,
        "extended_bounds": {
          "min": 0,
          "max": 100000
        }
      }
    }
  }
}

top_hits应用场景: 当获取分桶后,桶内最匹配的顶部文档列表

#返回每个分桶内年龄最大的3个
POST /employees/_search
{
  "size": 0,
  "aggs": {
    "jobs": {
      "terms": {
        "field": "job.keyword"
      },
      "aggs": {
        "old_employee": {
          "top_hits": {
            "size": 3,
            "sort": [
              {
                "age": {
                  "order": "desc"
                }
              }
            ]
          }
        }
      }
    }
  }
}

Pipeline Aggregation支持对聚合分析的结果,再次进行聚合分析。
min_bucket使用

# 平均工资最低的工种
POST employees/_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"
      }
    }
  }
}

聚合分析不精确原因分析

  1. 数据分散到多个分片,聚合是每个分片的取 Top X,然后在协调节点进行数据聚合,可能导致结果不精准。
  2. 解决方式
    • 设置主分片为1
    • 调大 shard_size 值。这个值是聚合时候每次从每个分片获取shard_size条。shard_size数值越大,结果越准确,但性能越差。官方推荐这个值为size*1.5+10。

聚合性能优化

  1. 启用 eager global ordinals 提升高基数聚合性能
  2. 插入数据时对索引进行预排序
    • 会导致写性能下降
  3. 使用节点查询缓存,可用于有效缓存过滤器(filter)操作的结果
  4. 使用分片请求缓存,设置:size:0,就会使用分片请求缓存缓存结果。
  5. 拆分聚合,使聚合并行化。借助msearch实现并行聚合。
    • 默认情况下,聚合不是并行运行。所以可以用msearch来优化并行聚合

8.Elasticsearch集群架构

核心概念

集群

  • 一个集群可以有一个或者多个节点
  • 不同的集群通过不同的名字来区分,默认名字“elasticsearch“
  • 通过配置文件修改,或者在命令行中 -E cluster.name=es-cluster进行设定

节点

  • master node:可以允许作为主节点的节点。被选为主节点的作用:
    • 处理索引的创建删除
    • 决定分片被分配到哪个节点
    • 维护并且更新Cluster State
  • data node:存储数据的节点
    • 通过增加数据节点可以解决数据水平扩展和解决数据单点问题。原有存在的索引需要重建索引才能做到水平拓展。
  • Coordinating Node:协调节点。负责接受Client的请求, 将请求分发到合适的节点,最终把结果汇集到一起
  • Ingest Node:数据前置处理转换节点,支持pipeline管道设置,可以使用ingest对数据进行过滤、转换等操作
  • 其他节点类型:Hot & Warm Node,Machine Learning Node

分片

  • 主分片
    • 数据水平拓展
    • 一个分片就是一个运行的lucene实例
    • 主分片数在索引创建时候指定,不允许修改,只能通过Reindex来重建
  • 副本分片
    • 解决数据高可用
    • 可以动态调整副本分片数
    • 增加副本数,可以一定程度提升服务可用性,读取的吞吐
    • 副本会影响写入数据的性能
  • 分片带来的问题
    • 影响聚合查询结果和相关性打分。
      • 相关性得分是在每个分片上面单独计算的,可以通过指定search_type=dfs_query_then_fetch来解决,这个原理是把数据从每个分片查询到协调节点,在协调节点上进行重新算分。
    • 分片数量不宜过多,对master节点维护元数据加大负担

集群的搭建

修改/etc/hosts

vim /etc/hosts
192.168.65.174 es‐node1
192.168.65.192 es‐node2
192.168.65.204 es‐node3

修改每个节点的elasticsearch.yml

#192.168.65.174的配置
# 指定集群名称3个节点必须一致
cluster.name: es‐cluster
#指定节点名称,每个节点名字唯一
node.name: node‐1
#是否有资格为master节点,默认为true
node.master: true
#是否为data节点,默认为true
node.data: true
# 绑定ip,开启远程访问,可以配置0.0.0.0
network.host: 0.0.0.0
#用于节点发现
discovery.seed_hosts: ["es‐node1", "es‐node2", "es‐node3"]
#7.0新引入的配置项,初始仲裁,仅在整个集群首次启动时才需要初始仲裁。
#该选项配置为node.name的值,指定可以初始化集群节点的名称
cluster.initial_master_nodes: ["node‐1","node‐2","node‐3"]
#解决跨域问题
http.cors.enabled: true
http.cors.allow‐origin: "*"

#192.168.65.192的配置
cluster.name: es‐cluster
node.name: node‐3
node.master: true
node.data: true
network.host: 0.0.0.0
discovery.seed_hosts: ["es‐node1", "es‐node2", "es‐node3"]
cluster.initial_master_nodes: ["node‐1","node‐2","node‐3"]
http.cors.enabled: true
http.cors.allow‐origin: "*"

#192.168.65.204的配置
cluster.name: es‐cluster
node.name: node‐2
node.master: true
node.data: true
network.host: 0.0.0.0
discovery.seed_hosts: ["es‐node1", "es‐node2", "es‐node3"]
cluster.initial_master_nodes: ["node‐1","node‐2","node‐3"]
http.cors.enabled: true
http.cors.allow‐origin: "*"

可以通过安装 Cerebro来访问集群。下载地址https://github.com/lmenezes/cerebro/releases/download/v0.9.4/cerebro-0.9.4.zip

生产环境常见部署结构

大量读请求架构,通过协调节点处理查询
在这里插入图片描述

读写分离架构
通过引入数据处理节点和协调节点处理不同业务
在这里插入图片描述

跨集群搜索方案Cross Cluster Search(CCS)

在每个集群上进行配置

//在每个集群上设置动态的设置
PUT _cluster/settings
{
  "persistent": {
    "cluster": {
      "remote": {
        "cluster0": {
          "seeds": [
            "127.0.0.1:9300"
          ],
          "transport.ping_schedule": "30s"
        },
        "cluster1": {
          "seeds": [
            "127.0.0.1:9301"
          ],
          "transport.compress": true,
          "skip_unavailable": true
        },
        "cluster2": {
          "seeds": [
            "127.0.0.1:9302"
          ]
        }
      }
    }
  }
}

跨集群搜索

#查询结果获取到所有集群符合要求的数据
#指定每个集群的索引
GET /users,cluster1:users,cluster2:users/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 30,
        "lte": 40
      }
    }
  }
}

Elasticsearch读写工作原理

写请求是写入 primary shard,然后同步给所有的 replica shard;读请求可以从 primaryshard 或 replica shard 读取,采用的是随机轮询算法。

ES写入数据的过程

  1. 客户端选择一个node发送请求过去,这个node就是coordinating node (协调节点)
  2. coordinating node,对document进行路由,将请求转发给对应的node
  3. node上的primary shard处理请求,然后将数据同步到replica node
  4. coordinating node如果发现primary node和所有的replica node都搞定之后,就会返回请求到客户端
    在这里插入图片描述

ES读取数据的过程
根据id查询数据的过程。根据 doc id 进行 hash,判断出来当时把 doc id 分配到了哪个 shard 上面去,从那个shard 去查询。

  1. 客户端发送请求到任意一个 node,成为 coordinate node 。
  2. coordinate node 对 doc id 进行哈希路由,将请求转发到对应的 node,此时会使用 round-robin 随机轮询算法,在 primary shard 以及其所有 replica 中随机选择一个,让读请求负载均衡。
  3. 接收请求的 node 返回 document 给 coordinate node 。
  4. coordinate node 返回 document 给客户端。

根据关键词查询数据的过程

  1. 客户端发送请求到一个 coordinate node 。
  2. 协调节点将搜索请求转发到所有的 shard 对应的 primary shard 或 replica shard ,都可以。
  3. query phase:每个 shard 将自己的搜索结果返回给协调节点,由协调节点进行
  4. 数据的合并、排序、分页等操作,产出最终结果。
  5. fetch phase:接着由协调节点根据 doc id 去各个节点上拉取实际的document 数据,最终返回给客户端。有点类似mysql的回表查询

写数据底层原理
在这里插入图片描述

  • segment file: 存储倒排索引的文件,每个segment本质上就是一个倒排索引,每秒都会生成一个segment文件,当文件过多时es会自动进行segment merge(合并文件),合并时会同时将已经标注删除的文档物理删除。
  • commit point: 记录当前所有可用的segment,每个commit point都会维护一个.del文件,即每个.del文件都有一个commit point文件(es删除数据本质是不属于物理删除),当es做删改操作时首先会在.del文件中声明某个document已经被删除,文件内记录了在某个segment内某个文档已经被删除,当查询请求过来时在segment中被删除的文件是能够查出来的,但是当返回结果时会根据commit point维护的那个.del文件把已经删除的文档过滤掉
  • translog日志文件: 为了防止elasticsearch宕机造成数据丢失保证可靠存储,es会将每次写入数据同时写到translog日志中。
  • os cache:操作系统里面,磁盘文件其实都有一个东西,叫做os cache,操作系统缓存,就是说数据写入磁盘文件之前,会先进入os cache,先进入操作系统级别的一个内存缓存中去
  • Refresh: 将文档先保存在Index buffer中,以refresh_interval为间隔时间,定期清空buffer,生成 segment,借助文件系统缓存的特性,先将segment放在文件系统缓存中,并开放查询,以提升搜索的实时性
  • Translog: Segment没有写入磁盘,即便发生了当机,重启后,数据也能恢复,从ES6.0开始默认配置是每次请求都会落盘
  • Flush: 删除旧的translog 文件生成Segment并写入磁盘│更新commit point并写入磁盘。ES自动完成,可优化点不多
  • 30
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值