ES核心干货学习(原理及数据结构)

本文主要为帮助大家理解ES原理,了解它为啥快,简化部分内容便于理解。

1. ES用途

1.1 ES是什么?

ES是建立在Lucene基础之上的分布式准实时搜索引擎。

核心:分布式和Lucene全文搜索。

1.2 什么场景需要用ES

1. 业务需要进行大量数据实时检索时,传统关系型数据库无法支撑。

2. 需要进行分词检索,语义检索

3. 需要大数据分析

符合上面特征都可以考虑,如日志收集、订单数据链查询,文章检索等。

1.3 为啥选ES

1. 能支持千万级亿级的实时检索。

2. 支持restful API接口,使用门槛低。

3. 分布式,可扩展。

4. 在OLAP场景下与市面上现有竞品(不包括ClickHouse)相比有明显性能优势。

5. 与ClickHouse相比主要是运维优势和用户基础,云服务平台支持较好,减低个人或公司的运维成本。

2. ES安装

    后续补上

3. ES基础概念

3.1 数据结构

类型

说明

举例

与Mysql逻辑对应

index

索引(自定义)

test

表名

doc

field

具体字段名,又称域(自定义)

name

字段名称

type

字段类型(ES定义)

text、keyword、integer

字段类型

1.索引

      在ES中用户的数据新增、搜索和更新等操作的对象全部对应索引。但是ES中的索引和Lucene中的索引不是一一对应的。ES中的一个索引对应一个或多个Lucene索引,这是由其分布式的设计方案决定的,在使用上类似Mysql表的概念。

2.文档

      文档对应关系型数据库中行的概念,ES的文档中可以有一个或多个字段,每个字段可以是各种类型。用户对数据操作的最细粒度对象就是文档。ES文档操作使用了版本的概念,即文档的初始版本为1,每次的写操作会把文档的版本加1,每次使用文档时,ES返回给用户的是最新版本的文档。另外,为了减轻集群负载和提升效率,ES提供了文档的批量索引、更新和删除功能。

3.字段

      一个文档可以包含一个或多个字段,每个字段都有一个类型与其对应。除了常用的数据类型(如字符串型、文本型和数值型)外,ES还提供了多种数据类型,如数组类型、经纬度类型和IP地址类型等。ES对不同类型的字段可以支持不同的搜索功能。例如,当使用文本类型的数据时,可以按照某种分词方式对数据进行搜索,并且可以设定搜索后的打分因子来影响最终的排序。再如,使用经纬度的数据时,ES可以搜索某个地点附近的文档,也可以查询地理围栏内的文档。在排序函数的使用上,ES也可以基于某个地点按照衰减函数进行排序。

3.2 存储结构 

ES核心干货学习(原理及数据结构)_第1张图片

1.集群和节点

      在分布式系统中,为了完成海量数据的存储、计算并提升系统的高可用性,需要多台计算机集成在一起协作,这种形式被称为集群,这些集群中的每台计算机叫作节点。ES集群的节点个数不受限制,用户可以根据需求增加计算机对搜索服务进行扩展。

要检查群集运行状况,我们可以在 Kibana 控制台中运行以下命令 GET /_cluster/health,得到如下信息:

{  
  "cluster_name" : "cluster",   //集群名称
  "status" : "yellow",          //集群状态
  "timed_out" : false,  
  "number_of_nodes" : 1,        //节点个数
  "number_of_data_nodes" : 1,   //数据节点个数(数据节点:存放数据的节点)
  "active_primary_shards" : 9,   //所有主分片数
  "active_shards" : 9,          //所有副本分片数
  "relocating_shards" : 0,      //正在迁移分片数量
  "initializing_shards" : 0,    //正在创建的分片数量
  "unassigned_shards" : 5,      // 未分配副本分片
  "delayed_unassigned_shards" : 0,  
  "number_of_pending_tasks" : 0,  
  "number_of_in_flight_fetch" : 0,  
  "task_max_waiting_in_queue_millis" : 0,  
  "active_shards_percent_as_number" : 64.28571428571429  
}  

