1、es写入数据的基本原理及过程
假设es中的一个index,有3个shard,部署在3台机器上,如图1所示。
图1 es部署图
当客户端写入一条数据的时候,可以挑选任意一个节点去写,假设客户端把数据分发到了机器02上去了。此时es进程02就被称为协调节点(coordinating node)。
图2 es协调节点
协调节点此时就会对这这条数据做hash,假设这条数据hash完之后属于shard01,因为shard01 primary在es进程01上,es进程02就会把把请求转发给es进程01,shard01 primay就会来处理这条数据。
shard01 primay接受到写请求后,就会执行写数据功能,写完之后就会把所有的数据同步给replica node。
图3 primary shard同步数据到replica节点
协调节点如果发现primary node和所有replica node都搞定之后,就返回响应结果给客户端。
以上就是es写入数据的基本原理,不过如果只理解到这个程度,就太浅了。
比如primary shard具体是如何写数据的?
2、es写入数据的过程
primary shard接收到写入数据请求时,会先把数据写入内存buffer中,如果只写内存buffer,机器宕机了,数据就会丢失,所以同时还会把数据写入translog文件中去。
需要注意的是,此时客户端来搜索,是搜索不到这条数据的。
如果内存buffer满了,或者es会定时把内存buffer中的数据刷入到磁盘文件中去,默认间隔时间是1秒,所以每秒钟会产生一个segment file文件。当然,如果内存buffer中没有数据,就不会产生一个空的segment file文件了。
现在操作系统写文件不是直接进入磁盘文件的,是先写入os cache中。所以es执行refresh操作时,是先把内存buffer中的数据刷到os cache,进入操作系统级别的一个内存缓冲中去。
只要内存buffer中的数据被刷入os cache中,客户端执行搜索就可以搜索到了。所以为什么称es是近实时(NRT)的,就是这个原因,你向es中写入一条数据,一般1秒之后才会刷入os cache,才可以搜索到。
你也可以通过调用es的restful api或者Java api,手动执行一次refresh操作,手动将内存buffer中的数据刷入os cache中,让数据立马可以被搜索到。
数据刷入os cache中之后,内存buffer中的数据就会被清空,不会占据很大内存空间,但是translog日志文件是怎么处理的呢?
当translog日志文件大到一定程度的时候,就会执行commit操作。
commit操作的第一步,就是把buffer中的数据refresh到os cache中去,清空buffer。然后将一个commit point写入磁盘文件,里面标识着这个commit point 对应的所有segment file。同时强行将os cache中目前所有的数据都fsync到磁盘文件中去。最后清空现有translog日志文件,重启一个 translog,此时 commit 操作完成。
那么translog日志文件的作用是什么呢?
在执行commit操作之前,数据要么是停留在buffer中,要么是在os cache 中,无论是buffer还是os cache都是内存,一旦这台机器宕机了,内存中的数据就全丢了。所以需要将数据对应的操作写入一个专门的日志文件translog中,一旦此时机器宕机,再次重启的时候,es 会自动读取translog 日志文件中的数据,恢复到内存 buffer 和 os cache 中去。
translog其实也是先写入os cache的,默认每隔5秒刷一次到磁盘中去,所以默认情况下,可能有5秒的数据会仅仅停留在buffer或者translog文件的os cache 中,如果此时机器挂了,会丢失5秒钟的数据。但是这样性能比较好,最多丢5秒的数据。也可以将translog设置成每次写操作必须是直接fsync到磁盘,但是性能会差很多。
这个commit操作叫做flush。默认30分钟自动执行一次flush,但如果 translog过大,也会触发flush。flush操作就对应着commit的全过程,我们可以通过es api,手动执行flush操作,手动将os cache中的数据fsync强刷到磁盘上去。
图4 primary shard写入数据详细流程
总结一下,数据写入过程是先写入内存buffer,然后每隔1s,将数据refresh到os cache中,到了os cache数据就能被搜索到(所以我们才说 es 从写入到能被搜索到,中间有 1s 的延迟)。每隔5s,将数据写入translog文件(这样如果机器宕机,内存数据全没,最多会有 5s 的数据丢失),translog大到一定程度,或者默认每隔30分钟,会触发commit操作,将缓冲区的数据都 flush到segment file磁盘文件中。
另外需要说明下的是,数据写入 segment file 之后,同时就建立好了倒排索引。
3、es读取
数据的过程
客户端如果是通过doc id来进行读取的,es就会根据doc id进行 hash,判断出来当时把 doc id 分配到了哪个 shard 上面去,从那个 shard 去查询。具体步骤如下:
-
客户端发送请求到任意一个node,成为协调节点(coordinate node)。
-
协调节点对doc id进行哈希路由,将请求转发到对应的node,此时会使用round-robin随机轮询算法,在primary shard以及其所有replica中随机选择一个,让读请求负载均衡。
-
接收请求的node返回document给协调节点。
-
协调节点返回document给客户端。
4、es搜索数据过程
es最强大的功能就是搜索,搜索是建立在倒排索引的基础上的,如果有伙伴们不知道什么是倒排索引,请自行百度。
比如你有这几条数据:
elasticsearch真强大
elasticsearch好难学啊
es特别赞
如果你搜索包含elasticsearch的document,es 就会给你返回:elasticsearch真强大 elasticsearch好难学啊,这两条数据。其具体搜索步骤是这样的:
-
客户端发送请求到一个协调节点(coordinate node)。
-
协调节点将搜索请求转发到所有的shard对应的primary shard 或replica shard。
-
查询阶段(query phase):每个 shard 将自己的搜索结果(其实就是一些 doc id)返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,生成最终结果。
-
返回阶段(fetch phase):接着由协调节点根据doc id去各个节点上拉取实际的document 数据,最终返回给客户端。
需要注意的是:
写请求是写入primary shard,然后同步给所有的replica shard;读请求可以从primary shard或replica shard读取,采用的是随机轮询算法。
5、es删除/更新数据底层原理
如果是删除操作,commit 的时候会生成一个 .del 文件,里面将某个 doc 标识为 deleted 状态,那么搜索的时候根据 .del 文件就知道这个doc是否被删除了。
如果是更新操作,就是将原来的doc 标识为deleted状态,然后新写入一条数据。
内存buffer每refresh一次,就会产生一个segment file,所以默认情况下1秒产生一个segment file,这样下来segment file 会越来越多,此时es会定期执行merge操作。
每次merge的时候,会将多个segment file合并成一个,同时这里会将标识为deleted的doc给物理删除掉,然后将新的segment file写入磁盘,这里会写一个commit point,标识所有新的segment file,然后打开segment file供搜索使用,同时删除旧的segment file。