集群部署
ES 部署状况:html
5 节点(配置:8 核 64 G 1T),总计 320 G,5 T。前端
约 10+ 索引,5 分片,每日新增数据量约为 2G,4000w 条。记录保存 30 天。java
性能优化
# filesystem cache
你往 es 里写的数据,实际上都写到磁盘文件里去了,查询的时候,操做系统会将磁盘文件里的数据自动缓存到 filesystem cache
里面去。node
es 的搜索引擎严重依赖于底层的 filesystem cache
,你若是给 filesystem cache
更多的内存,尽可能让内存能够容纳全部的 idx segment file
索引数据文件,那么你搜索的时候就基本都是走内存的,性能会很是高。
mysql
性能差距究竟能够有多大?咱们以前不少的测试和压测,若是走磁盘通常确定上秒,搜索性能绝对是秒级别的,1 秒、5 秒、10 秒。但若是是走 filesystem cache
,是走纯内存的,那么通常来讲性能比走磁盘要高一个数量级,基本上就是毫秒级的,从几毫秒到几百毫秒不等。git
这里有个真实的案例。某个公司 es 节点有 3 台机器,每台机器看起来内存不少,64G,总内存就是 64 * 3 = 192G
。每台机器给 es jvm heap 是 32G
,那么剩下来留给 filesystem cache
的就是每台机器才 32G
,总共集群里给 filesystem cache
的就是 32 * 3 = 96G
内存。而此时,整个磁盘上索引数据文件,在 3 台机器上一共占用了 1T
的磁盘容量,es 数据量是 1T
,那么每台机器的数据量是 300G
。这样性能好吗? filesystem cache
的内存才 100G,十分之一的数据能够放内存,其余的都在磁盘,而后你执行搜索操做,大部分操做都是走磁盘,性能确定差。程序员
归根结底,你要让 es 性能要好,最佳的状况下,就是你的机器的内存,至少能够容纳你的总数据量的一半。github
根据咱们本身的生产环境实践经验,最佳的状况下,是仅仅在 es 中就存少许的数据,就是你要用来搜索的那些索引,若是内存留给 filesystem cache
的是 100G,那么你就将索引数据控制在 100G
之内,这样的话,你的数据几乎所有走内存来搜索,性能很是之高,通常能够在 1 秒之内。面试
好比说你如今有一行数据。 id,name,age ....
30 个字段。可是你如今搜索,只须要根据 id,name,age
三个字段来搜索。若是你傻乎乎往 es 里写入一行数据全部的字段,就会致使说 90%
的数据是不用来搜索的,结果硬是占据了 es 机器上的 filesystem cache
的空间,单条数据的数据量越大,就会致使 filesystem cahce
能缓存的数据就越少。其实,仅仅写入 es 中要用来检索的少数几个字段就能够了,好比说就写入 es id,name,age
三个字段,而后你能够把其余的字段数据存在 mysql/hbase 里,咱们通常是建议用 es + hbase
这么一个架构。算法
hbase 的特色是适用于海量数据的在线存储,就是对 hbase 能够写入海量数据,可是不要作复杂的搜索,作很简单的一些根据 id 或者范围进行查询的这么一个操做就能够了。从 es 中根据 name 和 age 去搜索,拿到的结果可能就 20 个 doc id
,而后根据 doc id
到 hbase 里去查询每一个 doc id
对应的完整的数据,给查出来,再返回给前端。
写入 es 的数据最好小于等于,或者是略微大于 es 的 filesystem cache 的内存容量。而后你从 es 检索可能就花费 20ms,而后再根据 es 返回的 id 去 hbase 里查询,查 20 条数据,可能也就耗费个 30ms,可能你原来那么玩儿,1T 数据都放 es,会每次查询都是 5~10s,如今可能性能就会很高,每次查询就是 50ms。
# 数据预热
假如说,哪怕是你就按照上述的方案去作了,es 集群中每一个机器写入的数据量仍是超过了 filesystem cache
一倍,好比说你写入一台机器 60G 数据,结果 filesystem cache
就 30G,仍是有 30G 数据留在了磁盘上。
其实能够作数据预热。
举个例子,拿微博来讲,你能够把一些大 V,平时看的人不少的数据,你本身提早后台搞个系统,每隔一下子,本身的后台系统去搜索一下热数据,刷到 filesystem cache
里去,后面用户实际上来看这个热数据的时候,他们就是直接从内存里搜索了,很快。
或者是电商,你能够将平时查看最多的一些商品,好比说 iphone 8,热数据提早后台搞个程序,每隔 1 分钟本身主动访问一次,刷到 filesystem cache
里去。
对于那些你以为比较热的、常常会有人访问的数据,最好作一个专门的缓存预热子系统,就是对热数据每隔一段时间,就提早访问一下,让数据进入 filesystem cache
里面去。这样下次别人访问的时候,性能必定会好不少。
# 冷热分离
es 能够作相似于 mysql 的水平拆分,就是说将大量的访问不多、频率很低的数据,单独写一个索引,而后将访问很频繁的热数据单独写一个索引。最好是将冷数据写入一个索引中,而后热数据写入另一个索引中,这样能够确保热数据在被预热以后,尽可能都让他们留在 filesystem os cache
里,别让冷数据给冲刷掉。
你看,假设你有 6 台机器,2 个索引,一个放冷数据,一个放热数据,每一个索引 3 个 shard。3 台机器放热数据 index,另外 3 台机器放冷数据 index。而后这样的话,你大量的时间是在访问热数据 index,热数据可能就占总数据量的 10%,此时数据量不多,几乎全都保留在 filesystem cache
里面了,就能够确保热数据的访问性能是很高的。可是对于冷数据而言,是在别的 index 里的,跟热数据 index 不在相同的机器上,你们互相之间都没什么联系了。若是有人访问冷数据,可能大量数据是在磁盘上的,此时性能差点,就 10% 的人去访问冷数据,90% 的人在访问热数据,也无所谓了。
# document 模型设计
对于 MySQL,咱们常常有一些复杂的关联查询。在 es 里该怎么玩儿,es 里面的复杂的关联查询尽可能别用,一旦用了性能通常都不太好。
最好是先在 Java 系统里就完成关联,将关联好的数据直接写入 es 中。搜索的时候,就不须要利用 es 的搜索语法来完成 join 之类的关联搜索了。
document 模型设计是很是重要的,不少操做,不要在搜索的时候才想去执行各类复杂的乱七八糟的操做。es 能支持的操做就那么多,不要考虑用 es 作一些它很差操做的事情。若是真的有那种操做,尽可能在 document 模型设计的时候,写入的时候就完成。另外对于一些太复杂的操做,好比 join/nested/parent-child 搜索都要尽可能避免,性能都不好的。
# 分页性能优化
es 的分页是较坑的,为啥呢?举个例子吧,假如你每页是 10 条数据,你如今要查询第 100 页,其实是会把每一个 shard 上存储的前 1000 条数据都查到一个协调节点上,若是你有个 5 个 shard,那么就有 5000 条数据,接着协调节点对这 5000 条数据进行一些合并、处理,再获取到最终第 100 页的 10 条数据。
分布式的,你要查第 100 页的 10 条数据,不可能说从 5 个 shard,每一个 shard 就查 2 条数据,最后到协调节点合并成 10 条数据吧?你必须得从每一个 shard 都查 1000 条数据过来,而后根据你的需求进行排序、筛选等等操做,最后再次分页,拿到里面第 100 页的数据。你翻页的时候,翻的越深,每一个 shard 返回的数据就越多,并且协调节点处理的时间越长,很是坑爹。因此用 es 作分页的时候,你会发现越翻到后面,就越是慢。
咱们以前也是遇到过这个问题,用 es 做分页,前几页就几十毫秒,翻到 10 页或者几十页的时候,基本上就要 5~10 秒才能查出来一页数据了。
有什么解决方案吗?
# 不容许深度分页(默认深度分页性能不好)
跟产品经理说,你系统不容许翻那么深的页,默认翻的越深,性能就越差。
# 相似于 app 里的推荐商品不断下拉出来一页一页的
相似于微博中,下拉刷微博,刷出来一页一页的,你能够用 scroll api
,关于如何使用,自行上网搜索。
scroll 会一次性给你生成全部数据的一个快照,而后每次滑动向后翻页就是经过游标 scroll_id
移动,获取下一页下一页这样子,性能会比上面说的那种分页性能要高不少不少,基本上都是毫秒级的。
可是,惟一的一点就是,这个适合于那种相似微博下拉翻页的,不能随意跳到任何一页的场景。也就是说,你不能先进入第 10 页,而后去第 120 页,而后又回到第 58 页,不能随意乱跳页。因此如今不少产品,都是不容许你随意翻页的,app,也有一些网站,作的就是你只能往下拉,一页一页的翻。
初始化时必须指定 scroll
参数,告诉 es 要保存这次搜索的上下文多长时间。你须要确保用户不会持续不断翻页翻几个小时,不然可能由于超时而失败。
除了用 scroll api
,你也能够用 search_after
来作, search_after
的思想是使用前一页的结果来帮助检索下一页的数据,显然,这种方式也不容许你随意翻页,你只能一页页日后翻。初始化时,须要使用一个惟一值的字段做为 sort 字段。
1.一、设计阶段调优
(1)根据业务增量需求,采起基于日期模板建立索引,经过 roll over API 滚动索引;
(2)使用别名进行索引管理;
(3)天天凌晨定时对索引作 force_merge 操做,以释放空间;
(4)采起冷热分离机制,热数据存储到 SSD,提升检索效率;冷数据按期进行 shrink 操做,以缩减存储;
(5)采起 curator 进行索引的生命周期管理;
(6)仅针对须要分词的字段,合理的设置分词器;
(7)Mapping 阶段充分结合各个字段的属性,是否须要检索、是否须要存储等。……..
1.二、写入调优
(1)写入前副本数设置为 0;
(2)写入前关闭 refresh_interval 设置为-1,禁用刷新机制;
(3)写入过程当中:采起 bulk 批量写入;
(4)写入后恢复副本数和刷新间隔;
(5)尽可能使用自动生成的 id。
1.三、查询调优
(1)禁用 wildcard;
(2)禁用批量 terms(成百上千的场景);
(3)充分利用倒排索引机制,能 keyword 类型尽可能 keyword;
(4)数据量大时候,能够先基于时间敲定索引再检索;
(5)设置合理的路由机制。
1.四、其余调优
部署调优,业务调优等。
上面的说起一部分,面试者就基本对你以前的实践或者运维经验有所评估了。
# 工做原理
# es 写数据过程
客户端选择一个 node 发送请求过去,这个 node 就是
coordinating node
(协调节点)。coordinating node
对 document 进行路由,将请求转发给对应的 node(有 primary shard)。实际的 node 上的
primary shard
处理请求,而后将数据同步到replica node
。coordinating node
若是发现primary node
和全部replica node
都搞定以后,就返回响应结果给客户端。
# es 读数据过程
能够经过 doc id
来查询,会根据 doc id
进行 hash,判断出来当时把 doc id
分配到了哪一个 shard 上面去,从那个 shard 去查询。
客户端发送请求到任意一个 node,成为
coordinate node
。coordinate node
对doc id
进行哈希路由,将请求转发到对应的 node,此时会使用round-robin
随机轮询算法,在primary shard
以及其全部 replica 中随机选择一个,让读请求负载均衡。接收请求的 node 返回 document 给
coordinate node
。coordinate node
返回 document 给客户端。
# es 搜索数据过程
es 最强大的是作全文检索,就是好比你有三条数据:
你根据 java
关键词来搜索,将包含 java
的 document
给搜索出来。es 就会给你返回:java 真好玩儿啊,java 好难学啊。
客户端发送请求到一个
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 读取,采用的是随机轮询算法。
# 写数据底层原理
先写入内存 buffer,在 buffer 里的时候数据是搜索不到的;同时将数据写入 translog 日志文件。
若是 buffer 快满了,或者到必定时间,就会将内存 buffer 数据 refresh
到一个新的 segment file
中,可是此时数据不是直接进入 segment file
磁盘文件,而是先进入 os cache
。这个过程就是 refresh
。
每隔 1 秒钟,es 将 buffer 中的数据写入一个新的 segment file
,每秒钟会产生一个新的磁盘文件 segment file
,这个 segment file
中就存储最近 1 秒内 buffer 中写入的数据。
可是若是 buffer 里面此时没有数据,那固然不会执行 refresh 操做,若是 buffer 里面有数据,默认 1 秒钟执行一次 refresh 操做,刷入一个新的 segment file 中。
操做系统里面,磁盘文件其实都有一个东西,叫作 os cache
,即操做系统缓存,就是说数据写入磁盘文件以前,会先进入 os cache
,先进入操做系统级别的一个内存缓存中去。只要 buffer
中的数据被 refresh 操做刷入 os cache
中,这个数据就能够被搜索到了。
为何叫 es 是准实时的? NRT
,全称 near real-time
。默认是每隔 1 秒 refresh 一次的,因此 es 是准实时的,由于写入的数据 1 秒以后才能被看到。能够经过 es 的 restful api
或者 java api
,手动执行一次 refresh 操做,就是手动将 buffer 中的数据刷入 os cache
中,让数据立马就能够被搜索到。只要数据被输入 os cache
中,buffer 就会被清空了,由于不须要保留 buffer 了,数据在 translog 里面已经持久化到磁盘去一份了。
重复上面的步骤,新的数据不断进入 buffer 和 translog,不断将 buffer
数据写入一个又一个新的 segment file
中去,每次 refresh
完 buffer 清空,translog 保留。随着这个过程推动,translog 会变得愈来愈大。当 translog 达到必定长度的时候,就会触发 commit
操做。
commit 操做发生第一步,就是将 buffer 中现有数据 refresh
到 os cache
中去,清空 buffer。而后,将一个 commit point
写入磁盘文件,里面标识着这个 commit point
对应的全部 segment file
,同时强行将 os cache
中目前全部的数据都 fsync
到磁盘文件中去。最后清空 现有 translog 日志文件,重启一个 translog,此时 commit 操做完成。
这个 commit 操做叫作 flush
。默认 30 分钟自动执行一次 flush
,但若是 translog 过大,也会触发 flush
。flush 操做就对应着 commit 的全过程,咱们能够经过 es api,手动执行 flush 操做,手动将 os cache 中的数据 fsync 强刷到磁盘上去。
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
到磁盘,可是性能会差不少。
实际上你在这里,若是面试官没有问你 es 丢数据的问题,你能够在这里给面试官炫一把,你说,其实 es 第一是准实时的,数据写入 1 秒后能够搜索到;可能会丢失数据的。有 5 秒的数据,停留在 buffer、translog os cache、segment file os cache 中,而不在磁盘上,此时若是宕机,会致使 5 秒的数据丢失。
总结一下,数据先写入内存 buffer,而后每隔 1s,将数据 refresh 到 os cache,到了 os cache 数据就能被搜索到(因此咱们才说 es 从写入到能被搜索到,中间有 1s 的延迟)。每隔 5s,将数据写入 translog 文件(这样若是机器宕机,内存数据全没,最多会有 5s 的数据丢失),translog 大到必定程度,或者默认每隔 30mins,会触发 commit 操做,将缓冲区的数据都 flush 到 segment file 磁盘文件中。
数据写入 segment file 以后,同时就创建好了倒排索引。
# 删除/更新数据底层原理
若是是删除操做,commit 的时候会生成一个 .del
文件,里面将某个 doc 标识为 deleted
状态,那么搜索的时候根据 .del
文件就知道这个 doc 是否被删除了。
若是是更新操做,就是将原来的 doc 标识为 deleted
状态,而后新写入一条数据。
buffer 每 refresh 一次,就会产生一个 segment file
,因此默认状况下是 1 秒钟一个 segment file
,这样下来 segment file
会愈来愈多,此时会按期执行 merge。每次 merge 的时候,会将多个 segment file
合并成一个,同时这里会将标识为 deleted
的 doc 给物理删除掉,而后将新的 segment file
写入磁盘,这里会写一个 commit point
,标识全部新的 segment file
,而后打开 segment file
供搜索使用,同时删除旧的 segment file
。
# 底层 lucene
简单来讲,lucene 就是一个 jar 包,里面包含了封装好的各类创建倒排索引的算法代码。咱们用 Java 开发的时候,引入 lucene jar,而后基于 lucene 的 api 去开发就能够了。
经过 lucene,咱们能够将已有的数据创建索引,lucene 会在本地磁盘上面,给咱们组织索引的数据结构。
# 倒排索引
在搜索引擎中,每一个文档都有一个对应的文档 ID,文档内容被表示为一系列关键词的集合。例如,文档 1 通过分词,提取了 20 个关键词,每一个关键词都会记录它在文档中出现的次数和出现位置。
那么,倒排索引就是关键词到文档 ID 的映射,每一个关键词都对应着一系列的文件,这些文件中都出现了关键词。
举个栗子。
有如下文档:
DocId | Doc |
---|---|
1 | 谷歌地图之父跳槽 Facebook |
2 | 谷歌地图之父加盟 Facebook |
3 | 谷歌地图创始人拉斯离开谷歌加盟 Facebook |
4 | 谷歌地图之父跳槽 Facebook 与 Wave 项目取消有关 |
5 | 谷歌地图之父拉斯加盟社交网站 Facebook |
对文档进行分词以后,获得如下倒排索引。
WordId | Word | DocIds |
---|---|---|
1 | 谷歌 | 1, 2, 3, 4, 5 |
2 | 地图 | 1, 2, 3, 4, 5 |
3 | 之父 | 1, 2, 4, 5 |
4 | 跳槽 | 1, 4 |
5 | 1, 2, 3, 4, 5 | |
6 | 加盟 | 2, 3, 5 |
7 | 创始人 | 3 |
8 | 拉斯 | 3, 5 |
9 | 离开 | 3 |
10 | 与 | 4 |
.. | .. | .. |
另外,实用的倒排索引还能够记录更多的信息,好比文档频率信息,表示在文档集合中有多少个文档包含某个单词。
那么,有了倒排索引,搜索引擎能够很方便地响应用户的查询。好比用户输入查询 Facebook
,搜索系统查找倒排索引,从中读出包含这个单词的文档,这些文档就是提供给用户的搜索结果。
要注意倒排索引的两个重要细节:
倒排索引中的全部词项对应一个或多个文档;
倒排索引中的词项根据字典顺序升序排列
上面只是一个简单的栗子,并无严格按照字典顺序升序排列。
# elasticsearch 的倒排索引是什么
面试官:想了解你对基础概念的认知。
解答:通俗解释一下就能够。
传统的咱们的检索是经过文章,逐个遍历找到对应关键词的位置。
而倒排索引,是经过分词策略,造成了词和文章的映射关系表,这种词典+映射表即为倒排索引。有了倒排索引,就能实现 o(1)时间复杂度的效率检索文章了,极大的提升了检索效率。
学术的解答方式:
倒排索引,相反于一篇文章包含了哪些词,它从词出发,记载了这个词在哪些文档中出现过,由两部分组成——词典和倒排表。
加分项:倒排索引的底层实现是基于:FST(Finite State Transducer)数据结构。
lucene 从 4+版本后开始大量使用的数据结构是 FST。FST 有两个优势:
(1)空间占用小。经过对词典中单词前缀和后缀的重复利用,压缩了存储空间;
(2)查询速度快。O(len(str))的查询时间复杂度。
# 三、elasticsearch 索引数据多了怎么办,如何调优,部署
面试官:想了解大数据量的运维能力。
解答:索引数据的规划,应在前期作好规划,正所谓“设计先行,编码在后”,这样才能有效的避免突如其来的数据激增致使集群处理能力不足引起的线上客户检索或者其余业务受到影响。
如何调优,正如问题 1 所说,这里细化一下:
3.1 动态索引层面
基于模板+时间+rollover api 滚动建立索引,举例:设计阶段定义:blog 索引的模板格式为:blogindex时间戳的形式,天天递增数据。这样作的好处:不至于数据量激增致使单个索引数据量很是大,接近于上线 2 的 32 次幂-1,索引存储达到了 TB+甚至更大。
一旦单个索引很大,存储等各类风险也随之而来,因此要提早考虑+及早避免。
3.2 存储层面
冷热数据分离存储,热数据(好比最近 3 天或者一周的数据),其他为冷数据。
对于冷数据不会再写入新数据,能够考虑按期 force_merge 加 shrink 压缩操做,节省存储空间和检索效率。
3.3 部署层面
一旦以前没有规划,这里就属于应急策略。
结合 ES 自身的支持动态扩展的特色,动态新增机器的方式能够缓解集群压力,注意:若是以前主节点等规划合理,不须要重启集群也能完成动态新增的。
# 四、elasticsearch 是如何实现 master 选举的
面试官:想了解 ES 集群的底层原理,再也不只关注业务层面了。
解答:
前置前提:
(1)只有候选主节点(master:true)的节点才能成为主节点。
(2)最小主节点数(min_master_nodes)的目的是防止脑裂。
核对了一下代码,核心入口为 findMaster,选择主节点成功返回对应 Master,不然返回 null。选举流程大体描述以下:
第一步:确认候选主节点数达标,elasticsearch.yml 设置的值
discovery.zen.minimum_master_nodes;
第二步:比较:先断定是否具有 master 资格,具有候选主节点资格的优先返回;
若两节点都为候选主节点,则 id 小的值会主节点。注意这里的 id 为 string 类型。
题外话:获取节点 id 的方法。
# 详细描述一下 Elasticsearch 索引文档的过程
面试官:想了解 ES 的底层原理,再也不只关注业务层面了。
解答:
这里的索引文档应该理解为文档写入 ES,建立索引的过程。
文档写入包含:单文档写入和批量 bulk 写入,这里只解释一下:单文档写入流程。
记住官方文档中的这个图。
第一步:客户写集群某节点写入数据,发送请求。(若是没有指定路由/协调节点,请求的节点扮演路由节点的角色。)
第二步:节点 1 接受到请求后,使用文档_id 来肯定文档属于分片 0。请求会被转到另外的节点,假定节点 3。所以分片 0 的主分片分配到节点 3 上。
第三步:节点 3 在主分片上执行写操做,若是成功,则将请求并行转发到节点 1 和节点 2 的副本分片上,等待结果返回。全部的副本分片都报告成功,节点 3 将向协调节点(节点 1)报告成功,节点 1 向请求客户端报告写入成功。
若是面试官再问:第二步中的文档获取分片的过程?
回答:借助路由算法获取,路由算法就是根据路由和文档 id 计算目标的分片 id 的过程。
# 详细描述一下 Elasticsearch 搜索的过程?
面试官:想了解 ES 搜索的底层原理,再也不只关注业务层面了。
解答:
搜索拆解为“query then fetch” 两个阶段。
query 阶段的目的:定位到位置,但不取。
步骤拆解以下:
(1)假设一个索引数据有 5 主+1 副本 共 10 分片,一次请求会命中(主或者副本分片中)的一个。
(2)每一个分片在本地进行查询,结果返回到本地有序的优先队列中。
(3)第 2)步骤的结果发送到协调节点,协调节点产生一个全局的排序列表。
fetch 阶段的目的:取数据。
路由节点获取全部文档,返回给客户端。
# Elasticsearch 在部署时,对 Linux 的设置有哪些优化方法
面试官:想了解对 ES 集群的运维能力。
解答:
(1)关闭缓存 swap;
(2)堆内存设置为:Min(节点内存/2, 32GB);
(3)设置最大文件句柄数;
(4)线程池+队列大小根据业务须要作调整;
(5)磁盘存储 raid 方式——存储有条件使用 RAID10,增长单节点性能以及避免单节点存储故障。
# lucence 内部结构是什么?
面试官:想了解你的知识面的广度和深度。
解答:
Lucene 是有索引和搜索的两个过程,包含索引建立,索引,搜索三个要点。能够基于这个脉络展开一些。
# Elasticsearch 是如何实现 Master 选举的?
(1)Elasticsearch 的选主是 ZenDiscovery 模块负责的,主要包含 Ping(节点之间经过这个 RPC 来发现彼此)和 Unicast(单播模块包含一个主机列表以控制哪些节点须要 ping 通)这两部分;
(2)对全部能够成为 master 的节点(node.master: true)根据 nodeId 字典排序,每次选举每一个节点都把本身所知道节点排一次序,而后选出第一个(第 0 位)节点,暂且认为它是 master 节点。
(3)若是对某个节点的投票数达到必定的值(能够成为 master 节点数 n/2+1)而且该节点本身也选举本身,那这个节点就是 master。不然从新选举一直到知足上述条件。
(4)补充:master 节点的职责主要包括集群、节点和索引的管理,不负责文档级别的管理;data 节点能够关闭 http 功能*。
# 十、Elasticsearch 中的节点(好比共 20 个),其中的 10 个
选了一个 master,另外 10 个选了另外一个 master,怎么办?
(1)当集群 master 候选数量不小于 3 个时,能够经过设置最少投票经过数量(discovery.zen.minimum_master_nodes)超过全部候选节点一半以上来解决脑裂问题;
(3)当候选数量为两个时,只能修改成惟一的一个 master 候选,其余做为 data 节点,避免脑裂问题。
# 客户端在和集群链接时,如何选择特定的节点执行请求的?
TransportClient 利用 transport 模块远程链接一个 elasticsearch 集群。它并不加入到集群中,只是简单的得到一个或者多个初始化的 transport 地址,并以 轮询 的方式与这些地址进行通讯。
# 详细描述一下 Elasticsearch 索引文档的过程。
协调节点默认使用文档 ID 参与计算(也支持经过 routing),以便为路由提供合适的分片。
(1)当分片所在的节点接收到来自协调节点的请求后,会将请求写入到 MemoryBuffer,而后定时(默认是每隔 1 秒)写入到 Filesystem Cache,这个从 MomeryBuffer 到 Filesystem Cache 的过程就叫作 refresh;
(2)固然在某些状况下,存在 Momery Buffer 和 Filesystem Cache 的数据可能会丢失,ES 是经过 translog 的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到 translog 中 ,当 Filesystem cache 中的数据写入到磁盘中时,才会清除掉,这个过程叫作 flush;
(3)在 flush 过程当中,内存中的缓冲将被清除,内容被写入一个新段,段的 fsync 将建立一个新的提交点,并将内容刷新到磁盘,旧的 translog 将被删除并开始一个新的 translog。
(4)flush 触发的时机是定时触发(默认 30 分钟)或者 translog 变得太大(默认为 512M)时;
补充:关于 Lucene 的 Segement:
(1)Lucene 索引是由多个段组成,段自己是一个功能齐全的倒排索引。
(2)段是不可变的,容许 Lucene 将新的文档增量地添加到索引中,而不用从头重建索引。
(3)对于每个搜索请求而言,索引中的全部段都会被搜索,而且每一个段会消耗 CPU 的时钟周、文件句柄和内存。这意味着段的数量越多,搜索性能会越低。
(4)为了解决这个问题,Elasticsearch 会合并小段到一个较大的段,提交新的合并段到磁盘,并删除那些旧的小段。
# 详细描述一下 Elasticsearch 更新和删除文档的过程。
(1)删除和更新也都是写操做,可是 Elasticsearch 中的文档是不可变的,所以不能被删除或者改动以展现其变动;
(2)磁盘上的每一个段都有一个相应的.del 文件。当删除请求发送后,文档并无真的被删除,而是在.del 文件中被标记为删除。该文档依然能匹配查询,可是会在结果中被过滤掉。当段合并时,在.del 文件中被标记为删除的文档将不会被写入新段。
(3)在新的文档被建立时,Elasticsearch 会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del 文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,可是会在结果中被过滤掉。
# 详细描述一下 Elasticsearch 搜索的过程。
(1)搜索被执行成一个两阶段过程,咱们称之为 Query Then Fetch;
(2)在初始查询阶段时,查询会广播到索引中每个分片拷贝(主分片或者副本分片)。每一个分片在本地执行搜索并构建一个匹配文档的大小为 from + size 的优先队列。
PS:在搜索的时候是会查询 Filesystem Cache 的,可是有部分数据还在 MemoryBuffer,因此搜索是近实时的。
(3)每一个分片返回各自优先队列中 全部文档的 ID 和排序值 给协调节点,它合并这些值到本身的优先队列中来产生一个全局排序后的结果列表。
(4)接下来就是 取回阶段,协调节点辨别出哪些文档须要被取回并向相关的分片提交多个 GET 请求。每一个分片加载并 丰 富 文档,若是有须要的话,接着返回文档给协调节点。一旦全部的文档都被取回了,协调节点返回结果给客户端。
(5)补充:Query Then Fetch 的搜索类型在文档相关性打分的时候参考的是本分片的数据,这样在文档数量较少的时候可能不够准确,DFS Query Then Fetch 增长了一个预查询的处理,询问 Term 和 Document frequency,这个评分更准确,可是性能会变差。*
# 在 Elasticsearch 中,是怎么根据一个词找到对应的倒排索引的?
(1)Lucene 的索引过程,就是按照全文检索的基本过程,将倒排表写成此文件格式的过程。
(2)Lucene 的搜索过程,就是按照此文件格式将索引进去的信息读出来,而后计算每篇文档打分(score)的过程。
# Elasticsearch 在部署时,对 Linux 的设置有哪些优化方法?
(1)64 GB 内存的机器是很是理想的, 可是 32 GB 和 16 GB 机器也是很常见的。少于 8 GB 会拔苗助长。
(2)若是你要在更快的 CPUs 和更多的核心之间选择,选择更多的核心更好。多个内核提供的额外并发远赛过稍微快一点点的时钟频率。
(3)若是你负担得起 SSD,它将远远超出任何旋转介质。基于 SSD 的节点,查询和索引性能都有提高。若是你负担得起,SSD 是一个好的选择。
(4)即便数据中心们近在咫尺,也要避免集群跨越多个数据中心。绝对要避免集群跨越大的地理距离。
(5)请确保运行你应用程序的 JVM 和服务器的 JVM 是彻底同样的。在 Elasticsearch 的几个地方,使用 Java 的本地序列化。
(6)经过设置 gateway.recover_after_nodes、gateway.expected_nodes、gateway.recover_after_time 能够在集群重启的时候避免过多的分片交换,这可能会让数据恢复从数个小时缩短为几秒钟。
(7)Elasticsearch 默认被配置为使用单播发现,以防止节点无心中加入集群。只有在同一台机器上运行的节点才会自动组成集群。最好使用单播代替组播。
(8)不要随意修改垃圾回收器(CMS)和各个线程池的大小。
(9)把你的内存的(少于)一半给 Lucene(但不要超过 32 GB!),经过 ES_HEAP_SIZE 环境变量设置。
(10)内存交换到磁盘对服务器性能来讲是致命的。若是内存交换到磁盘上,一个 100 微秒的操做可能变成 10 毫秒。再想一想那么多 10 微秒的操做时延累加起来。不难看出 swapping 对于性能是多么可怕。
(11)Lucene 使用了大 量 的文件。同时,Elasticsearch 在节点和 HTTP 客户端之间进行通讯也使用了大量的套接字。全部这一切都须要足够的文件描述符。你应该增长你的文件描述符,设置一个很大的值,如 64,000。
补充:索引阶段性能提高方法
(1)使用批量请求并调整其大小:每次批量数据 5–15 MB 大是个不错的起始点。
(2)存储:使用 SSD
(3)段和合并:Elasticsearch 默认值是 20 MB/s,对机械磁盘应该是个不错的设置。若是你用的是 SSD,能够考虑提升到 100–200 MB/s。若是你在作批量导入,彻底不在乎搜索,你能够完全关掉合并限流。另外还能够增长 index.translog.flush_threshold_size 设置,从默认的 512 MB 到更大一些的值,好比 1 GB,这能够在一次清空触发的时候在事务日志里积累出更大的段。
(4)若是你的搜索结果不须要近实时的准确度,考虑把每一个索引的 index.refresh_interval 改到 30s。
(5)若是你在作大批量导入,考虑经过设置 index.number_of_replicas: 0 关闭副本。
# 对于 GC 方面,在使用 Elasticsearch 时要注意什么?
(1)倒排词典的索引须要常驻内存,没法 GC,须要监控 data node 上 segmentmemory 增加趋势。
(2)各种缓存,field cache, filter cache, indexing cache, bulk queue 等等,要设置合理的大小,而且要应该根据最坏的状况来看 heap 是否够用,也就是各种缓存所有占满的时候,还有 heap 空间能够分配给其余任务吗?避免采用 clear cache 等“自欺欺人”的方式来释放内存。
(3)避免返回大量结果集的搜索与聚合。确实须要大量拉取数据的场景,能够采用 scan & scroll api 来实现。
(4)cluster stats 驻留内存并没有法水平扩展,超大规模集群能够考虑分拆成多个集群经过 tribe node 链接。
(5)想知道 heap 够不够,必须结合实际应用场景,并对集群的 heap 使用状况作持续的监控。
(6)根据监控数据理解内存需求,合理配置各种 circuit breaker,将内存溢出风险下降到最低
# 1八、Elasticsearch 对于大数据量(上亿量级)的聚合如何实现?
Elasticsearch 提供的首个近似聚合是 cardinality 度量。它提供一个字段的基数,即该字段的 distinct 或者 unique 值的数目。它是基于 HLL 算法的。HLL 会先对咱们的输入做哈希运算,而后根据哈希运算的结果中的 bits 作几率估算从而获得基数。其特色是:可配置的精度,用来控制内存的使用(更精确 = 更多内存);小的数据集精度是很是高的;咱们能够经过配置参数,来设置去重须要的固定内存使用量。不管数千仍是数十亿的惟一值,内存使用量只与你配置的精确度相关。
# 1九、在并发状况下,Elasticsearch 若是保证读写一致?
(1)能够经过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲突;
(2)另外对于写操做,一致性级别支持 quorum/one/all,默认为 quorum,即只有当大多数分片可用时才容许写操做。但即便大多数可用,也可能存在由于网络等缘由致使写入副本失败,这样该副本被认为故障,分片将会在一个不一样的节点上重建。
(3)对于读操做,能够设置 replication 为 sync(默认),这使得操做在主分片和副本分片都完成后才会返回;若是设置 replication 为 async 时,也能够经过设置搜索请求参数_preference 为 primary 来查询主分片,确保文档是最新版本。
# 20、如何监控 Elasticsearch 集群状态?
Marvel 让你能够很简单的经过 Kibana 监控 Elasticsearch。你能够实时查看你的集群健康状态和性能,也能够分析过去的集群、索引和节点指标。
# 2一、介绍下大家电商搜索的总体技术架构。
# 介绍一下大家的个性化搜索方案?
基于 word2vec 和 Elasticsearch 实现个性化搜索
(1)基于 word2vec、Elasticsearch 和自定义的脚本插件,咱们就实现了一个个性化的搜索服务,相对于原有的实现,新版的点击率和转化率都有大幅的提高;
(2)基于 word2vec 的商品向量还有一个可用之处,就是能够用来实现类似商品的推荐;
(3)使用 word2vec 来实现个性化搜索或个性化推荐是有必定局限性的,由于它只能处理用户点击历史这样的时序数据,而没法全面的去考虑用户偏好,这个仍是有很大的改进和提高的空间;
# 是否了解字典树?
经常使用字典数据结构以下所示:
Trie 的核心思想是空间换时间,利用字符串的公共前缀来下降查询时间的开销以达到提升效率的目的。它有 3 个基本性质:
1)根节点不包含字符,除根节点外每个节点都只包含一个字符。
2)从根节点到某一节点,路径上通过的字符链接起来,为该节点对应的字符串。
3)每一个节点的全部子节点包含的字符都不相同。
(1)能够看到,trie 树每一层的节点数是 26^i 级别的。因此为了节省空间,咱们还能够用动态链表,或者用数组来模拟动态。而空间的花费,不会超过单词数 × 单词长度。
(2)实现:对每一个结点开一个字母集大小的数组,每一个结点挂一个链表,使用左儿子右兄弟表示法记录这棵树;
(3)对于中文的字典树,每一个节点的子节点用一个哈希表存储,这样就不用浪费太大的空间,并且查询速度上能够保留哈希的复杂度 O(1)。
# 拼写纠错是如何实现的?
(1)拼写纠错是基于编辑距离来实现;编辑距离是一种标准的方法,它用来表示通过插入、删除和替换操做从一个字符串转换到另一个字符串的最小操做步数;
(2)编辑距离的计算过程:好比要计算 batyu 和 beauty 的编辑距离,先建立一个 7×8 的表(batyu 长度为 5,coffee 长度为 6,各加 2),接着,在以下位置填入黑色数字。其余格的计算过程是取如下三个值的最小值:
若是最上方的字符等于最左方的字符,则为左上方的数字。不然为左上方的数字+1。(对于 3,3 来讲为 0)
左方数字+1(对于 3,3 格来讲为 2)
上方数字+1(对于 3,3 格来讲为 2)
最终取右下角的值即为编辑距离的值 3。
对于拼写纠错,咱们考虑构造一个度量空间(Metric Space),该空间内任何关系知足如下三条基本条件:
d(x,y) = 0 -- 假如 x 与 y 的距离为 0,则 x=y
d(x,y) = d(y,x) -- x 到 y 的距离等同于 y 到 x 的距离
d(x,y) + d(y,z) >= d(x,z) -- 三角不等式
(1)根据三角不等式,则知足与 query 距离在 n 范围内的另外一个字符转 B,其与 A 的距离最大为 d+n,最小为 d-n。
(2)BK 树的构造就过程以下:每一个节点有任意个子节点,每条边有个值表示编辑距离。全部子节点到父节点的边上标注 n 表示编辑距离刚好为 n。好比,咱们有棵树父节点是”book”和两个子节点”cake”和”books”,”book”到”books”的边标号 1,”book”到”cake”的边上标号 4。从字典里构造好树后,不管什么时候你想插入新单词时,计算该单词与根节点的编辑距离,而且查找数值为 d(neweord, root)的边。递归得与各子节点进行比较,直到没有子节点,你就能够建立新的子节点并将新单词保存在那。好比,插入”boo”到刚才上述例子的树中,咱们先检查根节点,查找 d(“book”, “boo”) = 1 的边,而后检查标号为 1 的边的子节点,获得单词”books”。咱们再计算距离 d(“books”, “boo”)=2,则将新单词插在”books”以后,边标号为 2。
(3)查询类似词以下:计算单词与根节点的编辑距离 d,而后递归查找每一个子节点标号为 d-n 到 d+n(包含)的边。假如被检查的节点与搜索单词的距离 d 小于 n,则返回该节点并继续查询。好比输入 cape 且最大容忍距离为 1,则先计算和根的编辑距离 d(“book”, “cape”)=4,而后接着找和根节点之间编辑距离为 3 到 5 的,这个就找到了 cake 这个节点,计算 d(“cake”, “cape”)=1,知足条件因此返回 cake,而后再找和 cake 节点编辑距离是 0 到 2 的,分别找到 cape 和 cart 节点,这样就获得 cape 这个知足条件的结果。
·END·
若是您喜欢本文,欢迎点击右上角,把文章分享到朋友圈~~
做者:dunwu
来源:dunwu.github.io/db-tutorial/nosql/elasticsearch/elasticsearch-interview.html
版权申明:内容来源网络,版权归原创者全部。除非没法确认,咱们都会标明做者及出处,若有侵权烦请告知,咱们会当即删除并表示歉意。谢谢!
IT技术分享社区
我的博客网站:https://programmerblog.xyz
文章推荐程序员效率:画流程图经常使用的工具程序员效率:整理经常使用的在线笔记软件远程办公:经常使用的远程协助软件,你都知道吗?51单片机程序下载、ISP及串口基础知识硬件:断路器、接触器、继电器基础知识