集群状态通过 绿,黄,红 来标识

绿色:集群健康完好,一切功能齐全正常,所有分片和副本都可以正常工作。

黄色:预警状态,所有主分片功能正常,但至少有一个副本是不能正常工作的。此时集群是可以正常工作的,但是高可用性在某种程度上会受影响。

红色:集群不可正常使用。某个或某些分片及其副本异常不可用,这时集群的查询操作还能执行,但是返回的结果会不准确。对于分配到这个分片的写入请求将会报错,最终会导致数据的丢失。当集群状态为红色时,它将会继续从可用的分片提供搜索请求服务,但是你需要尽快修复那些未分配的分片。

2.分片

在分布式系统中,为了能存储和计算海量的数据,会先对数据进行切分,然后再将它们存储到多台计算机中,每个切片就是分片,这样不仅能分担集群的存储和计算压力,而且在该架构基础上进一步优化,还可以提升系统中数据的高可用性。在ES中,一个分片对应的就是一个Lucene索引,每个分片可以设置多个副分片,这样当主分片所在的计算机因为发生故障而离线时,副分片会充当主分片继续服务。索引的分片个数只能设置一次,之后不能更改。在默认情况下,ES的每个索引设置为1个分片。

3.副本(副分片)

为了提升系统索引数据的高可用性并减轻集群搜索的负载,可以启用分片的副本,该副本叫作副分片,而原有分片叫作主分片。在一个索引中,主分片的副分片个数是没有限制的,用户可以按需设定。在默认情况下,ES不会为索引的分片开启副分片,用户需要手动设置。副分片的个数设定后,也可以进行更改。一个分片的主分片和副分片分别存储在不同的计算机上。

3.2 ES RestFul API接口

       RestFul API 主要分两个部分: 请求url 和请求方式。

请求方式包括:get、post、delete、put等,其中put请求要求是幂等操作,非幂等操作会报错。

在这就不细说了。

4. ES索引

4.1 索引属性

创建索引:

PUT my_index   
{  
   "settings" : {  
      "number_of_shards" : 5,  // 分片数(静态配置,不可变)
      "number_of_replicas" : 1,  // 副本数 (动态配置,可改)
      "refresh_interval": "2s"    // 索引刷新到文件缓存的时间
   }  
  "mappings": {  
    "_doc": {   
      "properties": {   
        "title":    { "type": "text"  },   
        "name":     { "type": "keyword"  },   
        "age":      { "type": "integer" },    
        "created":  {  
          "type":   "date",   
          "format": "strict_date_optional_time||epoch_millis"  
        }  
      }  
    }  
  }  
} 

4.2 字段(域)说明

(1). 字符串类型: text, keyword

    text类型(可以用分词器)

    a. 支持分词,全文检索,支持模糊、精确查询,不支持聚合,排序操作;

    b. text类型最大支持的字符长度无限制,适合大字段存储;

    使用场景:

    存储全文搜索数据, 例如: 邮箱内容、地址、代码块、文章内容等。

    默认结合standard analyzer(标准解析器)对文本进行分词、倒排索引。

    默认结合标准分析器进行词命中、词频相关度打分。

    keyword类型:

    a.不进行分词,直接索引,支持模糊、支持精确匹配,支持聚合、排序操作。

    b.keyword类型的最大支持的长度为32766个UTF-8类型的字符,可以通过设置ignore_above指定自持字符长度,超过给定长度后的数据将不被索引。

    使用场景:

    存储邮箱号码、url、name、title,手机号码、主机名、状态码、邮政编码、标签、年龄、性别等数据。

    用于筛选数据、排序、聚合(统计)。

    直接将完整的文本保存到倒排索引中。

(2)数字类型:long, integer, short, byte, double, float, half_float, scaled_float

(3)日期:date

(4)日期 纳秒:date_nanos

(5)布尔型:boolean

(6)Binary:binary

(7)Range: integer_range, float_range, long_range, double_range, date_rang

5. 交互流程说明

5.1 写入流程

