ElasticSearch入门

全文索引

数据分类:

  • 结构化数据:固定格式,有限长度 比如MySQL存储的数据
  • 非结构化数据:不定长度,无固定格式 比如邮件、word文档
  • 半结构化数据:前两者结合 比如xml、html
    搜索分类
  • 结构化数据搜索:使用关系型数据库
  • 非结构化数据搜索
    -顺序扫描
    -全文检索
    设想一个搜索场景,假设我们要搜索一首诗句内容中带“前”字的故事
name content author
静夜思 床前明月光,疑是地上霜,举头望明月,低头思故乡 李白
望庐山瀑布 日照香炉生紫烟,遥看瀑布挂前川。飞流直下三千尺,疑是银河落九天。 李白
----

如果用传统关系型数据库和ES会有什么差别?
如果用想MySQL这样的RDBMS来存储古诗的话,我们应该会使用这样的sql去查询

select name from poems where content like "%前%"

这种我们称之为顺序扫描法,需要遍历所有的记录进行匹配。不但效率低,而且不符合我们搜索时的期望,比如我们在搜索"ABCD"这样的关键词时,通常还希望看到"A",“AB”,“CD”,"ABC"的搜索结果。

什么是全文索引

全文索引是指:

  • 通过一个程序扫描文本中的每一个单词,针对单词来建立索引,并保存该单词在文本中的位置、以及出现的次数
  • 用户查询时,通过之前建立好的索引来查询,将索引中单词对应的文本位置、出现次数返回给用户,因为有了具体的文本位置,所以就将具体内容读取出来了
    image

搜索原理简单概括的话可以分为一下几步:

  • 内容爬取,停顿词过滤比如一些无用的像"的","了"之类的语气词/连接词
  • 内容分词,提取关键词
  • 根据关键词建立倒排索引
  • 用户输入关键词进行检索

倒排索引

索引就类似于目录,平时我们使用的都是索引,都是通过主键定位到某条数据,那么倒排索引刚好相反,数据对应主键。

image-1656666354972

以一个博客文章为例

正排索引(正向索引)

文章ID 文章标题 文章内容
1 浅析JAVA设计模式 JAVA设计模式是每一个JAVA程序员都应该掌握的进阶知识
2 JAVA多线程设计模式 JAVA多线程与设计模式结合

倒排索引(反向索引)

假如,我们有一个站内搜索的功能,通过某个关键词来搜索相关的文章,那么这个关键词可能出现在标题中,也可能出现在文章内容中,那我们将会在创建或修改文章的时候,建立一个关键词与文章的对应关系表,这种,我们可以称之为倒排索引。
like %java设计模式% java 设计模式

关键词 文章ID
Java 1,2
设计模式 1,2
多线程 2

简单理解,正向索引是通过key找value,反向索引则是通过value找key。ES底层在检索时底层使用的就是倒排索引。

ElasticSearch简介

ElasticSearch是什么

