快捷搜索:  汽车  科技

elasticsearch底层数据存储结构(一篇文章让你了解Elasticsearch存储原理)

elasticsearch底层数据存储结构(一篇文章让你了解Elasticsearch存储原理)分布式系统的经典网路问题,当出现网路问题时,一个节点与其他节点无法连接。选举时,通过单播发现,互相ping对方,比较Node Id 记录自己认为的主节点【Node Id 低的会成为被选举的节点】,最后多数者成为主节点;其他节点再加入集群时,不会承担主节点角色;主节点丢失,就会重新通过互相ping对方选出主节点。将用户的 bulkRequest 重新组织为基于 shard 的请求列表。例如,原始用户请求可能有10个写操作,如果这些文档的主分片都属于同一个,则写请求被合并为1个。根据路由算法计算某文档属于哪个分片。遍历所有的用户请求,重新封装后添加到上述map结构。Map<ShardId List<BulkItemRequest>> requestsByShard = new HashMap<>();根据职能的不同,es的服务可分为,协调节点、主节点、数据

前言

ElasticSearch是基于lucene封装的一个分布式、实时、可扩展、支持海量数据存储的分析、搜索引擎。

  • 相较于lucene,操作更简单。
  • 更好的索引、搜索性能。
  • 分布式、扩展能力更强。
一、索引全过程

elasticsearch底层数据存储结构(一篇文章让你了解Elasticsearch存储原理)(1)

  • Gateway代表ElasticSearch索引的持久化存储方式。在Gateway中,ElasticSearch默认先把索引存储在内存中,然后当内存满的时候,再持久化到Gateway里。当ES集群关闭或重启的时候,它就会从Gateway里去读取索引数据。比如LocalFileSystem和HDFS、AS3等。
  • DistributedLucene Directory 它是Lucene里的一些列索引文件组成的目录。它负责管理这些索引文件。包括数据的读取、写入,以及索引的添加和合并等。
  • Mapping,映射的意思,非常类似于静态语言中的数据类型。比如我们声明一个int类型的变量,那以后这个变量只能存储int类型的数据。比如我们声明一个double类型的mapping字段,则只能存储double类型的数据。Mapping不仅是告诉ElasticSearch,哪个字段是哪种类型。还能告诉ElasticSearch如何来索引数据,以及数据是否被索引到等。
  • Search Moudle,这个很简单
  • Index Moudle,这个很简单
  • Disvcovery,主要是负责集群的master节点发现。比如某个节点突然离开或进来的情况,进行一个分片重新分片等。这里有个发现机制。发现机制默认的实现方式是单播和多播的形式,即Zen,同时也支持点对点的实现。另外一种是以插件的形式,即EC2。
  • scripting,即脚本语言。包括很多,这里不多赘述。如mvel、js、python等。
  • Transport,代表ElasticSearch内部节点,代表跟集群的客户端交互。包括 Thrift、Memcached、Http等协议
  • RESTful Style API,通过RESTful方式来实现API编程。
  • 3rd plugins,代表第三方插件。
  • Java(Netty),是开发框架。
  • JMX,是监控。

elasticsearch底层数据存储结构(一篇文章让你了解Elasticsearch存储原理)(2)

1、客户端发送索引请求
  • 客户端向ES节点发送索引请求
2、参数检查
  • 对请求中的参数进行检查,检查参数是否合法,不合法的参数直接返回失败给客户端。
3、数据预处理
  • 如果请求指定了pipeline参数,则对数据进行预处理,数据预处理的节点为Ingest Node,如果接受请求的节点不具有数据处理能力,则转发给其他能处理的节点。在Ingest node上有定义好的处理数据的Pipeline,Pipeline中有一组定义好的Processor,每个Processor分别具有不同的处理功能,ES提供了一些内置的Processor,如:split、join、set 、script等,同时也支持通过插件的方式,实现自定义的Processor。数据经过Pipeline处理完毕后继续进行下一步操作。
