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 | #先在tar所在目录下打开cmd |
启动之后访问http://localhost:9200/就可以看到elasticsearch信息:
1.1.2 安装Kibana
通过下面的Docker命令,即可部署Kibana:
1 | #先在tar所在目录下打开cmd |
启动之后访问http://localhost:5601/就可以通过kibana数据化访问elasticsearch:
可以点击右上角Dev tools,进入开发工具页面:
点击之后:
2.倒排索引
elasticsearch的高性能搜索表现,因为底层的倒排索引技术解决的就是根据==部分词条模糊匹配==的问题。【Innodb底层就是用倒排索引做的全文索引】
2.1 正向索引(精准匹配)
我们有一张名为tb_goods的表:
其中,id字段已经创建了索引(底层使用b+树)所以根据id搜索的速度会非常快。但是其他字段例如title只在叶子结点上存在。
比如用户的sql语句为:
1 | select * |
搜索大概流程如图:
说明:
- 1)检查到搜索条件为
like '%手机%'
,需要找到title
中包含手机
的数据 - 2)逐条遍历每行数据(每个叶子节点),比如第1次拿到
id
为1的数据 - 3)判断数据中的
title
字段值是否符合条件 - 4)如果符合则放入结果集,不符合则丢弃
- 5)回到步骤1
综上,根据id搜索条件为精确匹配时,可以走索引,查询效率较高。而当搜索条件为模糊匹配时,由于索引无法生效,导致从索引查询退化为全表扫描,效率很差。
因此,正向索引适合于根据索引字段的精确搜索,不适合基于部分词条的模糊匹配。
而倒排索引恰好解决的就是根据部分词条模糊匹配的问题。
2.2 倒排索引(模糊匹配)
2.2.1 基本概念
倒排索引中两个重要的概念:
文档(Document):用来搜索的数据,每一条数据就是一个文档【一个网页,一个商品信息】
词条(Term):对文档数据/用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条【我是中国人,就可以分为:我,是,中国人,国人,人这几个词条】
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 | 方法一:同步双写,课程上架的时候数据写入Mysql,同步也写入ES |
策略 | 优点 | 缺点 |
---|---|---|
同步双写 | - 简单易实现 - 实时性高 |
- 代码侵入性强 - 存在不一致的风险 - 可能影响系统性能 |
异步双写(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 | { |
对应的每个字段映射(Mapping):
1.2 索引库的CRUD
由于Elasticsearch采用的是Restful风格的API,因此其请求方式和路径相对都比较规范,而且请求参数也都采用JSON风格。
我们直接基于Kibana的DevTools来编写请求做测试,由于有语法提示,会非常方便。
1.2.1 创建索引库和映射
基本语法:
- 请求方式:
PUT
- 请求路径:
/索引库名
,可以自定义 - 请求参数:
mapping
映射
格式:
1 | PUT /索引库名称 |
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 | POST /索引库名/_doc/文档id |
例如,目前要新增id=1的文档:
2.2 查询文档
语法:
1 | GET /{索引库名称}/_doc/{id} |
例如,查询id=1的文档:
2.3 删除文档
语法:
1 | DELETE /{索引库名}/_doc/id值 |
例如,删除id=1的文档:
2.4 修改文档
修改有两种方式:
- 全量修改:直接覆盖原来的文档【会删除旧文档,添加新文档(如果没有就直接删除)】
- 局部修改:修改文档中的部分字段
2.4.1 全量修改
全量修改是覆盖原来的文档,其本质是两步操作:
- 根据指定的id删除文档
- 新增一个相同id的文档
注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
语法:
1 | PUT /{索引库名}/_doc/文档id |
2.4.2 局部修改
局部修改是只修改指定id匹配的文档中的部分字段。【注意:局部修改是POST】
语法:
1 | POST /{索引库名}/_update/文档id |
2.5 批处理
类似于Mysql数据库,可以进行多条数据一次性操作【感觉很麻烦,主要是可读性很差】
批处理采用==POST请求==,基本语法如下:
1 | POST _bulk |
==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 | <dependency> |
1.1.2 覆盖ES版本
因为SpringBoot默认的ES版本是7.17.10
,所以我们需要覆盖默认的ES版本:
1 | <properties> |
1.1.3 初始化RestHighLevelClient
初始化的代码如下:
1 | RestHighLevelClient client = new RestHighLevelClient( |
==1.2 商品Mapping映射==
我们针对购物车数据库进行分析:
我们可以对购物车的所有字段进行分析,判断哪些字段必须添加到ElasticSearch中,判断哪些字段必须添加搜索功能。从而进行新建索引库和映射:
在网页上的代码如下:
1 | PUT /items |
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 | GET /{索引库名}/_search #_search是固定路径,不能修改 |
例如,我们以最简单的无条件查询为例【查询类型=match_all】:
1 | GET /items/_search #_search是固定路径,不能修改 |
虽然是match_all,但是响应结果中并不会包含索引库中的所有文档,而是仅有10条。这是因为处于安全考虑,elasticsearch设置了默认的查询页数
2.查询—-①叶子查询
2.1 全文检索–(分词)
全文检索的种类也很多,详情可以参考官方文档:
2.1.1 match–检索一个字段
1 | GET /{索引库名}/_search |
举例:
2.1.2 multi_match–检索多个字段
1 | GET /{索引库名}/_search |
举例:
2.2 精确查询–(不分词)
精确查询,英文是Term-level query
,顾名思义,词条级别的查询。也就是说不会对用户输入的搜索条件再分词,而是作为一个词条,与搜索的字段内容精确值匹配。因此推荐查找keyword
、数值、日期、boolean
类型的字段。例如:
- id
- price
- 城市
- 地名
- 人名
等等,作为一个整体才有含义的字段。
详情可以查看官方文档:
2.2.1 term–根据词条精确匹配
1 | GET /{索引库名}/_search |
举例:
2.2.2 range–根据数值范围查询
1 | GET /{索引库名}/_search |
举例:
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 | GET /indexName/_search |
4.2 分页
elasticsearch 【默认】只返回==top10数据==。而如果要查询更多数据就需要修改分页参数了
elasticsearch中通过修改from
、size
参数来控制要返回的分页结果:
from
:从第几个文档开始size
:总共查询几个文档
类似于mysql中的limit ?, ?
基本语法:
1 | GET /items/_search |
elasticsearch的数据一般会采用分片存储,也就是把一个索引中的数据分成N份,存储到不同节点上。这种存储方式比较有利于数据扩展,但给分页带来了一些麻烦。
举例如下:
如果一个索引库有100000条数据,分别存储到4个分片中,每个分片有25000条数据。现在每页查询10条,查询第99页。那分页查询条件如下:
1 | GET /items/_search |
从语句分析来讲:要查询的是第990-1000名数据。
从实现思路分析来讲:①将所有数据排序,找出前1000名②取出其中990-1000的部分
这样来看操作很复杂,因为每个片的数据不是顺序存储的,只能所有拿到一起再重新排序,才能找到最终前1000名截取出990-1000数据
因此,Elasticsearch对普通分页会有一个设置:(from+size)<10000
4.3 深度分页(解决普通分页)
针对深度分页,elasticsearch提供了两种解决方案:
search after
:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。scroll
:原理将排序后的文档id形成快照,保存下来,基于快照做分页。官方已经不推荐使用。
以search after为例:
优点:没有查询上限,支持深度分页【更智能,无上限】
缺点:只能向后逐页查询,不能随机翻页【一页一页查询】
场景:数据大规模顺序迁移、手机滚动查询【一页一页】
适用建议:
大多数情况下,我们采用普通分页就可以了。查看百度、京东等网站,会发现其分页都有限制。例如百度最多支持77页,每页不足20条。京东最多100页,每页最多60条。
因此,一般我们采用限制分页深度的方式即可,无需实现深度分页。
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查询【可以进行查询,分页,排序,高亮,聚合等操作】