ElasticSearch(简称ES)是一个分布式、RESTful风格的搜索和数据分析引擎,使用Java开发并且是当前最流行的企业级搜索引擎,能够达到近实时搜索,稳定,可靠,快速,安装使用方便。
客户端支持Java、.NET(C#)、PHP、Python、Ruby等多种语言。
官方网站: https://www.elastic.co/
下载地址: https://www.elastic.co/downloads/past-releases#elasticsearch

搜索引擎排名:
image-1656899068657
参考网站:https://db-engines.com/en/ranking/search+engine

起源–Lucence
  • 基于Java语言开发的搜索引擎库类
  • 创建于1999年,2005年成为Apache 顶级开源项目
  • Lucene具有高性能、易扩展的优点
  • Lucene的局限性︰
    • 只能基于Java语言开发
    • 类库的接口学习曲线陡峭
    • 原生并不支持水平扩展
Elasticsearch的诞生

Elasticsearch是构建在Apache Lucene之上的开源分布式搜索引擎。

  • 2004年 Shay Banon 基于Lucene开发了Compass
  • 2010年 Shay Banon重写了Compass,取名Elasticsearch
    • 支持分布式,可水平扩展
    • 降低全文检索的学习曲线,可以被任何编程语言调用

image-1656899180669

Elasticsearch 与 Lucene 核心库竞争的优势在于:

  • 完美封装了 Lucene 核心库,设计了友好的 Restful-API,开发者无需过多关注底层机制,直接开箱即用。
  • 分片与副本机制,直接解决了集群下性能与高可用问题。

ES Server进程 3节点 raft (奇数节点)
数据分片 -》lucene实例 分片和副本数 1个ES节点可以有多个lucene实例。也可以指定一个索引的多个分片
image-1656899232297

ElastcSearch版本特性

5.x新特性

  • Lucene 6.x, 性能提升,默认打分机制从TF-IDF改为BM 25
  • 支持Ingest节点/ Painless Scripting / Completion suggested支持/原生的Java REST客户端
  • Type标记成deprecated, 支持了Keyword的类型
  • 性能优化
    • 内部引擎移除了避免同一文档并发更新的竞争锁,带来15% - 20%的性能提升
    • Instant aggregation,支持分片,上聚合的缓存
    • 新增了Profile API

-6.x新特性

  • Lucene 7.x
  • 新功能
    • 跨集群复制(CCR)
    • 索引生命周期管理
    • SQL的支持
  • 更友好的的升级及数据迁移
    • 在主要版本之间的迁移更为简化,体验升级
    • 全新的基于操作的数据复制框架,可加快恢复数据
  • 性能优化
    • 有效存储稀疏字段的新方法,降低了存储成本
    • 在索引时进行排序,可加快排序的查询性能

7.x新特性

  • Lucene 8.0
  • 重大改进-正式废除单个索引下多Type的支持
  • 7.1开始,Security 功能免费使用
  • ECK - Elasticseach Operator on Kubernetes
  • 新功能
    • New Cluster coordination
    • Feature——Complete High Level REST Client
    • Script Score Query
  • 性能优化
    • 默认的Primary Shard数从5改为1,避免Over Sharding
    • 性能优化, 更快的Top K

8.x新特性

  • Rest API相比较7.x而言做了比较大的改动(比如彻底删除_type)
  • 默认开启安全配置
  • 存储空间优化:对倒排文件使用新的编码集,对于keyword、match_only_text、text类型字段有效,有3.5%的空间优化提升,对于新建索引和segment自动生效。
  • 优化geo_point,geo_shape类型的索引(写入)效率:15%的提升。
  • 技术预览版KNN API发布,(K邻近算法),跟推荐系统、自然语言排名相关。
  • https://www.elastic.co/guide/en/elastic-stack/current/elasticsearch-breaking-changes.html
ElasticSearch vs Solr

Solr 是第一个基于 Lucene 核心库功能完备的搜索引擎产品,诞生远早于 Elasticsearch。

当单纯的对已有数据进行搜索时,Solr更快。当实时建立索引时, Solr会产生io阻塞,查询性能较差,Elasticsearch具有明显的优势。

image-1656899445553

image-1656899452272

大型互联网公司,实际生产环境测试,将搜索引擎从Solr转到 Elasticsearch以后的平均查询速度有了50倍的提升。

image-1656899472500

总结:

  • Solr 利用 Zookeeper 进行分布式管理,而Elasticsearch 自身带有分布式协调管理功能。
  • Solr 支持更多格式的数据,比如JSON、XML、CSV,而 Elasticsearch 仅支持json文件格式。
  • Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 Elasticsearch。
  • Solr 是传统搜索应用的有力解决方案,但 Elasticsearch更适用于新兴的实时搜索应用。

Ealstic Stack介绍

在ElasticStack之前我们听说过ELK,ELK分别是ElasticSearch、Logstash、Kinbana这三款软件在一起的简称,在发展十七又有新成员Beats的加入,就形成了Elastic Stack。

image-1656899652839

					Elastic Stack生态圈

在Elastic Stack生态圈中ElasticSearch作为数据存储和搜索,是生态圈中的基石,Kibana在上层提供用户一个可视化以及操作的界面,Logstash和Beat可以对数据进行收集。在上图右侧X-Pack部分则是Elastic公司提供的商业化项目。

指标分析/日志分析:
image-1656900149742

ElasticSearch应用场景

  • 站内搜索
  • 日志管理与分析
  • 大数据分析
  • 应用性能监控
  • 机器学习

国内现在用大量的公司都在使用ElasticSearch,包括携程、滴滴、小米、头条、饿了么等诸多知名公司。除了搜索以外,结合Kibana、Logstash、Beats、Elastic Stack还被广泛运用在大数据近实时分析领域,包括日志分析、指标监控、信息安全等多个领域。它可以帮助你探索海量结构化数据,按需创建可视化报表,对监控数据设置报警阈值,甚至通过使用机器学习技术,自动识别异常状况。

通用数据处理流程:

image-1656900729831

ElasticSearch快速开始

ElasticSearch安装运行

环境准备
  • 运行Elasticsearch,需安装并配置JDK
    • 设置$JAVA_HOME
  • 各个版本对Java的依赖 https://www.elastic.co/support/matrix#matrix_jvm
    • Elasticsearch 5需要Java 8以上的版本
    • Elasticsearch 从6.5开始支持Java 11
    • 7.0开始,内置了Java环境
  • ES比较耗内存,建议虚拟机4G或以上内存,jvm1g以上的内存分配
    可以参考es的环境文件elasticsearch-env.bat
    image-1656900928314

ES的jdk环境生效的优先级配置ES_JAVA_HOME>JAVA_HOME>ES_HOME

下载并解压ElasticSearch

下载地址: https://www.elastic.co/cn/downloads/past-releases#elasticsearch
选择版本:7.17.3
image-1656902096475

ElasticSearch文件目录结构

目录 描述
bin 脚本文件,包括启动elasticsearch,安装插件,运行统计数据等
config 配置文件目录,如elasticsearch配置、角色配置、jvm配置等。
jdk Java运行环境
data 默认的数据存放目录,包含节点、分片、索引、文档的所有数据,生产环境需要修改。
lib elasticsearch依赖的Java类库
logs 默认的日志文件存储路径,生产环境需要修改。
modules 包含所有的Elasticsearch模块,如Cluster、Discovery、Indices等。
plugins 已安装插件目录

主配置文件elasticsearch.yml

  • cluster.name
    当前节点所属集群名称,多个节点如果要组成同一个集群,那么集群名称一定要配置成相同。默认值elasticsearch,生产环境建议根据ES集群的使用目的修改成合适的名字。
  • node.name
    当前节点名称,默认值当前节点部署所在机器的主机名,所以如果一台机器上要起多个ES节点的话,需要通过配置该属性明确指定不同的节点名称。
  • path.data
    配置数据存储目录,比如索引数据等,默认值 $ES_HOME/data,生产环境下强烈建议部署到另外的安全目录,防止ES升级导致数据被误删除。
  • path.logs
    配置日志存储目录,比如运行日志和集群健康信息等,默认值 $ES_HOME/logs,生产环境下强烈建议部署到另外的安全目录,防止ES升级导致数据被误删除。
  • bootstrap.memory_lock
    配置ES启动时是否进行内存锁定检查,默认值true。
    ES对于内存的需求比较大,一般生产环境建议配置大内存,如果内存不足,容易导致内存交换到磁盘,严重影响ES的性能。所以默认启动时进行相应大小内存的锁定,如果无法锁定则会启动失败。
    非生产环境可能机器内存本身就很小,能够供给ES使用的就更小,如果该参数配置为true的话很可能导致无法锁定内存以致ES无法成功启动,此时可以修改为false。
  • network.host
    配置能够访问当前节点的主机,默认值为当前节点所在机器的本机回环地址127.0.0.1 和[::1],这就导致默认情况下只能通过当前节点所在主机访问当前节点。可以配置为 0.0.0.0 ,表示所有主机均可访问。
  • http.port
    配置当前ES节点对外提供服务的http端口,默认值 9200
  • discovery.seed_hosts
    配置参与集群节点发现过程的主机列表,说白一点就是集群中所有节点所在的主机列表,可以是具体的IP地址,也可以是可解析的域名。
  • cluster.initial_master_nodes
    配置ES集群初始化时参与master选举的节点名称列表,必须与node.name配置的一致。ES集群首次构建完成后,应该将集群中所有节点的配置文件中的cluster.initial_master_nodes配置项移除,重启集群或者将新节点加入某个已存在的集群时切记不要设置该配置项。
#ES开启远程访问  
network.host: 0.0.0.0
修改JVM配置

修改config/jvm.options配置文件,调整jvm堆内存大小

vim jvm.options
-Xms4g
-Xmx4g

配置的建议

启动ElasticSearch服务

Windows
直接运行elasticsearch.bat
Linux(centos7)

ES不允许使用root账号启动服务,如果你当前账号是root,则需要创建一个专有账户

#非root用户
bin/elasticsearch 

# -d 后台启动
bin/elasticsearch -d

image-1656902572695

注意:es默认不能用root用户启动,生产环境建议为elasticsearch创建用户。

#为elaticsearch创建用户并赋予相应权限
adduser es
passwd es
chown -R es:es elasticsearch-17.3

运行http://localhost:9200/

image-1656905034604

如果ES服务启动异常,会有提示:
image-1656905056468

启动ES服务常见错误解决方案
[1]: max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]ES因为需要大量的创建索引文件,需要大量的打开系统的文件,所以我们需要解除linux系统当中打开文件最大数目的限制,不然ES启动就会抛错

