分布式搜索引擎Elasticsearch

  • elasticsearch:一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能

1. 初识elasticsearch

1.1 认识和安装

img

img

img

img

img

img

1.2.倒排索引

elasticsearch之所以有如此高性能的搜索表现,得益于底层的倒排索引技术。什么是倒排索引?

倒排索引的概念是基于MySQL这样的正向索引而言的。

1.2.1.正向索引

我们先来回顾一下正向索引。例如有一张名为tb_goods的表:

其中的id字段已经创建了索引,由于索引底层采用了B+树结构,因此我们根据id搜索的速度会非常快。但是其他字段例如title,只在叶子节点上存在。

因此要根据title搜索的时候只能遍历树中的每一个叶子节点,判断title数据是否符合要求。

比如用户的SQL语句为:select * from tb_goods where title like '%手机%';

img

综上,根据id精确匹配时,可以走索引,查询效率较高。而当搜索条件为模糊匹配时,由于索引无法生效,导致从索引查询退化为全表扫描,效率很差。

因此,正向索引适合于根据索引字段的精确搜索,不适合基于部分词条的模糊匹配。

而倒排索引恰好解决的就是根据部分词条模糊匹配的问题。

1.2.2.倒排索引

倒排索引中有两个非常重要的概念:

  • 文档(Document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息
  • 词条(Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条

创建倒排索引是对正向索引的一种特殊处理和应用,流程如下:
将每一个文档的数据利用分词算法根据语义拆分,得到一个个词条
创建表,每行数据包括词条、词条所在文档id、位置等信息
因为词条唯一性,可以给词条创建正向索引

此时形成的这张以词条为索引的表,就是倒排索引表,两者对比如下:

img

img

虽然要先查询倒排索引,再查询正向索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描

1.2.3.正向和倒排

那么为什么一个叫做正向索引,一个叫做倒排索引呢?

  • 正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程
  • 倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程
  • 正向索引通常用于精确查询,支持多字段索引和排序,但对于非索引字段或部分匹配查询的效率较低。
  • 倒排索引则擅长快速全文搜索和模糊查询,但只能处理基于词条的检索,不支持直接排序和字段级别的查询。

1.3.基础概念

elasticsearch中有很多独有的概念,与mysql中略有差别,但也有相似之处。

1.3.1.文档和字段

elasticsearch是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中:

img

因此,原本数据库中的一行数据就是ES中的一个JSON文档;而数据库中每行数据都包含很多列,这些列就转换为JSON文档中的字段(Field)

1.3.2.索引和映射

随着业务发展,需要在es中存储的文档也会越来越多,比如有商品的文档、用户的文档、订单文档等等:

img

所有文档都散乱存放显然非常混乱,也不方便管理。因此,我们要将类型相同的文档集中在一起管理,称为索引(Index)。例如:

img

所有用户文档,就可以组织在一起,称为用户的索引;
所有商品的文档,可以组织在一起,称为商品的索引;
所有订单的文档,可以组织在一起,称为订单的索引;

因此,我们可以把索引当做是数据库中的表。

数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。

1.3.3.mysql与elasticsearch

我们统一的把mysql与elasticsearch的概念做一下对比:

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实现
  • 两者再基于某种方式,实现数据的同步,保证一致性

img

1.4.IK分词器

img

img

img

img

2.索引库操作

Index就类似数据库表,Mapping映射就类似表的结构。我们要向es中存储数据,必须先创建Index和Mapping

2.1.Mapping映射属性

img

img

2.2.索引库的CRUD

由于Elasticsearch采用的是Restful风格的API,因此其请求方式和路径相对都比较规范,而且请求参数也都采用JSON风格。

我们直接基于Kibana的DevTools来编写请求做测试,由于有语法提示,会非常方便。

2.2.1.创建索引库和映射

img

img

2.2.2.查询索引库

img

2.2.3.修改索引库

倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping

虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。因此修改索引库能做的就是向索引库中添加新字段,或者更新索引库的基础属性。

img

2.2.4.删除索引库

img

2.2.5.总结

索引库操作有哪些?

  • 创建索引库:PUT /索引库名
  • 查询索引库:GET /索引库名
  • 删除索引库:DELETE /索引库名
  • 修改索引库,添加字段:PUT /索引库名/_mapping

可以看到,对索引库的操作基本遵循的Restful的风格,因此API接口非常统一,方便记忆

3.文档操作

有了索引库,接下来就可以向索引库中添加数据了。

Elasticsearch中的数据其实就是JSON风格的文档。操作文档自然包括等几种常见操作,我们分别来学习。

3.1.新增文档

img

3.2.查询文档

根据rest风格,新增是post,查询应该是get,不过查询一般都需要条件,这里我们把文档id带上。

img

3.3.删除文档

删除使用DELETE请求,同样,需要根据id进行删除:

img

3.4.修改文档

修改有两种方式:

  • 全量修改:直接覆盖原来的文档
  • 局部修改:修改文档中的部分字段

3.4.1.全量修改

全量修改是覆盖原来的文档,其本质是两步操作:

  • 根据指定的id删除文档
  • 新增一个相同id的文档

注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。

img

img

3.4.2.局部修改

局部修改是只修改指定id匹配的文档中的部分字段。

img

3.5.批处理

批处理采用POST请求,基本语法如下:

img

create:仅在 ID 不存在时创建文档到索引 test,ID 为 3,正确

3.6.总结

文档操作有哪些?

img

4.RestClient操作索引库

ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句通过http请求发送给ES

img

img

img

参与搜索的可以先copy到all,先copy两个。

在定义一个mapping的时候,考虑字段的名字,字段的数据类型。

  • 是否分词,分词就是text,部分词就是keyword,分词器是什么。
  • 如果下那个多个字段一起搜索可以用copyto。
  • 是否参与搜索,不参与index:false。
  • 地理坐标的类型geopoint,把经纬度匹配在一起。

4.0 初始化RestHighLevelClient

由于ES目前最新版本是8.8,提供了全新版本的客户端,老版本的客户端已经被标记为过时。而我们采用的是7.12版本,因此只能使用老版本客户端:

img

img

4.1 创建索引库

img

img

img

索引库创建没问题 注意不要导错包

1
2
3
4
5
6
// 1.创建Request对象
CreateIndexRequest request = new CreateIndexRequest("items");
// 2.准备请求参数
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3.发送请求
client.indices().create(request, RequestOptions.DEFAULT);

img

4.2 删除索引库

1
2
3
4
5
6
7
@Test
void testDeleteHotelIndex() throws IOException {
// 1.创建Request对象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 2.发起请求
client.indices().delete(request, RequestOptions.DEFAULT);
}

img

4.3 查询索引库信息

1
2
3
4
5
6
7
@Test
void testExistsHotelIndex() throws IOException {
// 1. 创建Request对象
GetIndexRequest request = new GetIndexRequest("indexName");
// 2. 发起请求
client.indices().get(request, RequestOptions.DEFAULT);
}

4.4 判断索引库是否存在

1
2
3
4
5
6
7
8
9
@Test
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);
}