4.判断索引是否存在
  • 判断索引是否存在。如果索引不存在,则判断是否能够自动创建,可以通过action.auto_create_index设置能否自动创建索引;如果节点支持Dynamic Mapping,写入文档时,如果字段尚未在mapping中定义,则会根据索引文档信息推算字段的类型,但并不能完全推算正确。配置Dynamic:true时,文档有新增字段的时候,索引的mapping也会同步更新。Dynamic:false时,索引的mapping不会被更新,新增字段无法被索引到。Dynamic:strict时,索引有新增字段时,将会报错。
5.创建索引
  • 创建索引请求被发送到Master节点,由Master节点负责进行索引的创建,索引创建成功后,Master节点会更新集群状态clusterstate【同步到其他节点,只有主节点可更新,其他节点只读】,更新完毕后将索引创建的情况返回给Coordinate节点,收到Master节点返回后,进入下一流程。
6.请求预处理
  • 获取集群状态信息,判断集群是否正常;从集群状态中获取对应索引的元信息,从元信息中获取索引的mapping、version等信息,从请求中解析routing、id信息,如果请求没有指定文档的id,则会生成一个UUID作为文档的id。
7、路由算法及构建shard请求
  • 路由算法

路由算法即根据请求的routing和文档id信息计算文档应该被索引到那个分片ID的过程。

计算公式如下:shard_num = hash(_routing) % num_primary_shards

默认情况下,_routing就是文档id,num_primary_shards是主分片个数,所以从算法中即可以看出索引的主分片个数一旦指定便无法修改,因为文档利用主分片的个数来进行定位。当使用自定义_routing或者id时,按照上面的公式计算,数据可能会大量聚集于某些分片,造成数据分布不均衡,所以ES提供了routing_partition_size参数,routing_partition_size越大,数据的分布越均匀。分片的计算公式变为:shard_num = (Hash(_routing) hash(_id) % routing_partition_size) % num_primary_shards也就是说,_routing字段用于计算索引中的一组分片,然后使用_id来选择该组内的分片。index.routing_partition_size取值应具有大于1且小于index.number_of_shards的值。

  • 构建shard请求

将用户的 bulkRequest 重新组织为基于 shard 的请求列表。例如,原始用户请求可能有10个写操作,如果这些文档的主分片都属于同一个,则写请求被合并为1个。根据路由算法计算某文档属于哪个分片。遍历所有的用户请求,重新封装后添加到上述map结构。Map<ShardId List<BulkItemRequest>> requestsByShard = new HashMap<>();

8、转发请求并等待响应
  • 根据集群状态中的内容路由表确定主分片所在节点,转发请求并等待响应。遍历所有需要写的 shard,将位于某个 shard 的请求封装为 BulkShardRequest类,调用TransportShardBulkAction#execute执行发送,在listener中等待响应,每个响应也是以shard为单位的【如果某个shard的响应中部分doc写失败了,则将异常信息填充到Response中,整体请求做成功处理】。待收到所有响应后(无论成功还是失败的),回复给客户端。
9、主分片节点流程
  • 主分片索引文档,详见第三节。
9、副本分片索引文档
  • 当主分片完成索引操作后,会循环处理要写的所有副本分片,向副本分片所在的节点发送请求。副本分片执行和主分片一样的文档写入流程,然后返回写入结果给主分片节点。
10、请求返回
  • 主分片收到副本分片的响应后,会执行finish()操作,将收到的响应信息返回给Coordinate节点,告知Coordinate节点文档写入分片成功、失败的情况;coordinate节点收到响应后,将索引执行情况返回给客户端。当文档写入失败时,主分片节点会向Master节点返送shardFieled请求,因为主副本分片未同步,Master会更新集群的状态,将写失败的副本分片从in-sync-allocation中去除;同时在路由表中将该分片的状态改为unassigned,即未分配状态。
二、集群管理

elasticsearch底层数据存储结构(一篇文章让你了解Elasticsearch存储原理)(3)

2.1、节点类型

