Elasticsearch-深入理解索引原理

2020-05-29 00:00:00 索引 文档 操作 更新 磁盘

近开始大面积使用ES,很多地方都是知其然不知其所以然,特地翻看了很多资料和大牛的文档,简单汇总一篇。内容多为摘抄,说是深入其实也是一点浅尝辄止的理解。希望大家领会精神。

首先学习要从官方开始地址如下。

es官网原文:elastic.co/guide/en/ela


索引(Index)

ES将数据存储于一个或多个索引中,索引是具有类似特性的文档的集合。类比传统的关系型数据库领域来说,索引相当于SQL中的一个数据库,或者一个数据存储方案(schema)。索引由其名称(必须为全小写字符)进行标识,并通过引用此名称完成文档的创建、搜索、更新及删除操作。一个ES集群中可以按需创建任意数目的索引。

如果不懂这块可以看我的写的上一篇入门的内容 cnblogs.com/wenBlog/p/8

我们了解索引的写操作后可知,更新、索引、删除文档都是写操作,这些操作必须在primary shard完全成功后才能拷贝至其对应的replicas上,默认情况下主分片等待所有备份完成索引后才返回客户端。


步骤:


  1. 客户端向Node1 发送索引文档请求
  2. Node1 根据文档ID(_id字段)计算出该文档应该属于shard0,然后请求路由到Node3的P0分片上
  3. Node3在P0上执行了请求。如果请求成功,则将请求并行的路由至Node1,Node2的R0上。当所有的Replicas报告成功后,Node3向请求的Node(Node1)发送成功报告,Node1再报告至Client。

当客户端收到执行成功后,操作已经在Primary shard和所有的replica shards上执行成功了


读操作

一个文档可以在primary shard和所有的replica shard上读取。见Figure10


读操作步骤:

1.客户端发送Get请求到NODE1。
2.NODE1使用文档的_id决定文档属于shard 0.shard 0的所有拷贝存在于所有3个节点上。这次,它将请求路由至NODE2。
3.NODE2将文档返回给NODE1,NODE1将文档返回给客户端。 对于读请求,请求节点(NODE1)将在每次请求到来时都选择一个不同的replica。
shard来达到负载均衡。使用轮询策略轮询所有的replica shards。


更新操作

更新操作,结合了以上的两个操作:读、写。见Figure11


步骤:

1.客户端发送更新操作请求至NODE1
2.NODE1将请求路由至NODE3,Primary shard所在的位置
3.NODE3从P0读取文档,改变source字段的JSON内容,然后试图重新对修改后的数据在P0做索引。如果此时这个文档已经被其他的进程修改了,那么它将重新执行3步骤,这个过程如果超过了retryon_conflict设置的次数,就放弃。
4.如果NODE3成功更新了文档,它将并行的将新版本的文档同步到NODE1和NODE2的replica shards重新建立索引。一旦所有的replica
shards报告成功,NODE3向被请求的节点(NODE1)返回成功,然后NODE1向客户端返回成功。


2.6 SHARD

本节将解决以下问题:

  • 为什么搜索是实时的
  • 为什么文档的CRUD操作是实时的
  • ES怎么保障你的更新在宕机的时候不会丢失
  • 为什么删除文档不会立即释放空间
    2.6.1不变性

写到磁盘的倒序索引是不变的:自从写到磁盘就再也不变。 这会有很多好处:

不需要添加锁。如果你从来不用更新索引,那么你就不用担心多个进程在同一时间改变索引。
一旦索引被内核的文件系统做了Cache,它就会待在那因为它不会改变。只要内核有足够的缓冲空间,绝大多数的读操作会直接从内存而不需要经过磁盘。这大大提升了性能。
其他的缓存(例如fiter cache)在索引的生命周期内保持有效,它们不需要每次数据修改时重构,因为数据不变。
写一个单一的大的倒序索引可以让数据压缩,减少了磁盘I/O的消耗以及缓存索引所需的RAM。


当然,索引的不变性也有缺点。如果你想让新修改过的文档可以被搜索到,你必须重新构建整个索引。这在一个index可以容纳的数据量和一个索引可以更新的频率上都是一个限制。

2.6.2动态更新索引

如何在不丢失不变形的好处下让倒序索引可以更改?答案是:使用不只一个的索引。 新添额外的索引来反映新的更改来替代重写所有倒序索引的方案。
Lucene引进了per-segment搜索的概念。一个segment是一个完整的倒序索引的子集,所以现在index在Lucene中的含义就是一个segments的集合,每个segment都包含一些提交点(commit

point)。见Figure16。新的文档建立时首先在内存建立索引buffer,见Figure17。然后再被写入到磁盘的segment,见Figure18。


一个per-segment的工作流程如下:

1.新的文档在内存中组织,见Figure17。
2.每隔一段时间,buffer将会被提交:
一个新的segment(一个额外的新的倒序索引)将被写到磁盘 一个新的提交点(commit
point)被写入磁盘,将包含新的segment的名称。 磁盘fsync,所有在内核文件系统中的数据等待被写入到磁盘,来保障它们被物理写入。
3.新的segment被打开,使它包含的文档可以被索引。
4.内存中的buffer将被清理,准备接收新的文档。


