elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容
elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域:
而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。
elasticsearch底层是基于lucene来实现的。
Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发。官网地址:https://lucene.apache.org/ 。
elasticsearch的发展历史:
目前比较知名的搜索引擎技术排名:
虽然在早期,Apache Solr是最主要的搜索引擎技术,但随着发展elasticsearch已经渐渐超越了Solr,独占鳌头:
什么是elasticsearch?
什么是elastic stack(ELK)?
什么是Lucene?
倒排索引的概念是基于MySQL这样的正向索引而言的。
那么什么是正向索引呢?例如给下表(tb_goods)中的id创建索引:
但如果是基于title做模糊查询,只能是逐行扫描数据,流程如下:
1)用户搜索数据,条件是title符合"%手机%"
2)逐行获取数据,比如id为1的数据
3)判断数据中的title是否符合用户搜索条件
4)如果符合则放入结果集,不符合则丢弃。回到步骤1
逐行扫描,也就是全表扫描,随着数据量增加,其查询效率也会越来越低。当数据量达到数百万时,就是一场灾难。
倒排索引中有两个非常重要的概念:
Document
):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息Term
):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条创建倒排索引是对正向索引的一种特殊处理,流程如下:
如图:
倒排索引的搜索流程如下(以搜索"华为手机"为例):
1)用户输入条件"华为手机"
进行搜索。
2)对用户输入内容分词,得到词条:华为
、手机
。
3)拿着词条在倒排索引中查找,可以得到包含词条的文档id:1、2、3。
4)拿着文档id到正向索引中查找具体文档。
如图:
虽然要先查询词条,再查询文档id,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。
那么为什么一个叫做正向索引,一个叫做倒排索引呢?
那么两者方式的优缺点是什么呢?
正向索引:
倒排索引:
elasticsearch中有很多独有的概念,与mysql中略有差别,但也有相似之处。
elasticsearch是面向**文档(Document)**存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中:
而Json文档中往往包含很多的字段(Field),类似于数据库中的列。
索引(Index),就是相同类型的文档的集合。
例如:
因此,我们可以把索引当做是数据库中的表。
数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。
我们统一的把mysql与elasticsearch的概念做一下对比:
MySQL | Elasticsearch | 说明 |
---|---|---|
Row | Document | 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式 |
Table | Index | 索引(index),就是文档的集合,类似数据库的表(table) |
Column | Field | 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column) |
Schema | Mapping | Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema) |
SQL | DSL | DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD |
是不是说,我们学习了elasticsearch就不再需要mysql了呢?
并不是如此,两者各自有自己的擅长支出:
因此在企业中,往往是两者结合使用:
https://editor.csdn.net/md/?articleId=125953385
分词器的作用是什么?
IK分词器有几种模式?
IK分词器如何拓展词条?如何停用词条?
索引库就类似数据库表,mapping映射就类似表的结构。
我们要向es中存储数据,必须先创建“库”和“表”。
mapping是对索引库中文档的约束,常见的mapping属性包括:
例如下面的json文档:
{
"age": 21,
"weight": 52.1,
"isMarried": false,
"info": "你好世界",
"email": "[email protected]",
"score": [99.1, 99.5, 98.9],
"name": {
"firstName": "云",
"lastName": "赵"
}
}
对应的每个字段映射(mapping):
这里我们统一使用Kibana编写DSL的方式来演示。
格式:
PUT /索引库名称
{
"mappings": {
"properties": {
"字段名":{
"type": "text",
"analyzer": "ik_smart"
},
"字段名2":{
"type": "keyword",
"index": "false"
},
"字段名3":{
"properties": {
"子字段": {
"type": "keyword"
}
}
},
// ...略
}
}
}
PUT /xin
{
"mappings": {
"properties": {
"info":{
"type": "text",
"analyzer": "ik_smart"
},
"email":{
"type": "keyword",
"index": "false"
},
"name":{
"properties": {
"firstName": {
"type": "keyword"
},
"lastName":{
"type": "keyword"
}
}
}
}
}
}
基本语法:
格式:
GET /索引库名
代码
GET /heima
倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping。
虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。
语法说明:
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
代码
PUT /heima/_mapping
{
"properties":{
"age":{
"type": "integer"
}
}
}
语法:
格式:
DELETE /索引库名
索引库操作有哪些?
语法:
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
// ...
}
示例:
POST /heima/_doc/1
{
"info": "黑马程序员Java讲师",
"email": "[email protected]",
"name": {
"firstName": "云",
"lastName": "赵"
}
}
如果不写文档id,kibana会自动生成一个。
根据REST风格,新增是post,查询应该是get,不过查询一般都需要条件,这里我们把文档id带上。
语法:
GET /{索引库名称}/_doc/{id}
通过kibana查看数据:
GET /xin/_doc/1
删除使用DELETE请求,同样,需要根据id进行删除:
语法:
DELETE /{索引库名}/_doc/id值
示例:
# 根据id删除数据
DELETE /heima/_doc/1
修改有两种方式:
全量修改是覆盖原来的文档,其本质是:
注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
语法:
PUT /{索引库名}/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
示例:
PUT /heima/_doc/1
{
"info": "黑马程序员高级Java讲师",
"email": "[email protected]",
"name": {
"firstName": "云",
"lastName": "赵"
}
}
增量修改是只修改指定id匹配的文档中的部分字段。
语法:
POST /{索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
示例:
POST /heima/_update/1
{
"doc":{
"email":"[email protected]"
}
}
文档操作有哪些?
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址:https://www.elastic.co/guide/en/elasticsearch/client/index.html
其中的Java Rest Client又包括两种:
我们学习的是Java HighLevel Rest Client客户端API
在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。
分为三步:
1)引入es的RestHighLevelClient依赖:
<dependency>
<groupId>org.elasticsearch.clientgroupId>
<artifactId>elasticsearch-rest-high-level-clientartifactId>
dependency>
2)因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本:
<properties>
<java.version>1.8java.version>
<elasticsearch.version>7.12.1elasticsearch.version>
properties>
3)初始化RestHighLevelClient
初始化的代码如下:
RestHighLevelClient restHighLevelclient = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://127.0.0.1:9200")
));
4)测试类
@SpringBootTest //测试类
class HotelIndicesTest{
private RestHighLevelClient restHighLevelClient;
@BeforeEach
public void before() {
//配置连接Es服务器
restHighLevelClient = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://127.0.0.1:9200")));
}
@AfterEach
public void after() throws IOException {
//关闭资源
restHighLevelClient.close();
}
}
//创建索引库和mapping
@Test
void testCreateIndices() throws IOException {
//创建索引库的请求对象
CreateIndexRequest createIndexRequest = new CreateIndexRequest("hotel");
//设置索引库的设置和映射的DSL语句,因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅。
createIndexRequest.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON);
//发送请求给ES服务端,restHighLevelClient.indices()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。
restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
}
//删除索引库
@Test
public void testDeleteIndices() throws IOException {
//创建删除索引库的Request请求对象
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("hotel");
//发送删除请求
restHighLevelClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
}
//查询索引库是否存在
@Test
public void testExistIndices() throws IOException {
//根据索引库名字获取索引库
GetIndexRequest getIndexRequest = new GetIndexRequest("hotel");
//发送请求
boolean exists = restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
System.out.println("hotel索引库,存在吗? -- " + exists);
}
JavaRestClient操作elasticsearch的流程基本类似。核心是client.indices()方法来获取索引库的操作对象。
索引库操作的基本步骤:
@SpringBootTest
class HotelDocumentTest {
private RestHighLevelClient restHighLevelClient;
@Autowired
private HotelService hotelService;
@BeforeEach
public void before() {
//配置连接Es服务器
restHighLevelClient = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://127.0.0.1:9200")));
}
@AfterEach
public void after() throws IOException {
//关闭资源
restHighLevelClient.close();
}
}
我们要将数据库的酒店数据查询出来,写入elasticsearch中。
//新增文档
@Test
public void testAddDocument() throws IOException {
//从数据库查询数据
Hotel hotel = hotelService.getById("36934");
//数据库的数据和ES中的不完全一样,因此属性拷贝
HotelDoc hotelDoc = new HotelDoc(hotel);
//序列化json格式
String str = JSON.toJSONString(hotelDoc);
//创建索引请求
IndexRequest indexRequest = new IndexRequest("hotel").id(hotelDoc.getId().toString());
//设置json格式的数据
indexRequest.source(str, XContentType.JSON);
//发送请求
restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
}
//查询文档
@Test
public void testGetDocument() throws IOException {
GetRequest request = new GetRequest("hotel").id("36934");
GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
//获取文档内容 (json格式)
String source = response.getSourceAsString();
//JSON -> Object
HotelDoc hotelDoc = JSON.parseObject(source, HotelDoc.class);
System.out.println(hotelDoc);
}
//删除文档
@Test
void testDeleteDocument() throws IOException {
// 1.准备Request
DeleteRequest request = new DeleteRequest("hotel", "36934");
// 2.发送请求
restHighLevelClient.delete(request, RequestOptions.DEFAULT);
}
//修改文档
@Test
public void testUpdateDocument() throws IOException {
//修改hotel索引库中的,文档id为36934的
UpdateRequest updateRequest = new UpdateRequest("hotel", "36934");
//将price的值修改为444,将score的值修改为40
updateRequest.doc("price", "444", "score", 40); //
restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
}
@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);
}
文档操作的基本步骤: