ES学习记录
- 参考文档视频
认识与安装
Lucene是Java语言的搜索引擎类库,其具有易扩展,高性能(基于倒排索引) 的优势
Elastic — 搜索 AI 公司 | Elastic具备以下两点优势:
- 支持分布式,可水平扩展
- 提供Restful接口,可被任何语言调用
什么是ELK:elasticsearch结合kibana、Logstash、Beats一整套技术栈,被广泛应用在日志数据分析、实时监控等领域
🥲安装
- 你必须会的Docker安装ElasticSearch教程里面包含IK分词器
网络创建:
1 | docker network create itmentu-net |
es:
1 | docker run -d --name elasticsearch -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 itmentu-net -p 9200:9200 -p 9300:9300 elasticsearch:7.12.1 |
kibana:
1 | docker run -d --name kibana -e ELASTICSEARCH_HOSTS=http://elasticsearch:9200 --network itmentu-net -p 5601:5601 kibana:7.12.1 |
注意!!!IK分词器严格对应es的版本安装
基础概念
elasticsearch中的文档数据会被序列化为jJSON格式后存储在elasticsearch中
文档(Document): 就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式
字段(Field): 就是JSON文档中的字段,类似数据库中的列(Column)
索引库: 相同类型的文档的集合
映射(mapping): 索引中文档的字段约束信息,类似表的结构约束
DSL: 是elasticsearch提供的JSON风格的请求语句,用来定义搜索条件
索引库操作
Mapping映射属性
✨常见的mapping属性包括:
- type: 字段数据类型,常见的简单类型有:
- 字符串: text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
- 数值: long、intege、short、byte、double、float
- 布尔: boolean
- 日期: date
- 对象: object
- index: 是否创建索引,默认为true
- analyzer: 使用哪种分词器(除了text以外,大部分时候都不需要)
- properties: 该字段的子字段
索引库的CRUD
这里注意elasticsearch不支持索引库修改,但是可以添加新的字段
文档操作
文档的CRUD
- 创建文档:POST /{索引库名}/_doc/文档id
- 查询文档:GET /{索引库名}/_doc/文档id
- 删除文档:DELETE /{索引库名}/_doc/文档id
- 修改文档:修改有两种方式:
- 全量修改:直接覆盖原来的文档
- PUT /{索引库名}/_doc/文档id
- 注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
- 增量修改:修改文档中的部分字段,增量修改是只修改指定id匹配的文档中的部分字段。
- POST /{索引库名}/_update/文档id { “doc”: {字段}}
- 全量修改:直接覆盖原来的文档
批量处理
Elasticsearch中允许通过一次请求中携带多次文档操作,也就是批量处理,语法格式如下:
1 | POST /_bulk |
JavaRestClient
客户端初始化
- 引入依赖
1
2
3
4<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency> - 覆盖默认的ES版本
1
2
3<properties>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties> - 初始化RestHighLevelClient
1
2
3
4
5RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200"),
HttpHost.create("http://192.168.150.102:9200"),
HttpHost.create("http://192.168.150.103:9200") //集群模式写多个
));
操作索引库
创建索引库
1
2
3
4
5
6
7
8
9
void testCreateHotelIndex() throws IOException {
// 1.创建Request对象
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2.请求参数,MAPPING_TEMPLATE是静态常量字符串,内容是创建索引库的DSL语句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3.发起请求
client.indices().create(request, RequestOptions.DEFAULT);
}创建索引库的DSL语句以静态字符串常量的形式统一写在常量类里
查询索引库
1
2
3
4
5
6
7
8
9
void testDeleteHotelIndex() throws IOException {
// 准备Request请求
GetIndexRequest request = new GetIndexRequest("items");
// 发送请求
GetIndexResponse response = client.indices().get(request,
RequestOptions.DEFAULT);
}删除索引库
1
2
3
4
5
6
7
void testDeleteHotelIndex() throws IOException {
// 1.创建Request对象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 2.发起请求
client.indices().delete(request, RequestOptions.DEFAULT);
}判断索引库是否存在
1
2
3
4
5
6
7
8
9
10
11
void testExistsHotelIndex() throws IOException {
// 1.创建Request对象
GetIndexRequest request = new GetIndexRequest("hotel");
// 2.发起请求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
// 3.输出
System.out.println(exists);
}
操作文档
- 新增文档
这是tb_hotel表对应的实体类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Hotel {
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String longitude;
private String latitude;
private String pic;
}这里写个新类HotelDoc,将Hotel类封装成为和ES索引库字段对应的类
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
public class HotelDoc {
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String location; //!
private String pic;
public HotelDoc(Hotel hotel) { //有参构造,传入要封装的对象
this.id = hotel.getId(); //对于这些不用包装的字段,
//直接get到后赋值给包装对象的属性就行
this.name = hotel.getName();
this.address = hotel.getAddress();
this.price = hotel.getPrice();
this.score = hotel.getScore();
this.brand = hotel.getBrand();
this.city = hotel.getCity();
this.starName = hotel.getStarName();
this.business = hotel.getBusiness();
this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
//注意这里
this.pic = hotel.getPic();
}
}接下来实现去数据库查询酒店数据,导入到hotel索引库:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
IHotelService iHotelService;
void testIndexDocument() throws IOException {
//从MySQL查
Hotel hotel = iHotelService.getById(60359L);
//封装
HotelDoc hotelDoc = new HotelDoc(hotel);
//创建request
IndexRequest request = new
IndexRequest("hotel").id(hotelDoc.getId().toString());
//放入要新增的json传
request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
//发请求
client.index(request, RequestOptions.DEFAULT);
}
- 查询文档
1
2
3
4
5
6
7
8
9
10
void testGetDocumentById() throws IOException {
GetRequest request = new GetRequest("hotel","60359");
GetResponse response = client.get(request,RequestOptions.DEFAULT);
//看上图DSL执行返回结果中有个_source,这个getSource方法就是拿这个数据
String json = response.getSourceAsString();
//解析
HotelDoc hotelDoc = JSON.parseObject(json,HotelDoc.class);
System.out.println(hotelDoc);
} - 修改文档
1 | //局部更新 |
- 删除文档
1 | //根据id删除文档 |
- 批量导入文档
1 |
|
DSL查询
叶子查询
- 一般是在特定的字段里查询特定值,属于简单查询,很少单独使用
叶子查询通常分为三类:
全文检索(full tetx)查询:利用分词器对用户输入内容分词,然后去词条列表中匹配。
- match查询:全文检索查询的一种,会对用户输入内容分词,然后去倒排索引库检索
1
2
3
4
5
6
7
8GET /indexName/_search
{
"query":{
"match":{
"FIELD": "TEXT"
}
}
}- multi_match:与match查询类似,只不过允许同时查询多个字段
1
2
3
4
5
6
7
8
9GET /indexName/_search
{
"query":{
"multi_match":{
"query": "TEXT",
"fields": ["FIELD1", "FIELD12"]
}
}
}精确查询:不对用户输入内容分词,直接精确匹配,一般是查找keyword、数值、日期、布尔等类型。
精确查询没有得分,或者说得分都是固定值
1
2
3
4
5
6
7
8
9
10
11// term查询语法
GET /indexName/_search
{
"query":{
"term":{
"FIELD"{
"value": "VALUE"
}
}
}
}- gte指的就是最小值,而lte指的就是最大值
1
2
3
4
5
6
7
8
9
10
11
12// range查询语法
GET /indexName/_search
{
"query":{
"range":{
"FIELD"{
"gte": 10,
"lte": 20
}
}
}
}地理(geo)查询:用于搜索地理位置,搜索方式很多。
复合查询
- 以逻辑方式组合多个叶子查询或者更改叶子查询的行为方式
基于逻辑运算
- 基于逻辑运算组合叶子查询,实现组合条件
布尔查询(Boolean Query)
布尔查询主要用于根据不同的逻辑条件,组合多个查询子句。常见的逻辑运算符 有“与(AND)”、“或(OR)”、“非(NOT)”。
1 | GTE /items/_search { |
must:必须匹配,等同于逻辑运算符“与(AND)”,参与评分计算。should:可以匹配,等同于逻辑运算符“或(OR)”,参与评分计算。must_not:必须不匹配,等同于逻辑运算符“非(NOT)”不参与评分计算。filter:过滤条件,与must相似,但不参与评分计算。由于不计算评分,它的性能通常更优。
影响相关性评分
- 基于某种算法修改查询时的文档相关性算分,从而改变文档排名。
- 其中最基础的算法评分就是
match查询和multi_match查询
boost设置
通过 boost 参数可以增强某些查询条件对相关性评分的影响。boost 是一个浮动系数,表示该查询条件相对于其他条件的权重。
1 | GET /indexName/_search |
在此查询中,
title字段的匹配结果会被加权处理,相对于description字段的匹配结果 ,其得分为 (boost值的)两倍。
function_score查询
function_score 查询允许开发者通过自定义的函数对查询结果的评分进行修改。可以使用不同的评分函数(如权重、衰减函数、随机函数等)来影响文档的最终评分。
1 | GET /indexName/_search |
function_score查询:这部分会基于查询的结果修改文档的评分。它接收一个查询和一个或多个评分函数,在这个例子中有一个评分函数是 gauss。boost_mode: "multiply":这个选项表示查询的原始相关性评分(来自 match 查询)将与函数评分结果相乘。这意味着如果函数评分较高,文档的总评分会被放大,影响其在结果中的排名。functions部分:gauss函数是一个常用的数学函数,通常用来对某些字段(比如 price)进行评分调整。price字段表示的是文档的价格。gauss 函数的作用是根据价格的值来对文档的评分进行调整。origin: "1000":这是函数的起始值,即价格为 1000 时,评分的衰减(Decay)开始。scale: "500":这是衰减的范围。当价格偏离 1000 达到 500 时,评分会降低。offset: "0":这个参数表示从 1000 开始,计算衰减的“偏移量”,通常设置为0。decay: 0.5:这个参数决定了衰减的速率。衰减越快,价格越高的文档评分会降低得越快
dis_max查询
dis_max 查询允许我们根据多个查询结果返回得分最高的一个,通常用于组合多个查询条件。它会选择所有查询中得分最高的一个,并返回该文档。这个查询适用于某些场景下,想要从多个查询条件中选择最匹配的一个。
1 | GET /indexName/_search |
dis_max 查询的核心组成部分:
queries:这是一个数组,里面包含了多个查询。在这个例子中,有两个 match 查询:match查询在 title 字段中查找“智能手机”。match查询在description字段中查找“智能手机”。
tie_breaker:这个参数用于控制当多个查询的相关性评分差距较小(即评分接近时)如何合并它们的评分。如果tie_breaker为 0(默认值),那么dis_max查询只会选择评分最高的查询结果。如果设置为一个值(例如 0.7),则当多个查询的评分相近时,它们的相关性评分会按比例合并,tie_breaker表示合并的权重。
DSL查询结果处理
排序
默认根据相关度算分 (_score)来排序,也可以指定字段排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。
1 | GET /indexName/_search |
分页
- From + Size
1 | GET /your_index/_search |
优点:
- 简单直观,支持随机跳转分页。
缺点:
- 深度分页性能差,from 值过大会导致内存和 CPU 消耗增加。
适用场景:
- 数据量较小或仅需浅分页的场景。
- Scroll
- 初始化滚动上下文:
1 | POST /your_index/_search?scroll=1m |
- 使用返回的 scroll_id 获取下一批数据:
1 | POST /_search/scroll |
优点:
- 高效处理大数据集,适合全量遍历。
缺点:
- 非实时,消耗服务器资源,不支持随机跳转。
适用场景:
- 数据导出或日志分析等需要全量遍历的场景。
- Search After
步骤:
- 初始查询并获取排序值:
1 | GET /your_index/_search |
- 使用上一页最后一条记录的排序值进行下一页查询:
1 | GET /your_index/_search |
优点:
- 深度分页性能优异,不受 max_result_window 限制。
缺点:
- 不支持随机跳转,依赖唯一排序字段。
适用场景:
- 深度分页或需要实时性较高的场景。
高亮显示
1 | GET /items/_search |
数据聚合(待补充)
- 桶(Bucket)聚合: 用来对文档做分组
- Term聚合:以分类字段对数据分组
1
2
3
4
5
6
7
8
9
10
11GET /items/_search
"query":{"match_all":{}},// 可以省略
"size":0,//设置size为0,结果中不包含文档,只包含聚合结果
"aggs":{ //定义聚合
"cateAgg":{ //给聚合起个名字
"terms":{//聚合的类型,按照品牌值聚合,所以选择term
"field":"category", //参与聚合的字段
"size":20 //希望获取的聚合结果数量
}
}
} - 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
- 管道(pipeline)聚合:其他聚合的结果为基础做聚合
RestClient查询与结果处理
记得引入依赖和添加配置,这里不多赘述
基础语法如下:
1 |
|
构建查询条件
- 全文检索的查询条件构造API
1 | // 单字段查询 |
- 精确查询的查询条件构造API如下:
1 | // 词条查询 |
- 布尔查询的查询条件构造API
1 | // 创建布尔查询 |
排序与分页
1 | // 查询 |
高亮显示
1 | request.source().highlighter( |
结果解析API:
1 | //得到_source,也就是原始json文档 |
聚合
以品牌为例:
1 | request.source().size(0); |






