这次面试的后半段,面试官抛出了一系列更深入的问题,涉及数据一致性、底层关系、中文分词、脑裂场景、客户端连接以及更新删除流程。以下是我对这些问题的回答复盘,整理成博客记录下来,也方便自己查漏补缺。
1. ES 如何保证数据一致性
面试官问:"ES 是怎么保证数据一致性的?"这个问题让我稍微停顿了一下,因为一致性在分布式系统中是个大话题。我整理了一下思路,回答如下:
ES 作为一个分布式系统,主要通过以下机制保证数据一致性:
-
主分片与副本同步
我提到,ES 的数据写入是先写主分片(Primary Shard),成功后再同步到副本分片(Replica Shard)。客户端可以设置
wait_for_active_shards
参数,确保一定数量的副本可用后再返回成功,这样提高了数据可靠性。 -
版本控制
每个文档都有一个版本号(
_version
),基于乐观锁机制。每次更新时,ES 会检查版本号是否匹配,如果不一致就拒绝操作,避免并发修改导致的数据覆盖。 -
集群状态同步
Master 节点负责维护全局的集群状态(Cluster State),包括分片分配和索引元数据。状态变更会广播到所有节点,确保大家看到的集群信息一致。如果 Master 挂了,新选举的 Master 会接管并同步状态。
面试官追问:"一致性是强一致性吗?"我说不是,ES 默认是最终一致性,因为副本同步有延迟。但可以通过参数调整(比如 refresh=wait_for
)接近强一致性。这部分我觉得自己答得还算清晰,但可以再补充一些参数的实际应用场景。
2. ES 和 Lucene 的关系
接着面试官问:"ES 和 Lucene 什么关系?"这个问题相对简单,我之前正好复习过。
我回答说,ES 是建立在 Apache Lucene 之上的分布式搜索引擎。Lucene 是核心库,提供了底层的索引和搜索能力,比如倒排索引、查询解析和评分模型。ES 在 Lucene 上封装了一层分布式框架,增加了集群管理、分片、副本、REST API 等功能。简单来说,Lucene 是单机引擎,ES 把它扩展成了分布式系统。
面试官点了点头,没追问。我觉得自己这部分答得简洁明了,但如果能举个例子(比如 Lucene 的 FST 在 ES 中的应用),会更有说服力。
3. 分析 ES 的中文分词
第三个问题是:"ES 的中文分词是怎么处理的?"这个问题挺有针对性,因为中文分词和英文分词差异很大。
我回答说,ES 的分词依赖分析器(Analyzer),而中文分词需要专门的分词器。ES 默认没有内置中文分词器,但可以通过插件集成,比如常用的 IK 分词器:
-
分词原理
中文不像英文有天然的空格分隔,IK 分词器会基于词库和算法把句子切分成词。比如"中华人民共和国"可以分成"中华""人民""共和国"。它支持两种模式:
ik_smart
(智能模式,粗粒度)和ik_max_word
(最大词量,细粒度)。 -
配置与优化
我提到,分词效果可以通过自定义词库优化,比如添加行业术语,或者停用词表过滤无意义的词。ES 的 Mapping 中可以指定字段用哪个分词器,搜索时会用同样的分词逻辑匹配。
面试官问:"分词对搜索性能有啥影响?"我说,分词越细,索引体积越大,查询时匹配的词也多,可能降低性能,但召回率会更高。我觉得自己这部分答得还可以,但没提到 HMM 模型或者 N-Gram,可能是个遗漏。
4. ES 的节点有 20 个,其中 10 个选了一个 Master,另外 10 个选了另一个 Master 怎么办?
这个问题让我有点紧张,因为它直指脑裂(Split Brain)问题。我尽量冷静地回答:
我说,这种情况说明集群发生了网络分区,导致 20 个节点分裂成两部分,每部分选了自己的 Master。这是典型的脑裂问题。ES 通过以下机制处理:
-
最小主节点数(minimum_master_nodes)
我提到,ES 有个参数
discovery.zen.minimum_master_nodes
,通常设为集群节点数的一半加一(比如 20 个节点设为 11)。选举 Master 时,必须有至少这个数量的节点支持。如果分裂成 10 和 10,双方都达不到 11 个节点的支持,就不会有合法的 Master,集群会暂停工作,直到网络恢复。 -
解决办法
如果脑裂真的发生了(比如参数没设好),需要人工介入,检查网络问题,合并集群,或者重启一部分节点让它们加入正确的 Master。
面试官问:"怎么预防?"我说,除了设置合理的 minimum_master_nodes
,还得确保网络稳定,避免频繁分区。这部分我觉得答得不够深入,下次可以提一下仲裁节点(Arbiter)的概念。
5. ES 的客户端在和集群连接时,怎么选择特定的节点执行请求?
这个问题考察客户端和集群的交互,我之前看过 Java 客户端的源码,回答起来还算有底气。
我说,ES 客户端(比如 Java High-Level REST Client)连接集群时,通常会配置一个节点列表。选择节点的逻辑是这样的:
-
节点发现
客户端初始化时会通过配置的节点地址获取集群信息,包括所有数据节点的列表(嗅探机制,Sniffing)。之后客户端会动态维护这个列表。
-
负载均衡
请求发送时,客户端会用轮询(Round-Robin)或者随机的方式从可用节点中挑一个,避免单点压力。如果某个节点挂了,客户端会自动剔除它,尝试其他节点。
-
特定节点选择
如果请求有特殊需求(比如只查某个分片),客户端会根据路由信息(Routing)选择对应的节点。但一般情况下,客户端把请求发给一个协调节点(Coordinating Node),由它分发。
面试官没追问,我觉得这部分答得还行,但可以再补充一下嗅探的配置细节。
6. 详细描述 ES 的更新和删除文档的过程
最后一个问题是:"ES 更新和删除文档的过程是怎样的?"这个问题让我回忆了一下 ES 的写操作流程。
我回答说,ES 的更新和删除都是基于"先标记,后重建"的逻辑,因为 Lucene 的索引是不可变的。详细过程如下:
-
更新文档
- 客户端发送更新请求(比如
PUT /index/_doc/1
),带上新内容。 - 请求到达协调节点,路由到对应的主分片。
- 主分片拿到请求后,先标记旧文档为"已删除"(逻辑删除),然后把新文档写入内存缓冲区(Buffer)。
- 缓冲区满了或者触发刷新(Refresh,默认 1 秒),新文档写入新的段文件(Segment),生成倒排索引。
- 主分片同步到副本分片,完成后返回成功。
- 旧文档在合并(Merge)时才会被物理删除。
- 客户端发送更新请求(比如
-
删除文档
- 客户端发送删除请求(比如
DELETE /index/_doc/1
)。 - 协调节点路由到主分片,主分片标记文档为"已删除",写入
.del
文件。 - 同步到副本分片,返回成功。
- 物理删除也在段合并时完成。
- 客户端发送删除请求(比如
面试官问:"为什么不直接删?"我说因为 Lucene 的索引是只读的,直接改代价太高,所以用追加和标记的方式。这部分我觉得自己答得挺完整,但可以再提一下 refresh_interval
对性能的影响。
总结
这次面试让我对 ES 的分布式特性和底层机制有了更深的认识。数据一致性、脑裂和更新流程是我回答时稍显薄弱的地方,下次得重点复习这些点,尤其是参数调优和异常场景的处理。整体来说,面试过程还算顺利,但细节决定成败,得继续努力!