ES基础

75 阅读7分钟

认识elasticsearch

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

ELK技术栈

elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域。而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。

image.png

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表结构索引

image.png

倒排索引的搜索流程如下(以搜索"华为手机"为例):

  1. 用户输入条件"华为手机"进行搜索
  1. 对用户输入内容分词,得到词条:华为手机
  1. 拿着词条在倒排索引中查找,可以得到包含词条的文档id:1、2、3
  2. 拿着文档id到正向索引中查找具体文档

image.png

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


二者对比:

正向索引

优点:

  • 可以给多个字段创建索引
  • 根据索引字段搜索、排序速度非常快

缺点:根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。

倒排索引

优点:根据词条搜索、模糊搜索时,速度非常快

缺点:

  • 只能给词条创建索引,而不是字段
  • 无法根据字段做排序

ES概念

文档(Document)

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

image.png

而Json文档中往往包含很多的字段(Field) ,类似于数据库中的列。


索引(Index) :就是相同类型的文档的集合。

image.png


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

mysql与elasticsearch

MySQLElasticsearch说明
TableIndex索引(index),就是文档的集合,类似数据库的表(table)
RowDocument文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式
ColumnField字段(Field),就是JSON文档中的字段,类似数据库中的列(Column)
SchemaMappingMapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema)
SQLDSLDSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD
  • Mysql:擅长事务类型操作,可以确保数据的安全和一致性
  • Elasticsearch:擅长海量数据的搜索、分析、计算

image.png

索引库

索引(库)类似数据库的表,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

初始化

  1. 引入elasticsearch-rest-high-level-client依赖,注意要和服务端的es版本保持一致,因为是SpringBoot项目,所以要覆盖默认的es版本。

     <properties>
         <java.version>1.8</java.version>
         <elasticsearch.version>7.12.1</elasticsearch.version>
     </properties>
    
  2. 初始化RestHighLevelClient:

     RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
             HttpHost.create("http://192.168.20.100:9200")
     ));
    

索引库操作

  1. 初始化RestHighLevelClient
  2. 创建XxxIndexRequest。Xxx是Create、Get、Delete
  3. 准备DSL(Create时需要,其它是无参)
  4. 发送请求。调用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 ? "索引库已经存在" : "索引库不存在");
 }

文档操作

  1. 初始化RestHighLevelClient
  2. 创建XxxRequest。XXX是Index、Get、Update、Delete、Bulk
  3. 准备参数(Index、Update、Bulk时需要)
  4. 发送请求。调用RestHighLevelClient.xxx()方法,xxx是index、get、update、delete、bulk
  5. 解析结果(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);
 }