#切换到root用户
vim /etc/security/limits.conf

末尾添加如下配置:
  *	    soft 	nofile 	65536
  *     hard 	nofile 	65536
  *     soft 	nproc 	4096
  *	    hard 	nproc 	4096

[2]: max number of threads [1024] for user [es] is too low, increase to at least [4096]
无法创建本地线程问题,用户最大可创建线程数太小

vim /etc/security/limits.d/20-nproc.conf

改为如下配置:
* soft nproc 4096

[3]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
最大虚拟内存太小,调大系统的虚拟内存

vim /etc/sysctl.conf
追加以下内容:
vm.max_map_count=262144
保存退出之后执行如下命令:
sysctl -p

[4]: the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured
缺少默认配置,至少需要配置discovery.seed_hosts/discovery.seed_providers/cluster.initial_master_nodes中的一个参数.

  • discovery.seed_hosts: 集群主机列表
  • discovery.seed_providers: 基于配置文件配置集群主机列表
  • cluster.initial_master_nodes: 启动时初始化的参与选主的node,生产环境必填
vim config/elasticsearch.yml
#添加配置
discovery.seed_hosts: ["127.0.0.1"]
cluster.initial_master_nodes: ["node-1"]

#或者  单节点(集群单节点)
discovery.type: single-node
客户端Kibana安装

Kibana是一个开源分析和可视化平台,旨在与Elasticsearch协同工作。
1)下载并解压缩Kibana
下载地址:https://www.elastic.co/cn/downloads/past-releases#kibana
选择版本:7.17.3
image-1656905357012
2)修改Kibana.yml

vim config/kibana.yml

server.port: 5601
server.host: "localhost"  #服务器ip
elasticsearch.hosts: ["http://localhost:9200"]  #elasticsearch的访问地址
i18n.locale: "zh-CN"   #Kibana汉化

3)运行Kibana
注意:kibana也需要非root用户启动

bin/kibana
#后台启动
nohup  bin/kibana &

访问Kibana: http://localhost:5601/
image-1656905673200

cat API

/_cat/allocation         #查看单节点的shard分配整体情况
/_cat/shards          #查看各shard的详细情况
/_cat/shards/{index}     #查看指定分片的详细情况
/_cat/master          #查看master节点信息
/_cat/nodes           #查看所有节点信息
/_cat/indices         #查看集群中所有index的详细信息
/_cat/indices/{index}      #查看集群中指定index的详细信息
/_cat/segments        #查看各index的segment详细信息,包括segment名, 所属shard, 内存(磁盘)占用大小, 是否刷盘
/_cat/segments/{index}#查看指定index的segment详细信息
/_cat/count           #查看当前集群的doc数量
/_cat/count/{index}   #查看指定索引的doc数量
/_cat/recovery        #查看集群内每个shard的recovery过程.调整replica。
/_cat/recovery/{index}#查看指定索引shard的recovery过程
/_cat/health          #查看集群当前状态:红、黄、绿
/_cat/pending_tasks   #查看当前集群的pending task
/_cat/aliases         #查看集群中所有alias信息,路由配置等
/_cat/aliases/{alias} #查看指定索引的alias信息
/_cat/thread_pool     #查看集群各节点内部不同类型的threadpool的统计信息,
/_cat/plugins         #查看集群各个节点上的plugin信息
/_cat/fielddata       #查看当前集群各个节点的fielddata内存使用情况
/_cat/fielddata/{fields}     #查看指定field的内存使用情况,里面传field属性对应的值
/_cat/nodeattrs              #查看单节点的自定义属性
/_cat/repositories           #输出集群中注册快照存储库
/_cat/templates              #输出当前正在存在的模板信息
Elasticsearch安装分词插件

Elasticsearch提供插件机制对系统进行扩展
以安装analysis-icu这个分词插件为例
在线安装

#查看已安装插件
bin/elasticsearch-plugin list
#安装插件
bin/elasticsearch-plugin install analysis-icu
#删除插件
bin/elasticsearch-plugin remove analysis-icu

注意:安装和删除完插件后,需要重启ES服务才能生效。
测试分词效果

POST _analyze
{
    "analyzer":"icu_analyzer",
    "text":"中华人民共和国"
}

image-1656905714619

离线安装
本地下载相应的插件,解压,然后手动上传到elasticsearch的plugins目录,然后重启ES实例就可以了。
比如ik中文分词插件:https://github.com/medcl/elasticsearch-analysis-ik

测试分词效果

#ES的默认分词设置是standard,会单字拆分
POST _analyze
{
    "analyzer":"standard",
    "text":"中华人民共和国"
}

#ik_smart:会做最粗粒度的拆
POST _analyze
{
    "analyzer": "ik_smart",
    "text": "中华人民共和国"
 }

#ik_max_word:会将文本做最细粒度的拆分
POST _analyze
{
    "analyzer":"ik_max_word",
    "text":"中华人民共和国"
}

创建索引时可以指定IK分词器作为默认分词器

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

image-1656905792828

ElasticSearch基本概念

关系型数据库 VS ElasticSearch

  • 在7.0之前,一个 Index可以设置多个Types
  • 目前Type已经被Deprecated,7.0开始,一个索引只能创建一个Type - “_doc”
  • 传统关系型数据库和Elasticsearch的区别:
    • Elasticsearch- Schemaless /相关性/高性能全文检索
    • RDMS —事务性/ Join

image-1656911925241

索引(Index)

一个索引就是一个拥有几分相似特征的文档的集合,比如说,可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。
一个索引由一个名字来标识(不许全部是小写字母的):并且当我们要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。
image-1656912176554

文档(Document)

  • Elasticsearch是面向文档的,文档是所有可搜索数据的最小单位。
    • 日志文件中的日志项
    • 一本电影的具体信息/一张唱片的详细信息
    • MP3播放器里的一首歌/一篇PDF文档中的具体内容
  • 文档会被序列化成JSON格式,保存在Elasticsearch中
    • JSON对象由字段组成
    • 每个字段都有对应的字段类型(字符串/数值/布尔/日期/二进制/范围类型)
  • 每个文档都有一个Unique ID
    • 可以自己指定ID或者通过Elasticsearch自动生成
  • 一篇文档包含了一系列字段,类似数据库表中的一条记录
  • JSON文档,格式灵活,不需要预先定义格式
    • 字段的类型可以指定或者通过Elasticsearch自动推算
    • 支持数组/支持嵌套

文档元数据
image-1656912307032

