Elasticsearch(1):Elasticsearch核心原理与基础操作总结

概述

Elasticsearch 是一个开源的分布式搜索和分析引擎,专为速度、扩展和 AI 应用而打造。

作为一个检索平台,它可以实时存储结构化、非结构化和向量数据,

提供快速的混合和向量搜索,支持可观测性与安全分析,并以高性能、高准确性和高相关性实现 AI 驱动的应用。

文档地址:Search use case | Elastic Docs

引入

lucene

Apache Lucene是一个高性能、功能齐全的全文搜索引擎库

地址:Apache Lucene - Welcome to Apache Lucene

优点:

  • 超高效的索引和搜索能力
  • 强大的查询语法
  • 多种语言的分析器支持
  • 拼写检查与纠错功能
  • 排序与过滤能力
  • 可扩展性极强

ElasticsSearch在其基础上重写得来

ELK

Elasticsearch是由elastic公司开发的一套搜索引擎技术,它是elastic技术栈中的一部分。

完整的技术栈包括:

  • Elasticsearch:用于数据存储、计算和搜索
  • Logstash/Beats:用于数据收集
  • Kibana:用于数据可视化

整套技术栈被称为ELK,经常用来做日志收集、系统监控和状态分析等

kibana

Kibana是elastic公司提供的用于操作Elasticsearch的可视化控制台。它的功能非常强大,包括:

  • 对Elasticsearch数据的搜索、展示
  • 对Elasticsearch数据的统计、聚合,并形成图形化报表、图形
  • 对Elasticsearch的集群状态监控
  • 它还提供了一个开发控制台(DevTools),在其中对Elasticsearch的Restful的API接口提供了语法提示

由于es通过发送http请求来完成操作,

而http请求的方式、路径、还有请求参数的格式都有严格的规范,

因此使用kibana可以方便操作

原理

正向索引

例如有一张名为tb_goods的表:

id title price
1 小米手机 3499
2 华为手机 4999
3 华为小米充电器 49
4 小米手环 49
... ... ...

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

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

比如用户的SQL语句为:

SQL 复制代码
select * from tb_goods where title like '%手机%';

此时会对表进行全表扫描,筛选出符合条件的信息。

即:

  1. 检查到搜索条件为like '%手机%',需要找到title中包含手机的数据
  2. 逐条遍历每行数据(每个叶子节点),比如第1次拿到id为1的数据
  3. 判断数据中的title字段值是否符合条件
  4. 如果符合则放入结果集,不符合则丢弃
  5. 回到步骤1

故:根据id精确匹配时,可以走索引,查询效率较高。

而当搜索条件为模糊匹配时,由于索引无法生效,导致从索引查询退化为全表扫描,效率很差。

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

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

倒排索引

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

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

创建倒排索引是对正向索引的一种特殊处理和应用,流程如下:

  • 将每一个文档的数据利用分词算法根据语义拆分,得到一个个词条
  • 创建表,每行数据包括词条、词条所在文档id、位置等信息
  • 因为词条唯一性,可以给词条创建正向索引

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

正向索引

id(索引) title price
1 小米手机 3499
2 华为手机 4999
3 华为小米充电器 49
4 小米手环 49
... ... ...

倒排索引

词条(索引) 文档id
小米 1,3,4
手机 1,2
华为 2,3
充电器 3
手环 4

流程描述:

  1. 用户输入条件"华为手机"进行搜索。
  2. 对用户输入条件分词 ,得到词条:华为手机
  3. 拿着词条在倒排索引中查找(由于词条有索引 ,查询效率很高),即可得到包含词条的文档id:1、2、3
  4. 拿着文档id到正向索引中查找具体文档即可(由于id也有索引,查询效率也很高)。

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

类似添加二级索引并进行回表

分词器

Elasticsearch的关键就是倒排索引,而倒排索引依赖于对文档内容的分词,

而分词则需要高效、精准的分词算法,IK分词器就是这样一个中文分词算法。

在搜索一句话时

搜索引擎不会直接搜索整句话,而是:

  • 先对文档分词
  • 再对搜索关键词分词
  • 然后匹配词项

ik分词器提供了基本词典

我们可以自己拓展词典

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
	<comment>IK Analyzer 扩展配置</comment>
	<!--用户可以在这里配置自己的扩展字典 -->
	<entry key="ext_dict">ext.dict</entry>
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords">ext_stopwords.dic</entry>
	<!--用户可以在这里配置远程扩展字典 -->
	<!-- <entry key="remote_ext_dict">words_location</entry> -->
	<!--用户可以在这里配置远程扩展停止词字典-->
	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