img

4.5 总结

JavaRestClient操作elasticsearch的流程基本类似。核心是client.indices()方法来获取索引库的操作对象。

索引库操作的基本步骤:

  • 初始化RestHighLevelClient
  • 创建XxxIndexRequest。XXX是CreateGetDelete
  • 准备请求参数( Create时需要,其它是无参,可以省略)
  • 发送请求。调用RestHighLevelClient的indices().xxx()方法,xxx是createexistsdelete

5.RestClient操作文档

索引库准备好以后,就可以操作文档了。为了与索引库操作分离,我们再次创建一个测试类,做两件事情:
初始化RestHighLevelClient
我们的商品数据在数据库,需要利用IHotelService去查询,所以注入这个接口

新增酒店数据到hotel索引库 从数据库里查询酒店数据,把酒店数据转换成索引库所需要的格式,然后把酒店数据写到索引库上

img

5.1 初始化JavaRestClien(即RestHighLevelClient)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ElasticsearchDocumentTest {

// 客户端
private RestHighLevelClient client;

@BeforeEach
void setUp() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.203.128:9200")
));
}

@AfterEach
void tearDown() throws IOException {
client.close();
}
}

5.2 添加酒店数据到索引库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
void testIndexDocument() throws IOException {
// 1.根据id查询酒店数据
Hotel hotel = hotelService.getById(61083L);
// 2.转换为文档类型
HotelDoc hotelDoc = new HotelDoc(hotel);
// 3.将hotelDTO转json
String doc = JSON.toJSONString(hotelDoc);

// 1.创建request对象
IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
// 2.准备JSON文档
request.source(doc, XContentType.JSON);
// 3.发送请求
client.index(request, RequestOptions.DEFAULT);
}

img

img

img

5.3 根据id查询酒店数据

1
2
3
4
5
6
7
8
9
10
11
12
@Test
void testGetDocumentById() throws IOException {
// 1.准备Request对象
GetRequest request = new GetRequest("hotel").id("61083");
// 2.发送请求
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 3.获取响应结果中的source
String json = response.getSourceAsString();

HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println("hotelDoc= " + hotelDoc);
}

img

img

img

5.4 根据id修改酒店数据

1
2
3
4
5
6
7
8
9
10
11
12
@Test
void testUpdateDocument() throws IOException {
// 1.准备Request
UpdateRequest request = new UpdateRequest("hotel", "61083");
// 2.准备请求参数
request.doc(
"price", 920,
"starName", "四钻"
);
// 3.发送请求
client.update(request, RequestOptions.DEFAULT);
}

img

img

5.5 根据id删除文档数据

1
2
3
4
5
6
7
@Test
void testDeleteDocument() throws IOException {
// 1.准备Request,两个参数,第一个是索引库名,第二个是文档id
DeleteRequest request = new DeleteRequest("hotel", "61083");
// 2.发送请求
client.delete(request, RequestOptions.DEFAULT);
}

新查询

img

img

5.6 批量导入文档

在之前的案例中,我们都是操作单个文档。而数据库中的商品数据实际会达到数十万条,某些项目中可能达到数百万条。

我们如果要将这些数据导入索引库,肯定不能逐条导入,而是采用批处理方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
void testBulk() throws IOException {
// 批量查询酒店数据
List<Hotel> hotels = hotelService.list();

// 1.创建Request
BulkRequest request = new BulkRequest();
// 2.准备请求参数 添加多个新增的Request
for (Hotel hotel : hotels) {
HotelDoc hotelDoc = new HotelDoc(hotel);
request.add(new IndexRequest("hotel")
.id(hotelDoc.getId().toString())
.source(JSON.toJSONString(hotelDoc), XContentType.JSON));
}
// 3.发送请求
client.bulk(request, RequestOptions.DEFAULT);
}

img

img

5.7 总结

文档操作的基本步骤:

  • 初始化RestHighLevelClient
  • 创建XxxRequest。XXX是Index、Get、Update、Delete
  • 准备参数(Index和Update时需要)
  • 发送请求。调用RestHighLevelClient的.xxx()方法,
    xxx是index、get、update、delete、bulk
  • 解析结果(Get时需要)

分布式搜索引擎Elasticsearch
https://blog.xirui.work/posts/2a1bc887.html
作者
xirui
发布于
2025年2月27日
许可协议