元数据,用于标注文档的相关信息:

  • _index:文档所属的索引名
  • _type:文档所属的类型名
  • _id:文档唯—ld
  • _source: 文档的原始Json数据
  • _version: 文档的版本号,修改删除操作_version都会自增1
  • _seq_no: 和_version一样,一旦数据发生更改,数据也一直是累计的。Shard级别严格递增,保证后写入的Doc的_seq_no大于先写入的Doc的_seq_no。
  • _primary_term: _primary_term主要是用来恢复数据时处理当多个文档的_seq_no一样时的冲突,避免Primary Shard上的写入被覆盖。每当Primary Shard发生重新分配时,比如重启,Primary选举等,_primary_term会递增1。

ElasticSearch索引操作

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/index.html

创建索引

索引命名必须小写,所以不能以下划线开头
格式: PUT /索引名称

#创建索引
PUT /es_db

#创建索引时可以设置分片数和副本数
PUT /es_db
{
    "settings" : {
        "number_of_shards" : 3,
        "number_of_replicas" : 2
    }
}

#修改索引配置
PUT /es_db/_settings
{
    "index" : {
        "number_of_replicas" : 1
    }
}

image-1656912597782

查询索引

格式: GET /索引名称

#查询索引
GET /es_db

#es_db是否存在
HEAD /es_db

image-1656912625965

删除索引

格式:DELETE /索引名称

DELETE /es_db

ElasticSearch文档操作

示例数据

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

PUT /es_db/_doc/1
{
"name": "张三",
"sex": 1,
"age": 25,
"address": "广州天河公园",
"remark": "java developer"
}
PUT /es_db/_doc/2
{
"name": "李四",
"sex": 1,
"age": 28,
"address": "广州荔湾大厦",
"remark": "java assistant"
}

PUT /es_db/_doc/3
{
"name": "王五",
"sex": 0,
"age": 26,
"address": "广州白云山公园",
"remark": "php developer"
}

PUT /es_db/_doc/4
{
"name": "赵六",
"sex": 0,
"age": 22,
"address": "长沙橘子洲",
"remark": "python assistant"
}

PUT /es_db/_doc/5
{
"name": "张龙",
"sex": 0,
"age": 19,
"address": "长沙麓谷企业广场",
"remark": "java architect assistant"
}	
	
PUT /es_db/_doc/6
{
"name": "赵虎",
"sex": 1,
"age": 32,
"address": "长沙麓谷兴工国际产业园",
"remark": "java architect"
}	

添加(索引)文档

  • 格式:[PUT][POST] /索引名称/[_doc |_create]/id
# 创建文档,指定id
# 如果id不存在,创建新的文档,否则先删除现有文档,再创建新的文档,版本会增加
PUT /es_db/_doc/1
{
"name": "张三",
"sex": 1,
"age": 25,
"address": "广州天河公园",
"remark": "java developer"
}	

#创建文档,ES生成id
POST /es_db/_doc
{
"name": "张三",
"sex": 1,
"age": 25,
"address": "广州天河公园",
"remark": "java developer"
}

image-1656912926292

注意:POST和PUT都能起到创建/更新的作用,PUT需要对一个具体的资源进行操作也就是要确定id才能进行更新/创建,而POST是可以针对整个资源集合进行操作的,如果不写就由ES生成一个唯一id进行创建新文档,如果填了id那就针对这个id的文档进行创建/更新
image-1656913127108
Create-如果ID已经存在,会失败
image-1656913166711

修改文档

  • 全量更新,整个json都会替换,格式:[PUT|POST]/索引名称/_doc/id

如果文档存在,现有文档会被删除,新的文档会被索引

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

#查询文档
GET /es_db/_doc/1

image-1656913298301

  • 使用_update部分更新,格式:POST /索引名称/_update/id

update不会删除原来的文档,而是实现真正的数据更新

# 部分更新:在原有文档上更新
# Update -文档必须已经存在,更新只会对相应字段做增量修改
POST /es_db/_update/1
{
  "doc": {
    "age": 28
  }
}

#查询文档
GET /es_db/_doc/1

image-1656913399582

  • 使用 _update_by_query 更新文档
POST /es_db/_update_by_query
{
  "query": { 
    "match": {
      "_id": 1
    }
  },
  "script": {
    "source": "ctx._source.age = 30"
  }
}

image-1656913431731

并发场景下修改文档

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

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

如果版本号不对,会抛出版本冲突异常:
image-1656913549745

查询文档

  • 根据id查询文档,格式:GET /索引名称/_doc/id
GET /es_db/_doc/_search
  • 条件查询_search,格式: /索引名称/_doc/_search
# 查询前10条文档
GET /es_db/_doc/_search

ES Search API提供了两种条件查询搜索方式:

  • REST风格的请求URI,直接将参数带过去
  • 封装到request body中,这种方式可以定义更加易读的JSON格式
#通过URI搜索,使用“q”指定查询字符串,“query string syntax” KV键值对

#条件查询, 如要查询age等于28岁的 _search?q=*:***
GET /es_db/_doc/_search?q=age:28

#范围查询, 如要查询age在25至26岁之间的 _search?q=***[** TO **]  注意: TO 必须为大写
GET /es_db/_doc/_search?q=age[25 TO 26]

#查询年龄小于等于28岁的 :<=
GET /es_db/_doc/_search?q=age:<=28
#查询年龄大于28前的 :>
GET /es_db/_doc/_search?q=age:>28

#分页查询 from=*&size=*
GET /es_db/_doc/_search?q=age[25 TO 26]&from=0&size=1

#对查询结果只输出某些字段 _source=字段,字段
GET /es_db/_doc/_search?_source=name,age

#对查询结果排序 sort=字段:desc/asc
GET /es_db/_doc/_search?sort=age:desc
GET /es_db/_search
{
  "query": {
    "match": {
      "address": "广州白云"
    }
  }
}

删除文档

格式: DELETE /索引名称/_doc/id

DELETE /es_db/_doc/1

ElasticSearch文档批量操作

批量操作可以减少网络连接所产生的开销,提升性能

  • 支持在一次API调用中,对不同的索引进行操作
  • 可以在URI中指定Index,也可以在请求的Payload中进行
  • 操作中单条操作失败,并不会影响其他操作
  • 返回结果包括了每一条操作执行的结果

批量写入

  • 批量对文档进行写操作是通过_bulk的API来实现的
  • 请求方式:POST
  • 请求地址:_bulk
  • 请求参数:通过_bulk操作文档,一般至少有两行参数(或偶数行参数)
    • 第一行参数为指定操作的类型及操作的对象(index,type和id)
    • 第二行参数才是操作的数据
      参数类似于:
{"actionName":{"_index":"indexName", "_type":"typeName","_id":"id"}}
{"field1":"value1", "field2":"value2"}
  • actionName:表示操作类型,主要有create,index,delete和update

批量创建文档create

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

普通创建或全量替换index

POST _bulk
{"index":{"_index":"article", "_type":"_doc", "_id":3}}
{"id":3,"title":"AAA","content":"AAA666","tags":["java", "面向对象"],"create_time":1554015482530}
{"index":{"_index":"article", "_type":"_doc", "_id":4}}
{"id":4,"title":"BBB","content":"BBB","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
{"create":{"_index":"article", "_type":"_doc", "_id":3}}
{"id":3,"title":"fox老师","content":"fox老师666","tags":["java", "面向对象"],"create_time":1554015482530}
{"delete":{"_index":"article", "_type":"_doc", "_id":3}}
{"update":{"_index":"article", "_type":"_doc", "_id":4}}
{"doc":{"create_time":1554018421008}}

批量读取

es的批量查询可以使用mget和msearch两种。其中mget是需要我们知道它的id,可以指定不同的index,也可以指定返回值source。msearch可以通过字段查询来进行一个批量的查找。
_mget

#可以通过ID批量获取不同index和type的数据
GET _mget
{
"docs": [
{
"_index": "es_db",
"_id": 1
},
{
"_index": "article",
"_id": 4
}
]
}

#可以通过ID批量获取es_db的数据
GET /es_db/_mget
{
"docs": [
{
"_id": 1
},
{
"_id": 4
}
]
}
#简化后
GET /es_db/_mget 
{
 "ids":["1","2"]  
 }

image-1656914122168

_msearch
在_msearch中,请求格式和bulk类似。查询一条数据需要两个对象,第一个设置index和type,第二个设置查询语句。查询语句和search相同。如果只是查询一个index,我们可以在url中带上index,这样,如果查该index可以直接用空对象表示。

GET /es_db/_msearch
{}
{"query" : {"match_all" : {}}, "from" : 0, "size" : 2}
{"index" : "article"}
{"query" : {"match_all" : {}}}

image-1656914147645

ES倒排索引

当数据写入ES时,数据将会通过 分词 被切分为不同的term,ES将term与其对应的文档列表建立一种映射关系,这种结构就是 倒排索引。
image-1656914911577

为了进一步提升索引的效率,ES在term的基础上利用term的前缀构建了term index,用于对term本身进行索引,ES实际的索引结构如下:
image-1656914989003

这样到我们去搜索某个关键词时,ES首先根据它的前缀或者后缀迅速缩小关键词在term dictionary中的范围,大大减少了磁盘io次数。

  • 单词词典(Term Dictionary) :记录所有文档的单词,记录单词到倒排列表的关联关系
  • 倒排列表(Posting List)-记录了单词对应的文档结合,由倒排索引项组成
  • 倒排索引项(Posting):
    • 文档ID
    • 词频TF–该单词在文档中出现的次数,用于相关性评分
    • 位置(Position)-单词在文档中分词的位置。用于短语搜索(match phrase query)
    • 偏移(Offset)-记录单词的开始结束位置,实现高亮显示

image-1656915144648

ElasticSearch的JSON文档中的每个字段,都有自己的倒排索引。
可以指定对某些字段不做索引:

  • 优点:节省存储空间
  • 缺点:字段无法被搜索

文档映射Mapping

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

  • 定义索引中的字段的名称
  • 定义字段的数据类型,了如字符串、数字、布尔等
  • 字段,倒排索引的相关配置(Analyzer)
    ES中Mapping映射可以分为动态映射和静态映射。

动态映射:
在数据库中,需要实现创建数据库,然后再该数据库下创建数据表,并创建该表的字段、类型‘、长度、主键等,最后才能基于表插入数据。而ElasticSearch中不需要定义Mapping映射(即关系型数据库的表、字段等),在文档写入ElasticSearch时,会根据文档字段自动识别类型,这种机制称之为动态映射。

静态映射:
静态映射是在ElasticSearch中也可以实现定义好映射,包含文档的各字段类型、分词器等,这种方法称之为静态映射。

动态映射(Dynamic Mapping)的机制,使得我们无需手动定义Mappings,ElasticSearch会自动根据文档信息,推算出字段的类型。但有时候会推算的不对。例如地理信息。当类型如果设置不对时,会导致一些功能无法正常运行,例如Range查询

DynamicMapping类型自动识别

image-1656915973263

示例

#删除原索引
DELETE /user

#创建文档(ES根据数据类型, 会自动创建映射)
PUT /user/_doc/1
{
  "name":"fox",
  "age":32,
  "address":"长沙麓谷"
}

#获取文档映射
GET /user/_mapping

image-1656915995171

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

两种情况:

  • 新增加字段
    • dynamic设为true时,一旦有新增字段的文档写入,Mapping 也同时被更新
    • dynamic设为false,Mapping 不会被更新,新增字段的数据无法被索引,但是信息会出现在_source中
    • dynamic设置成strict(严格控制策略),文档写入失败,抛出异常
true false strict
文档可索引 yes yes no
字段可索引 yes no no
Mapping被更新 yes no no
  • ** 对已有字段,一旦已经有数据写入,就不再支持修改字段定义**
    • Lucence实现的倒排索引,一旦生成后,就不允许修改
    • 如果希望改变字段类型,可以利用reindex API,重建索引

原因:

  • 如果修改了字段的数据类型,会导致已被索引的数据无法被搜索
  • 但是如果是新增加的字段,就不会有这样的影响

测试

PUT /user
{
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "name": {
        "type": "text"
      },
      "address": {
        "type": "object",
        "dynamic": "true"
      }
    }
  }
}
# 插入文档报错,原因为age为新增字段,会抛出异常
PUT /user/_doc/1
{
  "name":"fox",
  "age":32,
  "address":{
    "province":"湖南",
    "city":"长沙"
  }
}

dynamic设置成strict,新增age字段导致文档插入失败
image-1656916456283
修改dynamic后再次插入文档成功

#修改daynamic
PUT /user/_mapping
{
  "dynamic":true
}

对已有字段的mapping修改
具体方法:
1)如果要推倒现有的映射, 你得重新建立一个静态索引
2)然后把之前索引里的数据导入到新的索引里
3)删除原创建的索引
4)为新索引起个别名, 为原索引名