根据职能的不同,es的服务可分为,协调节点、主节点、数据节点、处理节点。【一个节点可以同时兼具多个角色】

  • 主节点Master:管理集群的机器(分片安排到哪个节点),尽量不处理用户的数据请求(master节点也可以做协调节点,尽量不要做)。
  • 协调节点coordinate:路由数据写到哪个分片。(数据存储路由所有节点都具备这个能力,如果是创建分片只能主节点能操作。)
  • 数据节点Data:存放分片数据。
  • 处理节点Ingest:对数据做预处理。(是数据具备数据预处理能力的数据节点)

数据可以分为主分片、副本分片。

  • 主分片:所有主分片加起来代表这个索引的总和。
  • 副本分片:主分片的备份。
2.2、节点状态
  • 红:部分主分片不可用
  • 黄:部分副本不可用
  • 绿:全部分片和副本都可用
2.3、节点职责
  • master node职责处理索引创建、删除;分片分配到哪个节点;集群状态维护和更新。
  • 在每个节点上都保存了集群的状态信息,但是只有master节点才能修改集群的状态信息,并负责同步到其他的节点。
2.4、选举
  • 不依赖第三方组件,没有节点数奇数的限制,满足一定规则即可。
  • ES 集群由一个或多个 Elasticsearch 节点组成,每个节点配置相同的 [cluster.name]即可加入集群【不同节点上,还需要单播主机列表】,默认值为 “elasticsearch”。
  • 每个节点都有一个名字node.name,启动后会分配一个UID,一份节点列表,并保存了集群状态。

选举时,通过单播发现,互相ping对方,比较Node Id 记录自己认为的主节点【Node Id 低的会成为被选举的节点】,最后多数者成为主节点;其他节点再加入集群时,不会承担主节点角色;主节点丢失,就会重新通过互相ping对方选出主节点。

2.5、脑裂问题

分布式系统的经典网路问题,当出现网路问题时,一个节点与其他节点无法连接。

elasticsearch底层数据存储结构(一篇文章让你了解Elasticsearch存储原理)(4)

  • Node2和Node3会重新选举Master
  • Node1自己还是作为Master,组成一个集群,同时更新Cluster State
  • 导致2个Master,维护不同的Cluster State,当网络恢复时,无法选择正确恢复

避免脑裂问题【7.0之后不需要配置】

限定选举条件,设置quorun【quorun = master节点总数/2 1】,只有在master eligible节点大于quorun时,才能进行选举。【当有一半的候选主节点宕机后,集群将不会自动恢复】

2.6、集群伸缩
  • 集群伸缩可以表现为节点配置提高或降低,也可以表现为节点数增加或减少。
  • 明确最后都是保证主节点正常,分片,副本与设置一致,集群才能正常提供服务支持。
  • 主节点负责索引管理、分片分配;数据节点负责数据存储,提供查询,主分片【所有主分片对待】接收请求,同步数据到副本。
2.7、故障转移

elasticsearch底层数据存储结构(一篇文章让你了解Elasticsearch存储原理)(5)

  • Node1主节点挂掉,集群变红,重新选举。
  • Node2选为主节点,将副本R0升级为分片P0,此时集群变黄。
  • 接着主节点下发生成R0 R1副本的命令到指定节点,生成后,集群恢复正常,变绿。
2.8、elasticsearch.yml
  • cluster.name

当前节点所属集群名称,多个节点如果要组成同一个集群,那么集群名称一定要配置成相同。默认值elasticsearch,生产环境建议根据ES集群的使用目的修改成合适的名字。

  • node.name

当前节点名称,默认值当前节点部署所在机器的主机名,所以如果一台机器上要起多个ES节点的话,需要通过配置该属性明确指定不同的节点名称。

  • path.data

配置数据存储目录,比如索引数据等,默认值 $ES_HOME/data,生产环境下强烈建议部署到另外的安全目录,防止ES升级导致数据被误删除。

  • path.logs

配置日志存储目录,比如运行日志和集群健康信息等,默认值 $ES_HOME/logs,生产环境下强烈建议部署到另外的安全目录,防止ES升级导致数据被误删除。

  • node.master

是否作为主节点,每个节点都可以被配置成为主节点,默认值为true。

  • node.data

是否存储数据,即存储索引片段,默认值为true。

  • bootstrap.memory_lock

配置ES启动时是否进行内存锁定检查,默认值true。

