ElasticSearch是一个基于Lucene的 搜索引擎 以及 存储引擎。
它提供了一个 分布式 的 全文搜索引擎,其对外服务是基于RESTful web接口发布的。
Elasticsearch是用Java开发的应用,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。
设计用于云计算中,能够达到近实时搜索,稳定,可靠,快速,安装使用方便。
官网https://www.elastic.co
Github
使用Elasticsearch搜索20TB
的数据,包括13亿的文件和1300亿行的代码”。
Github在2013年1月升级了他们的代码搜索,由solr转为Elasticsearch,
目前 集群规模为26个索引存储节点 和 8个客户端节点(负责处理搜索请求)。
ELK,分别是 ES, Logstash、Kibana 。
在发展过程中 新的成员Beats的加入, 就形成了 Elastic Stack (生态圈),
ES是 该 生态圈的 基石,Kibana提供可视化操作, Logstash和 Beats可以对数据进行收集
在国内,阿里巴巴、腾讯、滴滴、今日头条、饿了么、360安全、小米,vivo 等诸多知名公司都在使用Elasticsearch。
Solr是 第一个 基于Lucene核心库功能完备的搜索引擎产品,诞生远早于Elasticsearchs
当 单纯的对 已有数据 进行搜索时,Solr更快。
当 实时建立索引时, Solr会产生IO阻塞,查询性能较差,ES具有明显优势。
大型互联网公司,实际生产环境测试,ES的 平均查询速度 是 Solr的 50倍。
- Lucene 7.x
- 新功能
- 跨 集群 复制 (CCR)
- 索引 生命周期 管理
- SQL 的支持
- 更 友好的 升级 及 数据 迁移
- 在 主要版本 之间 的 迁移更为简化,体验升级
- 全新的 基于操作的数据复制框架,可加快恢复数据
- 性能优化
- 有效 存储 稀疏字段 的 新方法 , 降低了存储成本
- 在 索引 时进行 排序 , 可加快 排序的 查询 性能
- Lucene 8.0
- 重大改动,
废除 单个 索引 下 多Type 的支持
- Security 功能免费使用
- 性能优化
- 重大改动,
彻底删除 Type
- 默认开启安全配置
- 性能优化
- 站内搜索
- 日志管理、分析
- 大数据分析
- 应用性能检测
- 机器学习
docker pull elasticsearch:7.9.2
docker run -d --name esearch \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" -e "discovery.type=single-node" \
-p 9200:9200 -p 9300:9300 \
elasticsearch:7.9.2
# 进入容器 进行 跨域配置
docker exec -it esearch /bin/bash
vi config/elasticsearch.yml
# 加入以下信息:
http.cors.enabled: true
http.cors.allow-origin: "*"
# 测试是否 成功启动: 访问elocalhost:9200 获得如下信息
{
"name" : "bce8e8d3cddf",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "chngjxFJTQStsI70tOn2HQ",
"version" : {
"number" : "7.9.2",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "d34da0ea4a966c4e49417f2da2f244e3e97b4e6e",
"build_date" : "2020-09-23T00:45:33.626720Z",
"build_snapshot" : false,
"lucene_version" : "8.6.2",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
docker pull mobz/elasticsearch-head:5-alpine
docker run -d --name es-head -p 9100:9100 mobz/elasticsearch-head:5-alpine
# 测试是否 成功启动: 访问elocalhost:9100 得到 UI界面
# ElasticSearch-head 在进行操作时 若不修改配置,会报 406错误码,
# 这里需要再对 ElasticSearch-head 进行 配置修改。
docker cp es-head:/usr/src/app/_site/vendor.js ./ # 因为该容器中没有vi,所以拷出来修改
vim vendor.js
### 修改一下两部分:
part1: 第6886行 contentType:"application/x-www-form-urlencoded"
改为:contentType:"application/json;charset=UTF-8"
par2: 第7573行 var inspectData = s.contentType === "application/x-www-form-urlencoded" &&
改为:var inspectData = s.contentType === "application/json;charset=UTF-8" &&
# 完成修改,将文件 复制回容器(即 覆盖掉同名文件)
docker cp ./vendor.js es-head:/usr/src/app/_site
# 重启容器
docker restart es-head
# 浏览器在重新打开一下,别直接刷新,就可以操作ES了
全文检索: 通过一个程序 扫描文本 中的每一个单词, 针对单词 建立索引, 并保存 该单词在文本中的位置、以及出现的次数。
.
倒排索引:通过 搜索的关键字 在 倒排索引表 中找到 索引(id),然后再 通过 找到的索引 在 正排索引 中 找到数据。
例子:
id | content |
---|---|
1001 | my name is zhang san |
1002 | my name is li si |
key | id |
---|---|
name | 1001, 1002 |
zhang | 1001 |
正排查找: id ===> value 通过 索引 找 数据
倒排查找: value ===> id 通过 关键字 找 索引, 然后在 通过索引 找 数据。
目录 | 描述 |
---|---|
bin | 脚本文件,包括 启动ES、安装插件、运行、统计数据等 |
config | 配置文件目录 |
jdk | java运行环境 |
data | 默认的数据存放目录,包括节点、分片、索引、文档,生产环境需要修改 |
lib | es依赖的java类库 |
logs | 日志文件存放路径,生产环境需要修改 |
modules | 包含所有的ES模块 |
plugins | 已经安装的插件目录 |
当前节点 所属 集群名称,
多个节点如果要组成同一个集群,那么集群名称一定要配置成相同。
默认值elasticsearch,生产环境建议根据ES集群的使用目的修改成合适的名字。
当前节点名称,
默认值当前节点部署所在机器的主机名,所以如果一台机器上要起多个ES节点的话,需要通过配置该属性明确指定不同的节点名称。
配置 数据存储目录,
比如索引数据等,默认值$ES_HOME/data,
生产环境下强烈建议部署到另外的安全目录,防止Es升级 导致数据被误删除。
配置 日志存储目录,
比如运行日志和集群健康信息等,默认值$ES_HOME/logs,生产环境下强烈建议部署到另外的安全目录,
防止ES升级导致数据被误删除。
ES启动时 是否进行 内存锁定, 默认 true。
ES对于内存的需求比较大
,一般生产环境建议 配置大内存
,
如果内存不足,容易导致内存交换到磁盘,严重影响ES的性能。
所以默认在启动时进行相应大小内存的锁定,如果无法锁定则会启动失败。
在 config/jvm.option 配置文件中, Xms和 Xmx设置成一样, 但是不要超所 主机内存的 50%
配置 可以访问 当前节点的 主机。
默认值为 仅本机访问,可以配置为0.0.0.0,表示 所有主机 均可访问。
对外提供服务的端口,默认是 9200
配置 参与集群 节点 发现过程 的 主机列表,
说白一点就是 集群中 所有节点 所在的主机列表,可以是IP、域名。
配置 ES集群 初始化是 参与 master 选举的 节点名称列表,必须和 node.name配置一致。
ES集群首次 构建完成后,应该将 集群中所有节点 的配置文件 中的 clusterinitial_master_nodes配置项移除,
重启集群 或者 将新节点加入某个已存在的集群时 切记 不要设置该配置项。
cluster集群。ElasticSearch集群由 一 或 多个 节点组成,
其中有一个主节点
,这个主节点是可以通过选举产生的,主从节点是对于集群内部
来说的。
ElasticSearch的一个概念就是 去中心化,字面上理解就是无中心节点,这是对于集群外部
来说的,
因为从外部看ElasticSearch集群,在逻辑上是个整体,你与集群中的任何一个节点通信和与整个ElasticSearch集群通信是等价的。
也就是说,主节点的存在不会产生单点安全隐患、并发访问瓶颈等问题。
primary shard:代表索引的主分片,ElasticSearch可以把一个完整的索引分成多个primary shard,
这样的好处是可以把一个大的索引拆分成多个分片,分布存储在不同的ElasticSearch节点上,
从而形成分布式存储,并为搜索访问提供分布式服务,提高并发处理能力。
primary shard的数量
只能在索引创建时指定,并且索引创建后不能再更改primary shard数量 (重新分片需要重新定义分片规则)。
primary shard的数量
es5.x之后默认为5
,es7.x默认为1
。
replica shard:代表索引主分片的副本,ElasticSearch可以设置多个replica shard。可取值为0~n,默认为1。
replica shard的作用:
- 是提高系统的
容错性
,当某个节点某个primary shard损坏或丢失时可以从副本中恢复。- 是提高ElasticSearch的查询效率,ElasticSearch会自动对搜索请求进行负载均衡,将并发的搜索请求发送给合适的节点,增强
并发能力
。
mysql | 数据库 | 数据表 | 记录 | 字段 |
---|---|---|---|---|
ES | Index(索引) | Type(类型) | Document(文档) | Field |
注意:在8.x之后 ,Type被砍掉以后,Index 既是 数据库,又是 数据表。
这里以安装 分词器 为例,至于什么是 分词器,下面会进行解释说明。
# 查看已经安装的插件
bin/elasticsearch-plugin list
# 安装 analysis-icu 插件, 重启ES生效
bin/elasticsearch-plugin install analysis-icu
# 卸载插件,重启ES生效
bin/elasticsearch-plugin remove analysis-icu
下载 相应的插件 到本地, 解压后,手动上传到 ES的 plugins目录,然后重启 ES就可以了。
前面提到的 全文检索 就是 通过 分词器 来完成的,
分词器 是对 文件 进行 字词 划分的 唯一单位。
ES默认的分词器 是 standard 对中文不是太友好,分词的依据 就是将 单字 拆分。
ik中文分词器 是对中文 比较有好的 。
# 测试分词器 的分词 效果
POST 请求
/_analyze 请求路径
请求体:
{
"analyzer": "icu_analyzer",
"text":"我爱你中国"
}
ik分词器的测试:
一、粗粒度-少分次:一般用于 文章名称、人的姓名 等 不希望进一步拆分 的信息
POST /_analyze
{
"analyzer": "ik_smart",
"text":"我爱你中国"
}
分词效果: 我爱你中国 ===》 我爱你中国
二、细粒度-分多次:
{
"analyzer": "ik_max_word",
"text":"我爱你中国"
}
分词效果: 我爱你中国 ===》 我爱你中国、我爱你、爱你、中国
在创建 索引时 可以 指定 分词器:
{
"settings":{
"index": {
"analysis.analyzer.default.type": "ik_max_word"
}
}
}
这个地方 对 后面的 布尔查询 的算分 有理解上的帮助。
.
搜索行为 是 用户 和 搜索引擎 的交互, 用户往往关心 的是 搜索结果 的 相关性。
搜索的相关性算分, 描述的指标是 返回的文档 和 关键字的 匹配程度。
ES 会对 每一个 匹配查询 的结果 进行 算分 _score.
打分 的本质是 排序, 把 符合预期的 排在 前面。
ES 5 之前 算分的算法 使用的是 TF-IDF, 之后使用的是 BM 25, 是对 前者的 优化。
是一种 用于 信息检索 和 数据挖掘 的 常用 加权技术。
是 被公认 的 信息检索 领域 的 最重要的 发明。
其 公式的 三个 决断 算分 结果 的 变量如下:
- 词频TF: 对于 某一条 数据而言, 检索词 出现的频率越高, 相关性越高。
- 逆向文本频率IDF: 对于 多条 数据而言, 检索词 出现的频率越低, 相关性越高。举例:
我们 搜索: ES java, 假如一共有10条数据, 每条数据都有 ES信息, 只有 前两条 有java信息, 明显 这里 ES的频率 高于 java, 但是 java的 相关性 要 大于 ES。- 字段长度归一值: 为什么 字段长度越短 权重越高,因为 字段越长 越有出现 信息冗余的可能,每个人组织语言的能力都是有一定缺陷的,话说的越多 废话 就越多。
BM25 优化 TF-IDF 算法, 减少 分数计算的 资源损耗。
词频不断增加时, TF-IDF 算法 的打出的 分数 是 无限增加, 而 BM25是趋近于一个 数值。
一个索引 就是一个
拥有 几分相似特征
的文档的集合。
索引的命名 必须 全部是 小写字母, 不能以 下划线 开头。
索引 主要有三个大属性(部分)组成:
- aliases 别名
- mappings 文档映射:
数据字段 和 字段类型 的映射, 创建索引的时候不设置,添加数据后 会自动设置,当然也可以手动设置。- settings 设置:
设置 索引 相关的一些属性
Mapping 类似 数据库 中的 schema的定义,作用如下:
- 定义索引中的 字段名称
- 定义字段的 数据类型,例如字符串,数字,布尔等
- 字段,倒排索引 的 相关配置(AnalyzedorNotAnalyzed,Analyzer)
Mapping映射 分为 动态 和 静态:
- 动态:
在关系数据库中,需要事先创建数据库,然后在该数据库下创建数据表,并创建表字段、类型、长度、主键等,最后才能基于表插入数据。
而Elasticsearch中不需要定义Mapping映射(即关系型数据库的表、字段等),在文档写入Elasticsearch时,会根据文档字段自动识.- 静态:
事先 手动 定义好映射。
JSON类型 | ES类型 |
---|---|
字符串 | 1- 匹配日期格式,设置为Date 2- 配置数据设置为float或long,默认关闭 3- 设置为Text,并且增加keyword 字段 |
布尔值 | boolean |
浮点数 | float |
整数 | long |
对象 | object |
数组 | 由第一个非空元素决定 |
空值 | 忽略 |
- dynamic设为true时,一旦有新增字段的文档写入,Mapping也同时被更新
- dynamic设为false,Mapping不会被更新,新增字段的数据无法被索引,但是信息会出现在source中
- dynamic设置成strict(严格控制策略),文档写入失败,抛出异常
// dynamic 设置为 true | false:
PUT /wtt/_mapping
{
"dynamic": true
}
倒排索引表 一旦生成, 就不允许 修改,原因是 修改字段的数据类型 会导致 已被索引的 数据 无法被 搜索。
如果 就是任性 的想该字段,那么就 重建索引 呗。
// 重建索引
-- step1: 新建一个 静态索引,把之前的 索引的 数据 导入新的 索引中
POST /_reindex
{
"source": {
"index":"wtt"
},
"dest":{
"index":"wtt2"
}
}
-- step2: 删出原来的索引
DELETE /wtt
-- step3: 给新索引 起一个 老索引 的 别名
PUT /wtt2/_alias/wtt
PUT /wtt
{
"mapping":{
"properties": {
"address": {
"type":"text",
"index":false # address 不再被 索引了
}
}
}
}
记录内容: | doc id(文档id) | term frequency(词频) | term position(位置) | character offects(关联、影响) |
---|---|---|---|---|
docs | y |
n | n | n |
freqs | y |
y |
n | n |
positions | y |
y |
y |
n |
offsets | y |
y |
y |
y |
说明: text类型 默认记录 positions, 其他默认记为 docs。
PUT /wtt
{
"mappings": {
"name": {
"type": "text",
"index_options": "offsets" // 这里指定 记录内容
}
}
}
只有 keyword 类型 支持 null_value 的设置。
PUT /wtt
{
"mappings": {
"name": {
"type": "keyword",
"null_value": "NULL" // 这样一来,该字段就可以 通过null值 搜索了
}
}
}
# PUT: 创建 shopping 索引
http://127.0.0.1:9200/shopping
# 指定 分片数 和 副本数
PUT /shopping
{
"settings": {
"number_of_shards": 3, //分片
"number_of_replicas": 2 //副本
}
}
# GET: 查看 指定 索引
http://127.0.0.1:9200/shopping
# 查看 索引 是否存在
HEAD /shopping
# GET: 查看 全部 索引
http://127.0.0.1:9200/_cat/indices?v
# DELETE
http://127.0.0.1:9200/shopping
PUT /shopping/_settings
{
"index": {
"number_of_replicas": 3 //副本
}
}
ES 是面向 文档的, 文档(不是 字段) 是 可搜索数据的 最小单位。
文档 会被 序列化为 JSON格式,保存在 ES中。
每一个 文档 都有一个 唯一 ID,可以 自动生成 也可以 手动指定。
# seq_no 和 primary_term 属性 主要用来 并发场景下 修改文档, 是对 version 的优化
POST /wtt/_doc/11?if_seq_no=21&if_primary_term=6
{
"name":"tom"
}
-- 说明:
只有id为11的文档 满足 seq_no==21 且 primary_term == 6
才会 进行 数据加入 的操作。
通过 指定ID的方式 添加文档,
- ID不存在 :创建新的文档
- ID已存在 :先删除 原有文档,再 创建新的文档, version会增加。即
数据覆盖
# POST
http://127.0.0.1:9200/shopping/_doc
# 请求体
{
"title":"小米",
"category":"米",
"images":"http://***.png",
"price":123.01
}
# 返回 的 _id 字段 是ES随机为该条记录生成的 文档id。
# 可以通过这个文档id来查询 该条记录的文档信息。
### 创建 指定id的文档 ###
# POST: 指定id为1001
http://127.0.0.1:9200/shopping/_doc/1001
PUT也可以用来 添加数据,但是 和 POST有区别:
- POST
post本意是添加数据,所以可以不用 指定 文档id,如果制定了 文档id,那么es就会判断该 id是否存在,
存在则 更新操作,不存在 则算是 添加数据的操作。
PUT
本意是 更新数据,所以 必须 指定 文档id。
查看
# GET: 查看 指定id 文档
http://127.0.0.1:9200/shopping/_doc/1001
# GET: 查看 全部 文档
http://127.0.0.1:9200/shopping/_search
该种创建数据的方式 主要是为了 添加数据 不小心 变成了 数据覆盖 了,
所以 如果ID已经存在,那么就会 操作失败。
PUT /shopping/_create/11
{
"title":"小米",
"category":"米",
"images":"http://***.png",
"price":123.01
}
删除原有文档,创建新的文档
可以更新 部分字段, 是传统意义上的更新
# PUT: 全量更新
http://127.0.0.1:9200/shopping/_doc/1001
# 请求体中 含有 修改之后的信息
# POST: 部分更新
http://127.0.0.1:9200/shopping/_update/1001
# 请求体
{
"doc":{
"title":"华为"
}
}
POST /shopping/_update_by_query
{
"query": {
"match": {
"title":"小米手机"
}
},
"script":{
"source": "ctx,_source.age = 30"
}
}
# DELETE:
http://127.0.0.1:9200/shopping/_doc/11
GET /shopping/_doc/11
ES 提供 两种 条件查询方式:
- 条件数据 放在 query 中, 作为 URL的一部分
- 条件数据 放在 request body 中,官方推荐该种, 易读性强,后面会重点讲解
##### 放在 query 中
# 使用 q 指定查询字符串
# 搜索 年龄 在 15到35之间的, 跳过0个开始搜索,一共搜索10个数据
GET /shopping/_doc/_search?q=age[15 TO 35]&from=0&size10
##### 放在 request body 中
# GET
http://127.0.0.1:9200/shopping/_search
# 请求体
{
"query": {
"match": { //模糊 查词 匹配查询
"title": "小米手机" //查询字段 和 查询字段值
}
},
// 获取指定列的数据
"_source": [
"title",
"price"
],
// 排序
"sort": {
"price": {
"order": "desc" // 降序
}
},
// 分页
"from": 1, // 从第1条开始查
"size": 10 // 获取10条数据
}
接下来 重点 讲解 官方建议 通过 request body 的方式进行 查询,因为这种方式 可以 定义 更加易读的 json格式。
但在此之前 要 先讲一下 相关原理
索引 是加速数据查询的 重要手段,其 核心原理 就是 通过不不断的 缩小 想要获取数据的 范围, 来 筛选出 最终想要的结果。
磁盘IO 是程序设计中 非常 昂贵的 操作, 也是 影响 程序性能 的重要因素
,
因此 应当 避免过多的 磁盘IO,最直接有效的方式 就是 利用 内存。
局部性原理 告诉我们,当计算机访问一个地址的数据的时候,与其相邻的地址也会很快被访问到。
因此 预读 就是 发生一次 IO时, 不仅仅是 度全当前的 磁盘数据, 而且把相邻地址的数据也读取到内存中。
上图主要为了说明两点:
- 磁盘IO的空间关系
- 查询 数据的结果 和 词项-字典 有直接的关系, 而 词项-字典 的存储内容 有 分词器 直接指定。
所以查询数据的结果 和 分词器 息息相关
。
使用match_all, 默认 返回 10 条数据。
其原因是: 如果 全部数据有几十万条,一次性都查出来的话 内存一下子会 盛放不了 导致 宕机。
GET /wtt/_search
{
"query":{
"match_all": {
}
}
}
- size: limit
- from:offset
GET /wtt/_search
{
"query":{
"match_all": {
}
}
"size": 10,
"from": 0
}
注意:
size 不可以无线增加,size 默认 小于等于 10000,超过这个数会报错。
如果需要可以 手动修改默认值,如下PUT /wtt/_settings { "index.max_result_window": "20000" }
但是数据量需求过大的时候, 不推荐 修改上述配置,而是采用 scroll api,因为 更高效。
改动 index.max_result_window 的大小,只能解决一时的问题,当 数据持续增加时,
在 查询全量数据
时,若超过 手动指定的数据 还是会报错。
最佳的方式 还是采用 scroll api
# 查询命令中 新增 scroll=3m,说明采用 游标查询, 保持游标查询窗口 3分钟,
# 即游标变量 中存储的地址信息 在3分钟后 被 垃圾回收机制 回收掉
# 实际使用中,为了减少 游标的查询次数,可以将 size 适当增加,例如500---2000
GET /wtt/_search?scroll=3m
{
"query": {
"match_all": {}
},
"size": 10
}
-- 查询结果 除了放回 前10条记录,还返回一个 游标ID值 _scroll_id
# 下一次查询, 只需要带上 上一次的 游标ID 就可以了, ES就知道 怎么查,查什么,查多少了
GET /_search/scroll
{
"scroll": "2m",
"scroll_id": "jfldJLJfldjfLJDfjldlFJljf453JLFJfjdljl"
}
-- 多次根据 scroll_id游标查询,知道没有 数据的返回 则 结束查询。
-- 全量数据 用 游标查询 的好处:高效安全、限制单次对内存的 损耗。
GET /wtt/_search
{
"query": {
"match_all": {}
},
"sort": [
{"age": "desc"}
],
"_source": ["name", "age"]
}
我们知道,ES默认返回文档的 顺序 如果没有 sort的干预 是采用 打分排序的,
所以 可以 通过 调整 权重 来干预打分机制,
在 打分时, negative 部分 的 query 会乘以 negative_boost的值,
negative_boost 的取值范围: 0—1
-- 例如: 我们搜索 苹果 关键字时, 我们希望 苹果手机 排在前面, 而 苹果水果 排在后面
GET /wtt/_search
{
"query": {
"boosting": {
"positive":{ // 积极
"match": { "content": "apple" }
},
"positive":{ // 消极, 该部分 会乘以 negative_boost的值
"match": { "content": "pie" }
},
"negative_boost": 0.2
}
}
}
match 在匹配时 会对
所查找的 关键字 进行分词
, 然后 再 按 关键字 分词 进行匹配查找。
match支持以下参数:
- query: 指定 匹配的值
- operator: 匹配类型
- and : 关键字 的 分词 都要 匹配上
- or : 关键字 的 分词 至少有一个 能匹配上
- minmum_should_match: 最低匹配度, 配合 or的情况,因为or默认是1个,该配置 指定最少匹配的关键词数
GET /wtt/_search
{
"query": {
"match":{
"film_name": {
"query": "你好李焕英",
"operator": "and"
}
}
}
}
match_phrase查询分析文本 并 根据 分析的文本 创建一个 短语查询。
match_phrase 也会将 管理子 分词, 但是匹配机制 更严格:
- 分词结果 必须 都被匹配上。
- 分词结果 的匹配 顺序必须相同。
- 分词结果 的匹配 默认都是连续的。
-- 举例说明: 现ES存储一条文档, 字段address的内容是 “广州白云山” ===分词的顺序、结果为===>广州、白云山、白云
-- 查找1
GET /wtt/_search
{
"query": {
"match_phrase": {
"address": "广州白云山"
}
}
}
-- 查找2
GET /wtt/_search
{
"query": {
"match_phrase": {
"address": "广州白云"
}
}
}
--- 结果分析:
查找1 命中了数据, 查找2 没用命中一条数据。
这只因为 查找2的 搜索词被 拆成了 广州、白云, 文档库中的 分词是: 广州、白云山、白云
虽然 满足 1、全命中 2、顺序相同
但是不满足 3、连续, 明显 文档的分词 广州 和 白云 之前还隔着 一个 白云山。
-- 解决方法:
通过 slop 参数 告诉 match_phrase 中间 隔 几个词 也可以认为是连续的
GET /wtt/_search
{
"query": {
"match_phrase": {
"address": {
"query": "广州白云",
"slop": 1 // 这样一来 就能匹配上了
}
}
}
}
GET /wtt/_search
{
"query": {
"multi_match":{
"query": "你好",
"fields": ["address", "name"] // 这 俩字段 只要能 匹配上 你好 的 文档 都可以被命中
}
}
}
允许我们在单个查询 字符串 中指定 AND | OR | NOT 条件,
和 multi_match 一样 支持多字段搜索。
和 match 类型,但是match 需要指定 字段名, query_string在所有字段中搜索,范围更广。
注意:
- 查询的字段 使用分词, 就将 查询条件 分词查询。
- 查询的字段 未使用分词, 就将 查询条件 不分词查询。
GET /wtt/_search
{
"query": {
"query_string": {
"query":"张三 OR 山东省"
}
}
}
GET /wtt/_search
{
"query": {
"query_string": {
"default_field": "name"
"query":"张三 OR 李四"
}
}
}
GET /wtt/_search
{
"query": {
"query_string": {
"fields": ["name", "sex"]
"query":"张三 OR (李四 AND 女)"
}
}
}
term 是用来
精准查询
的,还可以用来 查询 没有被 进行分词的 数据类型。
term 是表达语义 的最小单位。
match 匹配时 会对 关键词 进行 分词处理,然后在 进行 分词匹配。
而 term不做 分词处理
, 会直接对 关键字 进行 匹配 。
因此 模糊查询的 时候 常用 match, 精准匹配 的时候 常用 term。
在ES中, keyword、date、integer、long、double、boolean、ip 这些类型不会 分词,
text类型 会分词。
-- 查找1
GET /wtt/_search
{
"query": {
"term": {
"address": {
"value": "山东省临沂市"
}
}
}
}
-- 查找2
GET /wtt/_search
{
"query": {
"term": {
"address.keyword": {
"value": "山东省临沂市"
}
}
}
}
--- 结果说明:
查找1 未命中数据, 查找2 命中数据
--- 原因解释:
term查询,不会对 山东省临沂市 进行分词, 而 address 的 text 类型 会对 山东省临沂市 ===分词为===> 山东省、临沂市。
所以 待配的 词库中 没有 能和 山东省临沂市 匹配上的。
而 address 的 keyword 类型 不会对 山东省临沂市 进行分词为, 所以可以命中数据。
所以 精准匹配的 时候 最好使用 keyword 类型
精准匹配这一块 是有个 值得 优化的点的,
每次 查询数据 es对于每一个 找到的 结果数据 都有一个分值 计算, 该分值 体现了 数据的匹配度。
在 精准查询 下, 这个 分值 没有多大意义, 所以去掉 算分动作(毕竟有资源损耗)可以优化性能。
实现机制:
将query 转成 filter 就可以 去掉 算分动作
, filter 可以有效利用 缓存。
GET /wtt/_search
{
"query": {
"contant_score": {
"filter":{
"term": {
"address.keyword": "山东省临沂市"
}
}
}
}
}
对bool、日期、数字、结构化的文本 都可以 利用 term 做 精准匹配。
GET /wtt/_search
{
"query": {
"term": {
"age": {
"value": 18
}
}
}
}
对于 多值字段, term 查询 是包含, 而不是 等于
-- 假设现在有两个文档:
{
"name": "tom",
"hobby": ["篮球", "足球"] // 多值字段
}
{
"name": "cat",
"hobby": ["游泳", "篮球"] // 多值字段
}
-- 多值字段的查询
GET /wtt/_search
{
"query": {
"term": {
"hobby.keyword": {
"value": "篮球" // 可以命中以上 两条数据
}
}
}
}
它 不会 对 关键字 进行分词, 查询的内容 就是 查找的 前缀。
它的原理: 遍历所有的 倒排索引 , 比较每个 term(基本单位)的前缀 是否能匹配上 。
它的行为 和 过滤器 很像, 区别在于 过滤器 是可以被缓存的, 它不行。
GET /wtt/_search
{
"query": {
"prefix": {
"address": {
"value": "山"
}
}
}
}
其 工作原理 和 prefix 相同,只不过 它能支持 更为复杂的 模式
GET /wtt/_search
{
"query": {
"wildcard": {
"address": {
"value": "*东*" // 可以命中 山东省
}
}
}
}
支持的 范围描述 关键字有:
- gte:大于等于
- lte:小于等于
- gt:大于
- lt:小于
-now :当前时间
GET /wtt/_search
{
"query": {
"range": {
"age": {
"gte":15,
"lte":35
}
}
}
}
GET /wtt/_search
{
"query": {
"range": {
"dates": {
"gte":"now-2y", //大于 两年前
"lte":"now-10m" //小于 10个月前
}
}
}
}
ids 关键字: 置为 数组类型, 用来 根据 一组 id 获取 对应的 多个 文档。
GET /wtt/_search
{
"query": {
"ids": {
"values": [1,2,3]
}
}
}
在实际中,我们又是 会 打错字,从而导致 搜索不到。
在ES中,使用 fuzziness 属性 来进行 模糊查询,来解决上述问题。
fuzzy 查询 会用到 两个很重要的参数:
- fuzziness: 输入的 关键字 通过几次 可以转换为 ES中 对应的 字段
- 操作是指: 新增、删除、修改,每次操作记为 1步
- 该参数值 默认为 0, 即 不开启 模糊查询。
- prefix_length: 表示 关键字 和 ES中字段 的开头前 几个 字符必须完全匹配,不可出错
- 默认值为0
- 加大该值 可以 提高 匹配准确率
GET /wtt/_search
{
"query": {
"fuzzy": {
"address": {
"value": "山冬省"
"fuzziness": 1 // 可以命中 山东省
}
}
}
}
注意: 摸出查询 的 最大模糊错误 必须在 0–2之间
关键字长度 | 是否允许存在模糊 | 模糊次数 |
---|---|---|
2 | 不允许 | |
3-5 | 允许 | 1 |
大于5 | 允许 | 2 |
hightlight 关键字 可以让 符合条件的 数据 高亮。
其相关属性:
- pre_tags 前缀标签
- post_tags 后缀标签
- tags_schema 设置为 styled 可以使用内置高亮样式
- require_field_match 多字段 高亮 需要设置为 false
GET /wtt/_search
{
"query": {
"fuzzy": {
"address": {
"value": "山冬省"
"fuzziness": 1
}
}
},
"highlight": {
"fields": {
"*"{} // 此时的高亮字段 就是 查询匹配字段 address
}
}
}
-- 可以自定义 高亮样式 且 多字段 高亮
GET /wtt/_search
{
"query": {
"fuzzy": {
"address": {
"value": "山冬省"
"fuzziness": 1
}
}
},
"highlight": {
"pre_tags": [""
],
"post_tags": [""],
"fields": {
"name":{} , // 也可以是 没有 查找匹配的 文档字段
"address":{}
}
}
}
一个 bool 查询 是 一个 or 多个 查询子句 的 组合, 总共包括 4 种句子。
其中 2 种 会影响 打分, 2种 不影响 打分。
子句类型 | 相当于 | 匹配说明 | 是否贡献算分 |
---|---|---|---|
must | && | 必须匹配 | 贡献 |
should | || | 选择性匹配 | 贡献 |
must_not | ! | 必须不能匹配 | 不 |
filter | 必须匹配 | 不 |
在ES中, 有 Query 和 Filter 两种不同的 Context
- Query Context: 相关性 算法
- Filter Context: 不需要算法, 可以利用 cache, 获得 更好的性能
子查询 可以 任意顺序出现,
可以 嵌套多个查询,
GET /wtt/_search
{
"query": {
"bool": {
"must": [
{ "term": { "sex": { "value": 1 } }},
{ "match": { "address": "山东临沂" }}
],
"shuold": [
{ "term": { "sex": { "value": 1 } }},
{ "match": { "address": "山东临沂" }}
],
"minimum_should_match": 1 // shuold 下的俩个条件 至少满足 1 个
}
}
}
演示一下 bool 嵌套
GET /wtt/_search
{
"query": {
"bool": { // 第一层
"must": [
{
"bool": { // 第二层
"must": [
{ "term": { "sex": { "value": 1 } }},
{ "match": { "address": "山东临沂" }}
]
}
}
]
}
}
}
{
"aggs":{ // 聚合操作
"price_group":{ // 名称,自定义
"avg":{ // 平均值
"field":"price" // 分组字段
}
}
}
}