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

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

es官网原文:https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-refresh

索引(Index)

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

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

我们了解索引的写操作后可知,更新、索引、删除文档都是写操作,这些操作必须在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" }'

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

curl -XPOST localhost:9200/test/test/1 -d '{ "title": "test2" }' ; curl
localhost:9200/test/test/_search?pretty

上面命令的结果类似如下:

{"ok":true,"_index":"test","_type":"test","_id":"1","_version":2}
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 1.0,
"hits" : [ {
"_index" : "test",
"_type" : "test",
"_id" : "1",
"_score" : 1.0, "_source" : { "title": "test" }
} ]
}
}

第一行是第一个命令,即索引命令的返回结果。可以看到,数据更新成功。因此,第二个命令,即查询命令查询到的文档title域值应该为test2。但是,可以看到结果并不如人所愿。这背后发生了什么呢?

在揭开前一个问题的答案之前,我们先退一步,来了解底层的Apache Lucene工具包是如何让新添加的文档对搜索可见的。

更新索引并且将改动提交

从 第1章 介绍ElasticSearch 的 介绍Apache Lucene一节中,我们已经了解到,在索引过程中,新添加的文档都是写入到段(segments)中。每个段都是有着独立的索引结构,这意味着查询与索引两个过程是可以并行存在的,索引过程中,系统会不定期创建新的段。Apache Lucene通过在索引目录中创建新的segments_N文件来标识新的段。段创建的过程就称为索引的提交。Lucene可以一种安全的方式实现索引的提交——我们可以确定段文件要么全部创建成功,要么失败。如果错误发生,我们可以确保索引状态的一致性。

回到我们的例子中,第一条命令添加文档到索引中,但是没有提交。这就是它的工作方式。然而,索引数据的提交也不能保证数据是搜索可见的。Lucene工具包使用一个名为Searcher的抽象类来读取索引。索引提交操作完成后,Searcher对象需要重新打开才能加载到新创建的索引段。这整个过程称为更新。出于性能的考虑,ElasticSearch会将推迟开销巨大的更新操作,默认情况下,单个文档的添加并不会触发搜索器的更新,Searcher对象会每秒更新一次。这个频率已经比较高了,但是在一些应用程序中,需要更频繁的更新。对面这个需求,我们可以考虑使用其它的解决方案或者再次核实我们是否真的需要这样做。如果确实需要,那么可以使用ElasticSearch API强制更新。比如,上面的例子中,我们可以执行如下的命令强制更新:

curl –XGET localhost:9200/test/_refresh

如果在搜索前执行了上面的命令,那么ElasticSearch就可以搜索到修改后的文档。

修改Searcher对象默认的更新时间

Searcher对象的默认更新时间可以通过使用index.refresh_interval参数来修改,该参数无论是添加到ElasticSearch的配置文件中或者使用update settings API都可以生效。例如:

curl -XPUT localhost:9200/test/_settings -d '{
"index" : {
"refresh_interval" : "5m"
}
}'

上面的命令将使Searcher每5秒钟自动更新一次。请记住在更新两个时间点之间添加到索引的数据对查询是不可见的。

总结

  本篇从索引的创建操作和原理等方面介绍了ES索引的一些内容,很多都来自各位大神的总结。经过使用ES越来越多开始作为数据库的辅助。希望大家多多指点。