PUT /user2
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      },
      "address": {
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

POST _reindex
{
"source": {
"index": "user"
},
"dest": {
"index": "user2"
}
}

DELETE /user

PUT /user2/_alias/user

GET /user

注意: 通过这几个步骤就实现了索引的平滑过渡,并且是零停机

常用mapping参数配置

  • index:控制当前字段是否被索引,默认为true。如果设置为false,该字段不可被搜索
DELETE /user
PUT /user
{
  "mappings" : {
      "properties" : {
        "address" : {
          "type" : "text",
          "index": false
        },
        "age" : {
          "type" : "long"
        },
        "name" : {
          "type" : "text"
        }
      }
    }
}

PUT /user/_doc/1
{
  "name":"fox",
  "address":"广州白云山公园",
  "age":30
}

GET /user

GET /user/_search
{
  "query": {
    "match": {
      "address": "广州"
    }
  }
}

image-1656916721482

  • 有四种不同基本的index options配置,控制倒排索引记录的内容:
    • docs:记录doc id
    • freqs:记录doc id和term frequencies(词频)
    • positions:记录doc id/term frequencies /term position
    • offsets:doc id / term frequencies / term posistion / character offsets

text类型默认记录postions,其他默认为docs。记录内容越多,占用存储空间越大

DELETE /user
PUT /user
{
  "mappings" : {
      "properties" : {
        "address" : {
          "type" : "text",
          "index_options": "offsets"
        },
        "age" : {
          "type" : "long"
        },
        "name" : {
          "type" : "text"
        }
      }
    }
}
  • null_value:需要对Null值进行搜索,只有keyword类型支持设计Null_Value
DELETE /user
PUT /user
{
  "mappings" : {
      "properties" : {
        "address" : {
          "type" : "keyword",
          "null_value": "NULL"
        },
        "age" : {
          "type" : "long"
        },
        "name" : {
          "type" : "text"
        }
      }
    }
}

PUT /user/_doc/1
{
  "name":"fox",
  "age":32,
  "address":null
}

GET /user/_search
{
  "query": {
    "match": {
      "address": "NULL"
    }
  }
}

image-1656917935989

  • copy_to设置:将字段的数值拷贝到目标字段,满足一些特定的搜索需求。copy_to的目标字段不出现在_source中。
# 设置copy_to
DELETE /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": "邵阳"}

GET /address/_search
{
  "query": {
    "match": {
      "full_address": {
        "query": "湖南常德",
        "operator": "and"
      }
    }
  }
}

Index Template

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

  • 模板仅在一个索引被新创建时,才会产生作用。修改模板不会影响已创建的索引
  • 可以设定多个索引模板,这些设置会被"merge"在一起
  • 可以指定"order"的数值,控制"merging"的过程
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
  }
}

lndex Template的工作方式
当一个索引被新创建时:

  • 应用elastic search默认的settings和mappings
  • 应用order数值低的indextemplate中的设定
  • 应用order搞得indextemplate中的设定,之前的设定会被覆盖
  • 应用创建索引时,用户所指定的Setting和Mappings,并覆盖之前模板中的设定
#查看template信息
GET /_template/template_default
GET /_template/temp*

PUT /testtemplate/_doc/1
{
  "orderNo": 1,
  "createDate": "2022/01/01"
}
GET /testtemplate/_mapping
GET /testtemplate/_settings

PUT /testmy
{
  "mappings": {
    "date_detection": true
  }
}

PUT /testmy/_doc/1
{
  "orderNo": 1,
  "createDate": "2022/01/01"
}

GET /testmy/_mapping

Dynamic Template

Dynamic Template定义在某个索引的Mapping中。

#Dynaminc Mapping 根据类型和字段名
DELETE my_index
PUT my_index/_doc/1
{
  "firstName":"Ruan",
  "isVIP":"true"
}

GET my_index/_mapping
DELETE my_index
PUT my_index
{
  "mappings": {
    "dynamic_templates": [
            {
        "strings_as_boolean": {
          "match_mapping_type":   "string",
          "match":"is*",
          "mapping": {
            "type": "boolean"
          }
        }
      },
      {
        "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"
          }
        }
      }
      ]
  }
}

PUT /my_test_index/_doc/1
{
  "name":{
    "first": "John",
    "middle": "Winston",
    "last": "Lennon"
  }
}


GET /my_test_index/_search
{
  "query": {
    "match": {
      "full_name": "John"
    }
  }
}

ES高级查询Query DSL

ES提供了一种强大的检索数据方式,这种检索方式称之为Query DSL(Domain Specified Language),Query DSL是利用Rest API传递JSON格式的请求体(RequestBody)数据与ES进行交互,这种方式的丰富查询让ES检索变得更强大,更简洁。

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl.html

语法:

GET /es_db/_doc/_search {json请求体数据}
可以简化为下面写法
GET /es_db/_search {json请求体数据}

示例

#无条件查询,默认返回10条数据
GET /es_db/_search
{
"query":{
"match_all":{}
}
}

image-1656918797562

示例数据

#指定ik分词器
PUT /es_db
{
  "settings" : {
      "index" : {
          "analysis.analyzer.default.type": "ik_max_word"
      }
  }
}

# 创建文档,指定id
PUT /es_db/_doc/1
{
"name": "张三",
"sex": 1,
"age": 25,
"address": "广州天河公园",
"remark": "java developer"
}
PUT /es_db/_doc/2
{
"name": "李四",
"sex": 1,
"age": 28,
"address": "广州荔湾大厦",
"remark": "java assistant"
}

PUT /es_db/_doc/3
{
"name": "王五",
"sex": 0,
"age": 26,
"address": "广州白云山公园",
"remark": "php developer"
}

PUT /es_db/_doc/4
{
"name": "赵六",
"sex": 0,
"age": 22,
"address": "长沙橘子洲",
"remark": "python assistant"
}

PUT /es_db/_doc/5
{
"name": "张龙",
"sex": 0,
"age": 19,
"address": "长沙麓谷企业广场",
"remark": "java architect assistant"
}	
	
PUT /es_db/_doc/6
{
"name": "赵虎",
"sex": 1,
"age": 32,
"address": "长沙麓谷兴工国际产业园",
"remark": "java architect"
}

查询所有match_all

使用match_all,默认只会返回10条数据。
原因:_search查询默认采用的是分页查询,每页记录数size的默认值为10。如果想显示更多数据,指定size

GET /es_db/_search
等同于
GET /es_db/_search
{
"query":{
"match_all":{}
}
}

返回指定条数size
size 关键字: 指定查询结果中返回指定条数。 默认返回值10条

GET /es_db/_search
{
  "query": {
    "match_all": {}
  },
  "size": 100
}

size可以无限增加吗?

测试

GET /es_db/_search
{
  "query": {
    "match_all": {}
  },
  "size": 20000
}

出现异常:
image-1656918931644

异常原因:
1、查询结果的窗口太大,from + size的结果必须小于或等于10000,而当前查询结果的窗口为20000。
2、可以采用scroll api更高效的请求大量数据集。
3、查询结果的窗口的限制可以通过参数index.max_result_window进行设置。

PUT /es_db/_settings
{ 
  "index.max_result_window" :"20000"
}
#修改现有所有的索引,但新增的索引,还是默认的10000
PUT /_all/_settings
{ 
  "index.max_result_window" :"20000"
}

#查看所有索引中的index.max_result_window值
GET /_all/_settings/index.max_result_window

注意:参数index.max_result_window主要用来限制单次查询满足查询条件的结果窗口的大小,窗口大小由from + size共同决定。不能简单理解成查询返回给调用方的数据量。这样做主要是为了限制内存的消耗。