在config文件中包含着字典位置

我们可以配置扩展字典或停止词,ik分词器也会访问这些文件进行分词

核心概念

文档

elasticsearch是面向**文档(Document)**存储的,可以是数据库中的一条商品数据,一个订单信息。

文档数据会被序列化为json格式后存储在elasticsearch中:

JSON 复制代码
{
    "id": 1,
    "title": "小米手机",
    "price": 3499
}
{
    "id": 2,
    "title": "华为手机",
    "price": 4999
}
{
    "id": 3,
    "title": "华为小米充电器",
    "price": 49
}
{
    "id": 4,
    "title": "小米手环",
    "price": 299
}

字段

原本数据库中的一行数据就是ES中的一个JSON文档;

而数据库中每行数据都包含很多列,这些列就转换为JSON文档中的字段(Field)

json 复制代码
{
    "id": 3,
    "title": "华为小米充电器",
    "price": 49
}

索引

将类型相同的文档集中在一起管理,称为索引(Index)。例如:

商品索引

JSON 复制代码
{
    "id": 1,
    "title": "小米手机",
    "price": 3499
}

{
    "id": 2,
    "title": "华为手机",
    "price": 4999
}

{
    "id": 3,
    "title": "三星手机",
    "price": 3999
}

用户索引

JSON 复制代码
{
    "id": 101,
    "name": "张三",
    "age": 21
}

{
    "id": 102,
    "name": "李四",
    "age": 24
}

{
    "id": 103,
    "name": "麻子",
    "age": 18
}

订单索引

JSON 复制代码
{
    "id": 10,
    "userId": 101,
    "goodsId": 1,
    "totalFee": 294
}

{
    "id": 11,
    "userId": 102,
    "goodsId": 2,
    "totalFee": 328
}

映射

数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。

因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。

json 复制代码
{
    "id":
    "userId":
    "goodsId": 
    "totalFee": 
}

属性包括:

  • type:字段数据类型,常见的简单类型有:
    • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
    • 数值:longintegershortbytedoublefloat
    • 布尔:boolean
    • 日期:date
    • 对象:object
  • index:是否创建索引,默认为true
  • analyzer:使用哪种分词器
  • properties:该字段的子字段

对比mysql

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实现:

    Mysql:擅长事务类型操作,可以确保数据的安全和一致性

  • 对查询性能要求较高的搜索需求,使用elasticsearch实现:

    Elasticsearch:擅长海量数据的搜索、分析、计算

操作es

索引库操作

mapping映射属性

Mapping是对索引库中文档的约束,常见的Mapping属性包括:

  • type:字段数据类型,常见的简单类型有:
    • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
    • 数值:longintegershortbytedoublefloat
    • 布尔:boolean
    • 日期:date
    • 对象:object
  • index:是否创建索引,默认为true
  • analyzer:使用哪种分词器
  • properties:该字段的子字段

例:

字段名 字段类型 类型说明 是否参与搜索 是否参与分词 分词器
age integer 整数 ------
weight float 浮点数 ------
isMarried boolean 布尔 ------
info text 字符串,但需要分词 IK
email keyword 字符串,但是不分词 ------
score float 只看数组中元素类型 ------
name firstName keyword 字符串,但是不分词 ------
lastName keyword 字符串,但是不分词 ------

索引库的crud

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

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

创建索引库和映射

基本语法

  • 请求方式:PUT
  • 请求路径:/索引库名,可以自定义
  • 请求参数:mapping映射

格式

JSON 复制代码
PUT /索引库名称
{
  "mappings": {
    "properties": {
      "字段名":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "字段名2":{
        "type": "keyword",
        "index": "false"
      },
      "字段名3":{
        "properties": {
          "子字段": {
            "type": "keyword"
          }
        }
      },
      // ...略
    }
  }
}
查询索引库

基本语法

  • 请求方式:GET
  • 请求路径:/索引库名
  • 请求参数:无

格式

Plain 复制代码
GET /索引库名
修改索引库

只可新增,不可修改

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

因此索引库一旦创建,无法修改mapping

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

语法说明

JSON 复制代码
PUT /索引库名/_mapping
{
  "properties": {
    "新字段名":{
      "type": "integer"
    }
  }
}
删除索引库

语法:

  • 请求方式:DELETE
  • 请求路径:/索引库名
  • 请求参数:无

格式:

Plain 复制代码
DELETE /索引库名
总结
  • 创建索引库:PUT /索引库名
  • 查询索引库:GET /索引库名
  • 删除索引库:DELETE /索引库名
  • 修改索引库,添加字段:PUT /索引库名/_mapping