ES对于内存的需求比较大,一般生产环境建议配置大内存,如果内存不足,容易导致内存交换到磁盘,严重影响ES的性能。所以默认启动时进行相应大小内存的锁定,如果无法锁定则会启动失败。非生产环境可能机器内存本身就很小,能够供给ES使用的就更小,如果该参数配置为true的话很可能导致无法锁定内存以致ES无法成功启动,此时可以修改为false。

  • network.host

配置能够访问当前节点的地址,

1、默认值为当前节点所在机器的本机回环地址127.0.0.1 和[::1],这就导致默认情况下只能通过当前节点所在主机访问当前节点。

2、配置成某一ip,例如192.168.1.0时,其他服务只能通过此ip访问es。

3、可以配置为 0.0.0.0 ,表示所有主机均可访问。

  • network.public_host

publish_host设置其他节点连接此集群的地址,公共集群网络(集群间通信)。

  • network.bind_host

network.bind_host: ["192.168.1.100" "10.210.32.xx"]外部网络可访问的地址。

  • http.port

配置当前ES节点对外提供服务的http端口,默认值 9200

  • transport

配置当前ES节点对内提供服务的服务发现端口,默认值 9300

  • http.max_content_length

设置内容的最大长度100M

  • discovery.seed_hosts

配置参与集群节点发现过程的主机列表,说白一点就是集群中所有符合主节点要求的主机列表,可以是具体的IP地址,也可以是可解析的域名。

  • cluster.initial_master_nodes【建议删除此配置】

配置ES集群初始化时参与master选举的节点名称列表,集群自举节点列表。【为了设置特定节点作为master时设置】启动Master候选节点时,可以在命令行上或elasticsearch.yml文件中提供此设置. 群集形成后,不再需要此设置,并且会忽略它,也就是说,这个属性就只是在集群首次启动时有用。并且可以不需要在非Master候选节点上设置。

  • discovery.zen.ping.unicast.hosts【过时配置】

设置新节点被启动时能够发现的主节点列表(主要用于不同网段机器连接)【transport端口】

  • discovery.zen.minimum_master_nodes

设置一个集群中参与选举的主节点的数量,当多于三个节点时,该值可在2-4之间。

  • discovery.zen.ping.timeout

设置ping其他节点时的超时时间,网络比较慢时可将该值设大。

  • discovery.zen.ping.multicast.enabled

禁止当前节点发现多个集群节点,默认值为true。

  • node.rack

每个节点都可以定义一些与之关联的通用属性,用于后期集群进行碎片分配时的过滤。

  • node.max_local_storage_nodes

默认情况下,多个节点可以在同一个安装路径启动,如果你想让你的es只启动一个节点,可以设置为1。

三、索引详细过程

elasticsearch底层数据存储结构(一篇文章让你了解Elasticsearch存储原理)(6)

elasticsearch底层数据存储结构(一篇文章让你了解Elasticsearch存储原理)(7)

3.1、路由

客户端发起数据写入请求,对你写的这条数据根据_routing规则选择发给哪个Shard。【确认Index Request中是否设置了使用哪个Filed的值作为路由参数,如果没有设置,则使用Mapping中的配置,如果mapping中也没有配置,则使用_id作为路由参数,然后通过_routing的Hash值选择出Shard,最后从集群的Meta中找出出该Shard的Primary节点。】

3.2、buffer

写入请求到达Shard后,先把数据写入到内存(buffer)中,同时会写入一条日志到translog日志文件中去。【还未构建倒排索引】

3.3、refresh

执行refresh操作:从内存buffer中将数据写入os cache(操作系统的内存),产生一个新的Segment file文件(不需要落盘,写入os cache后,文件句柄也就可以被访问了),buffer清空。默认是每隔1秒refresh一次的,所以es是准实时的,因为写入的数据1秒之后才能被看到。buffer内存占满的时候也会执行refresh操作,buffer默认值是JVM内存的10%,若要优化索引速度 而不注重实时性 可以降低刷新频率。通过es的restful api或者java api,手动执行一次refresh操作,就是手动将buffer中的数据刷入os cache中,让数据立马就可以被搜索到。