比如:from为1000000,size为10,逻辑意义是从满足条件的数据中取1000000到(1000000 + 10)的记录。这时ES一定要先将(1000000 + 10)的记录(即result_window)加载到内存中,再进行分页取值的操作。尽管最后我们只取了10条数据返回给客户端,但ES进程执行查询操作的过程中确需要将(1000000 + 10)的记录都加载到内存中,可想而知对内存的消耗有多大。这也是ES中不推荐采用(from + size)方式进行深度分页的原因。

同理,from为0,size为1000000时,ES进程执行查询操作的过程中确需要将1000000 条记录都加载到内存中再返回给调用方,也会对ES内存造成很大压力。

分页查询form

from 关键字: 用来指定起始返回位置,和size关键字连用可实现分页效果

GET /es_db/_search
{
  "query": {
    "match_all": {}
  },
  "size": 5,
  "from": 0
}
深分页查询Scroll

改动index.max_result_window参数值的大小,只能解决一时的问题,当索引的数据量持续增长时,在查询全量数据时还是会出现问题。而且会增加ES服务器内存大结果集消耗完的风险。最佳实践还是根据异常提示中的采用scroll api更高效的请求大量数据集。

#查询命令中新增scroll=1m,说明采用游标查询,保持游标查询窗口一分钟。
#这里由于测试数据量不够,所以size值设置为2。
#实际使用中为了减少游标查询的次数,可以将值适当增大,比如设置为1000。
GET /es_db/_search?scroll=1m 
{
    "query": { "match_all": {}},
    "size":  2
}

查询结果:
除了返回前2条记录,还返回了一个游标ID值_scroll_id。
image-1656919071905

采用游标id查询:

# scroll_id 的值就是上一个请求中返回的 _scroll_id 的值
GET /_search/scroll
{
    "scroll": "1m", 
    "scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmNwcVdjblRxUzVhZXlicG9HeU02bWcAAAAAAABmzRY2YlV3Z0o5VVNTdWJobkE5Z3MtXzJB"
}

image-1656919098200

多次根据scroll_id游标查询,直到没有数据返回则结束查询。采用游标查询索引全量数据,更安全高效,限制了单次对内存的消耗。

指定字段排序sort

注意:会让得分失效

GET /es_db/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "age": "desc"
    }
  ]
}

#排序,分页
GET /es_db/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "age": "desc"
    }
  ],
  "from": 10,
  "size": 5
}
返回指定字段_source

_source 关键字: 是一个数组,在数组中用来指定展示那些字段

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

match在匹配时会对所查找的关键词进行分词,然后按分词匹配查找
match支持以下参数:

  • query : 指定匹配的值
  • operator : 匹配条件类型
    • and : 条件分词后都要匹配
    • or : 条件分词后有一个匹配即可(默认)
  • minmum_should_match : 最低匹配度,即条件在倒排索引中最低的匹配度
#模糊匹配 match   分词后or的效果
GET /es_db/_search
{
  "query": {
    "match": {
      "address": "广州白云山公园"
    }
  }
}

# 分词后 and的效果
GET /es_db/_search
{
  "query": {
    "match": {
      "address": {
        "query": "广州白云山公园",
        "operator": "AND"
      }
    }
  }
}

在match中的应用: 当operator参数设置为or时,minnum_should_match参数用来控制匹配的分词的最少数量。

# 最少匹配广州,公园两个词
GET /es_db/_search
{
  "query": {
    "match": {
      "address": {
        "query": "广州公园",
        "minimum_should_match": 2
      }
    }
  }
}

短语查询match_phrase

match_phrase查询分析文本并根据分析的文本创建一个短语查询。match_phrase 会将检索关键词分词。match_phrase的分词结果必须在被检索字段的分词中都包含,而且顺序必须相同,而且默认必须都是连续的。

GET /es_db/_search
{
  "query": {
    "match_phrase": {
      "address": "广州白云山"
    }
  }
}
GET /es_db/_search
{
  "query": {
    "match_phrase": {
      "address": "广州白云"
    }
  }
}

为什么查询广州白云山有数据,广州白云没有数据?

image-1656919234077

分析原因:
先查看广州白云山公园分词结果,可以知道广州和白云不是相邻的词条,中间会隔一个白云山,而match_phrase匹配的是相邻的词条,所以查询广州白云山有结果,但查询广州白云没有结果。

POST _analyze
{
    "analyzer":"ik_max_word",
    "text":"广州白云山"
}
#结果
{
  "tokens" : [
    {
      "token" : "广州",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "白云山",
      "start_offset" : 2,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "白云",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "云山",
      "start_offset" : 3,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 3
    }
  ]
}

如何解决词条间隔的问题?可以借助slop参数,slop参数告诉match_phrase查询词条能够相隔多远时仍然将文档视为匹配。

#广州云山分词后相隔为2,可以匹配到结果
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条件,同时也和 multi_match query 一样,支持多字段搜索。和match类似,但是match需要指定字段名,query_string是在所有字段中搜索,范围更广泛。
注意: 查询字段分词就将查询条件分词查询,查询字段不分词将查询条件不分词查询

  • 未指定字段查询
GET /es_db/_search
{
  "query": {
    "query_string": {
      "query": "张三 OR 橘子洲"
    }
  }
}
  • 指定单个字段查询
#Query String
GET /es_db/_search
{
  "query": {
    "query_string": {
      "default_field": "address",
      "query": "白云山 OR 橘子洲"
    }
  }
}
  • 指定多个字段查询
GET /es_db/_search
{
  "query": {
    "query_string": {
      "fields": ["name","address"],
      "query": "张三 OR (广州 AND 王五)"
    }
  }
}
simple_query_string

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

  • +替代AND
  • | 替代OR
  • -替代NOT
#simple_query_string 默认的operator是OR
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用来使用关键词查询(精确匹配),还可以用来查询没有被进行分词的数据类型。Term是表达语意的最小单位,搜索和利用统计语言模型进行自然语言处理都需要处理Term。match在匹配时会对所查找的关键词进行分词,然后按分词匹配查找,而term会直接对关键词进行查找。一般模糊查找的时候,多用match,而精确查找时可以使用term。

  • ES中默认使用分词器为标准分词器(StandardAnalyzer),标准分词器对于英文单词分词,对于中文单字分词。
  • 在ES的Mapping Type 中 keyword , date ,integer, long , double , boolean or ip 这些类型不分词,只有text类型分词。
#关键字查询 term
# 思考: 查询广州白云是否有数据,为什么?
GET /es_db/_search
{
  "query":{
    "term": {
      "address": {
        "value": "广州白云"
      }
    }
  }
}

# 采用term精确查询, 查询字段映射类型为keyword
GET /es_db/_search
{
  "query":{
    "term": {
      "address.keyword": {
        "value": "广州白云山公园"
      }
    }
  }
}

