0.使用场景 数据库的模糊搜索功能单一,匹配条件非常苛刻,必须恰好包含用户搜索的关键字。
而在搜索引擎中,用户输入出现个别错字,或者用拼音搜索、同义词搜索都能正确匹配到数据。
综上,在面临海量数据的搜索,或者有一些复杂搜索需求的时候,推荐使用专门的搜索引擎来实现搜索功能。
0.1 全文搜索 Elasticsearch 凭借其强大、可扩展和快速的搜索功能,在全文搜索场景中表现出色。
它通常用于支持大型网站和应用程序的搜索功能,允许用户执行复杂的查询并获得近乎实时的响应 。
0.2 实时分析 Elasticsearch 能够实时执行分析,因此适用于跟踪实时数据(如用户活动、交易或传感器输出)的仪表盘。这种能力使企业能够根据最新信息及时做出决策。
0.3 机器学习 通过在 X-Pack 中添加机器学习功能,Elasticsearch 可以自动检测数据中的异常、模式和趋势 。这对于预测分析、个性化和提高运营效率非常有用。
0.4 地理数据应用 Elasticsearch 通过地理空间索引和搜索功能支持地理数据。这对于需要管理和可视化地理信息(如地图和基于位置的服务)的应用非常有用,使执行邻近搜索 和基于位置的数据可视化成为可能。
0.5 日志和事件数据分析 企业使用 Elasticsearch 聚合、监控和分析各种来源的日志和事件数据。它是 ELK 堆栈(Elasticsearch、Logstash、Kibana)的关键组件,该堆栈常用于管理系统和应用程序日志,以发现问题并监控系统健康状况。
0.6 安全信息和事件管理(SIEM) Elasticsearch 可用作 SIEM 的工具,帮助企业实时分析安全事件。这对于检测、分析和响应安全事件和漏洞至关重要。
上述每个用例都利用了 Elasticsearch 的优势(如可扩展性、速度和灵活性)来处理不同的数据类型和复杂的查询,为数据驱动型应用提供了重要价值。
1.Elasticsearch(ES) ES是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。
Elasticsearch结合Kibana,Logstash,beats是一整套技术栈,被叫做==ELK==。经常用来做日志收集、系统监控和状态分析等等:
1.1 安装 1.1.1 安装elasticsearch 通过下面的Docker命令即可安装单机版本的elasticsearch:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #先在tar所在目录下打开cmd docker load -i es.tar #创建一个网络【不然kibana不能连接es,踩坑了!!】 docker network create elastic #黑马安装: docker run -d \ --name es \ -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \ #配置jvm的内存 -e "discovery.type=single-node" \ #配置运行模式【单点模式/集群模式】 -v es-data:/usr/share/elasticsearch/data \ #挂载 -v es-plugins:/usr/share/elasticsearch/plugins \ --privileged \ --network hm-net \ -p 9200:9200 \ #访问http端口 -p 9300:9300 \ #集群使用 elasticsearch:7.12.1 #csdn安装: docker run -d --name es -e ES_JAVA_OPTS="-Xms512m -Xmx512m" -e "discovery.type=single-node" --privileged --network elastic -p 9200:9200 -p 9300:9300 elasticsearch:7.12.1
启动之后访问http://localhost:9200/就可以看到elasticsearch信息:
1.1.2 安装Kibana 通过下面的Docker命令,即可部署Kibana:
1 2 3 4 5 6 7 8 9 10 11 12 13 #先在tar所在目录下打开cmd docker load -i kibana.tar #黑马安装: docker run -d \ --name kibana \ -e ELASTICSEARCH_HOSTS=http://es:9200 \ #es的地址,这里的es要和es配置docker的时候--name一致 --network=hm-net \ #网络和es一个网络 -p 5601:5601 \ kibana:7.12.1 #要保证和es版本一致!!! #csdn安装: docker run -d --name kibana -e ELASTICSEARCH_HOSTS=http://es:9200 --network elastic -p 5601:5601 kibana:7.12.1
启动之后访问http://localhost:5601/就可以通过kibana数据化访问elasticsearch:
可以点击右上角Dev tools,进入开发工具页面:
点击之后:
2.倒排索引 elasticsearch的高性能搜索表现,因为底层的倒排索引技术解决的就是根据==部分词条模糊匹配==的问题。【Innodb底层就是用倒排索引做的全文索引】
2.1 正向索引(精准匹配) 我们有一张名为tb_goods的表:
其中,id字段已经创建了索引(底层使用b+树)所以根据id搜索的速度会非常快。但是其他字段例如title只在叶子结点上存在。
比如用户的sql语句为:
1 2 3 select * from tb_goods where title like '%手机%';
搜索大概流程如图:
说明:
1)检查到搜索条件为like '%手机%'
,需要找到title
中包含手机
的数据
2)逐条遍历每行数据(每个叶子节点),比如第1次拿到id
为1的数据
3)判断数据中的title
字段值是否符合条件
4)如果符合则放入结果集,不符合则丢弃
5)回到步骤1
综上,根据id搜索条件为精确匹配时,可以走索引,查询效率较高。而当搜索条件为模糊匹配时,由于索引无法生效,导致从索引查询退化为全表扫描,效率很差。
因此,正向索引适合于根据索引字段的精确搜索,不适合基于部分词条的模糊匹配。
而倒排索引恰好解决的就是根据部分词条模糊匹配的问题。
2.2 倒排索引(模糊匹配) 2.2.1 基本概念 倒排索引中两个重要的概念:
2.2.2 创建流程 创建倒排索引是对正向索引的一种特殊处理和应用,流程如下:
将每个文档的数据利用分词算法 根据语义拆分得到一个个词条
创建表,表中每行数据:{词条,词条所在文档id,词条位置}
因为词条唯一性,可以给词条创建正向索引(唯一索引)
此时形成的这张以词条为索引的表就是倒排索引表:
2.2.3 搜索流程 以搜索”华为手机”为例,如图:
流程描述:
1)用户输入条件"华为手机"
进行搜索。
2)对用户输入条件分词 ,得到词条:华为
、手机
。
3)拿着词条在倒排索引中查找(由于词条有唯一索引,查询效率很高 ),即可得到包含词条的文档id:1、2、3
。
4)拿着文档id
到正向索引中查找具体文档即可(由于id
也有索引 ,查询效率也很高)
==根据条件先分词,每个词条去倒排索引查询【词条有唯一索引】找到对应文档id,根据文档id到正向索引【id有索引】查询具体文档(一条数据)==
2.2.4 两者对比
正向索引 是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程 。
而倒排索引 则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程 。
两者优缺点:
正向索引
倒排索引
优点
1.可以给多个字段创建索引 2.根据索引字段搜索和排序速度非常快
部分词条查询效率高【创建唯一索引】
缺点
部分词条查询效率不高,只能全表扫描
1.只能给词条创建索引,而不是字段 2.无法根据字段做排序
3.基础概念 3.1 文档(一行数据)和字段(一个列) elasticsearch是面向文档(Document) 存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json
格式存储在elasticsearch
中:
因此, 数据库中一行数据 <==> ES中一个JSON文档;
而数据库中每行数据都包含很多列,这些列就转换为JSON文档中的字段(Field)
3.2 索引(数据库的表)和映射(数据库表结构约束) 随着业务发展,需要在es中存储的文档也会越来越多,比如有商品的文档,用户的文档,订单的文档等等;
所有文档都散乱存放显然非常混乱,也不方便管理。
因此,我们要将==类型相同的文档==(一行数据)集中在一起管理,称为索引(Index)
因此,==索引(类型相同的很多行文档) <—->数据库中的表==
数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。
因此,索引库中就有==映射(mapping) ,是索引中文档的字段约束信息,类似表的结构约束==
3.3 Mysql和Elasticsearch对比
注意:mysql的语法就是sql,而es的语法是dsl【提供json风格的请求语句,用来操作es进行crud】
Mysql:擅长事务类型操作,可以确保数据的安全和一致性
Elasticsearch:擅长海量数据的搜索、分析、计算
因此在企业中,往往是两者结合使用:
对安全性要求较高的写操作,使用mysql实现
对查询性能要求较高的搜索需求,使用elasticsearch实现
两者再基于某种方式,实现数据的同步,保证一致性
3.4 数据一致性 1 2 3 4 5 方法一:同步双写,课程上架的时候数据写入Mysql,同步也写入ES 方法二:异步双写,课程上架的时候数据写入Mysql,发送消息给MQ,MQ通知ES更新 【项目使用】 方法三:定时同步,对于数据库新增的时候,定时批量/全量同步到ES 方法四:基于Logstash输入输出插件 方法五:基于cancal数据库增量日志解析工具,伪装主从数据库进行同步
策略
优点
缺点
同步双写
- 简单易实现 - 实时性高
- 代码侵入性强 - 存在不一致的风险 - 可能影响系统性能
异步双写(MQ方式)
- 解耦数据写入操作 - 通过消息队列提升性能和扩展性
- 系统复杂度增加 - 可能存在消息丢失的风险 - 引入了消息中间件的依赖
定期同步
- 实现简单 - 无需改变现有业务逻辑
- 实时性差 - 可能给数据库带来额外压力
基于Binlog实时同步
- 无代码侵入 - 实时性较好 - 业务逻辑与数据同步解耦
- 构建Binlog系统复杂 - 可能存在MQ延时风险
使用Canal监听Binlog同步数据到ES
- 基于MySQL的Binlog,实现数据的实时同步 - 减少系统耦合
- 需要维护额外的Canal服务
4.IK分词器(ikun) Elasticsearch的关键就是倒排索引,而倒排索引依赖于对文档内容的分词情况(分词好那就效率高),而分词则需要高效、精准的分词算法,IK分词器就是这样一个中文分词算法
4.1 安装IK分词器 方案一:在线安装
1 docker exec -it es ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
方案二:离线安装
首先,查看之前安装的elasticsearch容器的plugins数据卷目录:
1 docker volume inspect es-plugins
可以看到elasticsearch的插件挂载到了/var/lib/docker/volumes/es-plugins/_data
这个目录。我们需要把IK分词器上传至这个目录
4.2 使用IK分词器 4.2.1 官方标准分词器(standard) 我们在Kibana的DevTools上来测试分词器,首先测试Elasticsearch官方提供的标准分词器:
我们可以看到,标准分词器只能1个字作为一个1个词条,无法正取对中文做分词
4.2.2 IK分词器(ik_smart智能语义切分) 这种情况下,可以智能的将词语切分。但是像程序员这种词可以拆分为程序员,程序,员。这个分词器无法实现
4.2.3 IK分词器(ik_max_word最细粒度切分) 这种情况下,可以在4.2.2的前提下继续细分【程序员这种词可以拆分为程序员,程序,员】
4.3 扩展词典 随着互联网的发展,“造词运动”也越发的频繁。出现了很多新的词语,在原有的词汇列表中并不存在。比如:“泰裤辣”,“传智播客” 等
所以想要正确分词,IK分词器的词库也需要不断地更新 ,IK分词器提供了扩展词汇的功能:
我们在ik-config文件夹下的IkAnalyzer.cfg.xml
文件添加拓展词典和停用词典,这样我们再调用的时候,宋亚翔和传智播客就可以被认为是一个词语作为词条
==基础操作(对索引库和文档基础操作)== ==方式一:通过ES手动创建–很繁琐== 1.索引库操作(数据库表) index类似数据库表,映射类似表的结构。我们要向es中存储数据,必须先创建索引(数据库表)和映射(数据库定义)
1.1 Mapping映射属性 Mapping是对索引库的文档设置约束,常见的mapping属性包括:
1 2 3 4 5 6 7 8 9 10 11 12 { "age": 21, "weight": 52.1, "isMarried": false, "info": "黑马程序员Java讲师", "email": "zy@itcast.cn", "score": [99.1, 99.5, 98.9], "name": { "firstName": "云", "lastName": "赵" } }
对应的每个字段映射(Mapping):
1.2 索引库的CRUD 由于Elasticsearch采用的是Restful风格的API,因此其请求方式和路径相对都比较规范,而且请求参数也都采用JSON风格。
我们直接基于Kibana的DevTools来编写请求做测试,由于有语法提示,会非常方便。
1.2.1 创建索引库和映射 基本语法 :
请求方式:PUT
请求路径:/索引库名
,可以自定义
请求参数:mapping
映射
格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 PUT /索引库名称 { "mappings": { "properties": { "字段名":{ "type": "text", "analyzer": "ik_smart" }, "字段名2":{ "type": "keyword", "index": "false" }, "字段名3":{ "properties": { "子字段": { "type": "keyword" } } }, // ...略 } } }
1.2.2 查询索引库 基本语法 :
请求方式:GET
请求路径:/索引库名
请求参数:无
1.2.3 修改索引库(只能修改新字段) 倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping
虽然==无法修改mapping中已有字段,却允许添加新的字段到mapping==,因为不会对倒排索引产生影响。因此修改索引库能做的就是向索引库中添加新字段,或者更新索引库的基础属性。
基本语法 :
请求方式:PUT
请求路径:/索引库名
请求参数:/_mapping
1.2.4 删除索引库 基本语法:
请求方式:DELETE
请求路径:/索引库名
请求参数:无
==1.2.5 索引库操作总结== 索引库操作:
创建索引库:PUT /索引库名{“mappings”:{“properties”:{部分新字段信息}}}}
查询索引库:GET /索引库名
删除索引库:DELETE /索引库名
修改索引库【添加字段】:PUT /索引库名/_mapping{“properties”:{部分新字段信息}}
可以看到,对索引库的操作基本遵循的Restful的风格,因此API接口非常统一,方便记忆。
2.文档操作(一行数据) 有了索引库,接下来就可以向索引库中添加数据。而ElasticSearch数据就是JSON风格的文档。
2.1 新增文档 语法:
1 2 3 4 5 6 7 8 9 POST /索引库名/_doc/文档id { "字段1": "值1", "字段2": "值2", "字段3": { "子属性1": "值3", "子属性2": "值4" }, }
例如,目前要新增id=1的文档:
2.2 查询文档 语法:
例如,查询id=1的文档:
2.3 删除文档 语法:
例如,删除id=1的文档:
2.4 修改文档 修改有两种方式:
全量修改:直接覆盖原来的文档【会删除旧文档,添加新文档(如果没有就直接删除)】
局部修改:修改文档中的部分字段
2.4.1 全量修改 全量修改是覆盖原来的文档,其本质是两步操作:
注意 :如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
语法:
1 2 3 4 5 6 PUT /{索引库名}/_doc/文档id { "字段1" : "值1" , "字段2" : "值2" , }
2.4.2 局部修改 局部修改是只修改指定id匹配的文档中的部分字段。【注意:局部修改是POST 】
语法:
1 2 3 4 5 6 POST /{索引库名}/_update/文档id { "doc" : { "字段名" : "新的值" , } }
2.5 批处理 类似于Mysql数据库,可以进行多条数据一次性操作【感觉很麻烦,主要是可读性很差】
批处理采用==POST请求==,基本语法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 POST _bulk # 1.修改 --如果文档id存在就覆盖,不存在就创建 # index代表新增操作 _index表示索引库名 _id表示要操作的文档id { "index" : { "_index" : "test" , "_id" : "1" } } # 代表新增的文档内容 { "field1" : "value1" } # 2.删除 { "delete" : { "_index" : "test" , "_id" : "2" } } # 3.新增 --如果文档id存在就报错 { "create" : { "_index" : "test" , "_id" : "3" } } { "field1" : "value3" } # 4.更新 { "update" : {"_id" : "1" , "_index" : "test" } } { "doc" : {"field2" : "value2" } }
==2.6 文档操作总结== 相对于索引库创建,大致就是中间多了一个_doc路径,修改文档类似于修改索引库比较特殊。
创建文档:POST /{索引库名}/_doc/文档id { json文档 }
查询文档:GET /{索引库名}/_doc/文档id
删除文档:DELETE /{索引库名}/_doc/文档id
修改文档:
全量修改:PUT /{索引库名}/_doc/文档id { json文档 }
局部修改:POST /{索引库名}/_update/文档id { "doc": {字段}}
==索引库操作和文档操作对比:==
5和6步骤主要是在网页端进行设置,因此提出了一个Java的客户端—==JavaRestClient==
==方式二:通过Java实现—不用繁琐的手动创建== 1.JavaRestClient 提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是==组装DSL语句==,通过http请求发送给ES。
由于ES目前最新版本是8.8,提供了全新版本的客户端,老版本的客户端已经被标记为过时。而我们采用的是7.12版本,因此只能使用老版本客户端:
然后选择7.12版本,HighLevelRestClient版本:
==1.1 初始化RestClient== 在Elasticsearch提供的API中,与Elasticsearch一切交互都封装在一个名为RestHighLevelClient
的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。
1.1.1 引入RestHighLevelClient依赖 在item-service
模块中引入es
的RestHighLevelClient
依赖:
1 2 3 4 <dependency > <groupId > org.elasticsearch.client</groupId > <artifactId > elasticsearch-rest-high-level-client</artifactId > </dependency >
1.1.2 覆盖ES版本 因为SpringBoot默认的ES版本是7.17.10
,所以我们需要覆盖默认的ES版本:
1 2 3 4 5 6 <properties > <maven.compiler.source > 11</maven.compiler.source > <maven.compiler.target > 11</maven.compiler.target > <elasticsearch.version > 7.12.1</elasticsearch.version > </properties >
1.1.3 初始化RestHighLevelClient 初始化的代码如下:
1 2 3 4 5 6 RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( HttpHost.create("http://192.168.xxx.xxx:9200" ) ) );
==1.2 商品Mapping映射== 我们针对购物车数据库进行分析:
我们可以对购物车的所有字段进行分析,判断哪些字段必须添加到ElasticSearch中,判断哪些字段必须添加搜索功能。从而进行新建索引库和映射:
在网页上的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 PUT /items { "mappings": { "properties": { "id": { "type": "keyword" }, "name":{ "type": "text", "analyzer": "ik_max_word" }, "price":{ "type": "integer" }, "stock":{ "type": "integer" }, "image":{ "type": "keyword", "index": false }, "category":{ "type": "keyword" }, "brand":{ "type": "keyword" }, "sold":{ "type": "integer" }, "commentCount":{ "type": "integer", "index": false }, "isAD":{ "type": "boolean" }, "updateTime":{ "type": "date" } } } }
1.3 索引库操作 创建索引库的JavaAPI和Restful接口API对比:
1.3.1 创建索引库
具体代码如下:
创建索引库:
1.3.2 删除索引库 具体代码如下:
1.3.3 查询索引库 具体代码如下:
==1.3.4 索引库操作总结== JavaRestClient操作elasticsearch的流程基本类似。核心是client.indices()
方法来获取索引库的操作对象。
索引库操作的基本步骤:
1.初始化RestHighLevelClient类对象client【创建客户端】
2.创建XxxIndexRequest对象request【XXX是Create
、Get
、Delete
】
3.准备请求参数request.source()方法【Create
时需要参数,其他情况不需要】
4.发送请求client.indices().xxx()方法【xxx是create
、exists
、delete
】
1.4 文档操作 1.4.1 新增文档
1.创建Request对象,这里是IndexRequest
,因为添加文档就是创建倒排索引的过程
2.准备请求参数,本例中就是Json文档
3.发送请求【client.index()方法就好了】
1.4.2 查询文档 与之前的流程类似,代码大概分2步:
创建Request对象
准备请求参数,这里是无参,【直接省略】
发送请求
解析结果【因为结果在_source部分内】
可以看到,响应结果是一个JSON,其中文档放在一个_source
属性中,因此解析就是拿到_source
,反序列化为Java对象即可
1.4.3 删除文档 与查询相比,仅仅是请求方式从DELETE
变成GET
,可以想象Java代码应该依然是2步走:
1)准备Request对象,因为是删除,这次是DeleteRequest
对象。要指定索引库名和id
2)准备参数,无参,直接省略
3)发送请求。因为是删除,所以是client.delete()
方法
1.4.4 修改文档 修改我们讲过两种方式:
全量修改:本质是先根据id删除,再新增【与1.4.1一致】
局部修改:修改文档中的指定字段值
在RestClient的API中,全量修改与新增的API完全一致,判断依据是ID:
如果新增时,ID已经存在,则修改
如果新增时,ID不存在,则新增
这里不再赘述,我们主要关注局部修改的API即可
与之前类似,也是三步走:
1)准备Request
对象。【这次是修改,所以是UpdateRequest
】
2)准备参数。【也就是JSON文档,里面包含要修改的字段】
3)更新文档。【这里调用client.update()
方法】
1.4.5 批量导入文档 7.4.1-7.4.4的单条处理通过BulkRequest 解决。因此BulkRequest
中提供了add
方法,用以添加其它CRUD的请求:
具体代码:
==1.4.6 文档操作总结== 文档操作的基本步骤:
1.初始化RestHighLevelClient类对象client【创建客户端】
2.创建XxxRequest对象request【Xxx是Index
、Update
、Delete
、Bulk
】
3.准备请求参数request.source()方法(Index
、Update
、Bulk
时需要)
4.发送请求client.Xxx()方法【Xxx是index
、get
、update
、delete
、bulk
】
5.解析结果(Get
查询时需要,数据在_source内部)
上述的操作都是围绕id来进行的,只能进行简单查询不符合我们所预期的搜索
==进阶操作(DSL查询,更高级的查询)== Elasticsearch提供基于JSON的DSL(Domain Specific Language )语句 来定义查询条件,其JavaAPI就是在组织DSL条件。
Elasticsearch的查询可以分为两大类:
叶子查询(Leaf query clauses) :一般是在特定的字段里查询特定值,属于简单查询,很少单独使用。
复合查询(Compound query clauses) :以逻辑方式组合多个叶子查询/更改叶子查询的行为方式。
在查询以后,还可以对查询的结果做处理,包括:
• 排序 :按照1个或多个字段值做排序
• 分页 :根据from和size做分页,类似MySQL
• 高亮 :对搜索结果中的关键字添加特殊样式,使其更加醒目
• 聚合 :对搜索结果做数据统计以形成报表
==后续内容总结图:==
==方式一:通过手动创建–DSL查询== 1.快速入门 查询的语法结构:
1 2 3 4 5 6 7 8 GET /{索引库名}/_search #_search是固定路径,不能修改 { "query" : { "查询类型" : { } } }
例如,我们以最简单的无条件查询为例【查询类型=match_all】:
1 2 3 4 5 6 7 8 GET /items/_search #_search是固定路径,不能修改 { "query" : { "match_all": { #查询类型=match_all #match_all无条件,所以条件位置不写即可 } } }
虽然是match_all,但是响应结果中并不会包含索引库中的所有文档,而是仅有10条。这是因为处于安全考虑,elasticsearch设置了默认的查询页数
2.查询—-①叶子查询
2.1 全文检索–(分词) 全文检索的种类也很多,详情可以参考官方文档 :
2.1.1 match–检索一个字段 1 2 3 4 5 6 7 8 GET /{索引库名}/_search { "query" : { "match" : { "字段名" : "搜索条件" } } }
举例:
2.1.2 multi_match–检索多个字段 1 2 3 4 5 6 7 8 9 GET /{索引库名}/_search { "query" : { "multi_match" : { "query" : "搜索条件" , "fields" : ["字段1" , "字段2" ] } } }
举例:
2.2 精确查询–(不分词) 精确查询,英文是Term-level query
,顾名思义,词条级别的查询。也就是说不会对用户输入的搜索条件再分词,而是作为一个词条,与搜索的字段内容精确值匹配。因此推荐查找keyword
、数值、日期、boolean
类型的字段。例如:
等等,作为一个整体才有含义的字段。
详情可以查看官方文档 :
2.2.1 term–根据词条精确匹配 1 2 3 4 5 6 7 8 9 10 GET /{索引库名}/_search { "query" : { "term" : { "字段名" : { "value" : "搜索条件" } } } }
举例:
2.2.2 range–根据数值范围查询 1 2 3 4 5 6 7 8 9 10 11 GET /{索引库名}/_search { "query" : { "range" : { "字段名" : { "gte": {最小值}, "lte": {最大值} } } } }
举例:
2.3 地理(geo)查询 未涉及,用到了补充
3.查询—-②复合查询
其他符合查询和相关语法可以参考官方文档 :
3.1 布尔查询– 与/或/非 bool查询,即布尔查询。就是利用逻辑运算来组合一个或多个查询子句的组合。bool查询支持的逻辑运算有:
must:必须匹配每个子查询,类似“与” 【输入框的搜索条件肯定要参与相关性算分】
should:选择性匹配子查询,类似“或”
must_not:必须不匹配,不参与算分 ,类似“非”
filter:必须匹配,不参与算分 【其他的过滤条件就可以不参与算分】
bool查询的语法如下:
出于性能考虑,与搜索关键字无关的查询尽量采用must_not或filter逻辑运算,避免参与相关性算分
3.2 function_score查询–人为修改相关性算分 3.2.1 相关性算分介绍 当我们利用match进行叶子查询,文档结果会根据与搜索词条的关联度打分 (_score ),返回结果时按照分值降序排列
例如,我们搜索”手机”,结果如下:
从Elasticsearch5.1开始,采用的相关性打分算法是BM25算法,其公式如下:
基于这套公式,就可以判断出某个文档 与用户搜索的关键字 之间的关联度,还是比较准确的。但是,在实际业务需求中,常常会有竞价排名的功能。不是相关度越高排名越靠前,而是掏的钱多的排名靠前。
例如在百度中搜索Java培训,排名靠前的就是广告推广
要想人为控制相关性算法【添加一个过滤条件,增加一个算分函数得到一个值,然后和原始相关性算分运算一下得到新的】,就需要利用Elasticsearch的function_score查询 :
3.2.2 function_score介绍 function score 查询中包含四部分内容:
1.原始查询 条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分 (query score)
2.过滤条件 :filter部分,符合该条件的文档才会重新算分
3.算分函数 :符合filter条件的文档要根据这个函数做运算,得到的函数算分 (function score),有四种函数
weight:函数结果=常量
field_value_factor:函数结果=文档中的某个字段值
random_score:函数结果=随机数
script_score:自定义算分函数算法
4.运算模式 :算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
multiply:相乘
replace:用function score原始算分替换query score
其它,例如:sum、avg、max、min
==【大致就是在原有BM25算法得到相关性算分,然后根据符合filter条件的文档根据算分函数得到一个值,最后两者进行一些运算方式得到最终值】==
function score的运行流程如下:
1)根据原始条件 查询搜索文档,并且计算相关性算分,称为原始算分 (query score)
2)根据过滤条件 ,过滤文档
3)符合过滤条件 的文档,基于算分函数 ,得到函数算分 (function score)–算分函数=①常量②文档中某字段值③随机数④自定义
4)将原始算分 (query score)和函数算分 (function score)基于运算模式[各种] 做运算,得到最终结果,作为相关性算分。
举例:给IPhone这个品牌的手机算分提高十倍,分析如下:
过滤条件:品牌必须为IPhone
算分函数:常量weight,值为10
算分模式:相乘multiply
对应代码:
4.排序和分页 1.默认排序:Elasticsearch支持对搜索结果根据相关度算分(_score)进行排序,按照分值降序排列。
2.指定字段排序:Elasticsearch支持对keyword类型,数值类型,地理坐标类型,日期类型等。
4.1 指定排序 基本语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 GET /indexName/_search { "query" : { "match_all" : {} }, "sort" : [ { "排序字段" : { "order" : "排序方式asc和desc" } } ] }
4.2 分页 elasticsearch 【默认】只返回==top10数据==。而如果要查询更多数据就需要修改分页参数了
elasticsearch中通过修改from
、size
参数来控制要返回的分页结果:
from
:从第几个文档开始
size
:总共查询几个文档
类似于mysql中的limit ?, ?
基本语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 GET /items/_search { "query" : { "match_all" : {} }, "from" : 0 , "size" : 10 , "sort" : [ { "price" : { "order" : "desc" } } ] }
elasticsearch的数据一般会采用分片存储 ,也就是把一个索引中的数据分成N份,存储到不同节点上。这种存储方式比较有利于数据扩展,但给分页带来了一些麻烦。
举例如下:
如果一个索引库有100000条数据,分别存储到4个分片中,每个分片有25000条数据。现在每页查询10条,查询第99页。那分页查询条件如下:
1 2 3 4 5 6 7 8 9 10 GET /items/_search { "from" : 990 , "size" : 10 , "sort" : [ { "price" : "asc" } ] }
从语句分析来讲:要查询的是第990-1000名数据。
从实现思路分析来讲:①将所有数据排序,找出前1000名②取出其中990-1000的部分
这样来看操作很复杂,因为每个片的数据不是顺序存储的,只能所有拿到一起再重新排序,才能找到最终前1000名截取出990-1000数据
因此,Elasticsearch对普通分页会有一个设置:(from+size)<10000
4.3 深度分页(解决普通分页) 针对深度分页,elasticsearch提供了两种解决方案:
search after
:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
scroll
:原理将排序后的文档id形成快照,保存下来,基于快照做分页。官方已经不推荐使用。
以search after为例:
优点:没有查询上限,支持深度分页【更智能,无上限】
缺点:只能向后逐页查询,不能随机翻页【一页一页查询】
场景:数据大规模顺序迁移、手机滚动查询【一页一页】
适用建议:
5.高亮显示 5.1 高亮原理 我们在百度,京东搜索时,关键字会变成红色,比较醒目,这叫高亮显示:
css样式肯定是前端实现页面的时候写好的,但是前端编写页面的时候是不知道页面要展示什么数据的,不可能给数据加标签。而服务端实现搜索功能,要是有elasticsearch
做分词搜索,是知道哪些词条需要高亮的。
因此词条的高亮标签肯定是由服务端提供数据的时候已经加上的
5.2 高亮操作 高亮的思路就是:
用户输入搜索关键字搜索数据
服务端根据搜索关键字到Elasticsearch搜索,并给搜索结果中的关键字词条添加html
标签
前端提前给约定好的html
标签添加CSS
样式
基本语法:
注意:
搜索必须有查询条件,而且是叶子查询的全文检索类型的查询条件(有分词),例如match
参与高亮的字段必须是text
类型的字段
默认情况下参与高亮的字段要与搜索字段一致,除非添加:required_field_match=false
6.聚合 聚合(aggregations
)可以让我们极其方便的实现对数据的统计、分析、运算。例如:
什么品牌的手机最受欢迎?
这些手机的平均价格、最高价格、最低价格?
这些手机每月的销售情况如何?
实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现≈实时搜索 效果。
聚合分类 :
==【注意】:参与聚合的字段必须是Keyword、数值、日期、布尔的类型的字段(这些字段一般不分词)==
举例:
具体位置解释:
==DSL手写规则总结==
==方式二:通过Java实现—RestClient查询== ==0.总体对照分析== 查询数据 我们可以分三步拼凑DSL语句和发起请求获取相应结果:
其中2.组织DSL参数的步骤中source()方法下面对应的查询/高亮/分页/排序/聚合:
在查询方面我们直接可以通过QueryBuilders类调用对应的叶子查询/复杂查询
解析数据 我们可以通过响应结果和Elasticsearch页面返回结果获取具体细节: 【可以扩展很多,但其实就是对照DSL查询结果写 】
黑马的图:
整体步骤 文档搜索的基本步骤是:
创建SearchRequest
对象实例request
准备request.source()
,也就是DSL语句【这个位置可以创建查询,分页,排序,聚合,高亮等操作】
QueryBuilders
来构建查询条件
传入request.source()
的query()
方法
发送请求,得到结果
解析结果(参考DSL查询得到的JSON结果,从外到内,逐层解析)
1.查询 上述手动创建DSL查询的时候讲过查询的分类:
1.1 叶子查询 1.1.1 全文检索
1.1.2 精确查询
1.2 复合查询
1.3 举例 举例:
具体代码如下:
2.分页和排序 2.1 基础API
3.高亮
条件同样是在request.source()
中指定,只不过高亮条件要基于HighlightBuilder
来构造
高亮响应结果与搜索的文档结果不在一起,需要单独解析
3.1 基础API
3.2 获取高亮值 每一条hits信息原始数据在_source部分,而高亮部分在同级highlight内部:
在整体代码的位置:
代码解读:
从结果中获取_source
。hit.getSourceAsString()
,这部分是非高亮结果,json字符串。还需要反序列为ItemDoc
对象
获取高亮结果。hit.getHighlightFields()
,返回值是一个Map
,key是高亮字段名称,值是HighlightField
对象,代表高亮值
从Map
中根据高亮字段名称,获取高亮字段值对象HighlightField
从HighlightField
中获取Fragments
,并且转为字符串。这部分就是真正的高亮字符串了
最后:用高亮的结果替换ItemDoc
中的非高亮结果
4.聚合 我们以品牌聚合为例:
4.1 基础API
在Java代码中位置:
4.2 获取桶结果
在Java代码中位置:
==Elasticsearch学习总结== ==1.基本使用思路:== 1.创建索引库和映射 –有了类似于数据库的表和表定义
2.对文档进行CRUD –有了类似于数据库的一行行数据
3.在对应位置进行复杂的DSL查询 –我们可以进行高级的查询,分页,排序,高亮,聚合操作
==2.如果写Java代码的话:== 1.pom.xml导入RestHighLevelClient依赖,然后在父工程覆盖ES版本,初始化RestHighLevelClient依赖
2.分析Mysql哪些字段需要搜索和必须存在,然后Elasticsearch在网页上进行手动设定索引库映射
3.对索引库进行操作【不过我认为网页上更方便】 —到这一步类似于完成了数据库的表和表定义
4.对文档操作【不过我认为网页上更方便】 —到这一步类似于有了数据库的数据
5.在具体位置就可以进行复杂的DSL查询【可以进行查询,分页,排序,高亮,聚合等操作】