Elasticsearch-深入理解索引原理的更多相关文章

  1. MySQL 深入浅出数据库索引原理(转)

    本文转自:https://www.cnblogs.com/aspwebchh/p/6652855.html 前段时间,公司一个新上线的网站出现页面响应速度缓慢的问题, 一位负责这个项目的但并不是搞技术 ...

  2. 【Important】数据库索引原理

    为什么要给表加上主键? 为什么加索引后会使查询变快? 为什么加索引后会使写入.修改.删除变慢? 什么情况下要同时在两个字段上建索引? 想理解索引原理必须清楚一种数据结构(平衡树非二叉)也就是b tre ...

  3. 索引原理 B tree

    数据库原理之-索引 背景介绍: 用数据库的时候经常有几个疑问: 1:为啥通过加索引就能提升数据的查询料率? 2:为啥加多了索引会导致增删改的效率变低? 3:为啥有的人能用好有的人用不好? 这些问题我们 ...

  4. Elasticsearch索引原理

    转载 http://blog.csdn.net/endlu/article/details/51720299 最近在参与一个基于Elasticsearch作为底层数据框架提供大数据量(亿级)的实时统计 ...

  5. Elasticsearch-基础介绍及索引原理分析(转载)

    最近在参与一个基于Elasticsearch作为底层数据框架提供大数据量(亿级)的实时统计查询的方案设计工作,花了些时间学习Elasticsearch的基础理论知识,整理了一下,希望能对Elastic ...

  6. Elasticsearch-基础介绍及索引原理分析

    介绍 Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎.当然 Elasticsearch 并不仅仅是 L ...

  7. Elasticsearch核心技术(四):索引原理分析

    本文探讨Elasticsearch的数据请求.路由和写入过程的原理,主要涉及ES的分布式存储架构.节点和副本的写入过程.近实时搜索的原因.持久化机制等. 4.1 ES存储架构 我们经常说,看一件事情千 ...

  8. Mysql高手系列 - 第22篇:深入理解mysql索引原理,连载中

    Mysql系列的目标是:通过这个系列从入门到全面掌握一个高级开发所需要的全部技能. 欢迎大家加我微信itsoku一起交流java.算法.数据库相关技术. 这是Mysql系列第22篇. 背景 使用mys ...

  9. Elasticsearch 技术分析(九):Elasticsearch的使用和原理总结

    前言 之前已经分享过Elasticsearch的使用和原理的知识,由于近期在公司内部做了一次内部分享,所以本篇主要是基于之前的博文的一个总结,希望通过这篇文章能让读者大致了解Elasticsearch ...

随机推荐

  1. oracle01

    01-基本的查询语句 A:一些命令 sqlplus sys/sys@192.168.22.220:1521/orcl as sysdba; conn scott/123456@192.168.22.2 ...

  2. vue初学者

    先分享一波福利 vue学习视频    链接:https://pan.baidu.com/s/1ggSfE75 密码:7h2a        1.先安装 webpack vue-cli          ...

  3. Redis跟Spring整合,sentinel模式

    普通模式整合 一.在pom.xml中引入redis的依赖 <dependency> <groupId>org.springframework.data</groupId& ...

  4. express官网学习笔记

    npm init 创建一个package.json npm install express --save-dev 安装到项目依赖 便于多人开发 路由结构定义 app.METHOD(PATH, HAND ...

  5. junit忽略测试方法

    JUnit 提供注解 org.junit.Ignore 用于暂时忽略某个测试方法或者说整个类.因为有时候由于测试环境受限,并不能保证每一个测试方法都能正确运行. 1,方法级别上使用@ignore来注释 ...

  6. JAVA中文件与Byte数组相互转换的方法

    JAVA中文件与Byte数组相互转换的方法,如下: public class FileUtil { //将文件转换成Byte数组 public static byte[] getBytesByFile ...

  7. spring 声明式事务管理详解

    前言:spring框架对于事务管理提供了两种方案.一,编程式事务.二,声明式事务.本例主要剖析 声明式事务. 何为声明式事务: 通过spring的配置文件配置事务规则,或使用spring @Trans ...

  8. redis数据类型-列表类型

    列表类型 列表类型(list)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的某一个片段. 列表类型内部是使用双向链表(double linked list)实现的,所以向 ...

  9. 【转】国外几款GPS卫星历预报软件

    以下是几款著名GPS厂商的星历软件: Trimble星历软件: http://www.trimble.com/planningsoftware.shtml Topcon星历软件: http://www ...

  10. lua版本的一个状态机

    这个状态机http://www.cnblogs.com/flytrace/p/5587033.html的lua版本 -- LUA 有实现枚举值的好办法么 local sc_enum = { -- ev ...