在ES中,Term查询,对输入不做分词。会将输入作为一个整体,在倒排索引中查找准确的词项,并且使用相关度算分公式为每个包含该词项的文档进行相关度算分。

PUT /product/_bulk
{"index":{"_id":1}}
{"productId":"xxx123","productName":"iPhone"}
{"index":{"_id":2}}
{"productId":"xxx111","productName":"iPad"}

# 思考: 查询iPhone可以查到数据吗?  
GET /product/_search
{
  "query":{
    "term": {
      "productName": {
        "value": "iPhone"
      }
    }
  }
}

GET /product/_analyze
{
  "analyzer":"standard",
  "text":"iPhone"
}

# 对于英文,可以考虑建立索引时忽略大小写
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,避免算分,并利用缓存,提高性能。

  • 将Query 转成 Filter,忽略TF-IDF计算,避免相关性算分的开销
  • Filter可以有效利用缓存
GET /es_db/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "term": {
          "address.keyword": "广州白云山公园"
        }
      }
    }
  }
}

ES中的结构化搜索

结构化搜索(Structured search)是指对结构化数据的搜索。
结构化数据:

  • 日期,布尔类型和数字都是结构化的
  • 文本也可以是结构化的。
    • 如彩色笔可以有离散的颜色集合:红(red) 、绿(green、蓝(blue)
    • 一个博客可能被标记了标签,例如,分布式(distributed)和搜索(search)
    • 电商网站上的商品都有UPC(通用产品码Universal Product Code)或其他的唯一

标识,它们都需要遵从严格规定的、结构化的格式。
应用场景:对bool,日期,数字,结构化的文本可以利用term做精确匹配

GET /es_db/_search
{
  "query": {
    "term": {
      "age": {
        "value": 28
      }
    }
  }
}

term处理多值字段,term查询是包含,不是等于

POST /employee/_bulk
{"index":{"_id":1}}
{"name":"小明","interest":["跑步","篮球"]}
{"index":{"_id":2}}
{"name":"小红","interest":["跳舞","画画"]}
{"index":{"_id":3}}
{"name":"小丽","interest":["跳舞","唱歌","跑步"]}

POST /employee/_search
{
  "query": {
    "term": {
      "interest.keyword": {
        "value": "跑步"
      }
    }
  }
}

前缀查询prefix

它会对分词后的term进行前缀搜索。

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

通配符查询wildcard

通配符查询:工作原理和prefix相同,只不过它不是只比较开头,它能支持更为复杂的匹配模式。

GET /es_db/_search
{
  "query": {
    "wildcard": {
      "address": {
        "value": "*白*"
      }
    }
  }
}

范围查询range

  • range:范围关键字
  • gte 大于等于
  • lte  小于等于
  • gt 大于
  • lt 小于
  • now 当前时间
POST /es_db/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 25,
        "lte": 28
      }
    }
  }
}
日期range
DELETE /product
POST /product/_bulk
{"index":{"_id":1}}
{"price":100,"date":"2021-01-01","productId":"XHDK-1293"}
{"index":{"_id":2}}
{"price":200,"date":"2022-01-01","productId":"KDKE-5421"}

GET /product/_mapping

GET /product/_search
{
  "query": {
    "range": {
      "date": {
        "gte": "now-2y"
      }
    }
  }
}

多id查询ids

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

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

模糊查询fuzzy

在实际的搜索中,我们有时候会打错字,从而导致搜索不到。在Elasticsearch中,我们可以使用fuzziness属性来进行模糊查询,从而达到搜索有错别字的情形。
fuzzy 查询会用到两个很重要的参数,fuzziness,prefix_length

  • fuzziness:表示输入的关键字通过几次操作可以转变成为ES库里面的对应field的字段
    • 操作是指:新增一个字符,删除一个字符,修改一个字符,每次操作可以记做编辑距离为1,
    • 如中文集团到中威集团编辑距离就是1,只需要修改一个字符;
    • 该参数默认值为0,即不开启模糊查询。
    • 如果fuzziness值在这里设置成2,会把编辑距离为2的东东集团也查出来。
  • prefix_length:表示限制输入关键字和ES对应查询field的内容开头的第n个字符必须完全匹配,不允许错别字匹配
    • 如这里等于1,则表示开头的字必须匹配,不匹配则不返回
    • 默认值也是0
    • 加大prefix_length的值可以提高效率和准确率。
GET /es_db/_search
{
  "query": {
    "fuzzy": {
      "address": {
        "value": "白运山",
        "fuzziness": 1    
      }
    }
  }
}

GET /es_db/_search
{
  "query": {
    "match": {
      "address": {
        "query": "广洲",
        "fuzziness": 1
      }
    }
  }
}

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

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

高亮highlight

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

  • pre_tags 前缀标签
  • post_tags 后缀标签
  • tags_schema 设置为styled可以使用内置高亮样式
  • require_field_match 多字段高亮需要设置为false

示例数据

#指定ik分词器
PUT /products
{
  "settings" : {
      "index" : {
          "analysis.analyzer.default.type": "ik_max_word"
      }
  }
}

PUT /products/_doc/1
{
  "proId" : "2",
  "name" : "牛仔男外套",
  "desc" : "牛仔外套男装春季衣服男春装夹克修身休闲男生潮牌工装潮流头号青年春秋棒球服男 7705浅蓝常规 XL",
  "timestamp" : 1576313264451,
  "createTime" : "2019-12-13 12:56:56"
}

PUT /products/_doc/2
{
  "proId" : "6",
  "name" : "HLA海澜之家牛仔裤男",
  "desc" : "HLA海澜之家牛仔裤男2019时尚有型舒适HKNAD3E109A 牛仔蓝(A9)175/82A(32)",
  "timestamp" : 1576314265571,
  "createTime" : "2019-12-18 15:56:56"
}

测试

GET /products/_search
{
  "query": {
    "term": {
      "name": {
        "value": "牛仔"
      }
    }
  },
  "highlight": {
    "fields": {
      "*":{}
    }
  }
}
自定义高亮html标签

可以在highlight中使用pre_tags和post_tags

GET /products/_search
{
  "query": {
    "term": {
      "name": {
        "value": "牛仔"
      }
    }
  },
  "highlight": {
    "post_tags": ["</span>"], 
    "pre_tags": ["<span style='color:red'>"],
    "fields": {
      "*":{}
    }
  }
}
多字段高亮
GET /products/_search
{
  "query": {
    "term": {
      "name": {
        "value": "牛仔"
      }
    }
  },
  "highlight": {
    "pre_tags": ["<font color='red'>"],
    "post_tags": ["<font/>"],
    "require_field_match": "false",
    "fields": {
      "name": {},
      "desc": {}
    }
  }
}

上一篇 下一篇