当一个新的请求来时,会遍历所有的segments。词条分析程序会聚合所有的segments来保障每个文档和词条相关性的准确。通过这种方式,新的文档轻量的可以被添加到对应的索引中。

删除和更新

segments是不变的,所以文档不能从旧的segments中删除,也不能在旧的segments中更新来映射一个新的文档版本。取之的是,每一个提交点都会包含一个.del文件,列举了哪一个segmen的哪一个文档已经被删除了。
当一个文档被”删除”了,它仅仅是在.del文件里被标记了一下。被”删除”的文档依旧可以被索引到,但是它将会在终结果返回时被移除掉。

文档的更新同理:当文档更新时,旧版本的文档将会被标记为删除,新版本的文档在新的segment中建立索引。也许新旧版本的文档都会本检索到,但是旧版本的文档会在终结果返回时被移除。

2.6.3实时索引

在上述的per-segment搜索的机制下,新的文档会在分钟级内被索引,但是还不够快。
瓶颈在磁盘。将新的segment提交到磁盘需要fsync来保障物理写入。但是fsync是很耗时的。它不能在每次文档更新时就被调用,否则性能会很低。
现在需要一种轻便的方式能使新的文档可以被索引,这就意味着不能使用fsync来保障。
在ES和物理磁盘之间是内核的文件系统缓存。之前的描述中,Figure19,Figure20,在内存中索引的文档会被写入到一个新的segment。但是现在我们将segment首先写入到内核的文件系统缓存,这个过程很轻量,然后再flush到磁盘,这个过程很耗时。但是一旦一个segment文件在内核的缓存中,它可以被打开被读取。


2.6.4更新持久化

不使用fsync将数据flush到磁盘,我们不能保障在断电后或者进程死掉后数据不丢失。ES是可靠的,它可以保障数据被持久化到磁盘。
在2.6.2中,一个完全的提交会将segments写入到磁盘,并且写一个提交点,列出所有已知的segments。当ES启动或者重新打开一个index时,它会利用这个提交点来决定哪些segments属于当前的shard。
如果在提交点时,文档被修改会怎么样?不希望丢失这些修改:

1.当一个文档被索引时,它会被添加到in-memory buffer,并且添加到Translog日志中,见Figure21.


2.refresh操作会让shard处于Figure22的状态:每秒中,shard都会被refreshed:



  • 在in-memory buffer中的文档会被写入到一个新的segment,但没有fsync。
  • in-memory buffer被清空

3.这个过程将会持续进行:新的文档将被添加到in-memory buffer和translog日志中,见Figure23


4.一段时间后,当translog变得非常大时,索引将会被flush,新的translog将会建立,一个完全的提交进行完毕。见Figure24



  • 在in-memory中的所有文档将被写入到新的segment
  • 内核文件系统会被fsync到磁盘。
  • 旧的translog日志被删除


translog日志提供了一个所有还未被flush到磁盘的操作的持久化记录。当ES启动的时候,它会使用新的commit
point从磁盘恢复所有已有的segments,然后将重现所有在translog里面的操作来添加更新,这些更新发生在新的一次commit的记录之后还未被fsync。

translog日志也可以用来提供实时的CRUD。当你试图通过文档ID来读取、更新、删除一个文档时,它会首先检查translog日志看看有没有新的更新,然后再从响应的segment中获得文档。这意味着它每次都会对新版本的文档做操作,并且是实时的。

2.6.5 Segment合并

通过每隔一秒的自动刷新机制会创建一个新的segment,用不了多久就会有很多的segment。segment会消耗系统的文件句柄,内存,CPU时钟。重要的是,每一次请求都会依次检查所有的segment。segment越多,检索就会越慢。

ES通过在后台merge这些segment的方式解决这个问题。小的segment merge到大的,大的merge到更大的。。。

这个过程也是那些被”删除”的文档真正被清除出文件系统的过程,因为被标记为删除的文档不会被拷贝到大的segment中。

合并过程如Figure25:


1.当在建立索引过程中,refresh进程会创建新的segments然后打开他们以供索引。
2.merge进程会选择一些小的segments然后merge到一个大的segment中。这个过程不会打断检索和创建索引。


3.Figure26,一旦merge完成,旧的segments将被删除


  • 新的segment被flush到磁盘
  • 一个新的提交点被写入,包括新的segment,排除旧的小的segments
  • 新的segment打开以供索引
  • 旧的segments被删除


merge大的segments会消耗大量的I/O和CPU,严重影响索引性能。默认,ES会节制merge过程来给留下足够多的系统资源。




近实时搜索,段数据刷新,数据可见性更新和事务日志

理想的搜索解决方案是这样的:新的数据一添加到索引中立马就能搜索到。眼看上去,这不正是ElasticSearch的工作方式吗,即使是多服务器环境也是如此。但是真实情况不是这样的(至少现在不是),后面会讲到为什么它是似是而非。首先,我们往新创建的索引中添加一个新的文档,命令如下:

curl -XPOST localhost:9200/test/test/1 -d '{ "title": "test" }'

接下来,我们在替换文档的同时查找该文档。我们用如下的链式命令来实现这一点:

相关文章