json 复制代码
PUT /user
{
  "mappings": {
    "properties": {
      "username":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "realname":{
        "type": "object",
        "properties": {
          "firstname":{"type":"keyword"},
          "lastname":{"type":"keyword"}
        }
      },
      "age":{
        "type":"byte"
      },
      "phonenumber":{
        "type": "keyword"
        , "index": false
      },
      "isMarried":{
        "type": "boolean"
      }
    }
  }
}

json 复制代码
GET /user

json 复制代码
PUT /user/_mapping
{
  "properties":{
    "email":{
      "type":"keyword"
    }
  }
}

json 复制代码
DELETE /user

文档操作

新增

json 复制代码
POST /索引库名/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    "字段3": {
        "子属性1": "值3",
        "子属性2": "值4"
    },
}

例:

json 复制代码
POST /user/_doc/1
{
  "username":"jack",
  "realname":{
    "firstname":"jason",
    "lastname":"mike"
  },
  "age":21,
  "phonenumber":"123",
  "isMarried":true
  
}

查询

JSON 复制代码
GET /{索引库名称}/_doc/{id}

例:

json 复制代码
GET /user/_doc/1

删除

json、 复制代码
DELETE /{索引库名}/_doc/id值

例:

json 复制代码
DELETE /user/_doc/1

修改

全量修改

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

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

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

JSON 复制代码
PUT /{索引库名}/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    // ... 略
}

例:

json 复制代码
Put /user/_doc/1
{
  "username":"jack",
  "realname":{
    "firstname":"jason",
    "lastname":"huang"
  },
  "age":21,
  "phonenumber":"123",
  "isMarried":true
  
}
局部修改

修改文档中的部分字段

JSON 复制代码
POST /{索引库名}/_update/文档id
{
    "doc": {
         "字段名": "新的值",
    }
}

例:

json 复制代码
POST /user/_update/1
{
  "doc":{"isMarried":false}
}

批处理

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

Java 复制代码
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

其中:

  • create 代表创建文档操作(文档已存在会报错)
    • _index:指定索引库名
    • _id:指定要操作的文档 id
    • { "field1" : "value1" }:则是要新增的文档内容
  • index 代表新增或覆盖操作(存在则覆盖,不存在则新增)
    • _index:指定索引库名
    • _id:指定要操作的文档 id
    • { "field1" : "value1" }:则是要新增或覆盖的文档内容
  • delete代表删除操作
    • _index:指定索引库名
    • _id指定要操作的文档id
  • update代表更新操作
    • _index:指定索引库名
    • _id指定要操作的文档id
    • { "doc" : {"field2" : "value2"} }:要更新的文档字段

例:

JSON 复制代码
#批量增
POST /_bulk
{"index": {"_index":"user", "_id": "2"}}
{"username":"jack","realname":{"firstname":"jason","lastname":"juddy"},"age":21,"phonenumber":"123","isMarried":true}
{"index": {"_index":"user", "_id": "3"}}
{"username":"jack","realname":{"firstname":"jason","lastname":"jenny"},"age":21,"phonenumber":"123","isMarried":true}
#批量删
POST /_bulk
{"delete":{"_index":"user", "_id": "2"}}
{"delete":{"_index":"user", "_id": "3"}}
相关推荐
晓梦林2 小时前
EVA靶场学习笔记
android·笔记·学习
Devin~Y2 小时前
大厂Java面试实录:Spring Boot/Cloud、Kafka、Redis、K8s 可观测性 + RAG/Agent(小Y翻车版)
java·spring boot·redis·spring cloud·kafka·kubernetes·mybatis
林森lsjs2 小时前
【日耕一题】2. 面向对象 Java 基础:构造方法与 toString
java·开发语言
humors2212 小时前
聊聊密码为啥会“白设”
大数据·运维·服务器·网络·网络安全
学代码的真由酱2 小时前
【自用】测开面试问题-Java
java·面试·职场和发展
过期动态2 小时前
【LeetCode 热题 100】三数之和
java·数据结构·算法·leetcode·职场和发展·排序算法
ZzYH222 小时前
文献阅读 260529-Burning Questions: Research Data, Tools, and Insights
笔记
Sharewinfo_BJ2 小时前
Power BI 5月重磅更新:8大新功能全面提升数据分析效率
大数据·人工智能·数据分析
希望永不加班2 小时前
SpringBoot 服务注册与发现:Nacos/Consul/Eureka
java·spring boot·eureka·consul·java-consul