elasticsearch是一款非常强大的开源搜索引擎,可以从海量数据中快速找到需要的内容。
elasticsearch结合kibana,Logstash,Beats,构成啦elastic stack(ELK)。被广泛应用于日志数据分析,实时监控等领域。
elasticsearch是elastic stack的核心,负责存储搜索,分析数据。
elasticsearch底层是基于lucene实现的。
Lucene
Lucene是一个java语言的搜索引擎类库,提供啦搜索引擎的核心API。是Apache公司的项目。(逐渐被淘汰)
官网地址:Apache Lucene - Welcome to Apache Lucene 。
Lucene 的优点:易扩展,高性能(基于倒叙索引)
缺点:只限于java,不支持水平扩展(通过增加更多的服务器或者程序实例来分散负载),学习难度高
elasticsearch
支持分布式,可水平扩展。提供Restful接口,可被任何语言调用。
elasticsearch采用倒排索引:
正向索引:最传统的根据id索引的方式,再根据词条查询时,先逐条获取文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程
倒排索引:先找到用户要搜索的词条,根据词条得到包含词条的文档的id,再根据id获取文档。是根据词条找文档的过程
文档(document)用于搜索的数据,其中的每一条数据就是一个文档
词条 (term):文档按照语义分成的词语
插入数据时创建倒排索引就是对正向索引的一种特殊处理,流程如下:1.将文档插入到索引库中 2.插入时会根据字段的类型来决定分词得到词条 3. 将词条和文档ID的对应关系存储到倒排索引表中
查询数据时使用倒排索引:1.ES根据用户搜索的关键字找到对应字段的分词器来进行分词得到词条 2.根据词条从倒排索引表中查询ID 3.再根据文档ID从索引库中查询到具体文档数据
ES的一些概念
es是面向文档(Document)存储的,文档数据会被序列化为json格式后存储再es中
json文档中包含很多字段(Field)类似数据库中的表
索引(index)相同类型的文档的集合
映射(mapping)是索引中文档的字段约束信息,类似表的结构约束
ES不能替代MySQL:ES没有多表关联设计,没有事务管理
在企业中,往往是两者结合使用:
对安全要求较高的写操作,用mysql实现。对查询性能要求较高的搜索需求,用elasticsearch实现
两者基于某种方式,实现数据的同步,保证一致性
索引库的操作
索引库类似数据库,mapping映射类似表的结构
mapping映射属性:mapping是索引库中文档的约束
-
type:字段数据类型,常见的简单类型有:
-
字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
-
数值:long、integer、short、byte、double、float、
-
布尔:boolean
-
日期:date
-
对象:object
-
-
index:是否创建索引,默认为true
-
analyzer:使用哪种分词器
-
properties:该字段的子字段
下面的案例统一使用kibana编写的DSL的方式演示
创建索引库和映射:请求方式:PUT 请求路径:/索引库(自定义) 请求参数:mapping映射
PUT /索引库名称
{
"mappings": {
"properties": {
"字段名":{
"type": "text",
"analyzer": "ik_smart"
},
"字段名2":{
"type": "keyword",
"index": "false"
},
"字段名3":{
"properties": {
"子字段": {
"type": "keyword"
}
}
},
// ...略
}
}
}
查询索引库
基本语法:请求方式GET 请求路径:/索引库名 请求参数:无
GET /索引库名
修改索引库:索引库一旦创建,无法修改mapping
无法修改mapping,但允许添加新的字段到mapping中
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
删除索引库:请求方式:DELETE 请求路径:/索引库名 请求参数:无
DELETE /索引库名
文档操作
新增文档
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
// ...
}
查询文档
GET /{索引库名称}/_doc/{id}
删除文档
DELETE /{索引库名}/_doc/id值
修改文档两种方式:1.全量修改,直接覆盖原来的文档 2.增量修改,修改文档中的部分字段
1.全量修改:本质是根据指定id删除文档,新增一个相同id的文档
PUT /{索引库名}/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
2.增量修改,只修改指定id匹配文档中的部分字段
POST /{索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
RestAPI
ES
es官方提供各种不同语言的客户端,用来操作es
官方文档地址:https://www.elastic.co/guide/en/elasticsearch/client/index.htm
选择Java HighLevel Rest Client客户端API
在idea中的应用
在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。
1)引入es的RestHighLevelClient依赖:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
2)因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本:
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
3)初始化RestHighLevelClient:
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://虚拟机ip:9200")
));
3)初始化RestHighLevelClient:
初始化的代码如下:
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
为了单元测试方便,我们创建一个测试类HotelIndexTest,然后将初始化的代码编写在@BeforeEach方法中
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
public class HotelIndexTest {
//初始化ES客户端连接实例
private RestHighLevelClient client;
//单元测试执行前创建客户端实例
@BeforeEach
void initClient() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://虚拟机ip:9200")
));
}
@AfterEach
void tearDown() throws IOException {
this.client.close();
}
}
RestClient 实现操作es的索引库
1.创建客户端连接实例RestHighLevelClient
2.创建请求对象
索引库:创建CreateIndexRequest 删除DeleteIndexRequest 查询GetIndexRequest
文档:新增IndexRequest 查询 GetRequest 修改UpdateRequest 删除DeleteRequest
3.可能会设置数据
4.使用RestHighLevelClient实例执行请求获取响应
package cn.itcast.hotel;
import cn.itcast.hotel.util.HotelConstants;
import com.alibaba.fastjson.JSON;
import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
/**
* RestClient操作ES索引库相关测试
*/
public class HotelIndexTest {
//初始化ES客户端连接实例
private RestHighLevelClient client ;
//单元测试执行前创建客户端实例
@BeforeEach
void initClient(){
client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://虚拟机中ip:9200")));
}
//单元测试执行后关系客户端实例
@AfterEach
void closeClient(){
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//创建索引库hotel
@Test
void testCreateIndex(){
//1.构建索引请求实例
CreateIndexRequest createIndexRequest = new CreateIndexRequest("hotel");
//2.构造DSL语句中的JSON(索引库映射关系的结构)
createIndexRequest.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON);
//3.发起请求执行创建索引库和映射关系
try {
CreateIndexResponse createIndexResponse = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
//4.获取响应信息
System.out.println(createIndexResponse.isAcknowledged());
} catch (IOException e) {
e.printStackTrace();
}
}
//查询索引库
@Test
void testGetIndex(){
//1.定义获取索引的请求实例
GetIndexRequest getIndexRequest = new GetIndexRequest("hotel");
try {
//2.发起请求,检查索引库是否存在
boolean result = client.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
//3.输出结果
System.out.println("索引库是否存在:" + result);
//4.发起请求获取索引库映射信息
GetIndexResponse getIndexResponse = client.indices().get(getIndexRequest, RequestOptions.DEFAULT);
System.out.println("具体的映射信息:" + JSON.toJSONString(getIndexResponse.getMappings()));
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
void testDeleteIndex(){
//1.构建删除索引请求对象
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("hotel");
try {
//2.发起请求执行删除索引库
AcknowledgedResponse acknowledgedResponse = client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
//3.输出响应结果
System.out.println(acknowledgedResponse.isAcknowledged());
} catch (IOException e) {
e.printStackTrace();
}
}
}
RestClient操作文档
我们导入酒店数据,基本流程一致,但是需要考虑几点变化:
-
酒店数据来自于数据库,我们需要先查询出来,得到hotel对象
-
hotel对象需要转为HotelDoc对象
-
HotelDoc需要序列化为json格式
因此,代码整体步骤如下:
-
1)根据id查询酒店数据Hotel
-
2)将Hotel封装为HotelDoc
-
3)将HotelDoc序列化为JSON
-
4)创建IndexRequest,指定索引库名和id
-
5)准备请求参数,也就是JSON文档
-
6)发送请求
编写单元测试
package cn.itcast.hotel;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.service.impl.HotelService;
import com.alibaba.fastjson.JSON;
import org.apache.http.HttpHost;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.util.List;
/**
* 酒店文档数据CRUD相关测试
*/
@SpringBootTest
public class HotelDocumentTest {
//初始化ES客户端连接实例
private RestHighLevelClient client ;
//单元测试执行前创建客户端实例
@BeforeEach
void initClient(){
client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.200.200:9200")));
}
//单元测试执行后关系客户端实例
@AfterEach
void closeClient(){
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Autowired
private HotelService hotelService;
/**
* 创建文档
*/
@Test
void testAddDocument(){
//1.从表中查询一条酒店数据
Hotel hotel = hotelService.getById(36934);
//2.将Hotel数据转为ES需要的HotelDoc数据
HotelDoc hotelDoc = new HotelDoc(hotel);
//3.将HotelDoc转为JSON数据
String hotelDocJson = JSON.toJSONString(hotelDoc);
//4.创建Index请求对象
IndexRequest indexRequest = new IndexRequest("hotel");
//5.设置JSON数据 (DSL的数据) 指定索引库名和id
indexRequest.source(hotelDocJson, XContentType.JSON).id(hotelDoc.getId().toString());
//6.使用client发起请求执行新增文档
try {
IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
//7.输出响应结果
System.out.println(indexResponse.getResult().getLowercase());// created
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 查询文档
*/
@Test
void testGetDocument(){
//1.创建查询文档请求实例
GetRequest getRequest = new GetRequest("hotel","36934");
//2.发起请求查询文档
try {
GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
//输出文档的JSON数据
String hotelDocJson = getResponse.getSourceAsString();
System.out.println("文档数据:" + hotelDocJson);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 更新文档
*/
@Test
void testUpdateDocument(){
//1.创建更新文档请求实例
UpdateRequest updateRequest = new UpdateRequest("hotel","36934");
//2.设置要更新的字段和值
updateRequest.doc("address","静安交通路41号", "business", "四川南路商业区");
try {
//3.发起请求执行更新文档
UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
//4.输出响应结果
String result = updateResponse.getResult().getLowercase(); //updated
System.out.println(result);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 删除文档
*/
@Test
void testDeleteDocumnet(){
//1.创建删除文档请求实例
DeleteRequest deleteRequest = new DeleteRequest("hotel","36934");
try {
//2.发起请求执行删除文档
DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT);
//3.输出响应结果
String result = deleteResponse.getResult().getLowercase(); //deleted
System.out.println(result);
} catch (IOException e) {
e.printStackTrace();
}
}
}
批量导入文档
-
1)创建Request对象。这里是BulkRequest
-
2)准备参数。批处理的参数,就是其它Request对象,这里就是多个IndexRequest
-
3)发起请求。这里是批处理,调用的方法为client.bulk()方法
@Test
void testBulkRequest() throws IOException {
// 批量查询酒店数据
List<Hotel> hotels = hotelService.list();
// 1.创建Request
BulkRequest request = new BulkRequest();
// 2.准备参数,添加多个新增的Request
for (Hotel hotel : hotels) {
// 2.1.转换为文档类型HotelDoc
HotelDoc hotelDoc = new HotelDoc(hotel);
// 2.2.创建新增文档的Request对象
request.add(new IndexRequest("hotel")
.id(hotelDoc.getId().toString())
.source(JSON.toJSONString(hotelDoc), XContentType.JSON));
}
// 3.发送请求
client.bulk(request, RequestOptions.DEFAULT);
}
文档操作的基本步骤:
-
初始化RestHighLevelClient
-
创建XxxRequest。XXX是Index、Get、Update、Delete、Bulk
-
准备参数(Index、Update、Bulk时需要)
-
发送请求。调用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk
-
解析结果(Get时需要)