Elasticsearch---高性能分布式搜索引擎
1、初识Elasticsearch
1.1、认识和安装
Elasticsearch结合kibana、logstash、Beats,是一整套技术栈,被叫做ELK。被广泛应用在日志数据分析、实时监控等领域
1.1.1、安装elasticsearch
通过下面的Docker命令安装单机版本的elasticsearch
Bash
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-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 \
-p 9300:9300 \
elasticsearch:7.12.1
1.1.2、安装Kibana
Bash
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=hm-net \
-p 5601:5601 \
kibana:7.12.1
安装完成后,直接访问5601端口,即可看到控制台页面
选择Explore进入主页面:
然后选中Dev tools,进入开发工具页面:
1.2、倒排索引
传统数据库(如MySql)采用正向索引,例如下表中id创建索引:
elasticsearch采用倒排索引:
- 文档(document):每条数据就是一个文档
- 词条(term):文档按照语义分成的词语
1.3、IK分词器
中文分词往往需要根据语义分析,比较复杂,这就需要用到中文分词器,例如IK分词器。IK分词器是林良益在2006年开源发布的,其采用的正向迭代最细粒度切分算法一直沿用至今
其安装方式也比较简单,只要将资料提供好的分词器放入elasticsearch的插件目录中即可
在Kibana的Dev tools中可以使用下面的语法来测试IK分词器:
json
POST /_analyze
{
"analyze": "standard",
"text": "Java"
}
语法说明:
- Post:请求方式
- /_analyze:请求路径,这里省略了地址,有Kibana帮我们补充
- 请求参数,json风格:
- analyze:分词器类型,这里是默认的standard分词器
- text:要分词的内容
Ik分词器允许我们配置扩展词典来增加自定义的词库:
1.4、基础概念
elasticsearch中的文档数据会被序列化为json格式后存储在elasticsearch中
索引(index):相同类型的文档的集合
映射(mapping):索引中文档的字段约束信息,类似表的结构约束
2、索引库操作
2.1、Mapping映射属性
mapping是对索引库中文档的约束,常见的mapping属性包括:
- type: 字段数据类型,常见的简单类型有:
- 字符串:text(可分词的文本),keyword(精确值,例如:品牌、国家、ip地址)
- 数值:long、integer、short、byte、double、float
- 布尔:boolean
- 日期:date
- 对象:object
- index:是否创建索引,默认为true
- analyze:使用哪种分词器
- properties:该字段的子字段
Elasticsearch提供的所有API都是RestFul的接口,遵循RestFul的基本规范:
2.2、索引库操作
创建索引库和mapping的请求语法如下:
查看索引库语法:
json
GET /索引库名
删除索引库:
DELETE /索引库名
修改索引库:索引库和mapping一旦创建无法修改,但是可以添加新的字段,语法如下:
json
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
3、文档操作
3.1、文档CRUD
新增文档的请求格式如下:{json文档}
查看文档请求格式:
GET /索引库名/_doc/文档id
删除索引库的请求格式:
DELETE /索引库名/_doc/文档id
修改文档:
3.2、批量处理
Elasticsearch中允许通过一次请求中携带多次文档操作,也就是批量处理,语法格式如下:
4、JavaRestClient
4.1、客户端初始化
1、引入es的RestHighLevelClient依赖:
xml
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
2、因为SpringBoot默认的ES版本是7.17.0,所以我们需要覆盖默认的ES版本
XML
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
3、初始化RestHighLevelClient:
初始化的代码如下:
Java
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
4.2、商品表Mapping映射
JSON
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"
}
}
}
}
4.3、索引库操作
创建索引库的JavaAPI与RestFul接口API对比:
索引库操作的基本步骤:
1、创建XxxIndexRequest。XXX是Create、Get、Delete
2、准备请求参数(Create时需要)
3、发送请求。调用RestHighLevelClient#indices)
4.4、文档操作
新增文档:
JSON
POST /{索引库名}/_doc/1
{
"name": "Jack",
"age": 21
}
查询文档:
JSON
GET /{索引库名}/_doc/{id}
删除文档:
JSON
DELETE /hotel/_doc/{id}
修改文档:
在RestClient的API中,全局修改和新增的API完全一致,判断依据是ID:
- 如果是新增时,ID已经存在,则修改
- 如果新增时,ID不存在,则新增
4.5、批处理
在之前的案例中,我们都是操作单个文档。而数据库中的商品数据实际会达到数十万条,某些项目中可能达到数百万条。
我们如果要将这些数据导入索引库,肯定不能逐条导入,而是采用批处理方案。常见的方案有:
- 利用Logstash批量导入
- 需要安装Logstash
- 对数据的再加工能力较弱
- 无需编码,但要学习编写Logstash导入配置
- 利用JavaAPI批量导入
- 需要编码,但基于JavaAPI,学习成本低
- 更加灵活,可以任意对数据做再加工处理后写入索引库
批处理代码流程与之前类似,只不过构建请求会用到一个名为BulkRequest来封装普通的CRUD请求
5、DSL查询
Elasticsearch提供DSL(Domain Specific Language)查询,就是以JSON格式来定义查询条件。类似:
DSL查询可以分为两大类:
- 叶子查询:一般是在特定的字段里查询特定值,属于简单查询,很少单独使用
- 复合查询:以逻辑方式组合多个叶子查询或者更改叶子查询的行为模式
在查询以后,还可以对查询的结果做处理,包括:
- 排序:按照1个或多个字段值做排序
- 分页:根据form和size做分页,类似MySQL
- 高亮:对搜索结果中的关键字添加特殊样式,使其更醒目
- 聚合:对搜索结果做数据统计以形成报表
5.1、快速入门
基于DSL的查询语法如下:
!
5.2、叶子查询
叶子查询还可以进一步细分,常见的有:
- **全文检索(full text)**查询:利用分词器对用户输入内容分词,然后去词条列表中匹配。例如:
- match_query
- multi_match_query
- 精确查询 :不对用户输入内容分词,直接精确匹配,一般是查找keyword、数值、日期、布尔类型等。例如:
- ids
- range:根据数值范围查询,可以是日期的范围
- term:根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段
- 地理(geo)查询 :用于搜索地理位置,搜索方式很多。例如:
- geo_distance
- geo_bounding_box
5.3、复合查询
复合查询大致可以分为两类:
- 第一类:基于逻辑运算组合叶子查询,实现组合条件,例如:
- bool(布尔查询)
- 第二类:基于某种算法修改查询时达到文档相关性算分,从而改变文档排名。例如:
- function_score
- dis_max
布尔查询是一个或多个查询子句的组合。子查询的组合方式有:
- must:必须匹配每一个子查询,类似"与"
- should:选择性匹配子查询,类似"或"
- must_not:必须不匹配,不参与算分,类似"非"
- filter:必须匹配,不参与算分
5.4、排序和分页
Elasticsearch支持对搜索结果排序,默认是根据相关度算分(_score)来排序,可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。
深度分页问题
Elasticsearch的数据一般会采用分片存储,也就是把一个索引中的数据分为N份,存储到不同节点上。查询数据时需要汇总各个分片的数据
针对深度分页,ES提供了两种解决方案:
- search after:分页时需要排序,原理是上一次的排序值开始,查询下一页数据。官方推荐使用的方式
- 优点:没有查询上限,支持深度分页
- 缺点:只能向后逐页查询,不能随机翻页
- 场景:数据迁移、手机滚动查询
- scorll:原理将数据形成快照,保存在内存。官方已经不推荐使用。
5.5、高亮显示
高亮显示:就是在搜索结果中把搜索关键字突出显示。
6、JavaRestClient查询
6.1、快速入门
数据搜索的Java代码我们分为两部分:
- 构建并发起请求
- 解析查询结果
解析查询结果的API:
6.2、构建查询条件
在JavaRestAPI中,所有类型的query查询条件都是由QueryBuilders来构建的:
全文检索的查询条件构造APi如下:
精确查询的查询条件构造API如下:
布尔查询的查询条件构造API如下:
6.3、排序和分页
与query类似,排序和分页参数都是基于request.source()来设置:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
6.4、高亮显示
高亮显示的条件构造API如下:
高亮显示的结果解析API如下:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
7、数据聚合
聚合
(aggregations)可以实现对文档数据的统计、分析、运算。聚合常见的有三类:
- 桶(Bucket)聚合:用来对文档做分组
- TermAggregation:按照文档字段值分组
- Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
- 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
- Avg
- Max
- Min
- Stats:同时求max、min、avg、sum等
- 管道(pipeline)聚合:其它聚合的结果为基础做聚合
注意:参与聚合的字段必须是keyword、数值、日期、布尔类型的字段
7.1、DSL聚合
我们要统计所有商品中共有哪些商品分类,其实就是以分类(category)字段对数据分组。category值一样的放在同一组,属于Bucket聚合中的Term聚合。
默认情况下,Bucket聚合是对索引库的所有文档做聚合,我们可以限定要聚合的文档范围,只要添加query条件即可。
例如,我想知道价格高于3000元的手机品牌有哪些:
除了对数据分组(Bucket以外),我们还可以对每一个Bucket内的数据进一步做数据计算和统计。
例如:我想知道手机有哪些品牌,每个品牌的价格最大值、最小值、平均值