ES核心干货学习(原理及数据结构)_第2张图片

核心步骤说明

写入请求流转流程

1. 调用者发起写入请求后会发给集群中的某个node节点,该节点被称为协调节点。

2. 协调节点会根据数据中的Routing(可指定参数,默认用_id)值进行Hash运算决定数据存放哪个分片,计算方式比较简单:计算Routing的hash值然后和分片数进行取模运算,得到的值就是主分片编号,具体如下:

shard = hash(routing) % number_of_primary_shards

随后写入请求会转给主分片所在节点处理。

3. 主分片收到请求后会将数据写入内存并同时生成日志文件Translog(用来防止突发情况下的内存丢失,分同步或异步写入两种方式),并且将数据转发到其他副本分片,等所有写入完成(可配置是否需要副本同步写入),返回写入成功响应。

Translog刷盘配置项:

index.translog.durability:

request:同步刷盘(默认)

async:异步刷盘

index.translog.sync_interval:5s //translog异步刷盘间隔时间;默认5s一次

数据写入文件系统缓存

4. 数据写入内存后会refresh到文件系统缓存中(可配置,默认每秒执行),并生成segment段,构建索引,此时用户即可查询到数据。

refresh配置项:

index.refresh_interval:10s //refresh刷新频率,默认1s一次,可以设置为-1为禁用

5. 索引的构建主要分为2个大步骤,生成正排索引和生成倒排索引。

正排索引:生成文档ID和文档数据的对应关系,建立存储索引;

倒排索引:文档数据关键字与文档id建立索引。

6. 当translog达到一定大小(参数:index.translog.flush_threshold_size,默认:512M)或者到设置的flush时间(参数:index.translog.flush_threshold_period,默认:30分钟)会进行一次flush操作,具体流程:

1.内存中数据写入新的segment放入缓存(清空内存区)

2.生成一个提交点(commit point)并写入磁盘,表示哪些segment已写入磁盘

3.将缓存的segement写入磁盘(fsync命令)

4.清空旧的translog日志

额外细节:

1. 段合并(整个写入过程中也伴随着段合并):

       由于端是不可变的,每次refresh会创建一个新的段 ,这样会导致短时间内的段数量暴增。每一个段都会消耗文件句柄、内存和 cpu 运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。Elasticsearch 通过在后台进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段。段合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中。

5.2 查询流程

ES核心干货学习(原理及数据结构)_第3张图片

查询阶段步骤(我们以普通参数查询为例):

1. 客户端发送一个search请求到Node1,Node1会创建一个查询数据量大小的空优先队列,该节点就会变成协调节点。

2. Node1将查询请求转发到索引的每个主分片或它对应某个副本分片中。每个分片在本地执行查询并添加结果到本地有序优先队列中。

3. 每个分片返回各自优先队列中所有文档的 ID 和排序值给协调节点,也就是Node1,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。

汇总一下:

        首先查询请求发送会发给集群的某一个节点,某个节点把最终的结果返回给客户端,当一个搜索请求被发送到某个节点时,这个节点就变成了协调节点。 这个节点的任务是广播查询请求到所有相关分片并将它们的响应整合成全局排序后的结果集合,这个结果集合会返回给客户端。

      查询请求可以被某个主分片或某个副本分片处理, 这就是为什么更多的副本(当结合更多的硬件)能够增加搜索吞吐率。协调节点将在之后的请求中轮询所有的分片拷贝来分摊负载。

      每个分片在本地执行查询请求并且创建一个长度为from + size的优先队列,也就是说,每个分片创建的结果集足够大,均可以满足全局的搜索请求。分片返回一个轻量级的结果列表到协调节点,它仅包含文档 ID 集合以及任何排序需要用到的值,例如 _score 。

     协调节点将这些分片级的结果合并到自己的有序优先队列里,它代表了全局排序结果集合。至此查询过程结束。

注:a. 每次查询过程中我们都是将请求分发给主分片或者副本(二选一)

       b. 按_id查询是会直接计算hash去对应分片查询,不用分发后在合并。

