type
status
date
May 6, 2024 02:49 AM
slug
summary
category
tags
password
icon
ES概述介绍倒排索引正向索引倒排索引正向和倒排es中的一些概念文档和字段索引和映射es与mysql安装es和kibana部署单点es部署单点kibana安装IK分词器索引库操作mapping映射属性索引库的CRUD创建索引库和映射查询索引库修改索引库删除索引库文档操作新增文档查询文档修改文档删除文档RestAPI创建索引库删除索引库判断索引库是否存在RestClient操作文档新增文档查询文档删除文档修改文档批量导入文档
ES概述
介绍
- elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容。
- elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域,而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。
倒排索引
倒排索引的概念是基于MySQL这样的正向索引而言的。
正向索引
- 如果是根据
id
查询,那么直接走索引,查询速度非常快。但如果是基于title
做模糊查询,只能是逐行扫描数据,流程如下: - 用户搜索数据,条件是title符合
"%手机%"
- 逐行获取数据,比如id为1的数据
- 判断数据中的title是否符合用户搜索条件
- 如果符合则放入结果集,不符合则丢弃。回到步骤1
逐行扫描,也就是全表扫描,随着数据量增加,其查询效率也会越来越低。当数据量达到数百万时,就是一场灾难。
倒排索引
- 倒排索引中有两个非常重要的概念:
- 文档(
Document
):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息。 - 词条(
Term
):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条。
- 创建倒排索引是对正向索引的一种特殊处理,流程如下:
- 将每一个文档的数据利用算法分词,得到一个个词条。
- 创建表,每行数据包括词条、词条所在文档id、位置等信息。
- 因为词条唯一性,可以给词条创建索引,例如hash表结构索引。
- 倒排索引的搜索流程如下(以搜索"华为手机"为例):
- 用户输入条件
"华为手机"
进行搜索。 - 对用户输入内容分词,得到词条:
华为
、手机
。 - 拿着词条在倒排索引中查找,可以得到包含词条的文档id:1、2、3。
- 拿着文档id到正向索引中查找具体文档。
虽然要先查询倒排索引,再查询倒排索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。
正向和倒排
那么为什么一个叫做正向索引,一个叫做倒排索引呢?
- 正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程。
- 而倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程。
是不是恰好反过来了?
那么两者方式的优缺点是什么呢?
ㅤ | 优点 | 缺点 |
正向索引 | • 可以给多个字段创建索引
• 根据索引字段搜索、排序速度非常快 | 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。 |
倒排索引 | 根据词条搜索、模糊搜索时,速度非常快 | • 只能给词条创建索引,而不是字段
• 无法根据字段做排序 |
es中的一些概念
文档和字段
elasticsearch是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中,而Json文档中往往包含很多的字段(Field),类似于数据库中的列。
索引和映射
索引(Index),就是相同类型的文档的集合。
例如:
- 所有用户文档,就可以组织在一起,称为用户的索引;
- 所有商品的文档,可以组织在一起,称为商品的索引;
- 所有订单的文档,可以组织在一起,称为订单的索引;
因此,我们可以把索引当做是数据库中的表。
数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。
es与mysql
MySQL | Elasticsearch | 说明 |
Table | Index | 索引(index),就是文档的集合,类似数据库的表(table) |
Row | Document | 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式 |
Column | Field | 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column) |
Schema | Mapping | Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema) |
SQL | DSL | DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD |
是不是说,我们学习了elasticsearch就不再需要mysql了呢?并不是如此,两者各自有自己的擅长支出:
- Mysql:擅长事务类型操作,可以确保数据的安全和一致性
- Elasticsearch:擅长海量数据的搜索、分析、计算
因此在企业中,往往是两者结合使用:
- 对安全性要求较高的写操作,使用mysql实现
- 对查询性能要求较高的搜索需求,使用elasticsearch实现
- 两者再基于某种方式,实现数据的同步,保证一致性
安装es和kibana
部署单点es
在浏览器输入:http://192.168.10.100:9200,即可看到elasticsearch的响应结果:
部署单点kibana
kibana启动一般比较慢,需要多等待一会,可以通过命令:docker logs -f kibana
查看运行日志,当查看到下面的日志,说明成功:
此时,在浏览器输入地址访问:http://192.168.10.100:5601,即可看到结果。
- kibana中提供了一个DevTools界面:这个界面中可以编写DSL来操作elasticsearch。并且对DSL语句有自动补全功能。
安装IK分词器
- 扩展词典
- 打开IK分词器config目录:
- 在IKAnalyzer.cfg.xml配置文件内容添加:
- 新建
ext.dic
和stopword.dic
,并在ext.dic
中添加一些扩展词,在stopword.dic
中添加停用词。 - 重启es和kibana。
【注意】:
ext.dic
和stopword.dic
文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑。索引库操作
索引库就类似数据库表,mapping映射就类似表的结构。我们要向es中存储数据,必须先创建“库”和“表”。
mapping映射属性
- mapping是对索引库中文档的约束,常见的mapping属性包括:
- type:字段数据类型,常见的简单类型有:
- 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
- 数值:long、integer、short、byte、double、float、
- 布尔:boolean
- 日期:date
- 对象:object
- index:是否创建索引,默认为true
- analyzer:使用哪种分词器
- properties:该字段的子字段
- 示例:
- age:类型为 integer;参与搜索,因此需要index为true;无需分词器
- weight:类型为float;参与搜索,因此需要index为true;无需分词器
- isMarried:类型为boolean;参与搜索,因此需要index为true;无需分词器
- info:类型为字符串,需要分词,因此是text;参与搜索,因此需要index为true;分词器可以用ik_smart
- email:类型为字符串,但是不需要分词,因此是keyword;不参与搜索,因此需要index为false;无需分词器
- score:虽然是数组,但是我们只看元素的类型,类型为float;参与搜索,因此需要index为true;无需分词器
- name:类型为object,需要定义多个子属性
- name.firstName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器
- name.lastName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器
对应的每个字段映射(mapping):
索引库的CRUD
这里统一使用Kibana编写DSL的方式来演示。
创建索引库和映射
- 基本语法:
- 请求方式:
PUT
- 请求路径:/索引库名,可以自定义
- 请求参数:mapping映射
- 格式:
- 示例:
查询索引库
- 基本语法
- 请求方式:
GET
- 请求路径:/索引库名
- 请求参数:无
- 格式:
GET /索引库名
- 示例:
修改索引库
倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping。虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。
- 语法说明:
- 示例:
删除索引库
- 语法:
- 请求方式:DELETE
- 请求路径:/索引库名
- 请求参数:无
- 格式:
DELETE /索引库名
- 示例:
文档操作
新增文档
- 语法:
- 示例:
- 响应:
查询文档
- 语法:
GET /{索引库名称}/_doc/{id}
- 示例:
GET /frank/_doc/1
- 响应:
修改文档
- 修改有两种方式:
- 全量修改:直接覆盖原来的文档
- 增量修改:修改文档中的部分字段
全量修改
- 全量修改是覆盖原来的文档,其本质是:
- 根据指定的id删除文档
- 新增一个相同id的文档
注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
- 语法:
- 示例:
- 响应:
增量修改
增量修改是只修改指定id匹配的文档中的部分字段。
- 语法:
- 示例:
- 响应:
删除文档
- 语法:
DELETE /{索引库名}/_doc/{id}
- 示例:
DELETE /frank/_doc/1
- 响应:
RestAPI
- ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址:https://www.elastic.co/guide/en/elasticsearch/client/index.html
- 其中的Java Rest Client又包括两种:
- Java Low Level Rest Client
- Java High Level Rest Client(推荐)
- 需要导入tb_hotel.sql
- mapping映射分析
- 创建索引库,最关键的是mapping映射,而mapping映射要考虑的信息包括:
- 字段名
- 字段数据类型
- 是否参与搜索
- 是否需要分词
- 如果分词,分词器是什么?
- 字段名、字段数据类型,可以参考数据表结构的名称和类型
- 是否参与搜索要分析业务来判断,例如图片地址,就无需参与搜索
- 是否分词呢要看内容,内容如果是一个整体就无需分词,反之则要分词
- 分词器,我们可以统一使用ik_max_word
- 几个特殊字段说明:
- location:地理坐标,里面包含精度、纬度
- all:一个组合字段,其目的是将多字段的值 利用
copy_to
合并,提供给用户搜索
其中:
- 初始化RestClient
- 引入es的RestHighLevelClient依赖
- 因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本
- 初始化RestHighLevelClient
创建索引库
步骤分为三步:
- 创建
Request
对象。因为是创建索引库的操作,因此Request
是CreateIndexRequest
。
- 添加请求参数,其实就是
DSL
的JSON
参数部分。因为json
字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE
,让代码看起来更加优雅。
- 发送请求,
client.indices()
方法的返回值是IndicesClient
类型,封装了所有与索引库操作有关的方法。
删除索引库
判断索引库是否存在
RestClient操作文档
新增文档
- 可以看到与创建索引库类似,同样是三步走:
- 创建Request对象
- 准备请求参数,也就是DSL中的JSON文档
- 发送请求
查询文档
删除文档
修改文档
批量导入文档
- 案例需求:利用BulkRequest批量将数据库数据导入到索引库中。步骤如下:
- 利用mybatis-plus查询酒店数据
- 将查询到的酒店数据(Hotel)转换为文档类型数据(HotelDoc)
- 利用JavaRestClient中的BulkRequest批处理,实现批量新增文档
- 作者:Frank
- 链接:https://blog.franksteven.me//article/elasticsearch
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。