认识elasticsearch
elasticsearch是一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能
ELK技术栈
elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域。而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。
lucene
elasticsearch底层是基于lucene来实现的。
Lucene是一个Java语言的搜索引擎类库,,提供了搜索引擎的核心API,是Apache公司的顶级项目,由DougCutting于1999年研发。
elasticsearch的发展历史:
- 2004年Shay Banon基于Lucene开发了Compass。
- 2010年Shay Banon 重写了Compass,取名为Elasticsearch。
倒排索引
倒排索引的概念是基于MySQL这样的正向索引而言的。
正向索引:
在Mysql中进行模糊查询时,只能是逐行扫描数据,随着数据量增加,其查询效率也会越来越低。
倒排索引:
- 文档(
Document
):每条数据就是一个文档。 - 词条(
Term
):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条。
创建倒排索引是对正向索引的一种特殊处理,流程如下:
- 将每一个文档的数据利用算法分词,得到一个个词条
- 创建表,每行数据包括词条、词条所在文档id、位置等信息
- 因为词条唯一性,可以给词条创建索引,例如hash表结构索引
倒排索引的搜索流程如下(以搜索"华为手机"为例):
- 用户输入条件
"华为手机"
进行搜索
- 对用户输入内容分词,得到词条:
华为
、手机
- 拿着词条在倒排索引中查找,可以得到包含词条的文档id:1、2、3
- 拿着文档id到正向索引中查找具体文档
虽然要先查询倒排索引,再查询正向索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快,无需全表扫描。
二者对比:
正向索引
优点:
- 可以给多个字段创建索引
- 根据索引字段搜索、排序速度非常快
缺点:根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。
倒排索引
优点:根据词条搜索、模糊搜索时,速度非常快
缺点:
- 只能给词条创建索引,而不是字段
- 无法根据字段做排序
ES概念
文档(Document) :
elasticsearch是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中:
而Json文档中往往包含很多的字段(Field) ,类似于数据库中的列。
索引(Index) :就是相同类型的文档的集合。
映射(mapping) :数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射,是索引中文档的字段约束信息,类似表的结构约束。
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 |
- Mysql:擅长事务类型操作,可以确保数据的安全和一致性
- Elasticsearch:擅长海量数据的搜索、分析、计算
索引库
索引(库)类似数据库的表,mapping映射就类似表的结构。
mapping映射属性
常见的mapping属性包括:
- type:字段数据类型
- index:是否创建索引,默认为true
- analyzer:使用哪种分词器
- properties:该字段的子字段
常见的type:
- 字符串:text(可分词的文本)、keyword(精确值,无需分词,例如:品牌、国家、ip地址)
- 数值:long、integer、short、byte、double、float、
- 布尔:boolean
- 日期:date
- 对象:object
基本操作
创建索引库和映射
语法:
- 请求方式:PUT
- 请求路径:/索引库名,可以自定义
- 请求参数:mapping映射
格式:
PUT /索引库名称
{
"mappings": {
"properties": {
"字段名":{
"type": "text",
"analyzer": "ik_smart"
},
"字段名2":{
"type": "keyword",
"index": "false"
},
"字段名3":{
"properties": {
"子字段": {
"type": "keyword"
}
}
},
// ...
}
}
}
新增映射属性
索引库一旦创建,无法修改mapping,只能添加新的字段到mapping中,因为不会对倒排索引产生影响。
语法:
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
查询&删除索引库
查询索引库,相当于查看表结构,格式如下:
GET /索引库名
删除索引库:
DELETE /索引库名
文档
新增文档
语法:
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
// ...
}
查询&删除文档
查询:
GET /{索引库名称}/_doc/{id}
删除:
DELETE /{索引库名}/_doc/{id}
修改文档
全量修改:覆盖原来的文档,如果id不存在,就从修改变成新增,语法如下:
PUT /{索引库名}/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ...
}
局部修改:只修改指定id匹配的文档中的部分字段,语法如下:
POST /{索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
RestClient
初始化
-
引入elasticsearch-rest-high-level-client依赖,注意要和服务端的es版本保持一致,因为是SpringBoot项目,所以要覆盖默认的es版本。
<properties> <java.version>1.8</java.version> <elasticsearch.version>7.12.1</elasticsearch.version> </properties>
-
初始化RestHighLevelClient:
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder( HttpHost.create("http://192.168.20.100:9200") ));
索引库操作
- 初始化RestHighLevelClient
- 创建XxxIndexRequest。Xxx是Create、Get、Delete
- 准备DSL(Create时需要,其它是无参)
- 发送请求。调用RestHighLevelClien的
indices().xxx()
方法,xxx是create、exists、delete
client.indices()
方法返回的是操作索引库的对象。
创建
@Test
void createHotelIndex() throws IOException {
//1.创建Request对象,指定索引库名称
CreateIndexRequest request = new CreateIndexRequest("hotel");
//2.准备请求的参数:DSL语句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
//3.发送请求
client.indices().create(request, RequestOptions.DEFAULT);
}
删除&&判断
删除:
@Test
void testDeleteHotelIndex() throws IOException {
//1.创建Request对象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
//2.删除没有参数,直接发送请求
client.indices().delete(request, RequestOptions.DEFAULT);
}
判断索引库是否存在:
@Test
void testExistsHotelIndex() throws IOException {
//1.创建Request对象
GetIndexRequest request = new GetIndexRequest("hotel");
//2.发送请求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.err.println(exists ? "索引库已经存在" : "索引库不存在");
}
文档操作
- 初始化RestHighLevelClient
- 创建XxxRequest。XXX是Index、Get、Update、Delete、Bulk
- 准备参数(Index、Update、Bulk时需要)
- 发送请求。调用
RestHighLevelClient.xxx()
方法,xxx是index、get、update、delete、bulk - 解析结果(Get时需要)
新增
将一条数据库的数据取出来,写入到es中:
@Test
void testAddDocument() throws IOException {
//根据id查询酒店数据
Hotel hotel = hotelService.getById(61083L);
//转换为文档类型,因为es的地理类型是geo_point,是两个经纬度字符串的拼接
HotelDoc hotelDoc = new HotelDoc(hotel);
//1.准备Request对象
IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
//2.准备Json文档
request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
//3.发送请求
client.index(request, RequestOptions.DEFAULT);
}
查询
@Test
void testGetDocumentById() throws IOException {
//1.准备Request
GetRequest request = new GetRequest("hotel", "61083");
//2.发送请求,得到响应
GetResponse response = client.get(request, RequestOptions.DEFAULT);
//3.解析响应结果
String json = response.getSourceAsString();
//4.将json转换为doc对象
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println(hotelDoc);
}
修改
@Test
void testUpdateDocument() throws IOException {
//1.准备Request
UpdateRequest request = new UpdateRequest("hotel", "61083");
//2.准备请求参数
request.doc(
"price", "999",
"starName", "一钻"
);
//3.发送请求
client.update(request, RequestOptions.DEFAULT);
}
删除
@Test
void testDeleteDocument() throws IOException {
//1.准备Request
DeleteRequest request = new DeleteRequest("hotel", "61083");
//2.发送请求
client.delete(request, RequestOptions.DEFAULT);
}
批量操作
以批量新增为例:
@Test
void testBulkRequest() throws IOException {
//批量查询酒店数据
List<Hotel> hotels = hotelService.list();
//1.创建Request
BulkRequest request = new BulkRequest();
//2.准备参数,添加多个新增的Request
for (Hotel hotel : hotels) {
//转换为文档类型HotelDoc
HotelDoc hotelDoc = new HotelDoc(hotel);
//创建新增文档的Request对象
request.add(new IndexRequest("hotel")
.id(hotelDoc.getId().toString())
.source(JSON.toJSONString(hotelDoc), XContentType.JSON));
}
//3.发送请求
client.bulk(request, RequestOptions.DEFAULT);
}