5.3 删除流程

      由于segment不可变,ES的删除操作是进行逻辑删除的,先生成标记删除文件(.del)记录那些被删除,在查询的时候做过滤,然后在合并segment 时物理删除。

6. 索引建立流程

整体流程图如下:

(只说明主流程,以下讲解中所有数据结构不代表实际结构,只是便于理解的简化版)

ES核心干货学习(原理及数据结构)_第4张图片

说明:

1. 索引建立会分为正排索引和倒排索引

2. 正排索引通过文档id定位文档数据位置。

3. 倒排索引通过建立词条和id的对应关系定位文档id,通过正派索引定位文档。

注:说文档id只是便于理解,只是个对应关系的标记id,并不一定是文档的id。

这个很好理解,正排索引和倒排索引都需要,只有倒排索引是没办法定位到具体数据的,毕竟巨量数据的检索也是个问题。

6.1. 建立正排索引 

ES核心干货学习(原理及数据结构)_第5张图片

说明:

1. 正排索引的目的是在于建立快捷的文档信息快速查找的机制,核心在于快速定位文档位置(存储偏移量),所以为了记录文档位置,我们建立了域索引信息表。

2. 域索引信息表中的每个元素会记录当前域数据对应的文档信息位置(偏移量),但查询域数据本身也是个问题,所以我们提前给域数据(分词数据)进行顺序编号, 域索引信息表会按照编号和定长64位的块顺序排列。

3. 当我们知道域数据编号可以计算域索引的偏移量(size(公共数据长度)+ 64*N),从而获取对于数据在文档信息表中的位置信息,获取具体数据。

4. 为降低存储冗余和便于维护,我们提前给域(字段)进行编号并建立对应关系,生成域信息表。

举个例子(10(id), name(field),zhansan(value)):

生成name的域索引表,给“zhansan”编号为5,分配在size+5*64 到size+6*64 这段区域存储id:10对应数据的在文档信息表中的位置。

6.2. 建立倒排索引

流程如下:

ES核心干货学习(原理及数据结构)_第6张图片

1. 数据项会先进行分词拆分(非text类型域跳过),建立词典,这块会用到FST算法进行索引,通过前缀进行匹配,词典会进行排序,并放在内存中。

2. 建立词典和文档的对应关系,也就是建立倒排表,这里的倒排表写的比较简单,实际上倒排表下有两个字表:词频倒排和位置倒排。

ES核心干货学习(原理及数据结构)_第7张图片

终极问题1:ES为什么亿级大数据全文检索领域那么快?

1. 倒排索引

      倒排的数据结构在全文搜索上有着天然的优势,在建立索引的情况下,单分片查询复杂度接近O(1)。

2. 词典压缩

     ES中有大量数据压缩算法的应用,对词典的压缩保证这部分数据可以放在内存中进行检索,大大提升查询效率。

3. 分布式架构

     分布式架构下保证了ES的可拓展性,多分片副本进行分段存储,使得在查询过程中可进行负载查询,降低单节点查询压力,提升并发,进而提高查询效率。

终极问题2:那些因素会影响ES性能?

1. 硬盘!! ,硬盘性能对ES影响很大,很多操作是频繁读写硬盘,所以SSD非常必要。

2. CPU和内存,这就不说了,大点好,需要注意合理设置jvm内存,不要影响文件系统缓存的使用

3. 分片和副本的设置需要注意,分片数最好大于等于数据节点数小于等于3倍数据节点数。

4. refresh主要是生成segment放文件缓存可供实时查询,但是过多小segment会影响效率,查询实时性没那么高时建议调大一些。

3. flush是一个刷盘操作,写密集的情况下建议调大flush时间间隔,来减低IO。

4. translog 日志文件,默认会同步写盘,接受部分风险,建议改成异步。

5. 写入时如无特需要求,文档ID不要自定义!会影响写入效率,因为es需要重新计算排序,最重要的时它会进行验重,占用系统资源。

你可能感兴趣的:(elasticsearch,lucene,java,全文检索,搜索引擎)