目录
回忆时光
许多年前,一个刚结婚的名叫 Shay Banon 的失业开发者,跟着他的妻子去了伦敦,他的妻子在那里学习厨师。 在寻找一个赚钱的工作的时候,为了给他的妻子做一个食谱搜索引擎,他开始使用 Lucene 的一个早期版本。
直接使用 Lucene 是很难的,因此 Shay 开始做一个抽象层,Java 开发者使用它可以很简单的给他们的程序添加搜索功能。 他发布了他的第一个开源项目 Compass。
后来 Shay 获得了一份工作,主要是高性能,分布式环境下的内存数据网格。这个对于高性能,实时,分布式搜索引擎的需求尤为突出, 他决定重写 Compass,把它变为一个独立的服务并取名 Elasticsearch。
第一个公开版本在2010年2月发布,从此以后,Elasticsearch 已经成为了 Github 上最活跃的项目之一,他拥有超过300名 contributors(目前736名 contributors )。 一家公司已经开始围绕 Elasticsearch 提供商业服务,并开发新的特性,但是,Elasticsearch 将永远开源并对所有人可用。
据说,Shay 的妻子还在等着她的食谱搜索引擎…
倒排索引原理:
将原始的文档进行编号,创建文档索引,形成文档列表,对文档进行分词操作,以词条为key,文档为value,建立词条到文档的映射。
当我们搜索词条时,可以得到对应的所有的文档列表,这个就是倒排索引。
路由一个文档到分片中
当索引一个文档的时候,文档会被存储到一个主分片中。 Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?
首先这肯定不会是随机的,否则将来要获取文档的时候我们就不知道从何处寻找了。实际上,这个过程是根据下面这个公式决定的:
shard = hash(routing) % number_of_primary_shards
routing
通过 hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards
(主分片的数量)后得到 余数 。
这也是为什么主分片的数量确定之后,不允许被更改。
主分片和副本分片如何交互
为了说明目的, 我们 假设有一个集群由三个节点组成。 它包含一个叫 blogs
的索引,有两个主分片,每个主分片有两个副本分片。相同分片的副本不会放在同一节点,所以我们的集群看起来像 下图所示。
我们可以发送请求到集群中的任一节点。 每个节点都有能力处理任意请求。 每个节点都知道集群中任一文档位置,所以可以直接将请求转发到需要的节点上。 在下面的例子中,将所有的请求发送到 Node 1
,我们将其称为 协调节点(coordinating node) 。
取回一个文档
可以从主分片或者从其它任意副本分片检索文档 ,如下图所示 Figure 10, “取回单个文档”.
Figure 10. 取回单个文档
以下是从主分片或者副本分片检索文档的步骤顺序:
1、客户端向 Node 1
发送获取请求。
2、节点使用文档的 _id
来确定文档属于分片 0
。分片 0
的副本分片存在于所有的三个节点上。 在这种情况下,它将请求转发到 Node 2
。
3、Node 2
将文档返回给 Node 1
,然后将文档返回给客户端。
在处理读取请求时,协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。
在文档被检索时,已经被索引的文档可能已经存在于主分片上但是还没有复制到副本分片。 在这种情况下,副本分片可能会报告文档不存在,但是主分片可能成功返回文档。 一旦索引请求成功返回给用户,文档在主分片和副本分片都是可用的。
写入一个文档,以及实时搜索的原理
Segment(段):Lucene里面的一个数据集概念
提交点文件:有一个列表存放着所有已知的所有段
ES底层是基于Lucene,最核心的概念就是Segment(段),每个段本身就是一个倒排索引。
ES中的Index由多个段的集合和commit point(提交点)文件组成。
提交点文件中有一个列表存放着所有已知的段,下面是一个带有1个提交点和3个段的Index示意图:
Doc新增提交主要过程如下:
一、写入磁盘后可见:
1、Doc写入Buffer
一个文档被索引之后,就会被添加到内存缓冲区,并且 追加到了 translog ,此时文档还不能被查询。
2、Doc Commit
每隔一段时间,会将buffer提交,在flush磁盘后打开新段使得搜索可见,详细过程如下:
- 这些在内存缓冲区的文档被写入到一个新的段中,且没有进行
fsync
操作。 - 这个段被打开,使其可被搜索。
- 内存缓冲区被清空。
下面展示了这个过程完成后的段和提交点的状态:
总结:
1.数据首先会被写入到内存之中,同时写入到translog文件。
2.每隔一秒执行refresh操作生成一个segment写入到炒作的文件缓存中,同时将内存清空,translog文件保留,此时segment文件是可访问状态。这是个ES为什么快的原理。
3.每隔一段时间(30分钟)或者translog文件太大时,会将segment文件和translog持久化,并生成一个新的translog。
局部更新文档
协调节点转发请求到主分片所在的节点上,主分片更新完成之后,转发文本到副分片上。
以下是部分更新一个文档的步骤:
- 客户端向
Node 1
发送更新请求。 - 它将请求转发到主分片所在的
Node 3
。 Node 3
从主分片检索文档,修改_source
字段中的 JSON ,并且尝试重新索引主分片的文档。 如果文档已经被另一个进程修改,它会重试步骤 3 ,超过retry_on_conflict
次后放弃。- 如果
Node 3
成功地更新文档,它将新版本的文档并行转发到Node 1
和Node 2
上的副本分片,重新建立索引。 一旦所有副本分片都返回成功,Node 3
向协调节点也返回成功,协调节点向客户端返回成功。
update
API 还接受在 新建、索引和删除文档 章节中介绍的 routing
、 replication
、 consistency
和 timeout
参数。
基于文档的复制
当主分片把更改转发到副本分片时, 它不会转发更新请求。 相反,它转发完整文档的新版本。请记住,这些更改将会异步转发到副本分片,并且不能保证它们以发送它们相同的顺序到达。 如果Elasticsearch仅转发更改请求,则可能以错误的顺序应用更改,导致得到损坏的文档。
乐观并发控制(使用的version版本号)
Elasticsearch 是分布式的。当文档创建、更新或删除时, 新版本的文档必须复制到集群中的其他节点。Elasticsearch 也是异步和并发的,这意味着这些复制请求被并行发送,并且到达目的地时也许 顺序是乱的 。 Elasticsearch 需要一种方法确保文档的旧版本不会覆盖新的版本。
当我们之前讨论 index
, GET
和 delete
请求时,我们指出每个文档都有一个 _version
(版本)号,当文档被修改时版本号递增。 Elasticsearch 使用这个 _version
号来确保变更以正确顺序得到执行。如果旧版本的文档在新版本之后到达,它可以被简单的忽略。