ES本身是schema less的,有比较特殊的字段需要通过Mapping设置一下,每个数据点就是一行数据Document,ES数据分类通过Index这层完成的
Elassticsearch的基础概念-数据模型,如上图把ES数据模型概念和传统数据库做了对比。
首先分为两层,一个是ES,下面是Lucene,一个ES集群有多个node组成的。一个ES实例会承载一些Shard,多个shard会落到不同机器上面,P1和P2是两个不同的Shard,并且shard有主从的概念,对于ES每个Shard落地下来是一个Lucene Index。
单个实例的ES主机称为节点,它是集群的一个成员,可以存储数据、参与集群索引及搜索操作。类似于集群,节点靠其名称进行标识,默认为启动时自动生成的随机Marvel字符名称。用户可以按需要自定义任何希望使用的名称,但出于管理的目的,此名称应该尽可能有较好的识别性。节点通过为其配置的ES集群名称确定其所要加入的集群。
ES的“分片(shard)”机制可将一个索引内部的数据分布地存储于多个节点,它通过将一个索引切分为多个底层物理的Lucene索引完成索引数据的分割存储功能,这每一个物理的Lucene索引称为一个分片(shard)。每个分片其内部都是一个全功能且独立的索引,因此可由集群中的任何主机存储。创建索引时,用户可指定其分片的数量,默认数量为5个。
Shard有两种类型:primary和replica,即主shard及副本shard。Primary shard用于文档存储,每个新的索引会自动创建5个Primary(最新版改成1个了) shard,当然此数量可在索引创建之前通过配置自行定义,不过,一旦创建完成,其Primary shard的数量将不可更改。Replica shard是Primary Shard的副本,用于冗余数据及提高搜索性能。每个Primary shard默认配置了一个Replica shard,但也可以配置多个,且其数量可动态更改。ES会根据需要自动增加或减少这些Replica shard的数量。
ES集群可由多个节点组成,各Shard分布式地存储于这些节点上。
ES可自动在节点间按需要移动shard,例如增加节点或节点故障时。简而言之,分片实现了集群的分布式存储,而副本实现了其分布式处理及冗余功能。
什么是 Elasticsearch ? Elasticsearch 是一个基于 Apache Lucene(TM) 的开源搜索引擎。
那 Lucene 是什么? 无论在开源还是专有领域,Lucene 可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库,并通过简单的 RESTful API 来隐藏 Lucene 的复杂性,从而让全文搜索变得简单。
Elasticsearch 不仅仅是 Lucene 和全文搜索,我们还能这样去描述它:
就像很多业务系统是基于 Spring 实现一样,Elasticsearch 和 Lucene 的关系很简单:Elasticsearch 是基于 Lucene 实现的。ES 基于底层这些包,然后进行了扩展,提供了更多的更丰富的查询语句,并且通过 RESTful API 可以更方便地与底层交互。类似 ES 还有 Solr 也是基于 Lucene 实现的。
在应用开发中,用 Elasticsearch 会很简单。但是如果你直接用 Lucene,会有大量的集成工作。
因此,入门 ES 的同学,稍微了解下 Lucene 即可。如果往高级走,还是需要学习 Lucene 底层的原理。因为倒排索引、打分机制、全文检索原理、分词原理等等,这些都是不会过时的技术。
lucene 中,单个倒排索引文件称为 segment。其中有一个文件,记录了所有 segments 的信息,称为 commit point:
新文档创建或者更新时,进行如下流程:
更新不会修改原来的 segment,更新和创建操作都会生成新的一个 segment。数据哪里来呢?先会存在内存的 bugger 中,然后持久化到 segment 。
数据持久化步骤如下:write -> refresh -> flush -> merge
一个新文档过来,会存储在 in-memory buffer 内存缓存区中,顺便会记录 Translog。
这时候数据还没到 segment ,是搜不到这个新文档的。数据只有被 refresh 后,才可以被搜索到。那么 讲下 refresh 过程
refresh 默认 1 秒钟,执行一次上图流程。ES 是支持修改这个值的,通过 index.refresh_interval 设置 refresh (冲刷)间隔时间。refresh 流程大致如下:
文档经过 refresh 后, segment 暂时写到文件系统缓存,这样避免了性能 IO 操作,又可以使文档搜索到。refresh 默认 1 秒执行一次,性能损耗太大。一般建议稍微延长这个 refresh 时间间隔,比如 5 s。因此,ES 其实就是准实时,达不到真正的实时。
上个过程中 segment 在文件系统缓存中,会有意外故障文档丢失。那么,为了保证文档不会丢失,需要将文档写入磁盘。那么文档从文件缓存写入磁盘的过程就是 flush。写入次怕后,清空 translog。
translog 作用很大:
具体可以看官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.3/indices-flush.html
上面几个步骤,可见 segment 会越来越多,那么搜索会越来越慢?怎么处理呢?
通过 merge 过程解决:
Lucence基于节点宕机的考虑,每次写入都会落盘Translog,类似db binlog,不同的是db binlog 通过expire_logs_days=7 设定过期时间以天为单位 默认7天
而translog 是每次flush后会清除 可以通过几个维度的设定清除策略
translog日志提供了一个所有还未被flush到磁盘的操作的持久化记录。当ES启动的时候,它会使用最新的commit point从磁盘恢复所有已有的segments,然后将重现所有在translog里面的操作来添加更新,这些更新发生在最新的一次commit的记录之后还未被fsync。
translog日志也可以用来提供实时的CRUD。当你试图通过文档ID来读取、更新、删除一个文档时,它会首先检查translog日志看看有没有最新的更新,然后再从响应的segment中获得文档。这意味着它每次都会对最新版本的文档做操作,并且是实时的。
理论上设定好这个过期策略,在flush之前把translog拿到后做双机房同步或者进一步的消息通知处理,还可以有很大作为,可行性还有待尝试。
主分片+至少一个副分片全部写成功后才返回成功
具体流程:
路由公式 shard = hash(routing) % number_of_primary_shards
Node3在p0上执行了写入请求后,如果成功则将请求并行的路由至Node1 Node2它的副本分片R0上,且都成功后Node1再报告至client
wait_for_active_shards 来配置副本分配同步策略 - 设置为1 表示仅写完主分片就返回 - 设置为all 表示等所有副本分片都写完才能返回 - 设置为1-number_of_replicas+1之间的数值,比如2个副本分片,有1个写成功即可返回 timeout 控制集群异常副本同步分片不可用时候的等待时间
一个文档可以在任意主副分片上读取
读取流程:
更新操作其实就是先读然后写
更新流程:
由于segments是不变的,所以文档不能从旧的segments中删除,也不能在旧的segments中更新来映射一个新的文档版本。取之的是,每一个提交点都会包含一个.del文件,列举了哪一个segment的哪一个文档已经被删除了。 当一个文档被”删除”了,它仅仅是在.del文件里被标记了一下。被”删除”的文档依旧可以被索引到,但是它将会在最终结果返回时被移除掉。
文档的更新同理:当文档更新时,旧版本的文档将会被标记为删除,新版本的文档在新的segment中建立索引。也许新旧版本的文档都会本检索到,但是旧版本的文档会在最终结果返回时被移除。
如这个图,ES 写入原理不难,记住关键点即可。
write -> refresh -> flush
写入的原理告诉我们,考虑的点很多:性能、数据不丢失等等