ElasticSearch 哪里不会点哪里

介绍

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。

ElasticSearch VS Solr

Solr是 第一个 基于Lucene核心库功能完备的搜索引擎产品,诞生远早于Elasticsearchs
当 单纯的对 已有数据 进行搜索时,Solr更快。
当 实时建立索引时, Solr会产生IO阻塞,查询性能较差,ES具有明显优势。
大型互联网公司,实际生产环境测试,ES的 平均查询速度 是 Solr的 50倍。

版本特性

6.x新特性
  • Lucene 7.x
  • 新功能
  • 跨 集群 复制 (CCR)
  • 索引 生命周期 管理
  • SQL 的支持
  • 更 友好的 升级 及 数据 迁移
  • 在 主要版本 之间 的 迁移更为简化,体验升级
  • 全新的 基于操作的数据复制框架,可加快恢复数据
  • 性能优化
  • 有效 存储 稀疏字段 的 新方法 , 降低了存储成本
  • 在 索引 时进行 排序 , 可加快 排序的 查询 性能
7.x新特性
  • Lucene 8.0
  • 重大改动废除 单个 索引 下 多Type 的支持
  • Security 功能免费使用
  • 性能优化
8.x新特性
  • 重大改动彻底删除 Type
  • 默认开启安全配置
  • 性能优化

应用场景

  • 站内搜索
  • 日志管理、分析
  • 大数据分析
  • 应用性能检测
  • 机器学习

Docker 安装

  • ES 安装
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"
}
  • ES可视化(ES-header)安装
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),然后再 通过 找到的索引 在 正排索引 中 找到数据。

例子:

  • 正排索引
idcontent
1001my name is zhang san
1002my name is li si
  • 倒排索引(在 正排索引 的基础上 通过 全文检索 创建出 倒排索引表)
keyid
name1001, 1002
zhang1001

正排查找: id ===> value 通过 索引 找 数据
倒排查找: value ===> id 通过 关键字 找 索引, 然后在 通过索引 找 数据。

*_*ES文件目录结构

目录描述
bin脚本文件,包括 启动ES、安装插件、运行、统计数据等
config配置文件目录
jdkjava运行环境
data默认的数据存放目录,包括节点、分片、索引、文档,生产环境需要修改
libes依赖的java类库
logs日志文件存放路径,生产环境需要修改
modules包含所有的ES模块
plugins已经安装的插件目录

*_*ES主配置文件 elasticsearch.yml

  • cluster.name

当前节点 所属 集群名称,
多个节点如果要组成同一个集群,那么集群名称一定要配置成相同。
默认值elasticsearch,生产环境建议根据ES集群的使用目的修改成合适的名字。

  • node.name

当前节点名称,
默认值当前节点部署所在机器的主机名,所以如果一台机器上要起多个ES节点的话,需要通过配置该属性明确指定不同的节点名称。

  • path.data

配置 数据存储目录,
比如索引数据等,默认值$ES_HOME/data,
生产环境下强烈建议部署到另外的安全目录,防止Es升级 导致数据被误删除

  • path.logs

配置 日志存储目录,
比如运行日志和集群健康信息等,默认值$ES_HOME/logs,生产环境下强烈建议部署到另外的安全目录,
防止ES升级导致数据被误删除。

  • boostrap.memory_lock

ES启动时 是否进行 内存锁定, 默认 true。
ES对于 内存的需求比较大,一般 生产环境建议 配置大内存
如果内存不足,容易导致内存交换到磁盘,严重影响ES的性能。
所以默认在启动时进行相应大小内存的锁定,如果无法锁定则会启动失败。

在 config/jvm.option 配置文件中, Xms和 Xmx设置成一样, 但是不要超所 主机内存的 50%

  • network.host

配置 可以访问 当前节点的 主机。
默认值为 仅本机访问,可以配置为0.0.0.0,表示 所有主机 均可访问。

  • http.port

对外提供服务的端口,默认是 9200

  • discovery.seed_hosts

配置 参与集群 节点 发现过程 的 主机列表,
说白一点就是 集群中 所有节点 所在的主机列表,可以是IP、域名。

  • cluster.initial_master_nodes

配置 ES集群 初始化是 参与 master 选举的 节点名称列表,必须和 node.name配置一致。
ES集群首次 构建完成后,应该将 集群中所有节点 的配置文件 中的 clusterinitial_master_nodes配置项移除,
重启集群 或者 将新节点加入某个已存在的集群时 切记 不要设置该配置项。

*_*ES两个重要的端口

  • 9200 是客户端 访问 ES服务端的 端口;
  • 9300 是 ES 节点间 交互的 端口。

*_*分布式概念

cluster

cluster集群。ElasticSearch集群由 一 或 多个 节点组成,
其中有一个主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。
ElasticSearch的一个概念就是 去中心化,字面上理解就是无中心节点,这是对于集群外部来说的,
因为从外部看ElasticSearch集群,在逻辑上是个整体,你与集群中的任何一个节点通信和与整个ElasticSearch集群通信是等价的。
也就是说,主节点的存在不会产生单点安全隐患、并发访问瓶颈等问题。

shards

primary shard:代表索引的主分片,ElasticSearch可以把一个完整的索引分成多个primary shard,
这样的好处是可以把一个大的索引拆分成多个分片,分布存储在不同的ElasticSearch节点上,
从而形成分布式存储,并为搜索访问提供分布式服务,提高并发处理能力。
primary shard的 数量 只能在索引创建时指定,并且索引创建后不能再更改primary shard数量 (重新分片需要重新定义分片规则)。
primary shard的 数量 es5.x之后默认为5,es7.x默认为1

replicas

replica shard:代表索引主分片的副本,ElasticSearch可以设置多个replica shard。可取值为0~n,默认为1。
replica shard的作用:

  1. 是提高系统的容错性,当某个节点某个primary shard损坏或丢失时可以从副本中恢复。
  2. 是提高ElasticSearch的查询效率,ElasticSearch会自动对搜索请求进行负载均衡,将并发的搜索请求发送给合适的节点,增强并发能力

*_*和 关系性数据库 概念类比

mysql数据库数据表记录字段
ESIndex(索引)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

是一种 用于 信息检索 和 数据挖掘 的 常用 加权技术。
是 被公认 的 信息检索 领域 的 最重要的 发明。
其 公式的 三个 决断 算分 结果 的 变量如下:

  • 词频TF: 对于 某一条 数据而言, 检索词 出现的频率越高, 相关性越高。
  • 逆向文本频率IDF: 对于 多条 数据而言, 检索词 出现的频率越低, 相关性越高。举例:
    我们 搜索: ES java, 假如一共有10条数据, 每条数据都有 ES信息, 只有 前两条 有java信息, 明显 这里 ES的频率 高于 java, 但是 java的 相关性 要 大于 ES。
  • 字段长度归一值: 为什么 字段长度越短 权重越高,因为 字段越长 越有出现 信息冗余的可能,每个人组织语言的能力都是有一定缺陷的,话说的越多 废话 就越多。
  • BM25

BM25 优化 TF-IDF 算法, 减少 分数计算的 资源损耗。
词频不断增加时, TF-IDF 算法 的打出的 分数 是 无限增加, 而 BM25是趋近于一个 数值。

索引index

一个索引 就是一个 拥有 几分相似特征 的文档的集合。
索引的命名 必须 全部是 小写字母, 不能以 下划线 开头

索引 主要有三个大属性(部分)组成:

  • aliases 别名
  • mappings 文档映射
    数据字段 和 字段类型 的映射, 创建索引的时候不设置,添加数据后 会自动设置,当然也可以手动设置。
  • settings 设置
    设置 索引 相关的一些属性

mappings 文档映射

Mapping 类似 数据库 中的 schema的定义,作用如下:

  • 定义索引中的 字段名称
  • 定义字段的 数据类型,例如字符串,数字,布尔等
  • 字段,倒排索引 的 相关配置(AnalyzedorNotAnalyzed,Analyzer)

Mapping映射 分为 动态 和 静态:

  • 动态
    在关系数据库中,需要事先创建数据库,然后在该数据库下创建数据表,并创建表字段、类型、长度、主键等,最后才能基于表插入数据。
    而Elasticsearch中不需要定义Mapping映射(即关系型数据库的表、字段等),在文档写入Elasticsearch时,会根据文档字段自动识.
  • 静态
    事先 手动 定义好映射。

动态映射 的 类型识别机制

JSON类型ES类型
字符串1- 匹配日期格式,设置为Date 2- 配置数据设置为float或long,默认关闭 3- 设置为Text,并且增加keyword字段
布尔值boolean
浮点数float
整数long
对象object
数组由第一个非空元素决定
空值忽略

Mapping生成之后,后期修改会怎么样?

  • 新增字段
  • 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(关联、影响)
docsynnn
freqsyynn
positionsyyyn
offsetsyyyy

说明: text类型 默认记录 positions, 其他默认记为 docs。

PUT /wtt
{
	"mappings": {
		"name": {
			"type": "text",
			"index_options": "offsets" // 这里指定 记录内容
		}
	}
}

对Null值 的 搜索

只有 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,可以 自动生成 也可以 手动指定

文档元数据

  • _index:文档所属的 索引 名。
  • _type:文档 所属的 类名
  • _id:文档的 唯一id
  • _source:文档的原始 json数据
  • version:文档的版本号, 修改、删除 操作 会使得 version 自增1
  • seq_no : 修改、删除 操作 会使得 version 自增1
  • primary_term :是 纪元数据,每次 分区选举 都会 自增1。即 记录朝代的更替。
# 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
才会 进行 数据加入 的操作。

相关操作

创建

  • POST

通过 指定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

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
  • CREST

该种创建数据的方式 主要是为了 添加数据 不小心 变成了 数据覆盖 了
所以 如果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

查询

  • 根据id
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 文档查询方式

接下来 重点 讲解 官方建议 通过 request body 的方式进行 查询,因为这种方式 可以 定义 更加易读的 json格式。
但在此之前 要 先讲一下 相关原理

ES检索原理

  • 索引的原理

索引 是加速数据查询的 重要手段,其 核心原理 就是 通过不不断的 缩小 想要获取数据的 范围, 来 筛选出 最终想要的结果。

  • 磁盘IO 和 预读

磁盘IO 是程序设计中 非常 昂贵的 操作, 也是 影响 程序性能 的重要因素
因此 应当 避免过多的 磁盘IO,最直接有效的方式 就是 利用 内存。
局部性原理 告诉我们,当计算机访问一个地址的数据的时候,与其相邻的地址也会很快被访问到。
因此 预读 就是 发生一次 IO时, 不仅仅是 度全当前的 磁盘数据, 而且把相邻地址的数据也读取到内存中。

在这里插入图片描述

上图主要为了说明两点:

  • 磁盘IO的空间关系
  • 查询 数据的结果 和 词项-字典 有直接的关系, 而 词项-字典 的存储内容 有 分词器 直接指定。
    所以 查询数据的结果 和 分词器 息息相关

常用查询操作

全量查询 match_all

使用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,因为 更高效。

分页查询 Scroll

改动 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 在匹配时 会对 所查找的 关键字 进行分词, 然后 再 按 关键字 分词 进行匹配查找。
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查询分析文本 并 根据 分析的文本 创建一个 短语查询
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  // 这样一来 就能匹配上了
			}
		}
	}
}

多字段 multi_match

GET /wtt/_search
{
	"query": {
		"multi_match":{
			"query": "你好""fields": ["address", "name"]  // 这 俩字段 只要能 匹配上 你好 的 文档 都可以被命中
		}
	}
}

query_string

允许我们在单个查询 字符串 中指定 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 是用来 精准查询 的,还可以用来 查询 没有被 进行分词的 数据类型。
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 多值字段(数组)的处理

对于 多值字段, term 查询 是包含, 而不是 等于

-- 假设现在有两个文档:
{
	"name": "tom",
	"hobby"["篮球", "足球"]  // 多值字段
}

{
	"name": "cat",
	"hobby"["游泳", "篮球"]  // 多值字段
}

-- 多值字段的查询
GET /wtt/_search
{
	"query": {
		"term": {
			"hobby.keyword": {
				"value": "篮球"  // 可以命中以上 两条数据
			}
		}
	}
}

prefix 前缀搜索

不会 对 关键字 进行分词, 查询的内容 就是 查找的 前缀。
它的原理: 遍历所有的 倒排索引 , 比较每个 term(基本单位)的前缀 是否能匹配上 。
它的行为 和 过滤器 很像, 区别在于 过滤器 是可以被缓存的, 它不行。

GET /wtt/_search
{
	"query": {
		"prefix": {
			"address": {
				"value": "山"
			}
		}
	}
}

wildcard 通配符查询

其 工作原理 和 prefix 相同,只不过 它能支持 更为复杂的 模式

GET /wtt/_search
{
	"query": {
		"wildcard": {
			"address": {
				"value": "*东*"  // 可以命中 山东省
			}
		}
	}
}

范围查询 range

支持的 范围描述 关键字有:

  • 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个月前 
			}
		}
	}
}

多id查询

ids 关键字: 置为 数组类型, 用来 根据 一组 id 获取 对应的 多个 文档。

GET /wtt/_search 
{
	"query": {
		"ids": {
			"values": [1,2,3]
		}
	}
}

模糊查询 fuzzy

在实际中,我们又是 会 打错字,从而导致 搜索不到。
在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": ["<h1 style='color:red'>"],
		"post_tags": ["</h1>"],
		"fields": {
			"name":{} ,  // 也可以是 没有 查找匹配的 文档字段
			"address":{} 
		}
	}
}

布尔查询 bool query

一个 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" // 分组字段
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值