3.4、translog

translog会每隔5秒或者在一个变更请求完成之后,将translog从缓存刷入磁盘。【也就是translog也有缓存】translog是存储在os cache【然后定期fsync】中,每个分片有一个,如果节点宕机会有5秒数据丢失,但是性能比较好,最多丢5秒的数据。可以将translog设置成每次写操作必须是直接fsync到磁盘,但是性能会差很多。【translog的fsync频率决定了数据的安全性,recovery就是在commit point没有,translog有的数据】可以通过配置增加transLog刷磁盘的频率来增加数据可靠性,最小可配置100ms,但不建议这么做,因为这会对性能有非常大的影响。

3.5、fsync

将os cache中的数据刷到磁盘,他是一个linux的系统命令。

flush参数设置:

index.translog.flush_threshold_period

index.translog.flush_threshold_size

控制每收到多少条数据后flush一次

index.translog.flush_threshold_ops

3.6、flush

每30分钟或者当tanslog的大小达到512M时候,就会执行commit操作(flush操作),将os cache中所有的数据全以segment file的形式,持久到磁盘上去。第一步,就是将buffer中现有数据refresh到os cache中去。清空buffer 然后强行将os cache中所有的数据全都一个一个的通过segmentfile的形式,持久到磁盘上去。将commit point这个文件更新到磁盘中,每个Shard都有一个提交点(commit point) 其中保存了当前Shard成功写入磁盘的所有segment。把translog文件删掉清空,再开一个空的translog文件。

3.7、Segment merge

  • Segment的merge操作:

每次refresh的时候都会生成一个新的segment,太多的Segment会占用过多的资源,而且每个搜索请求都会遍历所有的Segment,Segment过多会导致搜索变慢,所以ES会定期合并Segment,减少Segment的个数,并将Segment和并为一个大的Segment;每次在启动segment合并工作时,那些被标记为删除的文档才会被真正删除。在操作Segment时,会维护一个Commit Point文件,其中记录了所有成功写入磁盘的Segment的信息;同时维护.del文件用于记录所有删除的Segment信息。单个倒排索引文件被称为Segment。多个Segment汇总在一起,就是Lucene的索引,对应的就是ES中的shard。

  • 我们可以手动进行merge:POST index/_forcemerge。一般不需要,这是一个比较消耗资源的操作。
  • segment总和=os cache disk。
  • 每个shard有一个commit point,记录成功写入磁盘的所有segment。
四、Lucene结构

elasticsearch底层数据存储结构(一篇文章让你了解Elasticsearch存储原理)(8)

4.1、正排数据

elasticsearch底层数据存储结构(一篇文章让你了解Elasticsearch存储原理)(9)

4.2、倒排数据

elasticsearch底层数据存储结构(一篇文章让你了解Elasticsearch存储原理)(10)

4.3、DocValues

elasticsearch底层数据存储结构(一篇文章让你了解Elasticsearch存储原理)(11)

4.4、segment文件

elasticsearch底层数据存储结构(一篇文章让你了解Elasticsearch存储原理)(12)

注意细化索引

一个ES的index包含多个shard,一个shard就是lucene的一个索引,一个lucene的索引包含多个segment,一个segment包含多个doc,一个doc包含多个filed,一个field包含term。

索引不可变

一个ES索引创建后,各个字段的数据类型都确定,那么数据插入的时候,就会按照字段类型,为每个字段构建倒排索引,所以说索引一旦创建就不能修改,这个不能改指的是字段类型,添加字段是可以的。

分片不可改

索引一旦创建,不能修改分片数,因为数据写入索引是根据分片数路由,改了分片数,就需要将之前的数据重新路由,不然找不到之前写入的数据。

索引近实时

ES是近实时,是因为数据写入索引,先写入缓存,此时不可查,1s后刷新到os cache才能查,因为有了文件句柄就可以查,不用落盘。

副本增加吞吐量

添加副本数,可以提高搜索的效率,因为查询的时候,不管是主分片,还是副本只要一个位置搜索到,就可以返回。

猜您喜欢: